@arcships/morula-runtime 0.1.0-alpha.5 → 0.1.0-alpha.6
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/dist/index.js +719 -517
- package/dist/morula.js +2 -2
- package/dist/plugin/plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -729,7 +729,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
|
|
|
729
729
|
var init_build_env = __esm({
|
|
730
730
|
"../morula-cli-core/dist/build-env.js"() {
|
|
731
731
|
"use strict";
|
|
732
|
-
BUILD_ID = "
|
|
732
|
+
BUILD_ID = "20260917063948.nogit";
|
|
733
733
|
BUILD_CONTEXTS = {
|
|
734
734
|
"local": "http://localhost:8080/api",
|
|
735
735
|
"test": "https://morula.basecastle.com/api",
|
|
@@ -2042,10 +2042,219 @@ var init_dist2 = __esm({
|
|
|
2042
2042
|
}
|
|
2043
2043
|
});
|
|
2044
2044
|
|
|
2045
|
+
// src/daemon/spawn.ts
|
|
2046
|
+
var spawn_exports = {};
|
|
2047
|
+
__export(spawn_exports, {
|
|
2048
|
+
findCliEntry: () => findCliEntry,
|
|
2049
|
+
isSeaMode: () => isSeaMode,
|
|
2050
|
+
resolveDaemonRunCommand: () => resolveDaemonRunCommand,
|
|
2051
|
+
resolvePortalCommand: () => resolvePortalCommand,
|
|
2052
|
+
spawnDaemonProcess: () => spawnDaemonProcess
|
|
2053
|
+
});
|
|
2054
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2055
|
+
import { closeSync, existsSync as existsSync2, openSync } from "node:fs";
|
|
2056
|
+
import { dirname as dirname2, join as join2, sep } from "node:path";
|
|
2057
|
+
import { fileURLToPath } from "node:url";
|
|
2058
|
+
function findCliEntry() {
|
|
2059
|
+
try {
|
|
2060
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
2061
|
+
if (currentFile.includes(`${sep}dist${sep}`))
|
|
2062
|
+
return currentFile;
|
|
2063
|
+
const srcDir = dirname2(dirname2(currentFile));
|
|
2064
|
+
const srcEntry = join2(srcDir, "index.ts");
|
|
2065
|
+
if (existsSync2(srcEntry))
|
|
2066
|
+
return srcEntry;
|
|
2067
|
+
} catch {
|
|
2068
|
+
}
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
function isSeaMode() {
|
|
2072
|
+
const base = process.execPath.split("/").pop() ?? "";
|
|
2073
|
+
const isNode = base === "node" || base === "node.exe";
|
|
2074
|
+
return !isNode || findCliEntry() === null;
|
|
2075
|
+
}
|
|
2076
|
+
function resolveDaemonRunCommand() {
|
|
2077
|
+
const entry = findCliEntry();
|
|
2078
|
+
if (entry) {
|
|
2079
|
+
if (entry.endsWith(".ts"))
|
|
2080
|
+
return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "run"] };
|
|
2081
|
+
return { command: process.execPath, args: [entry, "daemon", "run"] };
|
|
2082
|
+
}
|
|
2083
|
+
return { command: process.execPath, args: ["daemon", "run"] };
|
|
2084
|
+
}
|
|
2085
|
+
function resolvePortalCommand() {
|
|
2086
|
+
const entry = findCliEntry();
|
|
2087
|
+
if (entry) {
|
|
2088
|
+
if (entry.endsWith(".ts"))
|
|
2089
|
+
return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "portal"] };
|
|
2090
|
+
return { command: process.execPath, args: [entry, "daemon", "portal"] };
|
|
2091
|
+
}
|
|
2092
|
+
return { command: process.execPath, args: ["daemon", "portal"] };
|
|
2093
|
+
}
|
|
2094
|
+
function spawnDaemonProcess(opts) {
|
|
2095
|
+
const { command, args } = resolveDaemonRunCommand();
|
|
2096
|
+
let logFd;
|
|
2097
|
+
try {
|
|
2098
|
+
logFd = openSync(opts.logFile, "a", 384);
|
|
2099
|
+
} catch (error) {
|
|
2100
|
+
return { pid: 0, error: `\u65E0\u6CD5\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6 ${opts.logFile}: ${error instanceof Error ? error.message : String(error)}` };
|
|
2101
|
+
}
|
|
2102
|
+
try {
|
|
2103
|
+
const child = spawn2(command, args, {
|
|
2104
|
+
cwd: process.cwd(),
|
|
2105
|
+
detached: true,
|
|
2106
|
+
stdio: ["ignore", logFd, logFd],
|
|
2107
|
+
env: { ...process.env, ...opts.env ?? {} }
|
|
2108
|
+
});
|
|
2109
|
+
child.on("error", () => {
|
|
2110
|
+
try {
|
|
2111
|
+
closeSync(logFd);
|
|
2112
|
+
} catch {
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
child.unref();
|
|
2116
|
+
return { pid: child.pid ?? 0 };
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
try {
|
|
2119
|
+
closeSync(logFd);
|
|
2120
|
+
} catch {
|
|
2121
|
+
}
|
|
2122
|
+
return { pid: 0, error: `\u65E0\u6CD5\u542F\u52A8 daemon: ${error instanceof Error ? error.message : String(error)}` };
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
var init_spawn = __esm({
|
|
2126
|
+
"src/daemon/spawn.ts"() {
|
|
2127
|
+
"use strict";
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
// src/daemon/portal-autostart.ts
|
|
2132
|
+
var portal_autostart_exports = {};
|
|
2133
|
+
__export(portal_autostart_exports, {
|
|
2134
|
+
buildLaunchAgentPlist: () => buildLaunchAgentPlist,
|
|
2135
|
+
launchAgentPath: () => launchAgentPath,
|
|
2136
|
+
reloadLaunchAgent: () => reloadLaunchAgent,
|
|
2137
|
+
removeAutostart: () => removeAutostart,
|
|
2138
|
+
setupAutostart: () => setupAutostart
|
|
2139
|
+
});
|
|
2140
|
+
import { execFileSync } from "node:child_process";
|
|
2141
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "node:fs";
|
|
2142
|
+
import { homedir as homedir2 } from "node:os";
|
|
2143
|
+
import { join as join3 } from "node:path";
|
|
2144
|
+
function launchAgentPath() {
|
|
2145
|
+
return join3(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
2146
|
+
}
|
|
2147
|
+
function legacyLaunchAgentPaths() {
|
|
2148
|
+
return LEGACY_LABELS.map((label) => join3(homedir2(), "Library", "LaunchAgents", `${label}.plist`));
|
|
2149
|
+
}
|
|
2150
|
+
async function removeLegacyAutostart() {
|
|
2151
|
+
const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
|
|
2152
|
+
for (const legacyPath of legacyLaunchAgentPaths()) {
|
|
2153
|
+
if (!existsSync3(legacyPath))
|
|
2154
|
+
continue;
|
|
2155
|
+
try {
|
|
2156
|
+
execFileSync("launchctl", ["bootout", `gui:${uid}`, legacyPath], { stdio: "pipe" });
|
|
2157
|
+
} catch {
|
|
2158
|
+
}
|
|
2159
|
+
rmSync2(legacyPath, { force: true });
|
|
2160
|
+
console.error(`[portal] \u5DF2\u79FB\u9664\u5386\u53F2\u81EA\u542F ${legacyPath}`);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
function escapePlistValue(value) {
|
|
2164
|
+
return value.replace(/&/g, "&").replace(/</g, "<");
|
|
2165
|
+
}
|
|
2166
|
+
function buildLaunchAgentPlist(spec) {
|
|
2167
|
+
const envEntries = Object.entries(spec.environmentVariables ?? {}).map(([key, value]) => ` <key>${escapePlistValue(key)}</key>
|
|
2168
|
+
<string>${escapePlistValue(value)}</string>`).join("\n");
|
|
2169
|
+
const envSection = envEntries ? `
|
|
2170
|
+
<key>EnvironmentVariables</key>
|
|
2171
|
+
<dict>
|
|
2172
|
+
${envEntries}
|
|
2173
|
+
</dict>` : "";
|
|
2174
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2175
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2176
|
+
<plist version="1.0">
|
|
2177
|
+
<dict>
|
|
2178
|
+
<key>Label</key>
|
|
2179
|
+
<string>${escapePlistValue(spec.label)}</string>
|
|
2180
|
+
<key>ProgramArguments</key>
|
|
2181
|
+
<array>${[spec.command, ...spec.args].map((a) => ` <string>${escapePlistValue(a)}</string>`).join("\n")}
|
|
2182
|
+
</array>
|
|
2183
|
+
<key>RunAtLoad</key>
|
|
2184
|
+
<true/>
|
|
2185
|
+
<key>KeepAlive</key>
|
|
2186
|
+
<dict>
|
|
2187
|
+
<key>SuccessfulExit</key>
|
|
2188
|
+
<false/>
|
|
2189
|
+
</dict>${envSection}
|
|
2190
|
+
<key>StandardOutPath</key>
|
|
2191
|
+
<string>${escapePlistValue(spec.logFile)}</string>
|
|
2192
|
+
<key>StandardErrorPath</key>
|
|
2193
|
+
<string>${escapePlistValue(spec.errFile ?? spec.logFile)}</string>
|
|
2194
|
+
</dict>
|
|
2195
|
+
</plist>
|
|
2196
|
+
`;
|
|
2197
|
+
}
|
|
2198
|
+
async function reloadLaunchAgent(plistPath, log) {
|
|
2199
|
+
const uid = typeof process.getuid === "function" ? process.getuid?.() : void 0;
|
|
2200
|
+
const domain = `gui/${uid ?? process.env.UID ?? ""}`;
|
|
2201
|
+
try {
|
|
2202
|
+
execFileSync("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
2203
|
+
} catch {
|
|
2204
|
+
}
|
|
2205
|
+
try {
|
|
2206
|
+
execFileSync("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
2207
|
+
return true;
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
log(`launchctl bootstrap \u5931\u8D25\uFF08\u53EF\u624B\u52A8 launchctl load -w ${plistPath}\uFF09: ${error instanceof Error ? error.message : String(error)}`);
|
|
2210
|
+
return false;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
async function setupAutostart(context) {
|
|
2214
|
+
if (process.platform !== "darwin")
|
|
2215
|
+
return false;
|
|
2216
|
+
await removeLegacyAutostart();
|
|
2217
|
+
const { command, args } = resolvePortalCommand();
|
|
2218
|
+
const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join3(homedir2(), ".morula-cli");
|
|
2219
|
+
const daemonDir2 = join3(stateDir2, "daemon");
|
|
2220
|
+
mkdirSync2(daemonDir2, { recursive: true, mode: 448 });
|
|
2221
|
+
const logFile = join3(daemonDir2, "portal.log");
|
|
2222
|
+
const plist = buildLaunchAgentPlist({ label: LABEL, command, args, logFile });
|
|
2223
|
+
const plistPath = launchAgentPath();
|
|
2224
|
+
mkdirSync2(join3(plistPath, ".."), { recursive: true });
|
|
2225
|
+
const fs = await import("node:fs");
|
|
2226
|
+
fs.writeFileSync(plistPath, plist, { mode: 420 });
|
|
2227
|
+
await reloadLaunchAgent(plistPath, (line) => context.err(`[portal] ${line}`));
|
|
2228
|
+
return true;
|
|
2229
|
+
}
|
|
2230
|
+
async function removeAutostart(context) {
|
|
2231
|
+
if (process.platform !== "darwin") {
|
|
2232
|
+
context.err("[portal] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u542F\u79FB\u9664");
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
await removeLegacyAutostart();
|
|
2236
|
+
const plistPath = launchAgentPath();
|
|
2237
|
+
const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
|
|
2238
|
+
try {
|
|
2239
|
+
execFileSync("launchctl", ["bootout", `gui:${uid}`, plistPath], { stdio: "pipe" });
|
|
2240
|
+
} catch {
|
|
2241
|
+
}
|
|
2242
|
+
rmSync2(plistPath, { force: true });
|
|
2243
|
+
}
|
|
2244
|
+
var LABEL, LEGACY_LABELS;
|
|
2245
|
+
var init_portal_autostart = __esm({
|
|
2246
|
+
"src/daemon/portal-autostart.ts"() {
|
|
2247
|
+
"use strict";
|
|
2248
|
+
init_spawn();
|
|
2249
|
+
LABEL = "com.morula.runtime.portal";
|
|
2250
|
+
LEGACY_LABELS = ["com.morula-cli.portal", "com.agentctl.portal"];
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
|
|
2045
2254
|
// src/daemon/daemon-config.ts
|
|
2046
2255
|
import { randomUUID } from "node:crypto";
|
|
2047
|
-
import { homedir as
|
|
2048
|
-
import { join as
|
|
2256
|
+
import { homedir as homedir4 } from "node:os";
|
|
2257
|
+
import { join as join5 } from "node:path";
|
|
2049
2258
|
function loadDaemonConfig(store) {
|
|
2050
2259
|
const config = store.load();
|
|
2051
2260
|
const d = config?.[DAEMON_KEY];
|
|
@@ -2076,16 +2285,16 @@ var DEFAULT_WORKDIR, DEFAULT_MAX_CONCURRENT, DAEMON_KEY;
|
|
|
2076
2285
|
var init_daemon_config = __esm({
|
|
2077
2286
|
"src/daemon/daemon-config.ts"() {
|
|
2078
2287
|
"use strict";
|
|
2079
|
-
DEFAULT_WORKDIR =
|
|
2288
|
+
DEFAULT_WORKDIR = join5(homedir4(), ".morula", "daemon");
|
|
2080
2289
|
DEFAULT_MAX_CONCURRENT = 6;
|
|
2081
2290
|
DAEMON_KEY = "daemon";
|
|
2082
2291
|
}
|
|
2083
2292
|
});
|
|
2084
2293
|
|
|
2085
2294
|
// src/daemon/daemon-lock.ts
|
|
2086
|
-
import { execFileSync } from "node:child_process";
|
|
2087
|
-
import { closeSync, mkdirSync as
|
|
2088
|
-
import { dirname as
|
|
2295
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2296
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync3, writeSync } from "node:fs";
|
|
2297
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
2089
2298
|
function isProcessAlive(pid) {
|
|
2090
2299
|
if (!pid || pid <= 0)
|
|
2091
2300
|
return false;
|
|
@@ -2098,7 +2307,7 @@ function isProcessAlive(pid) {
|
|
|
2098
2307
|
}
|
|
2099
2308
|
function processCommandLine(pid) {
|
|
2100
2309
|
try {
|
|
2101
|
-
const out =
|
|
2310
|
+
const out = execFileSync3("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
2102
2311
|
return out || null;
|
|
2103
2312
|
} catch {
|
|
2104
2313
|
return null;
|
|
@@ -2160,7 +2369,7 @@ function probeDaemonLock(lockPath) {
|
|
|
2160
2369
|
return { state: "active", record };
|
|
2161
2370
|
}
|
|
2162
2371
|
function writeRecord(lockPath, record) {
|
|
2163
|
-
|
|
2372
|
+
writeFileSync3(lockPath, `${JSON.stringify(record, null, 2)}
|
|
2164
2373
|
`, { mode: 384 });
|
|
2165
2374
|
}
|
|
2166
2375
|
function acquireDaemonLock(lockPath, input) {
|
|
@@ -2169,14 +2378,14 @@ function acquireDaemonLock(lockPath, input) {
|
|
|
2169
2378
|
startedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
2170
2379
|
workdir: input.workdir
|
|
2171
2380
|
};
|
|
2172
|
-
|
|
2381
|
+
mkdirSync4(dirname4(lockPath), { recursive: true, mode: 448 });
|
|
2173
2382
|
try {
|
|
2174
|
-
const fd =
|
|
2383
|
+
const fd = openSync2(lockPath, "wx", 384);
|
|
2175
2384
|
try {
|
|
2176
2385
|
writeSync(fd, `${JSON.stringify(record, null, 2)}
|
|
2177
2386
|
`);
|
|
2178
2387
|
} finally {
|
|
2179
|
-
|
|
2388
|
+
closeSync2(fd);
|
|
2180
2389
|
}
|
|
2181
2390
|
return { ok: true, handle: makeHandle(lockPath, record), tookOver: false, previous: null };
|
|
2182
2391
|
} catch (error) {
|
|
@@ -2204,7 +2413,7 @@ function releaseDaemonLock(lockPath, pid) {
|
|
|
2204
2413
|
if (read && "record" in read && read.record.pid !== pid)
|
|
2205
2414
|
return;
|
|
2206
2415
|
}
|
|
2207
|
-
|
|
2416
|
+
rmSync4(lockPath, { force: true });
|
|
2208
2417
|
}
|
|
2209
2418
|
async function waitForExit(pid, timeoutMs, pollMs) {
|
|
2210
2419
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -2252,9 +2461,9 @@ __export(portal_instances_exports, {
|
|
|
2252
2461
|
parseBaseUrl: () => parseBaseUrl,
|
|
2253
2462
|
readPidFile: () => readPidFile
|
|
2254
2463
|
});
|
|
2255
|
-
import { existsSync as
|
|
2256
|
-
import { homedir as
|
|
2257
|
-
import { dirname as
|
|
2464
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
|
|
2465
|
+
import { homedir as homedir5 } from "node:os";
|
|
2466
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
2258
2467
|
function parseBaseUrl(raw) {
|
|
2259
2468
|
if (!raw)
|
|
2260
2469
|
return null;
|
|
@@ -2279,7 +2488,7 @@ function normalizeBaseUrl2(raw) {
|
|
|
2279
2488
|
return "";
|
|
2280
2489
|
return `${parsed.protocol}//${parsed.host}:${parsed.port}${parsed.path}`;
|
|
2281
2490
|
}
|
|
2282
|
-
function candidateConfigFiles(env = process.env, homeDir =
|
|
2491
|
+
function candidateConfigFiles(env = process.env, homeDir = homedir5()) {
|
|
2283
2492
|
const files = [];
|
|
2284
2493
|
const push = (file) => {
|
|
2285
2494
|
if (file && !files.includes(file))
|
|
@@ -2287,11 +2496,11 @@ function candidateConfigFiles(env = process.env, homeDir = homedir3()) {
|
|
|
2287
2496
|
};
|
|
2288
2497
|
if (env.MORULA_CONFIG)
|
|
2289
2498
|
push(env.MORULA_CONFIG);
|
|
2290
|
-
push(
|
|
2499
|
+
push(join7(env.MORULA_CONFIG_DIR ?? join7(homeDir, ".morula-cli"), "config.yaml"));
|
|
2291
2500
|
try {
|
|
2292
2501
|
for (const name of readdirSync(homeDir).sort()) {
|
|
2293
2502
|
if (name.startsWith(".morula-cli-"))
|
|
2294
|
-
push(
|
|
2503
|
+
push(join7(homeDir, name, "config.yaml"));
|
|
2295
2504
|
}
|
|
2296
2505
|
} catch {
|
|
2297
2506
|
}
|
|
@@ -2299,7 +2508,7 @@ function candidateConfigFiles(env = process.env, homeDir = homedir3()) {
|
|
|
2299
2508
|
const item = raw.trim();
|
|
2300
2509
|
if (!item)
|
|
2301
2510
|
continue;
|
|
2302
|
-
push(item.endsWith(".yaml") || item.endsWith(".yml") ? item :
|
|
2511
|
+
push(item.endsWith(".yaml") || item.endsWith(".yml") ? item : join7(item, "config.yaml"));
|
|
2303
2512
|
}
|
|
2304
2513
|
return files;
|
|
2305
2514
|
}
|
|
@@ -2319,8 +2528,8 @@ function readContexts(file) {
|
|
|
2319
2528
|
}
|
|
2320
2529
|
}
|
|
2321
2530
|
function readPidFile(configDir) {
|
|
2322
|
-
const file =
|
|
2323
|
-
if (!
|
|
2531
|
+
const file = join7(configDir, "daemon", "daemon.pid");
|
|
2532
|
+
if (!existsSync5(file))
|
|
2324
2533
|
return 0;
|
|
2325
2534
|
try {
|
|
2326
2535
|
return Number(readFileSync3(file, "utf8").trim() || 0);
|
|
@@ -2332,7 +2541,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2332
2541
|
const env = options.env ?? process.env;
|
|
2333
2542
|
const log = options.log ?? (() => {
|
|
2334
2543
|
});
|
|
2335
|
-
const defaultConfigDir =
|
|
2544
|
+
const defaultConfigDir = dirname5(env.MORULA_CONFIG ?? join7(env.MORULA_CONFIG_DIR ?? join7(options.homeDir ?? homedir5(), ".morula-cli"), "config.yaml"));
|
|
2336
2545
|
let index = [];
|
|
2337
2546
|
let signature = "";
|
|
2338
2547
|
function buildIndex() {
|
|
@@ -2349,7 +2558,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2349
2558
|
const normalized = normalizeBaseUrl2(entry.url);
|
|
2350
2559
|
if (!normalized)
|
|
2351
2560
|
continue;
|
|
2352
|
-
const configDir =
|
|
2561
|
+
const configDir = dirname5(file);
|
|
2353
2562
|
built.push({
|
|
2354
2563
|
configDir,
|
|
2355
2564
|
context,
|
|
@@ -2397,7 +2606,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2397
2606
|
hasToken: !!instance.token,
|
|
2398
2607
|
runtimeId: () => {
|
|
2399
2608
|
try {
|
|
2400
|
-
return loadDaemonConfig(new FileConfigStore(
|
|
2609
|
+
return loadDaemonConfig(new FileConfigStore(join7(instance.configDir, "config.yaml")))?.runtimeId;
|
|
2401
2610
|
} catch {
|
|
2402
2611
|
return void 0;
|
|
2403
2612
|
}
|
|
@@ -2456,110 +2665,24 @@ var init_portal_instances = __esm({
|
|
|
2456
2665
|
}
|
|
2457
2666
|
});
|
|
2458
2667
|
|
|
2459
|
-
// src/daemon/spawn.ts
|
|
2460
|
-
var spawn_exports = {};
|
|
2461
|
-
__export(spawn_exports, {
|
|
2462
|
-
findCliEntry: () => findCliEntry,
|
|
2463
|
-
isSeaMode: () => isSeaMode,
|
|
2464
|
-
resolveDaemonRunCommand: () => resolveDaemonRunCommand,
|
|
2465
|
-
resolvePortalCommand: () => resolvePortalCommand,
|
|
2466
|
-
spawnDaemonProcess: () => spawnDaemonProcess
|
|
2467
|
-
});
|
|
2468
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
2469
|
-
import { closeSync as closeSync2, existsSync as existsSync3, openSync as openSync2 } from "node:fs";
|
|
2470
|
-
import { dirname as dirname4, join as join5, sep } from "node:path";
|
|
2471
|
-
import { fileURLToPath } from "node:url";
|
|
2472
|
-
function findCliEntry() {
|
|
2473
|
-
try {
|
|
2474
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
2475
|
-
if (currentFile.includes(`${sep}dist${sep}`))
|
|
2476
|
-
return currentFile;
|
|
2477
|
-
const srcDir = dirname4(dirname4(currentFile));
|
|
2478
|
-
const srcEntry = join5(srcDir, "index.ts");
|
|
2479
|
-
if (existsSync3(srcEntry))
|
|
2480
|
-
return srcEntry;
|
|
2481
|
-
} catch {
|
|
2482
|
-
}
|
|
2483
|
-
return null;
|
|
2484
|
-
}
|
|
2485
|
-
function isSeaMode() {
|
|
2486
|
-
const base = process.execPath.split("/").pop() ?? "";
|
|
2487
|
-
const isNode = base === "node" || base === "node.exe";
|
|
2488
|
-
return !isNode || findCliEntry() === null;
|
|
2489
|
-
}
|
|
2490
|
-
function resolveDaemonRunCommand() {
|
|
2491
|
-
const entry = findCliEntry();
|
|
2492
|
-
if (entry) {
|
|
2493
|
-
if (entry.endsWith(".ts"))
|
|
2494
|
-
return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "run"] };
|
|
2495
|
-
return { command: process.execPath, args: [entry, "daemon", "run"] };
|
|
2496
|
-
}
|
|
2497
|
-
return { command: process.execPath, args: ["daemon", "run"] };
|
|
2498
|
-
}
|
|
2499
|
-
function resolvePortalCommand() {
|
|
2500
|
-
const entry = findCliEntry();
|
|
2501
|
-
if (entry) {
|
|
2502
|
-
if (entry.endsWith(".ts"))
|
|
2503
|
-
return { command: process.execPath, args: ["--import", "tsx", entry, "daemon", "portal"] };
|
|
2504
|
-
return { command: process.execPath, args: [entry, "daemon", "portal"] };
|
|
2505
|
-
}
|
|
2506
|
-
return { command: process.execPath, args: ["daemon", "portal"] };
|
|
2507
|
-
}
|
|
2508
|
-
function spawnDaemonProcess(opts) {
|
|
2509
|
-
const { command, args } = resolveDaemonRunCommand();
|
|
2510
|
-
let logFd;
|
|
2511
|
-
try {
|
|
2512
|
-
logFd = openSync2(opts.logFile, "a", 384);
|
|
2513
|
-
} catch (error) {
|
|
2514
|
-
return { pid: 0, error: `\u65E0\u6CD5\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6 ${opts.logFile}: ${error instanceof Error ? error.message : String(error)}` };
|
|
2515
|
-
}
|
|
2516
|
-
try {
|
|
2517
|
-
const child = spawn2(command, args, {
|
|
2518
|
-
cwd: process.cwd(),
|
|
2519
|
-
detached: true,
|
|
2520
|
-
stdio: ["ignore", logFd, logFd],
|
|
2521
|
-
env: { ...process.env, ...opts.env ?? {} }
|
|
2522
|
-
});
|
|
2523
|
-
child.on("error", () => {
|
|
2524
|
-
try {
|
|
2525
|
-
closeSync2(logFd);
|
|
2526
|
-
} catch {
|
|
2527
|
-
}
|
|
2528
|
-
});
|
|
2529
|
-
child.unref();
|
|
2530
|
-
return { pid: child.pid ?? 0 };
|
|
2531
|
-
} catch (error) {
|
|
2532
|
-
try {
|
|
2533
|
-
closeSync2(logFd);
|
|
2534
|
-
} catch {
|
|
2535
|
-
}
|
|
2536
|
-
return { pid: 0, error: `\u65E0\u6CD5\u542F\u52A8 daemon: ${error instanceof Error ? error.message : String(error)}` };
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
var init_spawn = __esm({
|
|
2540
|
-
"src/daemon/spawn.ts"() {
|
|
2541
|
-
"use strict";
|
|
2542
|
-
}
|
|
2543
|
-
});
|
|
2544
|
-
|
|
2545
2668
|
// src/daemon/portal-server.ts
|
|
2546
2669
|
var portal_server_exports = {};
|
|
2547
2670
|
__export(portal_server_exports, {
|
|
2548
2671
|
DEFAULT_PORTAL_PORT: () => DEFAULT_PORTAL_PORT,
|
|
2549
2672
|
PortalServer: () => PortalServer
|
|
2550
2673
|
});
|
|
2551
|
-
import { existsSync as
|
|
2674
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2552
2675
|
import { createServer as createServer2 } from "node:http";
|
|
2553
|
-
import { homedir as
|
|
2554
|
-
import { join as
|
|
2676
|
+
import { homedir as homedir6 } from "node:os";
|
|
2677
|
+
import { join as join8 } from "node:path";
|
|
2555
2678
|
function stateDir() {
|
|
2556
|
-
return process.env.MORULA_CONFIG_DIR ??
|
|
2679
|
+
return process.env.MORULA_CONFIG_DIR ?? join8(homedir6(), ".morula-cli");
|
|
2557
2680
|
}
|
|
2558
2681
|
function pidFileIn(configDir) {
|
|
2559
|
-
return
|
|
2682
|
+
return join8(configDir, "daemon", "daemon.pid");
|
|
2560
2683
|
}
|
|
2561
2684
|
function daemonLogIn(configDir) {
|
|
2562
|
-
return
|
|
2685
|
+
return join8(configDir, "daemon", "daemon.log");
|
|
2563
2686
|
}
|
|
2564
2687
|
function isPidAlive(pid) {
|
|
2565
2688
|
if (!pid || pid <= 0)
|
|
@@ -2572,7 +2695,7 @@ function isPidAlive(pid) {
|
|
|
2572
2695
|
}
|
|
2573
2696
|
}
|
|
2574
2697
|
function readPidFrom(file) {
|
|
2575
|
-
if (!
|
|
2698
|
+
if (!existsSync6(file))
|
|
2576
2699
|
return 0;
|
|
2577
2700
|
return Number(readFileSync4(file, "utf8").trim() || 0);
|
|
2578
2701
|
}
|
|
@@ -2667,7 +2790,10 @@ var init_portal_server = __esm({
|
|
|
2667
2790
|
/**
|
|
2668
2791
|
* 解析请求声明的目标环境。
|
|
2669
2792
|
* - 未声明 `server` → `{ target: undefined }`:沿用旧行为(portal 自身配置目录)
|
|
2670
|
-
* -
|
|
2793
|
+
* - `server` 解析不到时按 `origin`(portal-token 下发的请求来源地址)兜底:dev 用 ngrok 等
|
|
2794
|
+
* 隧道暴露 webhook 时 `server`(`APP_BASE_URL`)是隧道地址,而本机 daemon 实际注册的是本地地址
|
|
2795
|
+
* —— 两个 URL 指向同一服务进程,`origin` 能命中本机索引(docs/issues/dev-app-base-url-ngrok-portal-stop-404.md)
|
|
2796
|
+
* - 两个键都只作为**本机索引内的查找键**,均未命中 → fail closed 404
|
|
2671
2797
|
*/
|
|
2672
2798
|
resolveTarget(res, url) {
|
|
2673
2799
|
const requested = url.searchParams.get("server");
|
|
@@ -2689,10 +2815,11 @@ var init_portal_server = __esm({
|
|
|
2689
2815
|
});
|
|
2690
2816
|
return { failed: true };
|
|
2691
2817
|
}
|
|
2692
|
-
const
|
|
2818
|
+
const origin = url.searchParams.get("origin");
|
|
2819
|
+
const target = this.environments.resolve(requested) ?? (origin && normalizeBaseUrl2(origin) ? this.environments.resolve(origin) : void 0);
|
|
2693
2820
|
if (!target) {
|
|
2694
2821
|
const indexed = this.environments.list();
|
|
2695
|
-
this.log(`[portal] \u672A\u7D22\u5F15\u7684\u76EE\u6807\u73AF\u5883: ${normalizeBaseUrl2(requested)}\uFF1B\u5DF2\u7D22\u5F15 ${indexed.map((i) => `${i.context}(${i.baseUrl})`).join(", ") || "\uFF08\u7A7A\uFF09"}`);
|
|
2822
|
+
this.log(`[portal] \u672A\u7D22\u5F15\u7684\u76EE\u6807\u73AF\u5883: ${normalizeBaseUrl2(requested)}${origin ? `\uFF08origin \u515C\u5E95 ${normalizeBaseUrl2(origin)} \u4E5F\u672A\u547D\u4E2D\uFF09` : ""}\uFF1B\u5DF2\u7D22\u5F15 ${indexed.map((i) => `${i.context}(${i.baseUrl})`).join(", ") || "\uFF08\u7A7A\uFF09"}`);
|
|
2696
2823
|
this.json(res, 404, {
|
|
2697
2824
|
ok: false,
|
|
2698
2825
|
error: "UNKNOWN_ENV",
|
|
@@ -2758,7 +2885,7 @@ var init_portal_server = __esm({
|
|
|
2758
2885
|
return;
|
|
2759
2886
|
}
|
|
2760
2887
|
try {
|
|
2761
|
-
|
|
2888
|
+
writeFileSync4(pidFile, String(result.pid), { mode: 384 });
|
|
2762
2889
|
} catch {
|
|
2763
2890
|
try {
|
|
2764
2891
|
process.kill(result.pid, "SIGTERM");
|
|
@@ -2829,8 +2956,8 @@ var init_portal_server = __esm({
|
|
|
2829
2956
|
|
|
2830
2957
|
// src/daemon/dim-locate.ts
|
|
2831
2958
|
import { accessSync, constants } from "node:fs";
|
|
2832
|
-
import { homedir as
|
|
2833
|
-
import { join as
|
|
2959
|
+
import { homedir as homedir7 } from "node:os";
|
|
2960
|
+
import { join as join9 } from "node:path";
|
|
2834
2961
|
function isExecutable(p) {
|
|
2835
2962
|
try {
|
|
2836
2963
|
accessSync(p, constants.X_OK);
|
|
@@ -2840,13 +2967,13 @@ function isExecutable(p) {
|
|
|
2840
2967
|
}
|
|
2841
2968
|
}
|
|
2842
2969
|
function knownDimLocations() {
|
|
2843
|
-
const home =
|
|
2970
|
+
const home = homedir7();
|
|
2844
2971
|
return [
|
|
2845
2972
|
"/Applications/DimAgent.app/Contents/Resources/runtime/cli/dim",
|
|
2846
2973
|
"/Applications/Dim.app/Contents/Resources/runtime/cli/dim",
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2974
|
+
join9(home, "Applications", "DimAgent.app", "Contents", "Resources", "runtime", "cli", "dim"),
|
|
2975
|
+
join9(home, ".dimcode", "bin", "dim"),
|
|
2976
|
+
join9(home, ".local", "bin", "dim")
|
|
2850
2977
|
];
|
|
2851
2978
|
}
|
|
2852
2979
|
function resolveDimCandidates(primary) {
|
|
@@ -2859,7 +2986,7 @@ function resolveDimCandidates(primary) {
|
|
|
2859
2986
|
for (const c of knownDimLocations())
|
|
2860
2987
|
push(c);
|
|
2861
2988
|
for (const dir of (process.env.PATH ?? "").split(":").filter(Boolean))
|
|
2862
|
-
push(
|
|
2989
|
+
push(join9(dir, "dim"));
|
|
2863
2990
|
return out;
|
|
2864
2991
|
}
|
|
2865
2992
|
function resolveDimBinary() {
|
|
@@ -2874,7 +3001,7 @@ function resolveDimBinary() {
|
|
|
2874
3001
|
}
|
|
2875
3002
|
const pathDirs = (process.env.PATH ?? "").split(":").filter(Boolean);
|
|
2876
3003
|
for (const dir of pathDirs) {
|
|
2877
|
-
const candidate =
|
|
3004
|
+
const candidate = join9(dir, "dim");
|
|
2878
3005
|
if (isExecutable(candidate)) {
|
|
2879
3006
|
return candidate;
|
|
2880
3007
|
}
|
|
@@ -2895,7 +3022,7 @@ __export(acp_connection_exports, {
|
|
|
2895
3022
|
countDimAcpProcesses: () => countDimAcpProcesses,
|
|
2896
3023
|
resolveAcpInitTimeoutMs: () => resolveAcpInitTimeoutMs
|
|
2897
3024
|
});
|
|
2898
|
-
import { execFileSync as
|
|
3025
|
+
import { execFileSync as execFileSync4, spawn as spawn3 } from "node:child_process";
|
|
2899
3026
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2900
3027
|
function resolveAcpInitTimeoutMs() {
|
|
2901
3028
|
const raw = Number(process.env.MORULA_ACP_INIT_TIMEOUT_MS);
|
|
@@ -2905,7 +3032,7 @@ function resolveDimVersion(dimPath) {
|
|
|
2905
3032
|
if (cachedDimVersion !== null)
|
|
2906
3033
|
return cachedDimVersion;
|
|
2907
3034
|
try {
|
|
2908
|
-
cachedDimVersion =
|
|
3035
|
+
cachedDimVersion = execFileSync4(dimPath, ["--version"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 }).trim().split("\n")[0] || "unknown";
|
|
2909
3036
|
} catch {
|
|
2910
3037
|
cachedDimVersion = "unknown";
|
|
2911
3038
|
}
|
|
@@ -2915,7 +3042,7 @@ function countDimAcpProcesses() {
|
|
|
2915
3042
|
if (process.platform === "win32")
|
|
2916
3043
|
return null;
|
|
2917
3044
|
try {
|
|
2918
|
-
const out =
|
|
3045
|
+
const out = execFileSync4("ps", ["-Ao", "args"], { encoding: "utf8", stdio: "pipe", timeout: 5e3 });
|
|
2919
3046
|
return out.split("\n").filter((line) => /(^|\/)dim(\.exe)?\s+acp(\s|$)/.test(line.trim())).length;
|
|
2920
3047
|
} catch {
|
|
2921
3048
|
return null;
|
|
@@ -2976,9 +3103,9 @@ var init_acp_connection = __esm({
|
|
|
2976
3103
|
process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
|
|
2977
3104
|
`);
|
|
2978
3105
|
try {
|
|
2979
|
-
const { existsSync:
|
|
3106
|
+
const { existsSync: existsSync19 } = await import("node:fs");
|
|
2980
3107
|
process.stderr.write(
|
|
2981
|
-
`[daemon] diag: fileExists=${
|
|
3108
|
+
`[daemon] diag: fileExists=${existsSync19(dimPath)} cwdExists=${this.options.cwd ? existsSync19(this.options.cwd) : "n/a"} PATH=${(process.env.PATH ?? "").slice(0, 140)} DYLD=${process.env.DYLD_LIBRARY_PATH ?? "-"} NODE_OPTIONS=${process.env.NODE_OPTIONS ?? "-"}
|
|
2982
3109
|
`
|
|
2983
3110
|
);
|
|
2984
3111
|
} catch {
|
|
@@ -3135,7 +3262,7 @@ var init_acp_connection = __esm({
|
|
|
3135
3262
|
this.exited = true;
|
|
3136
3263
|
if (process.platform === "win32") {
|
|
3137
3264
|
try {
|
|
3138
|
-
|
|
3265
|
+
execFileSync4("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
3139
3266
|
} catch {
|
|
3140
3267
|
}
|
|
3141
3268
|
return;
|
|
@@ -3250,12 +3377,12 @@ __export(dim_home_exports, {
|
|
|
3250
3377
|
isolatedDimHome: () => isolatedDimHome,
|
|
3251
3378
|
resolveIsolatedDimEnv: () => resolveIsolatedDimEnv
|
|
3252
3379
|
});
|
|
3253
|
-
import { execFileSync as
|
|
3254
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as
|
|
3255
|
-
import { homedir as
|
|
3256
|
-
import { join as
|
|
3380
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3381
|
+
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, rmSync as rmSync5 } from "node:fs";
|
|
3382
|
+
import { homedir as homedir8 } from "node:os";
|
|
3383
|
+
import { join as join10 } from "node:path";
|
|
3257
3384
|
function isolatedDimHome() {
|
|
3258
|
-
return
|
|
3385
|
+
return join10(homedir8(), ".morula", "dimcode");
|
|
3259
3386
|
}
|
|
3260
3387
|
function resolveIsolatedDimEnv() {
|
|
3261
3388
|
return {};
|
|
@@ -3277,13 +3404,13 @@ __export(dim_model_exports, {
|
|
|
3277
3404
|
});
|
|
3278
3405
|
import { createRequire } from "node:module";
|
|
3279
3406
|
import { spawnSync } from "node:child_process";
|
|
3280
|
-
import { closeSync as closeSync3, existsSync as
|
|
3281
|
-
import { homedir as
|
|
3282
|
-
import { join as
|
|
3407
|
+
import { closeSync as closeSync3, existsSync as existsSync10, openSync as openSync3, readFileSync as readFileSync7, unlinkSync } from "node:fs";
|
|
3408
|
+
import { homedir as homedir10, tmpdir } from "node:os";
|
|
3409
|
+
import { join as join13 } from "node:path";
|
|
3283
3410
|
function dimSqlitePath(env) {
|
|
3284
3411
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3285
|
-
const candidates = home && home !== "" ? [
|
|
3286
|
-
return candidates.find((p) =>
|
|
3412
|
+
const candidates = home && home !== "" ? [join13(home, "v2", "dimcode.sqlite"), join13(home, "dimcode.sqlite")] : [join13(homedir10(), ".dimcode", "v2", "dimcode.sqlite")];
|
|
3413
|
+
return candidates.find((p) => existsSync10(p)) ?? null;
|
|
3287
3414
|
}
|
|
3288
3415
|
function listDimModels(dimPath = "dim", env) {
|
|
3289
3416
|
const fromCli = (() => {
|
|
@@ -3317,12 +3444,12 @@ function listDimModels(dimPath = "dim", env) {
|
|
|
3317
3444
|
function hostDimEnv(env) {
|
|
3318
3445
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3319
3446
|
if (home && home !== "" && home.includes(".morula") && home.includes("dimcode")) {
|
|
3320
|
-
return { ...process.env, DIMCODE_HOME:
|
|
3447
|
+
return { ...process.env, DIMCODE_HOME: join13(homedir10(), ".dimcode", "v2") };
|
|
3321
3448
|
}
|
|
3322
3449
|
return env ? { ...env } : null;
|
|
3323
3450
|
}
|
|
3324
3451
|
function listDimModelsFromCli(dimPath, env) {
|
|
3325
|
-
const tmpFile =
|
|
3452
|
+
const tmpFile = join13(tmpdir(), `${DIM_MODELS_TMP_PREFIX}${process.pid}-${Date.now()}.json`);
|
|
3326
3453
|
const fd = openSync3(tmpFile, "w");
|
|
3327
3454
|
let collected = "";
|
|
3328
3455
|
try {
|
|
@@ -3359,9 +3486,9 @@ function listDimModelsFromCli(dimPath, env) {
|
|
|
3359
3486
|
}
|
|
3360
3487
|
function dimOAuthTokenPath(env) {
|
|
3361
3488
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3362
|
-
const dir = home && home !== "" ? home :
|
|
3363
|
-
for (const p of [
|
|
3364
|
-
if (
|
|
3489
|
+
const dir = home && home !== "" ? home : join13(homedir10(), ".dimcode", "v2");
|
|
3490
|
+
for (const p of [join13(dir, "auth.json"), join13(dir, "v2", "auth.json")]) {
|
|
3491
|
+
if (existsSync10(p))
|
|
3365
3492
|
return p;
|
|
3366
3493
|
}
|
|
3367
3494
|
return null;
|
|
@@ -3498,138 +3625,92 @@ var init_dim_model = __esm({
|
|
|
3498
3625
|
}
|
|
3499
3626
|
});
|
|
3500
3627
|
|
|
3501
|
-
// src/
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3628
|
+
// src/index.ts
|
|
3629
|
+
init_dist2();
|
|
3630
|
+
|
|
3631
|
+
// src/commands/registry-runtime.ts
|
|
3632
|
+
init_dist2();
|
|
3633
|
+
|
|
3634
|
+
// src/build-env.ts
|
|
3635
|
+
var BUILD_VERSION2 = "0.1.0-alpha.6";
|
|
3636
|
+
|
|
3637
|
+
// src/commands/daemon.ts
|
|
3638
|
+
init_dist();
|
|
3639
|
+
init_dist2();
|
|
3640
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync11, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
3641
|
+
import { homedir as homedir12 } from "node:os";
|
|
3642
|
+
import { dirname as dirname9, join as join20 } from "node:path";
|
|
3643
|
+
|
|
3644
|
+
// src/daemon/daemon-autostart.ts
|
|
3645
|
+
init_portal_autostart();
|
|
3646
|
+
init_spawn();
|
|
3647
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3648
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3649
|
+
import { homedir as homedir3 } from "node:os";
|
|
3650
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
3651
|
+
var DAEMON_AUTOSTART_LABEL = "com.morula.runtime.daemon";
|
|
3652
|
+
var DAEMON_SUPERVISED_ENV = "MORULA_DAEMON_SUPERVISED";
|
|
3653
|
+
function daemonAutostartPlistPath() {
|
|
3654
|
+
return join4(homedir3(), "Library", "LaunchAgents", `${DAEMON_AUTOSTART_LABEL}.plist`);
|
|
3514
3655
|
}
|
|
3515
|
-
function
|
|
3516
|
-
return
|
|
3656
|
+
function daemonAutostartEnabled() {
|
|
3657
|
+
return process.platform === "darwin" && existsSync4(daemonAutostartPlistPath());
|
|
3517
3658
|
}
|
|
3518
|
-
|
|
3519
|
-
const
|
|
3520
|
-
|
|
3521
|
-
if (!existsSync15(legacyPath))
|
|
3522
|
-
continue;
|
|
3523
|
-
try {
|
|
3524
|
-
execFileSync9("launchctl", ["bootout", `gui:${uid}`, legacyPath], { stdio: "pipe" });
|
|
3525
|
-
} catch {
|
|
3526
|
-
}
|
|
3527
|
-
rmSync6(legacyPath, { force: true });
|
|
3528
|
-
console.error(`[portal] \u5DF2\u79FB\u9664\u5386\u53F2\u81EA\u542F ${legacyPath}`);
|
|
3529
|
-
}
|
|
3659
|
+
function daemonLogFile() {
|
|
3660
|
+
const stateDir2 = process.env.MORULA_CONFIG_DIR ?? join4(homedir3(), ".morula-cli");
|
|
3661
|
+
return join4(stateDir2, "daemon", "daemon.log");
|
|
3530
3662
|
}
|
|
3531
|
-
function
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
<array>${[command, ...args].map((a) => ` <string>${a.replace(/&/g, "&").replace(/</g, "<")}</string>`).join("\n")}
|
|
3540
|
-
</array>
|
|
3541
|
-
<key>RunAtLoad</key>
|
|
3542
|
-
<true/>
|
|
3543
|
-
<key>KeepAlive</key>
|
|
3544
|
-
<dict>
|
|
3545
|
-
<key>SuccessfulExit</key>
|
|
3546
|
-
<false/>
|
|
3547
|
-
</dict>
|
|
3548
|
-
<key>StandardOutPath</key>
|
|
3549
|
-
<string>${logFile}</string>
|
|
3550
|
-
<key>StandardErrorPath</key>
|
|
3551
|
-
<string>${errFile}</string>
|
|
3552
|
-
</dict>
|
|
3553
|
-
</plist>
|
|
3554
|
-
`;
|
|
3663
|
+
function planDaemonAutostart() {
|
|
3664
|
+
const { command, args } = resolveDaemonRunCommand();
|
|
3665
|
+
return {
|
|
3666
|
+
label: DAEMON_AUTOSTART_LABEL,
|
|
3667
|
+
plistPath: daemonAutostartPlistPath(),
|
|
3668
|
+
program: [command, ...args].join(" "),
|
|
3669
|
+
logFile: daemonLogFile()
|
|
3670
|
+
};
|
|
3555
3671
|
}
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
const plist = buildPlist(command, args, logFile, logFile);
|
|
3566
|
-
const plistPath = launchAgentPath();
|
|
3567
|
-
mkdirSync10(join18(plistPath, ".."), { recursive: true });
|
|
3568
|
-
const fs = await import("node:fs");
|
|
3569
|
-
fs.writeFileSync(plistPath, plist, { mode: 420 });
|
|
3570
|
-
const uid = typeof process.getuid === "function" ? process.getuid?.() : void 0;
|
|
3571
|
-
const domain = `gui/${uid ?? process.env.UID ?? ""}`;
|
|
3572
|
-
try {
|
|
3573
|
-
execFileSync9("launchctl", ["bootout", domain, plistPath], { stdio: "pipe" });
|
|
3574
|
-
} catch {
|
|
3575
|
-
}
|
|
3576
|
-
try {
|
|
3577
|
-
execFileSync9("launchctl", ["bootstrap", domain, plistPath], { stdio: "pipe" });
|
|
3578
|
-
} catch (error) {
|
|
3579
|
-
context.err(`[portal] launchctl bootstrap \u5931\u8D25\uFF08\u53EF\u624B\u52A8 launchctl load -w ${plistPath}\uFF09: ${error instanceof Error ? error.message : String(error)}`);
|
|
3580
|
-
return true;
|
|
3581
|
-
}
|
|
3582
|
-
return true;
|
|
3672
|
+
function buildDaemonAutostartPlist() {
|
|
3673
|
+
const { command, args } = resolveDaemonRunCommand();
|
|
3674
|
+
return buildLaunchAgentPlist({
|
|
3675
|
+
label: DAEMON_AUTOSTART_LABEL,
|
|
3676
|
+
command,
|
|
3677
|
+
args,
|
|
3678
|
+
logFile: daemonLogFile(),
|
|
3679
|
+
environmentVariables: { [DAEMON_SUPERVISED_ENV]: "1" }
|
|
3680
|
+
});
|
|
3583
3681
|
}
|
|
3584
|
-
async function
|
|
3585
|
-
|
|
3586
|
-
|
|
3682
|
+
async function setupDaemonAutostart(log) {
|
|
3683
|
+
const plan = planDaemonAutostart();
|
|
3684
|
+
mkdirSync3(dirname3(plan.logFile), { recursive: true, mode: 448 });
|
|
3685
|
+
mkdirSync3(dirname3(plan.plistPath), { recursive: true });
|
|
3686
|
+
writeFileSync2(plan.plistPath, buildDaemonAutostartPlist(), { mode: 420 });
|
|
3687
|
+
return await reloadLaunchAgent(plan.plistPath, (line) => log(`[autostart] ${line}`));
|
|
3688
|
+
}
|
|
3689
|
+
async function removeDaemonAutostart(log) {
|
|
3690
|
+
const plistPath = daemonAutostartPlistPath();
|
|
3691
|
+
if (!existsSync4(plistPath)) {
|
|
3692
|
+
log("[autostart] \u672A\u6CE8\u518C\u8FC7 daemon \u5F00\u673A\u81EA\u542F\uFF08\u5E42\u7B49\uFF0C\u65E0\u64CD\u4F5C\uFF09");
|
|
3587
3693
|
return;
|
|
3588
3694
|
}
|
|
3589
|
-
await removeLegacyAutostart();
|
|
3590
|
-
const plistPath = launchAgentPath();
|
|
3591
3695
|
const uid = typeof process.getuid === "function" ? process.getuid?.() : 0;
|
|
3592
3696
|
try {
|
|
3593
|
-
|
|
3697
|
+
execFileSync2("launchctl", ["bootout", `gui:${uid}`, plistPath], { stdio: "pipe" });
|
|
3594
3698
|
} catch {
|
|
3595
3699
|
}
|
|
3596
|
-
|
|
3700
|
+
rmSync3(plistPath, { force: true });
|
|
3597
3701
|
}
|
|
3598
|
-
var LABEL, LEGACY_LABELS;
|
|
3599
|
-
var init_portal_autostart = __esm({
|
|
3600
|
-
"src/daemon/portal-autostart.ts"() {
|
|
3601
|
-
"use strict";
|
|
3602
|
-
init_spawn();
|
|
3603
|
-
LABEL = "com.morula.runtime.portal";
|
|
3604
|
-
LEGACY_LABELS = ["com.morula-cli.portal", "com.agentctl.portal"];
|
|
3605
|
-
}
|
|
3606
|
-
});
|
|
3607
|
-
|
|
3608
|
-
// src/index.ts
|
|
3609
|
-
init_dist2();
|
|
3610
|
-
|
|
3611
|
-
// src/commands/registry-runtime.ts
|
|
3612
|
-
init_dist2();
|
|
3613
|
-
|
|
3614
|
-
// src/build-env.ts
|
|
3615
|
-
var BUILD_VERSION2 = "0.1.0-alpha.5";
|
|
3616
3702
|
|
|
3617
3703
|
// src/commands/daemon.ts
|
|
3618
|
-
init_dist();
|
|
3619
|
-
init_dist2();
|
|
3620
3704
|
init_daemon_config();
|
|
3621
3705
|
init_daemon_lock();
|
|
3622
3706
|
init_portal_server();
|
|
3623
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync11, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
3624
|
-
import { homedir as homedir11 } from "node:os";
|
|
3625
|
-
import { dirname as dirname8, join as join19 } from "node:path";
|
|
3626
3707
|
|
|
3627
3708
|
// src/daemon/runner.ts
|
|
3628
3709
|
init_dist2();
|
|
3629
|
-
import { execFileSync as
|
|
3630
|
-
import { existsSync as
|
|
3710
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3711
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync11, statSync as statSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
3631
3712
|
import { arch, hostname } from "node:os";
|
|
3632
|
-
import { dirname as
|
|
3713
|
+
import { dirname as dirname7, join as join18 } from "node:path";
|
|
3633
3714
|
|
|
3634
3715
|
// src/daemon/acp/acp-connection-pool.ts
|
|
3635
3716
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -4463,9 +4544,9 @@ function isFlagEnabled(name, defaultValue = true) {
|
|
|
4463
4544
|
}
|
|
4464
4545
|
|
|
4465
4546
|
// src/daemon/git-env.ts
|
|
4466
|
-
import { execFileSync as
|
|
4467
|
-
import { existsSync as
|
|
4468
|
-
import { join as
|
|
4547
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
4548
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "node:fs";
|
|
4549
|
+
import { join as join11 } from "node:path";
|
|
4469
4550
|
var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
|
|
4470
4551
|
function prependToPath(dirs) {
|
|
4471
4552
|
const cur = process.env.PATH ?? "";
|
|
@@ -4477,18 +4558,18 @@ function prependToPath(dirs) {
|
|
|
4477
4558
|
}
|
|
4478
4559
|
function ensureGitOnPath() {
|
|
4479
4560
|
try {
|
|
4480
|
-
|
|
4561
|
+
execFileSync6("git", ["--version"], { stdio: "pipe" });
|
|
4481
4562
|
return;
|
|
4482
4563
|
} catch {
|
|
4483
4564
|
}
|
|
4484
|
-
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) =>
|
|
4565
|
+
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync8(join11(d, "git"))));
|
|
4485
4566
|
if (missing.length) {
|
|
4486
4567
|
console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
4487
4568
|
}
|
|
4488
4569
|
}
|
|
4489
4570
|
function ensureNodeOnPath() {
|
|
4490
|
-
const nodeDir = process.execPath ?
|
|
4491
|
-
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d &&
|
|
4571
|
+
const nodeDir = process.execPath ? join11(process.execPath, "..") : "";
|
|
4572
|
+
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync8(join11(d, "node")));
|
|
4492
4573
|
const missing = prependToPath(candidates);
|
|
4493
4574
|
if (missing.length) {
|
|
4494
4575
|
console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
@@ -4525,18 +4606,18 @@ function buildGitlabSshRemote(repoFullName, baseUrl) {
|
|
|
4525
4606
|
return `git@${host}:${repoFullName}.git`;
|
|
4526
4607
|
}
|
|
4527
4608
|
function ensureRepo(workdir, tenantId, repo, env, remoteUrl) {
|
|
4528
|
-
const repoDir =
|
|
4529
|
-
if (
|
|
4609
|
+
const repoDir = join11(workdir, tenantId, repo);
|
|
4610
|
+
if (existsSync8(join11(repoDir, ".git"))) {
|
|
4530
4611
|
if (remoteUrl)
|
|
4531
4612
|
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4532
4613
|
try {
|
|
4533
|
-
|
|
4614
|
+
execFileSync6("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
|
|
4534
4615
|
} catch {
|
|
4535
4616
|
}
|
|
4536
4617
|
return repoDir;
|
|
4537
4618
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4619
|
+
mkdirSync6(repoDir, { recursive: true });
|
|
4620
|
+
execFileSync6("git", ["clone", "--filter=blob:none", remoteUrl ?? `https://github.com/${repo}`, repoDir], {
|
|
4540
4621
|
env,
|
|
4541
4622
|
stdio: "pipe"
|
|
4542
4623
|
});
|
|
@@ -4544,17 +4625,17 @@ function ensureRepo(workdir, tenantId, repo, env, remoteUrl) {
|
|
|
4544
4625
|
}
|
|
4545
4626
|
function syncRemoteUrl(repoDir, remoteUrl, env) {
|
|
4546
4627
|
try {
|
|
4547
|
-
const cur =
|
|
4628
|
+
const cur = execFileSync6("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
|
|
4548
4629
|
if (cur === remoteUrl)
|
|
4549
4630
|
return;
|
|
4550
|
-
|
|
4631
|
+
execFileSync6("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
|
|
4551
4632
|
} catch {
|
|
4552
4633
|
}
|
|
4553
4634
|
}
|
|
4554
4635
|
function isLinkedWorktree(repoDir) {
|
|
4555
4636
|
try {
|
|
4556
|
-
const gitDir =
|
|
4557
|
-
return !!gitDir &&
|
|
4637
|
+
const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4638
|
+
return !!gitDir && existsSync8(join11(gitDir, "commondir"));
|
|
4558
4639
|
} catch {
|
|
4559
4640
|
return false;
|
|
4560
4641
|
}
|
|
@@ -4562,7 +4643,7 @@ function isLinkedWorktree(repoDir) {
|
|
|
4562
4643
|
function enableWorktreeConfig(repoDir) {
|
|
4563
4644
|
const getLocal = () => {
|
|
4564
4645
|
try {
|
|
4565
|
-
return
|
|
4646
|
+
return execFileSync6("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4566
4647
|
} catch {
|
|
4567
4648
|
return null;
|
|
4568
4649
|
}
|
|
@@ -4570,7 +4651,7 @@ function enableWorktreeConfig(repoDir) {
|
|
|
4570
4651
|
if (getLocal() === "true")
|
|
4571
4652
|
return true;
|
|
4572
4653
|
try {
|
|
4573
|
-
|
|
4654
|
+
execFileSync6("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
|
|
4574
4655
|
return true;
|
|
4575
4656
|
} catch {
|
|
4576
4657
|
return getLocal() === "true";
|
|
@@ -4581,41 +4662,41 @@ function ensureGitIdentity(repoDir, personaName) {
|
|
|
4581
4662
|
const email = `${personaName.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}@morula.local`;
|
|
4582
4663
|
if (isLinkedWorktree(repoDir)) {
|
|
4583
4664
|
if (enableWorktreeConfig(repoDir)) {
|
|
4584
|
-
|
|
4585
|
-
|
|
4665
|
+
execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
|
|
4666
|
+
execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
|
|
4586
4667
|
return;
|
|
4587
4668
|
}
|
|
4588
4669
|
console.warn(`[daemon] worktree \u7EA7 config \u4E0D\u53EF\u7528\uFF08extensions.worktreeConfig \u542F\u7528\u5931\u8D25\uFF09\uFF0C\u9000\u56DE\u5199\u5171\u4EAB config: ${repoDir}`);
|
|
4589
4670
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4671
|
+
execFileSync6("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
|
|
4672
|
+
execFileSync6("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
|
|
4592
4673
|
}
|
|
4593
4674
|
|
|
4594
4675
|
// src/daemon/outbox.ts
|
|
4595
|
-
import { existsSync as
|
|
4596
|
-
import { homedir as
|
|
4597
|
-
import { join as
|
|
4676
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4677
|
+
import { homedir as homedir9 } from "node:os";
|
|
4678
|
+
import { join as join12 } from "node:path";
|
|
4598
4679
|
var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
|
|
4599
4680
|
var OUTBOX_MAX_AGE_MS = 24 * 36e5;
|
|
4600
4681
|
function outboxDir(configDir) {
|
|
4601
|
-
const base = configDir ?? process.env.MORULA_CONFIG_DIR ??
|
|
4602
|
-
return
|
|
4682
|
+
const base = configDir ?? process.env.MORULA_CONFIG_DIR ?? join12(homedir9(), ".morula-cli");
|
|
4683
|
+
return join12(base, "daemon", "outbox");
|
|
4603
4684
|
}
|
|
4604
4685
|
function writeOutbox(entry, configDir) {
|
|
4605
4686
|
const dir = outboxDir(configDir);
|
|
4606
|
-
|
|
4607
|
-
|
|
4687
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
4688
|
+
writeFileSync5(join12(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
|
|
4608
4689
|
}
|
|
4609
4690
|
function readOutboxEntries(configDir) {
|
|
4610
4691
|
const dir = outboxDir(configDir);
|
|
4611
|
-
if (!
|
|
4692
|
+
if (!existsSync9(dir))
|
|
4612
4693
|
return [];
|
|
4613
4694
|
const entries = [];
|
|
4614
4695
|
for (const file of readdirSync2(dir)) {
|
|
4615
4696
|
if (!file.endsWith(".json"))
|
|
4616
4697
|
continue;
|
|
4617
4698
|
try {
|
|
4618
|
-
const raw = readFileSync6(
|
|
4699
|
+
const raw = readFileSync6(join12(dir, file), "utf8");
|
|
4619
4700
|
const parsed = JSON.parse(raw);
|
|
4620
4701
|
if (parsed && typeof parsed.taskId === "string")
|
|
4621
4702
|
entries.push(parsed);
|
|
@@ -4625,17 +4706,17 @@ function readOutboxEntries(configDir) {
|
|
|
4625
4706
|
return entries;
|
|
4626
4707
|
}
|
|
4627
4708
|
function removeOutbox(taskId, configDir) {
|
|
4628
|
-
|
|
4709
|
+
rmSync6(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
|
|
4629
4710
|
}
|
|
4630
4711
|
function staleDir(configDir) {
|
|
4631
|
-
return
|
|
4712
|
+
return join12(outboxDir(configDir), "stale");
|
|
4632
4713
|
}
|
|
4633
4714
|
function archiveOutbox(entry, reason, configDir) {
|
|
4634
4715
|
const dir = staleDir(configDir);
|
|
4635
|
-
|
|
4716
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
4636
4717
|
try {
|
|
4637
|
-
|
|
4638
|
-
|
|
4718
|
+
writeFileSync5(
|
|
4719
|
+
join12(dir, `${entry.taskId}.json`),
|
|
4639
4720
|
JSON.stringify({ ...entry, archivedReason: reason, archivedAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
4640
4721
|
{ mode: 384 }
|
|
4641
4722
|
);
|
|
@@ -5622,8 +5703,8 @@ var DimAgentAdapter = class {
|
|
|
5622
5703
|
}
|
|
5623
5704
|
}
|
|
5624
5705
|
async probe() {
|
|
5625
|
-
const { existsSync:
|
|
5626
|
-
const installed =
|
|
5706
|
+
const { existsSync: existsSync19 } = await import("node:fs");
|
|
5707
|
+
const installed = existsSync19(this.dimPath) || this.dimPath === "dim";
|
|
5627
5708
|
let authStatus = installed ? "unknown" : "error";
|
|
5628
5709
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
5629
5710
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -5914,15 +5995,15 @@ function pickLegacyDimModels(inventory) {
|
|
|
5914
5995
|
}
|
|
5915
5996
|
|
|
5916
5997
|
// src/daemon/repo-cache.ts
|
|
5917
|
-
import { execFileSync as
|
|
5918
|
-
import { existsSync as
|
|
5919
|
-
import { dirname as
|
|
5998
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
5999
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
|
|
6000
|
+
import { dirname as dirname6, join as join14 } from "node:path";
|
|
5920
6001
|
function ownerOf(repoFullName) {
|
|
5921
6002
|
const idx = repoFullName.indexOf("/");
|
|
5922
6003
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
5923
6004
|
}
|
|
5924
6005
|
function buildRepoCachePath(workdir, tenantId, repoFullName) {
|
|
5925
|
-
return
|
|
6006
|
+
return join14(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
|
|
5926
6007
|
}
|
|
5927
6008
|
function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
|
|
5928
6009
|
const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
|
|
@@ -5930,20 +6011,20 @@ function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
|
|
|
5930
6011
|
return {
|
|
5931
6012
|
repoCachePath,
|
|
5932
6013
|
ensure() {
|
|
5933
|
-
if (
|
|
6014
|
+
if (existsSync11(join14(repoCachePath, ".git"))) {
|
|
5934
6015
|
if (url)
|
|
5935
6016
|
syncRemoteUrl(repoCachePath, url, env);
|
|
5936
6017
|
return;
|
|
5937
6018
|
}
|
|
5938
|
-
|
|
5939
|
-
|
|
6019
|
+
mkdirSync8(dirname6(repoCachePath), { recursive: true });
|
|
6020
|
+
execFileSync7("git", ["clone", "--filter=blob:none", url, repoCachePath], {
|
|
5940
6021
|
env,
|
|
5941
6022
|
stdio: "pipe"
|
|
5942
6023
|
});
|
|
5943
6024
|
},
|
|
5944
6025
|
fetch() {
|
|
5945
6026
|
try {
|
|
5946
|
-
|
|
6027
|
+
execFileSync7("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
|
|
5947
6028
|
} catch {
|
|
5948
6029
|
}
|
|
5949
6030
|
}
|
|
@@ -5952,30 +6033,30 @@ function openRepoCache(workdir, tenantId, repoFullName, env, remoteUrl) {
|
|
|
5952
6033
|
|
|
5953
6034
|
// src/daemon/repo-checkout-service.ts
|
|
5954
6035
|
import { createServer as createServer3 } from "node:http";
|
|
5955
|
-
import { execFileSync as
|
|
5956
|
-
import { existsSync as
|
|
5957
|
-
import { basename as basename2, join as
|
|
6036
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
6037
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6038
|
+
import { basename as basename2, join as join17 } from "node:path";
|
|
5958
6039
|
|
|
5959
6040
|
// src/daemon/worktree-manager.ts
|
|
5960
|
-
import { execFileSync as
|
|
5961
|
-
import { existsSync as
|
|
5962
|
-
import { basename, join as
|
|
6041
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
6042
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6043
|
+
import { basename, join as join16, sep as sep2 } from "node:path";
|
|
5963
6044
|
|
|
5964
6045
|
// src/daemon/workspace-marker.ts
|
|
5965
|
-
import { existsSync as
|
|
5966
|
-
import { join as
|
|
6046
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6047
|
+
import { join as join15 } from "node:path";
|
|
5967
6048
|
var WORKTREE_MARKER_FILE = ".morula-worktree.json";
|
|
5968
6049
|
var WORKTREE_MARKER_SCHEMA = 1;
|
|
5969
6050
|
function markerPath(worktreePath) {
|
|
5970
|
-
return
|
|
6051
|
+
return join15(worktreePath, WORKTREE_MARKER_FILE);
|
|
5971
6052
|
}
|
|
5972
6053
|
function writeWorktreeMarker(worktreePath, marker) {
|
|
5973
|
-
|
|
6054
|
+
writeFileSync6(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
|
|
5974
6055
|
`, "utf8");
|
|
5975
6056
|
}
|
|
5976
6057
|
function readWorktreeMarker(worktreePath) {
|
|
5977
6058
|
const p = markerPath(worktreePath);
|
|
5978
|
-
if (!
|
|
6059
|
+
if (!existsSync12(p))
|
|
5979
6060
|
return null;
|
|
5980
6061
|
try {
|
|
5981
6062
|
const raw = JSON.parse(readFileSync8(p, "utf8"));
|
|
@@ -6002,42 +6083,42 @@ function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
|
|
|
6002
6083
|
throw new Error("WORKTREE_RUNTIME_ID_MISSING: \u7F3A\u5C11 runtimeId\uFF0C\u65E0\u6CD5\u6784\u9020\u6309\u6267\u884C\u8005\u9694\u79BB\u7684 worktree \u8DEF\u5F84");
|
|
6003
6084
|
}
|
|
6004
6085
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6005
|
-
return
|
|
6086
|
+
return join16(workdir, "worktrees", tenantId, runtimeSegment, dirName);
|
|
6006
6087
|
}
|
|
6007
6088
|
function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
|
|
6008
|
-
return
|
|
6089
|
+
return join16(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
|
|
6009
6090
|
}
|
|
6010
6091
|
function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
|
|
6011
6092
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6012
|
-
return
|
|
6093
|
+
return join16(workdir, "worktrees", tenantId, dirName);
|
|
6013
6094
|
}
|
|
6014
6095
|
var legacyPathWarned = /* @__PURE__ */ new Set();
|
|
6015
6096
|
function warnLegacyWorktreeResidue(legacyPath) {
|
|
6016
|
-
if (legacyPathWarned.has(legacyPath) || !
|
|
6097
|
+
if (legacyPathWarned.has(legacyPath) || !existsSync13(legacyPath))
|
|
6017
6098
|
return;
|
|
6018
6099
|
legacyPathWarned.add(legacyPath);
|
|
6019
6100
|
console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5F62\u6001 worktree \u6B8B\u7559\uFF08\u4E0D\u542B runtimeId \u6BB5\uFF09: ${legacyPath} \u2014\u2014 \u8BE5\u8DEF\u5F84\u5DF2\u5F03\u7528\uFF0C\u65B0\u4EFB\u52A1\u4F7F\u7528 <worktree>/<tenant>/<runtimeId8>/<task>\uFF1B\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
|
|
6020
6101
|
}
|
|
6021
6102
|
var legacyLayoutWarned = /* @__PURE__ */ new Set();
|
|
6022
6103
|
function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
|
|
6023
|
-
if (legacyLayoutWarned.has(taskRoot) ||
|
|
6104
|
+
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join16(taskRoot, WORKTREE_MARKER_FILE)))
|
|
6024
6105
|
return;
|
|
6025
6106
|
legacyLayoutWarned.add(taskRoot);
|
|
6026
6107
|
console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5E03\u5C40\u4EFB\u52A1\u5DE5\u4F5C\u533A\uFF08\u4ED3\u5E93\u76F4\u63A5\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u6839\uFF09: ${taskRoot} \u2014\u2014 \u65B0\u5E03\u5C40\u4F1A\u5728\u5176\u4E0B\u521B\u5EFA\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF0C\u65E7\u76EE\u5F55\u5185\u5BB9\u4E0D\u518D\u88AB\u4F7F\u7528\uFF1B\u5176\u4E2D\u53EF\u80FD\u6709\u672A\u63A8\u9001\u6539\u52A8\uFF0C\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
|
|
6027
6108
|
}
|
|
6028
6109
|
function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
6029
|
-
const gitignorePath =
|
|
6030
|
-
const existing =
|
|
6110
|
+
const gitignorePath = join16(worktreePath, ".gitignore");
|
|
6111
|
+
const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
|
|
6031
6112
|
const requiredLines = [WORKTREE_MARKER_FILE];
|
|
6032
6113
|
const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
|
|
6033
6114
|
if (missing.length > 0) {
|
|
6034
6115
|
const next = `${existing.trimEnd() ? `${existing.trimEnd()}
|
|
6035
6116
|
` : ""}${missing.join("\n")}
|
|
6036
6117
|
`;
|
|
6037
|
-
|
|
6118
|
+
writeFileSync7(gitignorePath, next, "utf8");
|
|
6038
6119
|
try {
|
|
6039
|
-
|
|
6040
|
-
|
|
6120
|
+
execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
|
|
6121
|
+
execFileSync8(
|
|
6041
6122
|
"git",
|
|
6042
6123
|
["-C", worktreePath, "-c", "user.name=morula-daemon", "-c", "user.email=morula-daemon@morula.local", "commit", "-q", "-m", "chore: track morula worktree marker"],
|
|
6043
6124
|
{ env, stdio: "pipe" }
|
|
@@ -6048,7 +6129,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
|
6048
6129
|
}
|
|
6049
6130
|
function currentBranch(repoPath) {
|
|
6050
6131
|
try {
|
|
6051
|
-
const out =
|
|
6132
|
+
const out = execFileSync8("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
|
|
6052
6133
|
encoding: "utf8",
|
|
6053
6134
|
stdio: "pipe"
|
|
6054
6135
|
}).trim();
|
|
@@ -6059,13 +6140,13 @@ function currentBranch(repoPath) {
|
|
|
6059
6140
|
}
|
|
6060
6141
|
function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
|
|
6061
6142
|
try {
|
|
6062
|
-
const current =
|
|
6143
|
+
const current = execFileSync8("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
|
|
6063
6144
|
if (current)
|
|
6064
6145
|
return current;
|
|
6065
6146
|
} catch {
|
|
6066
6147
|
}
|
|
6067
6148
|
try {
|
|
6068
|
-
const out =
|
|
6149
|
+
const out = execFileSync8("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
|
|
6069
6150
|
const first = out.split("\n").map((l) => l.trim()).find((l) => l && !l.includes("->"));
|
|
6070
6151
|
if (first)
|
|
6071
6152
|
return first.replace(/^origin\//, "");
|
|
@@ -6076,7 +6157,7 @@ function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
|
|
|
6076
6157
|
}
|
|
6077
6158
|
function headCommitSubject(repoPath, env) {
|
|
6078
6159
|
try {
|
|
6079
|
-
const out =
|
|
6160
|
+
const out = execFileSync8("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
|
|
6080
6161
|
encoding: "utf8",
|
|
6081
6162
|
stdio: "pipe",
|
|
6082
6163
|
...env ? { env } : {}
|
|
@@ -6088,7 +6169,7 @@ function headCommitSubject(repoPath, env) {
|
|
|
6088
6169
|
}
|
|
6089
6170
|
function hasLocalChanges(repoPath, env) {
|
|
6090
6171
|
try {
|
|
6091
|
-
const out =
|
|
6172
|
+
const out = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], {
|
|
6092
6173
|
encoding: "utf8",
|
|
6093
6174
|
stdio: "pipe",
|
|
6094
6175
|
...env ? { env } : {}
|
|
@@ -6124,17 +6205,17 @@ function isIndexLockConflict(err) {
|
|
|
6124
6205
|
return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
|
|
6125
6206
|
}
|
|
6126
6207
|
function worktreeIndexLockPath(repoCachePath, worktreePath) {
|
|
6127
|
-
return
|
|
6208
|
+
return join16(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
|
|
6128
6209
|
}
|
|
6129
6210
|
function indexLockPathFromError(text) {
|
|
6130
6211
|
const m = text.match(/[^\s'‘’"]*index\.lock/);
|
|
6131
6212
|
return m && m[0].length > "index.lock".length ? m[0] : null;
|
|
6132
6213
|
}
|
|
6133
6214
|
function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, pollMs = INDEX_LOCK_POLL_MS) {
|
|
6134
|
-
if (!
|
|
6215
|
+
if (!existsSync13(lockPath))
|
|
6135
6216
|
return true;
|
|
6136
6217
|
const deadline = Date.now() + timeoutMs;
|
|
6137
|
-
while (
|
|
6218
|
+
while (existsSync13(lockPath)) {
|
|
6138
6219
|
const remain = deadline - Date.now();
|
|
6139
6220
|
if (remain <= 0)
|
|
6140
6221
|
return false;
|
|
@@ -6144,7 +6225,7 @@ function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, p
|
|
|
6144
6225
|
}
|
|
6145
6226
|
function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
|
|
6146
6227
|
const runAdd = deps.runAdd ?? ((argv2) => {
|
|
6147
|
-
|
|
6228
|
+
execFileSync8("git", argv2, { stdio: "pipe" });
|
|
6148
6229
|
});
|
|
6149
6230
|
const argv = ["-C", repoCachePath, "worktree", "add", "--detach", worktreePath, ref];
|
|
6150
6231
|
const waitMs = deps.indexLockWaitMs ?? INDEX_LOCK_WAIT_MAX_MS;
|
|
@@ -6200,7 +6281,7 @@ function inspectForeignDirectory(dir) {
|
|
|
6200
6281
|
if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
|
|
6201
6282
|
return;
|
|
6202
6283
|
count++;
|
|
6203
|
-
const full =
|
|
6284
|
+
const full = join16(p, e.name);
|
|
6204
6285
|
try {
|
|
6205
6286
|
const st = statSync2(full);
|
|
6206
6287
|
bytes += st.size;
|
|
@@ -6238,7 +6319,7 @@ function prepareWorktree(input) {
|
|
|
6238
6319
|
const requestedBase = input.baseBranch?.trim();
|
|
6239
6320
|
const hasRef = (ref) => {
|
|
6240
6321
|
try {
|
|
6241
|
-
|
|
6322
|
+
execFileSync8("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
|
|
6242
6323
|
return true;
|
|
6243
6324
|
} catch {
|
|
6244
6325
|
return false;
|
|
@@ -6250,14 +6331,14 @@ function prepareWorktree(input) {
|
|
|
6250
6331
|
} else {
|
|
6251
6332
|
const remoteHead = (() => {
|
|
6252
6333
|
try {
|
|
6253
|
-
const out =
|
|
6334
|
+
const out = execFileSync8("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
6254
6335
|
const name = out.replace(/^refs\/remotes\/origin\//, "");
|
|
6255
6336
|
if (name && hasRef(name))
|
|
6256
6337
|
return name;
|
|
6257
6338
|
} catch {
|
|
6258
6339
|
}
|
|
6259
6340
|
try {
|
|
6260
|
-
const out =
|
|
6341
|
+
const out = execFileSync8("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
|
|
6261
6342
|
const m = out.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD/m);
|
|
6262
6343
|
return m?.[1] ?? null;
|
|
6263
6344
|
} catch {
|
|
@@ -6276,10 +6357,10 @@ function prepareWorktree(input) {
|
|
|
6276
6357
|
const worktreePath = buildWorktreePath(input.workdir, input.tenantId, input.runtimeId, input.workContextId, input.code, input.repoFullName);
|
|
6277
6358
|
warnLegacyWorktreeResidue(buildLegacyWorktreePath(input.workdir, input.tenantId, input.workContextId, input.code));
|
|
6278
6359
|
warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath);
|
|
6279
|
-
if (
|
|
6360
|
+
if (existsSync13(worktreePath)) {
|
|
6280
6361
|
const isGitRepo = (() => {
|
|
6281
6362
|
try {
|
|
6282
|
-
|
|
6363
|
+
execFileSync8("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
|
|
6283
6364
|
return true;
|
|
6284
6365
|
} catch {
|
|
6285
6366
|
return false;
|
|
@@ -6288,7 +6369,7 @@ function prepareWorktree(input) {
|
|
|
6288
6369
|
if (!isGitRepo) {
|
|
6289
6370
|
const forensics = inspectForeignDirectory(worktreePath);
|
|
6290
6371
|
try {
|
|
6291
|
-
|
|
6372
|
+
rmSync7(worktreePath, { recursive: true, force: true });
|
|
6292
6373
|
} catch {
|
|
6293
6374
|
reportForeignResidue(worktreePath, forensics, false);
|
|
6294
6375
|
throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
|
|
@@ -6306,11 +6387,11 @@ function prepareWorktree(input) {
|
|
|
6306
6387
|
}
|
|
6307
6388
|
const branch = currentBranch(worktreePath);
|
|
6308
6389
|
if (!branch) {
|
|
6309
|
-
const dirtyOut =
|
|
6390
|
+
const dirtyOut = execFileSync8("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6310
6391
|
const dirty = dirtyOut.split("\n").filter(
|
|
6311
6392
|
(l) => l.trim() && !l.trim().startsWith("?? .agents") && !l.trim().startsWith("?? .agent_context") && !l.trim().startsWith("?? .morula")
|
|
6312
6393
|
).join("\n");
|
|
6313
|
-
const extraCommits = Number(
|
|
6394
|
+
const extraCommits = Number(execFileSync8(
|
|
6314
6395
|
"git",
|
|
6315
6396
|
["-C", worktreePath, "rev-list", "--count", `origin/${input.baseBranch}..HEAD`],
|
|
6316
6397
|
{ encoding: "utf8", stdio: "pipe" }
|
|
@@ -6318,7 +6399,7 @@ function prepareWorktree(input) {
|
|
|
6318
6399
|
const rescueTag = buildRescueTag(input.workContextId, input.code);
|
|
6319
6400
|
if (dirty.length > 0) {
|
|
6320
6401
|
try {
|
|
6321
|
-
|
|
6402
|
+
execFileSync8(
|
|
6322
6403
|
"git",
|
|
6323
6404
|
["-C", worktreePath, "stash", "push", "-u", "-m", `morula-rescue ${rescueTag}`],
|
|
6324
6405
|
{ stdio: "pipe" }
|
|
@@ -6329,9 +6410,9 @@ function prepareWorktree(input) {
|
|
|
6329
6410
|
}
|
|
6330
6411
|
}
|
|
6331
6412
|
if (extraCommits > 1) {
|
|
6332
|
-
const headSha2 =
|
|
6413
|
+
const headSha2 = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6333
6414
|
try {
|
|
6334
|
-
|
|
6415
|
+
execFileSync8("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
|
|
6335
6416
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: detached \u672A\u63A8\u9001\u63D0\u4EA4\u5DF2\u5B58 refs/morula-rescue/${rescueTag}\uFF08git -C ${cache.repoCachePath} log -1 refs/morula-rescue/${rescueTag} \u67E5\u770B\uFF09`);
|
|
6336
6417
|
} catch {
|
|
6337
6418
|
throw new Error("WORKTREE_BRANCH_DRIFT: worktree \u5904\u4E8E detached \u4E14\u542B\u672A\u63A8\u9001\u63D0\u4EA4\uFF0C\u81EA\u52A8\u5F15\u7528\u4FDD\u5168\u5931\u8D25\uFF0C\u62D2\u7EDD\u81EA\u52A8\u91CD\u5EFA\uFF08\u8BF7\u5148\u4EBA\u5DE5\u5904\u7406\uFF09");
|
|
@@ -6343,8 +6424,8 @@ function prepareWorktree(input) {
|
|
|
6343
6424
|
}
|
|
6344
6425
|
}
|
|
6345
6426
|
}
|
|
6346
|
-
if (!
|
|
6347
|
-
|
|
6427
|
+
if (!existsSync13(worktreePath)) {
|
|
6428
|
+
mkdirSync9(join16(worktreePath, ".."), { recursive: true });
|
|
6348
6429
|
addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
|
|
6349
6430
|
writeWorktreeMarker(worktreePath, {
|
|
6350
6431
|
schemaVersion: 1,
|
|
@@ -6360,9 +6441,9 @@ function prepareWorktree(input) {
|
|
|
6360
6441
|
}
|
|
6361
6442
|
{
|
|
6362
6443
|
const dotMorulaRescue = `${worktreePath}.morula-rescue`;
|
|
6363
|
-
if (
|
|
6364
|
-
const dotMorulaDir =
|
|
6365
|
-
if (!
|
|
6444
|
+
if (existsSync13(dotMorulaRescue)) {
|
|
6445
|
+
const dotMorulaDir = join16(worktreePath, ".morula");
|
|
6446
|
+
if (!existsSync13(dotMorulaDir)) {
|
|
6366
6447
|
try {
|
|
6367
6448
|
renameSync2(dotMorulaRescue, dotMorulaDir);
|
|
6368
6449
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: \u65E7\u7248 .morula/ \u5907\u4EFD\u5DF2\u5F52\u8FD8\uFF08checkout \u51ED\u636E\u4E0E\u6B21\u4ED3\u5E93\u4FDD\u7559\uFF09`);
|
|
@@ -6370,11 +6451,11 @@ function prepareWorktree(input) {
|
|
|
6370
6451
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
|
|
6371
6452
|
}
|
|
6372
6453
|
} else {
|
|
6373
|
-
|
|
6454
|
+
rmSync7(dotMorulaRescue, { recursive: true, force: true });
|
|
6374
6455
|
}
|
|
6375
6456
|
}
|
|
6376
6457
|
}
|
|
6377
|
-
const headSha =
|
|
6458
|
+
const headSha = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
|
|
6378
6459
|
encoding: "utf8",
|
|
6379
6460
|
stdio: "pipe"
|
|
6380
6461
|
}).trim();
|
|
@@ -6390,10 +6471,10 @@ function prepareWorktree(input) {
|
|
|
6390
6471
|
};
|
|
6391
6472
|
}
|
|
6392
6473
|
function resolveWorkspaceLayout(root, expected) {
|
|
6393
|
-
const primaryDir =
|
|
6394
|
-
if (
|
|
6474
|
+
const primaryDir = join16(root, safeDirName(expected.repoFullName));
|
|
6475
|
+
if (existsSync13(join16(primaryDir, WORKTREE_MARKER_FILE)))
|
|
6395
6476
|
return { primaryDir, legacy: false };
|
|
6396
|
-
if (
|
|
6477
|
+
if (existsSync13(join16(root, WORKTREE_MARKER_FILE)))
|
|
6397
6478
|
return { primaryDir: root, legacy: true };
|
|
6398
6479
|
return null;
|
|
6399
6480
|
}
|
|
@@ -6408,7 +6489,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6408
6489
|
return "MARKER_MISMATCH";
|
|
6409
6490
|
const { primaryDir, legacy } = layout;
|
|
6410
6491
|
try {
|
|
6411
|
-
const status3 =
|
|
6492
|
+
const status3 = execFileSync8("git", ["-C", primaryDir, "status", "--porcelain"], {
|
|
6412
6493
|
encoding: "utf8",
|
|
6413
6494
|
stdio: "pipe"
|
|
6414
6495
|
}).trim();
|
|
@@ -6417,11 +6498,11 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6417
6498
|
).join("\n");
|
|
6418
6499
|
if (statusRelevant.length > 0)
|
|
6419
6500
|
return "WORKTREE_DIRTY";
|
|
6420
|
-
const additionalActivity = legacy ? findActivityInReposDir(
|
|
6501
|
+
const additionalActivity = legacy ? findActivityInReposDir(join16(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
|
|
6421
6502
|
if (additionalActivity)
|
|
6422
6503
|
return "WORKTREE_DIRTY";
|
|
6423
6504
|
try {
|
|
6424
|
-
const unpushed =
|
|
6505
|
+
const unpushed = execFileSync8("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
|
|
6425
6506
|
encoding: "utf8",
|
|
6426
6507
|
stdio: "pipe"
|
|
6427
6508
|
}).trim();
|
|
@@ -6437,13 +6518,13 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6437
6518
|
}
|
|
6438
6519
|
function repoHasActivity(repoPath) {
|
|
6439
6520
|
try {
|
|
6440
|
-
const status3 =
|
|
6521
|
+
const status3 = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6441
6522
|
if (status3)
|
|
6442
6523
|
return true;
|
|
6443
|
-
const branches =
|
|
6524
|
+
const branches = execFileSync8("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
|
|
6444
6525
|
for (const b of branches) {
|
|
6445
6526
|
try {
|
|
6446
|
-
const unpushed =
|
|
6527
|
+
const unpushed = execFileSync8("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6447
6528
|
if (unpushed)
|
|
6448
6529
|
return true;
|
|
6449
6530
|
} catch {
|
|
@@ -6456,7 +6537,7 @@ function repoHasActivity(repoPath) {
|
|
|
6456
6537
|
return false;
|
|
6457
6538
|
}
|
|
6458
6539
|
function findActivityInReposDir(reposDir) {
|
|
6459
|
-
if (!
|
|
6540
|
+
if (!existsSync13(reposDir))
|
|
6460
6541
|
return null;
|
|
6461
6542
|
let names = [];
|
|
6462
6543
|
try {
|
|
@@ -6465,8 +6546,8 @@ function findActivityInReposDir(reposDir) {
|
|
|
6465
6546
|
return null;
|
|
6466
6547
|
}
|
|
6467
6548
|
for (const name of names) {
|
|
6468
|
-
const repoPath =
|
|
6469
|
-
if (!
|
|
6549
|
+
const repoPath = join16(reposDir, name);
|
|
6550
|
+
if (!existsSync13(join16(repoPath, ".git")))
|
|
6470
6551
|
continue;
|
|
6471
6552
|
if (repoHasActivity(repoPath))
|
|
6472
6553
|
return repoPath;
|
|
@@ -6481,16 +6562,16 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6481
6562
|
} catch {
|
|
6482
6563
|
return null;
|
|
6483
6564
|
}
|
|
6484
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
6485
|
-
const legacyReposDir =
|
|
6486
|
-
if (
|
|
6565
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join16(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
|
|
6566
|
+
const legacyReposDir = join16(taskRoot, ".morula", "repos");
|
|
6567
|
+
if (existsSync13(legacyReposDir)) {
|
|
6487
6568
|
try {
|
|
6488
|
-
candidates.push(...readdirSync3(legacyReposDir).map((n) =>
|
|
6569
|
+
candidates.push(...readdirSync3(legacyReposDir).map((n) => join16(legacyReposDir, n)));
|
|
6489
6570
|
} catch {
|
|
6490
6571
|
}
|
|
6491
6572
|
}
|
|
6492
6573
|
for (const repoPath of candidates) {
|
|
6493
|
-
if (!
|
|
6574
|
+
if (!existsSync13(join16(repoPath, ".git")))
|
|
6494
6575
|
continue;
|
|
6495
6576
|
if (repoHasActivity(repoPath))
|
|
6496
6577
|
return repoPath;
|
|
@@ -6498,18 +6579,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6498
6579
|
return null;
|
|
6499
6580
|
}
|
|
6500
6581
|
function cleanupInjectedSkills(dir) {
|
|
6501
|
-
const injectedSkillsRoot =
|
|
6502
|
-
if (!
|
|
6582
|
+
const injectedSkillsRoot = join16(dir, ".agents", "skills");
|
|
6583
|
+
if (!existsSync13(injectedSkillsRoot))
|
|
6503
6584
|
return;
|
|
6504
6585
|
try {
|
|
6505
6586
|
for (const name of readdirSync3(injectedSkillsRoot)) {
|
|
6506
6587
|
if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
|
|
6507
|
-
|
|
6588
|
+
rmSync7(join16(injectedSkillsRoot, name), { recursive: true, force: true });
|
|
6508
6589
|
}
|
|
6509
6590
|
}
|
|
6510
6591
|
if (readdirSync3(injectedSkillsRoot).length === 0) {
|
|
6511
|
-
|
|
6512
|
-
|
|
6592
|
+
rmSync7(join16(dir, ".agents", "skills"), { recursive: true, force: true });
|
|
6593
|
+
rmSync7(join16(dir, ".agents"), { recursive: true, force: true });
|
|
6513
6594
|
}
|
|
6514
6595
|
} catch {
|
|
6515
6596
|
}
|
|
@@ -6525,11 +6606,11 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
|
|
|
6525
6606
|
for (const e of entries) {
|
|
6526
6607
|
if (!e.isDirectory() || e.name.startsWith("."))
|
|
6527
6608
|
continue;
|
|
6528
|
-
const repoPath =
|
|
6529
|
-
if (repoPath === excludeDir || !
|
|
6609
|
+
const repoPath = join16(taskRoot, e.name);
|
|
6610
|
+
if (repoPath === excludeDir || !existsSync13(join16(repoPath, ".git")))
|
|
6530
6611
|
continue;
|
|
6531
6612
|
try {
|
|
6532
|
-
const commonDir =
|
|
6613
|
+
const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6533
6614
|
if (commonDir)
|
|
6534
6615
|
dirs.add(commonDir);
|
|
6535
6616
|
} catch {
|
|
@@ -6543,37 +6624,37 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
|
|
|
6543
6624
|
throw new Error("MARKER_MISMATCH");
|
|
6544
6625
|
const { primaryDir, legacy } = layout;
|
|
6545
6626
|
if (!legacy) {
|
|
6546
|
-
if (primaryDir !==
|
|
6627
|
+
if (primaryDir !== join16(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
|
|
6547
6628
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6548
6629
|
}
|
|
6549
6630
|
cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
|
|
6550
6631
|
const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
|
|
6551
6632
|
try {
|
|
6552
|
-
|
|
6633
|
+
rmSync7(taskRoot, { recursive: true, force: true });
|
|
6553
6634
|
} catch {
|
|
6554
6635
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6555
6636
|
}
|
|
6556
6637
|
for (const dir of [repoCachePath, ...siblingCommonDirs]) {
|
|
6557
6638
|
try {
|
|
6558
|
-
|
|
6639
|
+
execFileSync8("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
|
|
6559
6640
|
} catch {
|
|
6560
6641
|
}
|
|
6561
6642
|
}
|
|
6562
6643
|
}
|
|
6563
6644
|
function removeWorktree(repoCachePath, worktreePath) {
|
|
6564
|
-
if (!
|
|
6645
|
+
if (!existsSync13(worktreePath))
|
|
6565
6646
|
return;
|
|
6566
6647
|
try {
|
|
6567
6648
|
cleanupInjectedSkills(worktreePath);
|
|
6568
|
-
|
|
6569
|
-
|
|
6649
|
+
execFileSync8("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
|
|
6650
|
+
execFileSync8("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
|
|
6570
6651
|
} catch {
|
|
6571
6652
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6572
6653
|
}
|
|
6573
6654
|
}
|
|
6574
6655
|
|
|
6575
6656
|
// src/daemon/repo-checkout-service.ts
|
|
6576
|
-
var CHECKOUT_JSON_FILE =
|
|
6657
|
+
var CHECKOUT_JSON_FILE = join17(".morula", "checkout.json");
|
|
6577
6658
|
var LocalRepoCheckoutService = class {
|
|
6578
6659
|
constructor(opts) {
|
|
6579
6660
|
this.opts = opts;
|
|
@@ -6661,9 +6742,9 @@ var LocalRepoCheckoutService = class {
|
|
|
6661
6742
|
return;
|
|
6662
6743
|
this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
|
|
6663
6744
|
try {
|
|
6664
|
-
const dir =
|
|
6665
|
-
|
|
6666
|
-
|
|
6745
|
+
const dir = join17(workdir, ".morula");
|
|
6746
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
6747
|
+
writeFileSync8(join17(dir, "checkout.json"), JSON.stringify({
|
|
6667
6748
|
taskId,
|
|
6668
6749
|
port: this.boundPort,
|
|
6669
6750
|
checkToken
|
|
@@ -6727,12 +6808,12 @@ var LocalRepoCheckoutService = class {
|
|
|
6727
6808
|
repoFullName,
|
|
6728
6809
|
env
|
|
6729
6810
|
});
|
|
6730
|
-
const headSha =
|
|
6811
|
+
const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
6731
6812
|
return {
|
|
6732
6813
|
ok: true,
|
|
6733
6814
|
path: worktreePath,
|
|
6734
6815
|
headSha,
|
|
6735
|
-
branchName:
|
|
6816
|
+
branchName: execFileSync9("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
|
|
6736
6817
|
};
|
|
6737
6818
|
} catch (e) {
|
|
6738
6819
|
return { ok: false, code: "CHECKOUT_FAILED", error: e instanceof Error ? e.message : String(e) };
|
|
@@ -6792,7 +6873,7 @@ var LocalRepoCheckoutService = class {
|
|
|
6792
6873
|
env
|
|
6793
6874
|
});
|
|
6794
6875
|
syncGitIdentity(task.primaryRepoDir ?? task.workdir, targetPath, env);
|
|
6795
|
-
const headSha =
|
|
6876
|
+
const headSha = execFileSync9("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
6796
6877
|
this.recordPreparedRepo(req.taskId, repoFullName, {
|
|
6797
6878
|
workContextId: serverRes.workContextId,
|
|
6798
6879
|
baseBranch: baseBranch || void 0,
|
|
@@ -6823,23 +6904,23 @@ function isReadOnlyRepoDir(dirName) {
|
|
|
6823
6904
|
return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
|
|
6824
6905
|
}
|
|
6825
6906
|
function resolveRepoTargetDir(taskRoot, base) {
|
|
6826
|
-
const direct =
|
|
6827
|
-
if (!
|
|
6907
|
+
const direct = join17(taskRoot, base);
|
|
6908
|
+
if (!existsSync14(direct) || existsSync14(join17(direct, ".git")))
|
|
6828
6909
|
return direct;
|
|
6829
6910
|
for (let i = 2; i < 100; i++) {
|
|
6830
|
-
const candidate =
|
|
6831
|
-
if (!
|
|
6911
|
+
const candidate = join17(taskRoot, `${base}-${i}`);
|
|
6912
|
+
if (!existsSync14(candidate))
|
|
6832
6913
|
return candidate;
|
|
6833
6914
|
}
|
|
6834
6915
|
throw new Error(`REPO_DIR_CONFLICT: ${taskRoot} \u4E0B\u65E0\u6CD5\u4E3A ${base} \u5206\u914D\u4ED3\u5E93\u76EE\u5F55`);
|
|
6835
6916
|
}
|
|
6836
6917
|
function materializeReadWorktree(input) {
|
|
6837
6918
|
const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
|
|
6838
|
-
if (
|
|
6839
|
-
|
|
6919
|
+
if (existsSync14(join17(worktreePath, ".git"))) {
|
|
6920
|
+
execFileSync9("git", ["-C", worktreePath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
|
|
6840
6921
|
} else {
|
|
6841
|
-
|
|
6842
|
-
|
|
6922
|
+
mkdirSync10(join17(worktreePath, ".."), { recursive: true });
|
|
6923
|
+
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, resolveBaseRef(input.repoCachePath, input.env)], {
|
|
6843
6924
|
env: input.env,
|
|
6844
6925
|
stdio: "pipe"
|
|
6845
6926
|
});
|
|
@@ -6849,7 +6930,7 @@ function materializeReadWorktree(input) {
|
|
|
6849
6930
|
function resolveBaseRef(repoCachePath, env) {
|
|
6850
6931
|
for (const ref of ["origin/main", "origin/master", "HEAD"]) {
|
|
6851
6932
|
try {
|
|
6852
|
-
|
|
6933
|
+
execFileSync9("git", ["-C", repoCachePath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
|
|
6853
6934
|
return ref;
|
|
6854
6935
|
} catch {
|
|
6855
6936
|
}
|
|
@@ -6864,7 +6945,7 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
|
|
|
6864
6945
|
} catch {
|
|
6865
6946
|
return [];
|
|
6866
6947
|
}
|
|
6867
|
-
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
6948
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join17(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename2(dir))).filter((dir) => existsSync14(join17(dir, ".git"))).sort();
|
|
6868
6949
|
}
|
|
6869
6950
|
function parseRepoFullNameFromRemoteUrl(url) {
|
|
6870
6951
|
const raw = url?.trim();
|
|
@@ -6886,7 +6967,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
|
|
|
6886
6967
|
}
|
|
6887
6968
|
function collectMaterializedRepoDeliveryTargets(input) {
|
|
6888
6969
|
const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
|
|
6889
|
-
const excludedDirs = [...excludedNames].map((name) =>
|
|
6970
|
+
const excludedDirs = [...excludedNames].map((name) => join17(input.taskRoot, safeRepoDirName(name)));
|
|
6890
6971
|
const recordsByDir = /* @__PURE__ */ new Map();
|
|
6891
6972
|
const recordsByDirName = /* @__PURE__ */ new Map();
|
|
6892
6973
|
for (const [repoFullName, record] of input.prepared ?? []) {
|
|
@@ -6895,7 +6976,7 @@ function collectMaterializedRepoDeliveryTargets(input) {
|
|
|
6895
6976
|
recordsByDir.set(record.dir, entry);
|
|
6896
6977
|
recordsByDirName.set(safeRepoDirName(repoFullName), entry);
|
|
6897
6978
|
}
|
|
6898
|
-
const runGit = input.gitRunner ?? ((dir, args) =>
|
|
6979
|
+
const runGit = input.gitRunner ?? ((dir, args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
|
|
6899
6980
|
const targets = [];
|
|
6900
6981
|
for (const dir of listMaterializedRepoDirs(input.taskRoot, { excludeDirs: excludedDirs })) {
|
|
6901
6982
|
const known = recordsByDir.get(dir) ?? recordsByDirName.get(basename2(dir));
|
|
@@ -6932,7 +7013,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
6932
7013
|
const read = (key) => {
|
|
6933
7014
|
for (const scope of ["--worktree", "--local"]) {
|
|
6934
7015
|
try {
|
|
6935
|
-
const out =
|
|
7016
|
+
const out = execFileSync9("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
6936
7017
|
if (out)
|
|
6937
7018
|
return out;
|
|
6938
7019
|
} catch {
|
|
@@ -6943,21 +7024,21 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
6943
7024
|
const name = read("user.name") ?? "morula-daemon[bot]";
|
|
6944
7025
|
const email = read("user.email") ?? "morula-daemon@morula.local";
|
|
6945
7026
|
try {
|
|
6946
|
-
|
|
6947
|
-
|
|
7027
|
+
execFileSync9("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
|
|
7028
|
+
execFileSync9("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
|
|
6948
7029
|
} catch {
|
|
6949
7030
|
}
|
|
6950
7031
|
}
|
|
6951
7032
|
function materializeAdditionalRepo(input) {
|
|
6952
|
-
if (
|
|
6953
|
-
|
|
7033
|
+
if (existsSync14(join17(input.targetPath, ".git"))) {
|
|
7034
|
+
execFileSync9("git", ["-C", input.targetPath, "fetch", "origin", "--prune"], { env: input.env, stdio: "pipe" });
|
|
6954
7035
|
return input.targetPath;
|
|
6955
7036
|
}
|
|
6956
7037
|
const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master", "HEAD"] : ["origin/main", "origin/master", "HEAD"];
|
|
6957
7038
|
let baseRef = null;
|
|
6958
7039
|
for (const ref of candidates) {
|
|
6959
7040
|
try {
|
|
6960
|
-
|
|
7041
|
+
execFileSync9("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
|
|
6961
7042
|
baseRef = ref;
|
|
6962
7043
|
break;
|
|
6963
7044
|
} catch {
|
|
@@ -6965,8 +7046,8 @@ function materializeAdditionalRepo(input) {
|
|
|
6965
7046
|
}
|
|
6966
7047
|
if (!baseRef)
|
|
6967
7048
|
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master / HEAD\uFF09");
|
|
6968
|
-
|
|
6969
|
-
|
|
7049
|
+
mkdirSync10(join17(input.targetPath, ".."), { recursive: true });
|
|
7050
|
+
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
|
|
6970
7051
|
env: input.env,
|
|
6971
7052
|
stdio: "pipe"
|
|
6972
7053
|
});
|
|
@@ -7435,6 +7516,14 @@ var DaemonRunner = class {
|
|
|
7435
7516
|
if (this.running)
|
|
7436
7517
|
return;
|
|
7437
7518
|
this.running = true;
|
|
7519
|
+
try {
|
|
7520
|
+
await this.startInner();
|
|
7521
|
+
} catch (error) {
|
|
7522
|
+
this.stop();
|
|
7523
|
+
throw error;
|
|
7524
|
+
}
|
|
7525
|
+
}
|
|
7526
|
+
async startInner() {
|
|
7438
7527
|
ensureGitOnPath();
|
|
7439
7528
|
ensureNodeOnPath();
|
|
7440
7529
|
{
|
|
@@ -8157,12 +8246,12 @@ var DaemonRunner = class {
|
|
|
8157
8246
|
});
|
|
8158
8247
|
if (spaceToken?.token)
|
|
8159
8248
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
8160
|
-
repoDir =
|
|
8249
|
+
repoDir = join18(cfg.workdir, "scratch", taskId);
|
|
8161
8250
|
taskRoot = repoDir;
|
|
8162
|
-
|
|
8163
|
-
const readme =
|
|
8164
|
-
if (!
|
|
8165
|
-
|
|
8251
|
+
mkdirSync11(repoDir, { recursive: true });
|
|
8252
|
+
const readme = join18(repoDir, "README.md");
|
|
8253
|
+
if (!existsSync15(readme)) {
|
|
8254
|
+
writeFileSync9(
|
|
8166
8255
|
readme,
|
|
8167
8256
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
8168
8257
|
|
|
@@ -8448,7 +8537,7 @@ var DaemonRunner = class {
|
|
|
8448
8537
|
};
|
|
8449
8538
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
8450
8539
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
8451
|
-
const canResume = !!resumeSessionId && !!resumeWorkDir &&
|
|
8540
|
+
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
|
|
8452
8541
|
const input = {
|
|
8453
8542
|
executionId: `${taskId}:1`,
|
|
8454
8543
|
// task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
|
|
@@ -8655,7 +8744,7 @@ var DaemonRunner = class {
|
|
|
8655
8744
|
const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
|
|
8656
8745
|
try {
|
|
8657
8746
|
const execEnv = mergeExecEnv(env);
|
|
8658
|
-
|
|
8747
|
+
execFileSync10("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
|
|
8659
8748
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5206\u652F\u5DF2\u8865\u63A8\u9001: ${branch}`);
|
|
8660
8749
|
const listRes = await fetch(
|
|
8661
8750
|
`${baseUrl}/api/v4/projects/${projectPath}/merge_requests?source_branch=${encodeURIComponent(branch)}&state=opened`,
|
|
@@ -8722,7 +8811,7 @@ var DaemonRunner = class {
|
|
|
8722
8811
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
8723
8812
|
return;
|
|
8724
8813
|
}
|
|
8725
|
-
const managedRoot =
|
|
8814
|
+
const managedRoot = join18(cfg.workdir, "worktrees", this.tenantId);
|
|
8726
8815
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
8727
8816
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
8728
8817
|
ok: false,
|
|
@@ -8770,38 +8859,38 @@ function isDebugLogEnabled() {
|
|
|
8770
8859
|
var spaceTokenFailureLogged = false;
|
|
8771
8860
|
function writeCustomSkills(targetDir, skills, log) {
|
|
8772
8861
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
8773
|
-
const base =
|
|
8862
|
+
const base = join18(targetDir, ".agents", "skills");
|
|
8774
8863
|
for (const skill of skills) {
|
|
8775
8864
|
const name = skill.name.trim();
|
|
8776
8865
|
if (!NAME_RE.test(name)) {
|
|
8777
8866
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
8778
8867
|
continue;
|
|
8779
8868
|
}
|
|
8780
|
-
const dir =
|
|
8781
|
-
if (
|
|
8869
|
+
const dir = join18(base, name);
|
|
8870
|
+
if (dirname7(dir) !== base) {
|
|
8782
8871
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
8783
8872
|
continue;
|
|
8784
8873
|
}
|
|
8785
|
-
|
|
8874
|
+
mkdirSync11(dir, { recursive: true });
|
|
8786
8875
|
const meta = ["---", `name: ${name}`];
|
|
8787
8876
|
if (skill.description?.trim()) {
|
|
8788
8877
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
8789
8878
|
}
|
|
8790
8879
|
meta.push("---", "");
|
|
8791
|
-
|
|
8880
|
+
writeFileSync9(join18(dir, "SKILL.md"), `${meta.join("\n")}
|
|
8792
8881
|
${skill.content ?? ""}`, "utf8");
|
|
8793
8882
|
}
|
|
8794
8883
|
}
|
|
8795
8884
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
8796
|
-
const dir =
|
|
8797
|
-
const refsDir =
|
|
8798
|
-
|
|
8885
|
+
const dir = join18(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
8886
|
+
const refsDir = join18(dir, "references");
|
|
8887
|
+
mkdirSync11(refsDir, { recursive: true });
|
|
8799
8888
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
8800
8889
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
8801
8890
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
8802
|
-
|
|
8891
|
+
writeFileSync9(join18(dir, "SKILL.md"), `${front}
|
|
8803
8892
|
${body}`, "utf8");
|
|
8804
|
-
|
|
8893
|
+
writeFileSync9(join18(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
8805
8894
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u534F\u4F5C\u5951\u7EA6 skill ${COLLABORATION_SKILL_NAME}\uFF08${isGitlab ? "GitLab" : "GitHub"} \u7248\uFF0C\u4ED3\u5E93=${repo ?? "\u65E0"}\uFF09\u5230 .agents/skills/`);
|
|
8806
8895
|
}
|
|
8807
8896
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -8832,8 +8921,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
8832
8921
|
};
|
|
8833
8922
|
}
|
|
8834
8923
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
8835
|
-
const briefDir =
|
|
8836
|
-
|
|
8924
|
+
const briefDir = join18(targetDir, ".agent_context");
|
|
8925
|
+
mkdirSync11(briefDir, { recursive: true });
|
|
8837
8926
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
8838
8927
|
|
|
8839
8928
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
@@ -8842,7 +8931,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
|
8842
8931
|
---
|
|
8843
8932
|
|
|
8844
8933
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
8845
|
-
|
|
8934
|
+
writeFileSync9(join18(briefDir, "task-context.md"), brief, "utf8");
|
|
8846
8935
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
8847
8936
|
}
|
|
8848
8937
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -8856,15 +8945,15 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
8856
8945
|
log?.(`[daemon] \u7A7A\u95F4 AGENTS.md \u5185\u5BB9\u8D85\u957F\uFF08${content.length} \u5B57\u7B26 > ${PROJECT_AGENTS_MD_MAX_CHARS}\uFF09\uFF0C\u5DF2\u622A\u65AD\u5199\u5165`);
|
|
8857
8946
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
8858
8947
|
}
|
|
8859
|
-
|
|
8860
|
-
|
|
8948
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
8949
|
+
writeFileSync9(join18(targetDir, "AGENTS.md"), `${content}
|
|
8861
8950
|
`, "utf8");
|
|
8862
8951
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u7A7A\u95F4\u9879\u76EE\u8BF4\u660E\u5230 AGENTS.md\uFF08${content.length} \u5B57\u7B26\uFF0C\u4F5C\u4E3A project.instructions \u81EA\u52A8\u52A0\u8F7D\uFF09`);
|
|
8863
8952
|
return true;
|
|
8864
8953
|
}
|
|
8865
8954
|
function gitHead(repoDir) {
|
|
8866
8955
|
try {
|
|
8867
|
-
return
|
|
8956
|
+
return execFileSync10("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
|
|
8868
8957
|
} catch {
|
|
8869
8958
|
return null;
|
|
8870
8959
|
}
|
|
@@ -8955,7 +9044,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
8955
9044
|
}
|
|
8956
9045
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
8957
9046
|
const execEnv = mergeExecEnv(env);
|
|
8958
|
-
if (!
|
|
9047
|
+
if (!existsSync15(target.dir) || !existsSync15(join18(target.dir, ".git"))) {
|
|
8959
9048
|
ops.log(`[daemon] \u4EFB\u52A1 ${taskId} \u4ED3\u5E93 ${target.repoFullName} \u4EA4\u4ED8\u8DF3\u8FC7\uFF1A\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728\uFF08cleanup/\u5206\u652F\u6539\u540D\u65F6\u5E8F\uFF09`);
|
|
8960
9049
|
return;
|
|
8961
9050
|
}
|
|
@@ -8970,7 +9059,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
|
8970
9059
|
ops.log(`[daemon] \u4EFB\u52A1 ${taskId} \u4ED3\u5E93 ${target.repoFullName} \u4EA4\u4ED8\u8DF3\u8FC7\uFF1A\u65E0\u53EF\u4EA4\u4ED8\u5206\u652F\uFF08branch=${branch ?? "null"}\uFF0C\u5360\u4F4D/\u4E3B\u5E72\u5206\u652F\u4E0D\u4EA4\u4ED8\uFF09`);
|
|
8971
9060
|
return;
|
|
8972
9061
|
}
|
|
8973
|
-
const headSha =
|
|
9062
|
+
const headSha = execFileSync10("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
|
|
8974
9063
|
const headSubject = headCommitSubject(target.dir, execEnv);
|
|
8975
9064
|
const probePushState = ops.pushState ?? resolveRemotePushState;
|
|
8976
9065
|
let dirty = hasLocalChanges(target.dir, execEnv);
|
|
@@ -9010,7 +9099,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
|
9010
9099
|
}
|
|
9011
9100
|
}
|
|
9012
9101
|
function execGit(repoPath, args, env) {
|
|
9013
|
-
return
|
|
9102
|
+
return execFileSync10("git", ["-C", repoPath, ...args], {
|
|
9014
9103
|
encoding: "utf8",
|
|
9015
9104
|
stdio: "pipe",
|
|
9016
9105
|
env,
|
|
@@ -9051,7 +9140,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
|
|
|
9051
9140
|
if (!taskCode)
|
|
9052
9141
|
return;
|
|
9053
9142
|
const execEnv = mergeExecEnv(env);
|
|
9054
|
-
const branch =
|
|
9143
|
+
const branch = execFileSync10("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
|
|
9055
9144
|
if (!branch || branch === "main" || branch === "master")
|
|
9056
9145
|
return;
|
|
9057
9146
|
if (env.MORULA_GITLAB_URL && env.MORULA_GITLAB_TOKEN) {
|
|
@@ -9060,7 +9149,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
|
|
|
9060
9149
|
}
|
|
9061
9150
|
let prList;
|
|
9062
9151
|
try {
|
|
9063
|
-
prList =
|
|
9152
|
+
prList = execFileSync10("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
|
|
9064
9153
|
cwd: repoDir,
|
|
9065
9154
|
env: execEnv,
|
|
9066
9155
|
encoding: "utf-8"
|
|
@@ -9080,7 +9169,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
|
|
|
9080
9169
|
const newBody = `${pr.body ?? ""}
|
|
9081
9170
|
|
|
9082
9171
|
Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4E8E PR \u5173\u8054\u4E0E\u5173\u95ED\u8054\u52A8\uFF09`;
|
|
9083
|
-
|
|
9172
|
+
execFileSync10("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
|
|
9084
9173
|
cwd: repoDir,
|
|
9085
9174
|
env: execEnv,
|
|
9086
9175
|
encoding: "utf-8"
|
|
@@ -9123,12 +9212,12 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
9123
9212
|
|
|
9124
9213
|
// src/commands/doctor.ts
|
|
9125
9214
|
init_dist2();
|
|
9215
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
9216
|
+
import { homedir as homedir11 } from "node:os";
|
|
9217
|
+
import { dirname as dirname8, join as join19 } from "node:path";
|
|
9126
9218
|
init_daemon_config();
|
|
9127
9219
|
init_daemon_lock();
|
|
9128
9220
|
init_dim_locate();
|
|
9129
|
-
import { existsSync as existsSync14, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
9130
|
-
import { homedir as homedir9 } from "node:os";
|
|
9131
|
-
import { dirname as dirname7, join as join17 } from "node:path";
|
|
9132
9221
|
async function doctorCommand(context) {
|
|
9133
9222
|
const probe = context.parsed.flags.probe === true || context.parsed.flags.probe === "true";
|
|
9134
9223
|
const { facts, checks } = await collectDoctor(context, probe);
|
|
@@ -9145,16 +9234,16 @@ async function doctorCommand(context) {
|
|
|
9145
9234
|
async function collectDoctor(context, probe) {
|
|
9146
9235
|
const checks = [];
|
|
9147
9236
|
const env = context.env;
|
|
9148
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
9149
|
-
const configPath = env.MORULA_CONFIG ??
|
|
9150
|
-
const daemonPath =
|
|
9151
|
-
const logFile =
|
|
9237
|
+
const configDir = env.MORULA_CONFIG_DIR ?? join19(homedir11(), ".morula-cli");
|
|
9238
|
+
const configPath = env.MORULA_CONFIG ?? join19(configDir, "config.yaml");
|
|
9239
|
+
const daemonPath = join19(dirname8(configPath), "daemon");
|
|
9240
|
+
const logFile = join19(daemonPath, "daemon.log");
|
|
9152
9241
|
const contextName = context.runtime.context ?? null;
|
|
9153
9242
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
9154
9243
|
const tokenPresent = Boolean(context.runtime.token);
|
|
9155
9244
|
const tokenSource = context.runtime.tokenSource ?? null;
|
|
9156
9245
|
let configReadable = false;
|
|
9157
|
-
if (
|
|
9246
|
+
if (existsSync16(configPath)) {
|
|
9158
9247
|
try {
|
|
9159
9248
|
readFileSync10(configPath, "utf8");
|
|
9160
9249
|
configReadable = true;
|
|
@@ -9162,10 +9251,10 @@ async function collectDoctor(context, probe) {
|
|
|
9162
9251
|
configReadable = false;
|
|
9163
9252
|
}
|
|
9164
9253
|
}
|
|
9165
|
-
const lockPath =
|
|
9254
|
+
const lockPath = join19(daemonPath, DAEMON_LOCK_FILE);
|
|
9166
9255
|
const lock = probeDaemonLock(lockPath);
|
|
9167
|
-
const pidFile =
|
|
9168
|
-
const filePid =
|
|
9256
|
+
const pidFile = join19(daemonPath, "daemon.pid");
|
|
9257
|
+
const filePid = existsSync16(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim() || 0) : 0;
|
|
9169
9258
|
const lockPid = lock.state === "active" ? lock.record.pid : 0;
|
|
9170
9259
|
const daemonPid = lockPid || (isProcessAlive(filePid) ? filePid : 0) || null;
|
|
9171
9260
|
const cfg = loadDaemonConfig(context.store);
|
|
@@ -9200,7 +9289,8 @@ async function collectDoctor(context, probe) {
|
|
|
9200
9289
|
logFile,
|
|
9201
9290
|
dimPath,
|
|
9202
9291
|
dimFound,
|
|
9203
|
-
envOverrides
|
|
9292
|
+
envOverrides,
|
|
9293
|
+
daemonAutostart: process.platform === "darwin" ? daemonAutostartEnabled() : null
|
|
9204
9294
|
};
|
|
9205
9295
|
checks.push(lock.state === "stale" ? {
|
|
9206
9296
|
id: "daemon.lock",
|
|
@@ -9226,7 +9316,7 @@ async function collectDoctor(context, probe) {
|
|
|
9226
9316
|
hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
|
|
9227
9317
|
});
|
|
9228
9318
|
}
|
|
9229
|
-
checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } :
|
|
9319
|
+
checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } : existsSync16(configPath) ? { id: "config.readable", status: "fail", message: `\u914D\u7F6E\u5B58\u5728\u4F46\u4E0D\u53EF\u8BFB\uFF08\u6743\u9650\uFF1F\uFF09\uFF1A${configPath}`, hint: `ls -l ${configPath}` } : { id: "config.readable", status: "warn", message: `\u914D\u7F6E\u6587\u4EF6\u5C1A\u4E0D\u5B58\u5728\uFF1A${configPath}`, hint: "morula-runtime auth login\uFF08\u767B\u5F55\u540E\u751F\u6210\uFF09" });
|
|
9230
9320
|
if (!dimFound) {
|
|
9231
9321
|
checks.push({
|
|
9232
9322
|
id: "dim.present",
|
|
@@ -9247,6 +9337,18 @@ async function collectDoctor(context, probe) {
|
|
|
9247
9337
|
} else {
|
|
9248
9338
|
checks.push({ id: "daemon.running", status: "ok", message: `daemon \u8FD0\u884C\u4E2D\uFF08pid=${daemonPid}\uFF09` });
|
|
9249
9339
|
}
|
|
9340
|
+
if (facts.daemonAutostart !== null) {
|
|
9341
|
+
if (facts.daemonAutostart) {
|
|
9342
|
+
checks.push({
|
|
9343
|
+
id: "daemon.autostart",
|
|
9344
|
+
status: "ok",
|
|
9345
|
+
message: `daemon \u5F00\u673A\u81EA\u542F\uFF1Aon\uFF08${daemonAutostartPlistPath()}\uFF09`,
|
|
9346
|
+
...facts.daemonRunning ? {} : { hint: `tail -n 50 ${logFile}\uFF08\u81EA\u542F\u5B9E\u4F8B\u53EF\u80FD\u6CE8\u518C\u5931\u8D25\u540E\u5DF2\u9000\u51FA\uFF09` }
|
|
9347
|
+
});
|
|
9348
|
+
} else {
|
|
9349
|
+
checks.push({ id: "daemon.autostart", status: "ok", message: "daemon \u5F00\u673A\u81EA\u542F\uFF1Aoff" });
|
|
9350
|
+
}
|
|
9351
|
+
}
|
|
9250
9352
|
if (envOverrides.includes("MORULA_CONFIG_DIR") || envOverrides.includes("MORULA_CONFIG")) {
|
|
9251
9353
|
checks.push({
|
|
9252
9354
|
id: "config.override",
|
|
@@ -9322,10 +9424,10 @@ function detectInstallMode() {
|
|
|
9322
9424
|
|
|
9323
9425
|
// src/commands/daemon.ts
|
|
9324
9426
|
function configFilePath(env) {
|
|
9325
|
-
return env.MORULA_CONFIG ??
|
|
9427
|
+
return env.MORULA_CONFIG ?? join20(env.MORULA_CONFIG_DIR ?? join20(homedir12(), ".morula-cli"), "config.yaml");
|
|
9326
9428
|
}
|
|
9327
9429
|
function daemonDir(configPath) {
|
|
9328
|
-
return
|
|
9430
|
+
return join20(dirname9(configPath), "daemon");
|
|
9329
9431
|
}
|
|
9330
9432
|
var PID_FILE = "daemon.pid";
|
|
9331
9433
|
var LOG_FILE = "daemon.log";
|
|
@@ -9343,9 +9445,11 @@ async function daemonCommand(context) {
|
|
|
9343
9445
|
return models(context);
|
|
9344
9446
|
if (action === "portal")
|
|
9345
9447
|
return portal(context);
|
|
9448
|
+
if (action === "autostart")
|
|
9449
|
+
return autostart(context);
|
|
9346
9450
|
if (action === "doctor")
|
|
9347
9451
|
return doctorCommand(context);
|
|
9348
|
-
throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|doctor>", {
|
|
9452
|
+
throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|autostart|doctor>", {
|
|
9349
9453
|
hint: "morula-runtime daemon --help"
|
|
9350
9454
|
});
|
|
9351
9455
|
}
|
|
@@ -9356,29 +9460,29 @@ async function portal(context) {
|
|
|
9356
9460
|
const { PortalServer: PortalServer2 } = await Promise.resolve().then(() => (init_portal_server(), portal_server_exports));
|
|
9357
9461
|
const { createPortalEnvironmentResolver: createPortalEnvironmentResolver2 } = await Promise.resolve().then(() => (init_portal_instances(), portal_instances_exports));
|
|
9358
9462
|
const port = Number(flagString(context.parsed.flags, "port") ?? process.env.MORULA_PORTAL_PORT ?? String(DEFAULT_PORTAL_PORT));
|
|
9359
|
-
const
|
|
9463
|
+
const autostart2 = flagString(context.parsed.flags, "autostart");
|
|
9360
9464
|
const dryRun = isDryRun(context);
|
|
9361
|
-
if (
|
|
9465
|
+
if (autostart2 === "on" || autostart2 === "off") {
|
|
9362
9466
|
const { setupAutostart: setupAutostart2, removeAutostart: removeAutostart2 } = await Promise.resolve().then(() => (init_portal_autostart(), portal_autostart_exports));
|
|
9363
9467
|
if (dryRun) {
|
|
9364
9468
|
return {
|
|
9365
9469
|
ok: true,
|
|
9366
9470
|
dryRun: true,
|
|
9367
|
-
autostart,
|
|
9471
|
+
autostart: autostart2,
|
|
9368
9472
|
planned: {
|
|
9369
|
-
action:
|
|
9473
|
+
action: autostart2 === "on" ? "register LaunchAgent (portal autostart)" : "remove LaunchAgent (portal autostart)",
|
|
9370
9474
|
note: "no file was written"
|
|
9371
9475
|
}
|
|
9372
9476
|
};
|
|
9373
9477
|
}
|
|
9374
|
-
if (
|
|
9478
|
+
if (autostart2 === "on") {
|
|
9375
9479
|
const installed = await setupAutostart2(context);
|
|
9376
9480
|
context.err(installed ? "[portal] \u5DF2\u6CE8\u518C\u5F00\u673A\u81EA\u542F" : "[portal] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u542F\u6CE8\u518C\uFF08macOS LaunchAgent \u5DF2\u652F\u6301\uFF09");
|
|
9377
9481
|
} else {
|
|
9378
9482
|
await removeAutostart2(context);
|
|
9379
9483
|
context.err("[portal] \u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F");
|
|
9380
9484
|
}
|
|
9381
|
-
return { ok: true, autostart };
|
|
9485
|
+
return { ok: true, autostart: autostart2 };
|
|
9382
9486
|
}
|
|
9383
9487
|
if (dryRun) {
|
|
9384
9488
|
return {
|
|
@@ -9425,6 +9529,75 @@ async function portal(context) {
|
|
|
9425
9529
|
});
|
|
9426
9530
|
return { running: true };
|
|
9427
9531
|
}
|
|
9532
|
+
async function autostart(context) {
|
|
9533
|
+
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9534
|
+
const state = context.parsed.positionals[0];
|
|
9535
|
+
if (state !== "on" && state !== "off") {
|
|
9536
|
+
throw usage("usage: morula-runtime daemon autostart <on|off>", {
|
|
9537
|
+
hint: "morula-runtime daemon autostart --help"
|
|
9538
|
+
});
|
|
9539
|
+
}
|
|
9540
|
+
const dryRun = isDryRun(context);
|
|
9541
|
+
if (process.platform !== "darwin") {
|
|
9542
|
+
context.err("[autostart] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF08\u4EC5 macOS LaunchAgent\uFF09");
|
|
9543
|
+
return { ok: true, autostart: state, supported: false };
|
|
9544
|
+
}
|
|
9545
|
+
const envOverridden = ["MORULA_ENV", "MORULA_CONFIG", "MORULA_CONFIG_DIR", "MORULA_API_URL", "MORULA_TOKEN", "MORULA_TASK_TOKEN"].filter((name) => Boolean(context.env[name]));
|
|
9546
|
+
const flagOverridden = ["env", "url", "token"].filter((name) => context.parsed.flags[name] !== void 0);
|
|
9547
|
+
if (envOverridden.length || flagOverridden.length) {
|
|
9548
|
+
throw new CliError(
|
|
9549
|
+
"USAGE",
|
|
9550
|
+
EXIT.USAGE,
|
|
9551
|
+
`\u5F00\u673A\u81EA\u542F\u53EA\u652F\u6301\u9ED8\u8BA4\u914D\u7F6E\u76EE\u5F55\uFF08launchd \u4E0D\u7EE7\u627F\u73AF\u5883\u53D8\u91CF/\u65D7\u6807\uFF09\uFF0C\u5F53\u524D\u4F1A\u8BDD\u5B58\u5728\u8986\u76D6\uFF1A${[...envOverridden.map((n) => `${n}`), ...flagOverridden.map((f) => `--${f}`)].join("\u3001")}`,
|
|
9552
|
+
false,
|
|
9553
|
+
{ hint: "unset \u8986\u76D6\u53D8\u91CF\u540E\u91CD\u8BD5\uFF1B\u591A\u73AF\u5883\u5171\u5B58\u8BF7\u624B\u52A8\u542F\u505C\uFF08docs/research/runtime-dev-prod-coexist.md\uFF09" }
|
|
9554
|
+
);
|
|
9555
|
+
}
|
|
9556
|
+
let identityError = null;
|
|
9557
|
+
if (dryRun) {
|
|
9558
|
+
try {
|
|
9559
|
+
resolveDaemonIdentity(context, { quiet: true });
|
|
9560
|
+
} catch (error) {
|
|
9561
|
+
identityError = error instanceof Error ? error.message : String(error);
|
|
9562
|
+
}
|
|
9563
|
+
} else {
|
|
9564
|
+
resolveDaemonIdentity(context, { quiet: true });
|
|
9565
|
+
}
|
|
9566
|
+
if (state === "on") {
|
|
9567
|
+
const plan = planDaemonAutostart();
|
|
9568
|
+
if (dryRun) {
|
|
9569
|
+
return {
|
|
9570
|
+
ok: true,
|
|
9571
|
+
dryRun: true,
|
|
9572
|
+
autostart: "on",
|
|
9573
|
+
planned: {
|
|
9574
|
+
action: "register LaunchAgent (daemon boot autostart)",
|
|
9575
|
+
...plan,
|
|
9576
|
+
note: "no file was written"
|
|
9577
|
+
},
|
|
9578
|
+
...identityError ? { blockedBy: { code: "NOT_LOGGED_IN", message: identityError, hint: "morula-runtime auth login" } } : {}
|
|
9579
|
+
};
|
|
9580
|
+
}
|
|
9581
|
+
const installed = await setupDaemonAutostart((line) => context.err(line));
|
|
9582
|
+
context.err(installed ? "[autostart] \u5DF2\u6CE8\u518C daemon \u5F00\u673A\u81EA\u542F\uFF08RunAtLoad \u5DF2\u62C9\u8D77\uFF1B\u91CD\u542F\u673A\u5668\u540E\u81EA\u52A8\u8FD0\u884C\uFF09" : "[autostart] plist \u5DF2\u5199\u5165\uFF0C\u4F46 launchctl bootstrap \u5931\u8D25\uFF08\u89C1\u4E0A\u65B9\u63D0\u793A\uFF09");
|
|
9583
|
+
return { ok: true, autostart: "on", installed };
|
|
9584
|
+
}
|
|
9585
|
+
if (dryRun) {
|
|
9586
|
+
return {
|
|
9587
|
+
ok: true,
|
|
9588
|
+
dryRun: true,
|
|
9589
|
+
autostart: "off",
|
|
9590
|
+
planned: {
|
|
9591
|
+
action: "remove LaunchAgent (daemon boot autostart)",
|
|
9592
|
+
plistPath: daemonAutostartPlistPath(),
|
|
9593
|
+
note: "no file was written"
|
|
9594
|
+
}
|
|
9595
|
+
};
|
|
9596
|
+
}
|
|
9597
|
+
await removeDaemonAutostart((line) => context.err(line));
|
|
9598
|
+
context.err("[autostart] \u5DF2\u79FB\u9664 daemon \u5F00\u673A\u81EA\u542F\uFF08\u6B63\u5728\u8FD0\u884C\u7684 daemon \u4E0D\u53D7\u5F71\u54CD\uFF09");
|
|
9599
|
+
return { ok: true, autostart: "off" };
|
|
9600
|
+
}
|
|
9428
9601
|
async function models(context) {
|
|
9429
9602
|
rejectUnknownFlags(context.parsed.flags, ["provider"]);
|
|
9430
9603
|
const { listDimModels: listDimModels2, DIM_DEFAULT_PROVIDER: DIM_DEFAULT_PROVIDER2 } = await Promise.resolve().then(() => (init_dim_model(), dim_model_exports));
|
|
@@ -9448,11 +9621,15 @@ async function run2(context) {
|
|
|
9448
9621
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
9449
9622
|
}
|
|
9450
9623
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
9451
|
-
|
|
9452
|
-
const lockPath =
|
|
9624
|
+
mkdirSync12(resolvedWorkdir, { recursive: true });
|
|
9625
|
+
const lockPath = join20(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
|
|
9453
9626
|
const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
9454
9627
|
if (!acquired.ok) {
|
|
9455
9628
|
const pid = acquired.existing?.pid;
|
|
9629
|
+
if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
|
|
9630
|
+
context.err(`[daemon] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08pid=${pid ?? "\u672A\u77E5"}\uFF09\uFF0C\u81EA\u542F\u5B9E\u4F8B\u793C\u8BA9\u9000\u51FA`);
|
|
9631
|
+
return { running: false, yieldedTo: pid ?? null };
|
|
9632
|
+
}
|
|
9456
9633
|
throw new CliError(
|
|
9457
9634
|
"ALREADY_RUNNING",
|
|
9458
9635
|
EXIT.CONFLICT,
|
|
@@ -9487,11 +9664,21 @@ async function run2(context) {
|
|
|
9487
9664
|
context.err(`[daemon] morula-runtime daemon \u524D\u53F0\u8FD0\u884C\u4E2D\uFF08Ctrl+C \u9000\u51FA\uFF09`);
|
|
9488
9665
|
try {
|
|
9489
9666
|
await runner.start();
|
|
9667
|
+
} catch (error) {
|
|
9668
|
+
if (isAuthRejected(error)) {
|
|
9669
|
+
context.err(`[daemon] \u6CE8\u518C\u88AB\u62D2\uFF08\u767B\u5F55\u6001\u5931\u6548\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
9670
|
+
context.err("[daemon] \u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u6B21\u542F\u52A8\uFF1Amorula-runtime auth login && morula-runtime daemon start");
|
|
9671
|
+
return { running: false, stoppedReason: "auth" };
|
|
9672
|
+
}
|
|
9673
|
+
throw error;
|
|
9490
9674
|
} finally {
|
|
9491
9675
|
releaseLock();
|
|
9492
9676
|
}
|
|
9493
9677
|
return { running: false };
|
|
9494
9678
|
}
|
|
9679
|
+
function isAuthRejected(error) {
|
|
9680
|
+
return error instanceof MorulaApiError && (error.code === "UNAUTHENTICATED" || error.status === 401);
|
|
9681
|
+
}
|
|
9495
9682
|
async function start(context) {
|
|
9496
9683
|
rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent", "dry-run"]);
|
|
9497
9684
|
const name = flagString(context.parsed.flags, "name") ?? void 0;
|
|
@@ -9509,10 +9696,10 @@ async function start(context) {
|
|
|
9509
9696
|
}
|
|
9510
9697
|
const dir = daemonDir(configFilePath(context.env));
|
|
9511
9698
|
if (!dryRun)
|
|
9512
|
-
|
|
9513
|
-
const pidFile =
|
|
9514
|
-
const logFile =
|
|
9515
|
-
const lockPath =
|
|
9699
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
9700
|
+
const pidFile = join20(dir, PID_FILE);
|
|
9701
|
+
const logFile = join20(dir, LOG_FILE);
|
|
9702
|
+
const lockPath = join20(dir, DAEMON_LOCK_FILE);
|
|
9516
9703
|
const lock = probeDaemonLock(lockPath);
|
|
9517
9704
|
if (lock.state === "active") {
|
|
9518
9705
|
throw new CliError(
|
|
@@ -9523,7 +9710,7 @@ async function start(context) {
|
|
|
9523
9710
|
{ hint: "morula-runtime daemon status" }
|
|
9524
9711
|
);
|
|
9525
9712
|
}
|
|
9526
|
-
if (
|
|
9713
|
+
if (existsSync17(pidFile)) {
|
|
9527
9714
|
const pid = Number(readFileSync11(pidFile, "utf8").trim());
|
|
9528
9715
|
if (isProcessAlive(pid)) {
|
|
9529
9716
|
throw new CliError("ALREADY_RUNNING", EXIT.CONFLICT, `daemon \u5DF2\u5728\u8FD0\u884C\uFF08pid=${pid}\uFF09\uFF1B\u5982\u9700\u5F3A\u5236\u91CD\u542F\u8BF7\u5148\u6267\u884C kill -9 ${pid}`, false, {
|
|
@@ -9531,7 +9718,7 @@ async function start(context) {
|
|
|
9531
9718
|
});
|
|
9532
9719
|
}
|
|
9533
9720
|
if (!dryRun)
|
|
9534
|
-
|
|
9721
|
+
rmSync8(pidFile, { force: true });
|
|
9535
9722
|
}
|
|
9536
9723
|
const env = {
|
|
9537
9724
|
...process.env,
|
|
@@ -9569,12 +9756,12 @@ async function start(context) {
|
|
|
9569
9756
|
const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
|
|
9570
9757
|
const result = spawnDaemonProcess2({ logFile, env });
|
|
9571
9758
|
if (result.error || result.pid <= 0) {
|
|
9572
|
-
|
|
9759
|
+
rmSync8(pidFile, { force: true });
|
|
9573
9760
|
throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
|
|
9574
9761
|
hint: `tail -n 50 ${logFile}`
|
|
9575
9762
|
});
|
|
9576
9763
|
}
|
|
9577
|
-
|
|
9764
|
+
writeFileSync10(pidFile, String(result.pid), { mode: 384 });
|
|
9578
9765
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
9579
9766
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
9580
9767
|
}
|
|
@@ -9582,11 +9769,11 @@ async function stop(context) {
|
|
|
9582
9769
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9583
9770
|
const dryRun = isDryRun(context);
|
|
9584
9771
|
const dir = daemonDir(configFilePath(context.env));
|
|
9585
|
-
const pidFile =
|
|
9586
|
-
const lockPath =
|
|
9772
|
+
const pidFile = join20(dir, PID_FILE);
|
|
9773
|
+
const lockPath = join20(dir, DAEMON_LOCK_FILE);
|
|
9587
9774
|
const lock = probeDaemonLock(lockPath);
|
|
9588
9775
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
9589
|
-
const filePid =
|
|
9776
|
+
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
|
|
9590
9777
|
const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
|
|
9591
9778
|
if (dryRun) {
|
|
9592
9779
|
return {
|
|
@@ -9596,7 +9783,7 @@ async function stop(context) {
|
|
|
9596
9783
|
};
|
|
9597
9784
|
}
|
|
9598
9785
|
if (!pid) {
|
|
9599
|
-
|
|
9786
|
+
rmSync8(pidFile, { force: true });
|
|
9600
9787
|
releaseDaemonLock(lockPath);
|
|
9601
9788
|
return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
|
|
9602
9789
|
}
|
|
@@ -9605,7 +9792,7 @@ async function stop(context) {
|
|
|
9605
9792
|
releaseDaemonLock(lockPath);
|
|
9606
9793
|
else
|
|
9607
9794
|
releaseDaemonLock(lockPath, pid);
|
|
9608
|
-
|
|
9795
|
+
rmSync8(pidFile, { force: true });
|
|
9609
9796
|
if (result.outcome === "killed")
|
|
9610
9797
|
context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
|
|
9611
9798
|
return { running: false, stoppedPid: pid, outcome: result.outcome };
|
|
@@ -9614,11 +9801,11 @@ async function status2(context) {
|
|
|
9614
9801
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
9615
9802
|
const cfg = loadDaemonConfig(context.store);
|
|
9616
9803
|
const dir = daemonDir(configFilePath(context.env));
|
|
9617
|
-
const pidFile =
|
|
9618
|
-
const lockPath =
|
|
9804
|
+
const pidFile = join20(dir, PID_FILE);
|
|
9805
|
+
const lockPath = join20(dir, DAEMON_LOCK_FILE);
|
|
9619
9806
|
const lock = probeDaemonLock(lockPath);
|
|
9620
9807
|
const lockPid = lock.state === "active" ? lock.record.pid : null;
|
|
9621
|
-
const filePid =
|
|
9808
|
+
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
|
|
9622
9809
|
const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
|
|
9623
9810
|
return {
|
|
9624
9811
|
running: pid !== null,
|
|
@@ -9628,7 +9815,7 @@ async function status2(context) {
|
|
|
9628
9815
|
name: cfg?.name ?? null,
|
|
9629
9816
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
9630
9817
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
9631
|
-
logFile:
|
|
9818
|
+
logFile: join20(dir, LOG_FILE),
|
|
9632
9819
|
lockFile: lockPath
|
|
9633
9820
|
};
|
|
9634
9821
|
}
|
|
@@ -9658,32 +9845,32 @@ function parseMaxConcurrent(context) {
|
|
|
9658
9845
|
init_dist2();
|
|
9659
9846
|
|
|
9660
9847
|
// src/daemon/plugin-register.ts
|
|
9661
|
-
import { chmodSync as chmodSync3, cpSync, existsSync as
|
|
9662
|
-
import { homedir as
|
|
9663
|
-
import { dirname as
|
|
9848
|
+
import { chmodSync as chmodSync3, cpSync, existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9849
|
+
import { homedir as homedir13 } from "node:os";
|
|
9850
|
+
import { dirname as dirname10, join as join21 } from "node:path";
|
|
9664
9851
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9665
9852
|
var PLUGIN_NAME = "morula-cli";
|
|
9666
9853
|
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
9667
|
-
function legacyAgentsPluginDir(home =
|
|
9668
|
-
return
|
|
9854
|
+
function legacyAgentsPluginDir(home = homedir13()) {
|
|
9855
|
+
return join21(home, ".agents", "plugins", PLUGIN_NAME);
|
|
9669
9856
|
}
|
|
9670
|
-
function resolveDimcodeHome(env = process.env, home =
|
|
9857
|
+
function resolveDimcodeHome(env = process.env, home = homedir13()) {
|
|
9671
9858
|
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
9672
|
-
return fromEnv ||
|
|
9859
|
+
return fromEnv || join21(home, ".dimcode", "v2");
|
|
9673
9860
|
}
|
|
9674
|
-
function defaultPluginsRoot(env = process.env, home =
|
|
9675
|
-
return
|
|
9861
|
+
function defaultPluginsRoot(env = process.env, home = homedir13()) {
|
|
9862
|
+
return join21(resolveDimcodeHome(env, home), "plugins");
|
|
9676
9863
|
}
|
|
9677
9864
|
function resolvePackagePaths(entryUrl) {
|
|
9678
|
-
const distDir =
|
|
9865
|
+
const distDir = dirname10(fileURLToPath2(entryUrl));
|
|
9679
9866
|
return {
|
|
9680
|
-
packageRoot:
|
|
9867
|
+
packageRoot: dirname10(distDir),
|
|
9681
9868
|
distDir,
|
|
9682
|
-
assetsDir:
|
|
9869
|
+
assetsDir: join21(distDir, "plugin")
|
|
9683
9870
|
};
|
|
9684
9871
|
}
|
|
9685
9872
|
function buildLauncherScript(opts) {
|
|
9686
|
-
const entry =
|
|
9873
|
+
const entry = join21(opts.packageRoot, "dist", "morula.js");
|
|
9687
9874
|
return `#!/bin/sh
|
|
9688
9875
|
# Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
|
|
9689
9876
|
# \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
|
|
@@ -9697,15 +9884,15 @@ exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
|
9697
9884
|
`;
|
|
9698
9885
|
}
|
|
9699
9886
|
function assetsReady(paths) {
|
|
9700
|
-
return
|
|
9887
|
+
return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
|
|
9701
9888
|
}
|
|
9702
9889
|
function listSkillNames(assetsDir) {
|
|
9703
|
-
const dir =
|
|
9704
|
-
if (!
|
|
9890
|
+
const dir = join21(assetsDir, "skills");
|
|
9891
|
+
if (!existsSync18(dir))
|
|
9705
9892
|
return [];
|
|
9706
9893
|
return readdirSync5(dir).filter((name) => {
|
|
9707
9894
|
try {
|
|
9708
|
-
return statSync4(
|
|
9895
|
+
return statSync4(join21(dir, name)).isDirectory();
|
|
9709
9896
|
} catch {
|
|
9710
9897
|
return false;
|
|
9711
9898
|
}
|
|
@@ -9714,24 +9901,24 @@ function listSkillNames(assetsDir) {
|
|
|
9714
9901
|
function registerPlugin(opts) {
|
|
9715
9902
|
const { pluginsRoot, paths, version } = opts;
|
|
9716
9903
|
const nodePath = opts.nodePath ?? process.execPath;
|
|
9717
|
-
const pluginDir =
|
|
9904
|
+
const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
|
|
9718
9905
|
if (!assetsReady(paths))
|
|
9719
9906
|
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
9720
|
-
const skillsSrc =
|
|
9907
|
+
const skillsSrc = join21(paths.assetsDir, "skills");
|
|
9721
9908
|
const skills = listSkillNames(paths.assetsDir);
|
|
9722
|
-
const manifestDir =
|
|
9723
|
-
|
|
9724
|
-
const manifest = JSON.parse(readFileSync12(
|
|
9725
|
-
|
|
9909
|
+
const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
|
|
9910
|
+
mkdirSync13(manifestDir, { recursive: true });
|
|
9911
|
+
const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
|
|
9912
|
+
writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
9726
9913
|
`);
|
|
9727
|
-
const skillsDest =
|
|
9728
|
-
|
|
9729
|
-
|
|
9914
|
+
const skillsDest = join21(pluginDir, "skills");
|
|
9915
|
+
rmSync9(skillsDest, { recursive: true, force: true });
|
|
9916
|
+
mkdirSync13(skillsDest, { recursive: true });
|
|
9730
9917
|
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
9731
|
-
const binDir =
|
|
9732
|
-
|
|
9733
|
-
const binPath =
|
|
9734
|
-
|
|
9918
|
+
const binDir = join21(pluginDir, "bin");
|
|
9919
|
+
mkdirSync13(binDir, { recursive: true });
|
|
9920
|
+
const binPath = join21(binDir, "morula");
|
|
9921
|
+
writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
9735
9922
|
chmodSync3(binPath, 493);
|
|
9736
9923
|
const legacy = legacyAgentsPluginDir(opts.home);
|
|
9737
9924
|
return {
|
|
@@ -9740,7 +9927,7 @@ function registerPlugin(opts) {
|
|
|
9740
9927
|
skills,
|
|
9741
9928
|
version,
|
|
9742
9929
|
node: nodePath,
|
|
9743
|
-
legacyDir:
|
|
9930
|
+
legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
9744
9931
|
};
|
|
9745
9932
|
}
|
|
9746
9933
|
|
|
@@ -9917,6 +10104,21 @@ var SPEC = {
|
|
|
9917
10104
|
idempotent: true,
|
|
9918
10105
|
emits: ["running", "autostart", "ok"]
|
|
9919
10106
|
},
|
|
10107
|
+
{
|
|
10108
|
+
action: "autostart",
|
|
10109
|
+
summary: "register/remove the macOS LaunchAgent so the daemon starts at login and is relaunched by launchd on abnormal exit (unattended executor machines)",
|
|
10110
|
+
args: [
|
|
10111
|
+
{ name: "state", required: true, description: "on = register LaunchAgent (RunAtLoad starts the daemon now and at every login); off = remove it (a running daemon is untouched)" }
|
|
10112
|
+
],
|
|
10113
|
+
flags: [
|
|
10114
|
+
{ name: "dry-run", type: "boolean", description: "print the LaunchAgent plan without writing anything (read-only)" }
|
|
10115
|
+
],
|
|
10116
|
+
examples: ["morula-runtime daemon autostart on", "morula-runtime daemon autostart off"],
|
|
10117
|
+
writes: "local",
|
|
10118
|
+
interactive: false,
|
|
10119
|
+
idempotent: true,
|
|
10120
|
+
emits: ["ok", "autostart", "installed", "dryRun", "planned", "blockedBy"]
|
|
10121
|
+
},
|
|
9920
10122
|
{
|
|
9921
10123
|
action: "doctor",
|
|
9922
10124
|
summary: "read-only diagnostics: node/cli/config/auth/daemon/dim facts plus checks[] with hints (offline by default; --probe goes online)",
|