@arcships/morula-runtime 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +861 -545
- 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 = "20260917073516.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.7";
|
|
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(", ")}`);
|
|
@@ -4524,37 +4605,62 @@ function buildGitlabSshRemote(repoFullName, baseUrl) {
|
|
|
4524
4605
|
return `ssh://git@${host}:${url.port}/${repoFullName}.git`;
|
|
4525
4606
|
return `git@${host}:${repoFullName}.git`;
|
|
4526
4607
|
}
|
|
4527
|
-
function
|
|
4528
|
-
|
|
4529
|
-
|
|
4608
|
+
function buildDaemonRepoUrl(repoFullName, baseUrl) {
|
|
4609
|
+
if (!baseUrl)
|
|
4610
|
+
return `https://github.com/${repoFullName}.git`;
|
|
4611
|
+
const url = new URL(baseUrl);
|
|
4612
|
+
const path = repoFullName.replace(/^\/+/, "");
|
|
4613
|
+
return `${url.protocol}//${url.host}/${path}.git`;
|
|
4614
|
+
}
|
|
4615
|
+
function ensureRepo(workdir, tenantId, repo, env, remoteUrl, daemonRemoteUrl) {
|
|
4616
|
+
const repoDir = join11(workdir, tenantId, repo);
|
|
4617
|
+
const fetchUrl = daemonRemoteUrl ?? remoteUrl ?? buildDaemonRepoUrl(repo);
|
|
4618
|
+
if (existsSync8(join11(repoDir, ".git"))) {
|
|
4530
4619
|
if (remoteUrl)
|
|
4531
4620
|
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4532
4621
|
try {
|
|
4533
|
-
|
|
4534
|
-
|
|
4622
|
+
if (daemonRemoteUrl) {
|
|
4623
|
+
execFileSync6(
|
|
4624
|
+
"git",
|
|
4625
|
+
["-C", repoDir, "fetch", "--prune", fetchUrl, "+refs/heads/*:refs/remotes/origin/*"],
|
|
4626
|
+
{ env, stdio: "pipe" }
|
|
4627
|
+
);
|
|
4628
|
+
} else {
|
|
4629
|
+
execFileSync6("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
|
|
4630
|
+
}
|
|
4631
|
+
} catch (err) {
|
|
4632
|
+
throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
|
|
4535
4633
|
}
|
|
4536
4634
|
return repoDir;
|
|
4537
4635
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
env,
|
|
4541
|
-
|
|
4542
|
-
|
|
4636
|
+
mkdirSync6(repoDir, { recursive: true });
|
|
4637
|
+
try {
|
|
4638
|
+
execFileSync6("git", ["clone", "--filter=blob:none", fetchUrl, repoDir], { env, stdio: "pipe" });
|
|
4639
|
+
} catch (err) {
|
|
4640
|
+
throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
|
|
4641
|
+
}
|
|
4642
|
+
if (remoteUrl && remoteUrl !== fetchUrl)
|
|
4643
|
+
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4543
4644
|
return repoDir;
|
|
4544
4645
|
}
|
|
4646
|
+
function gitErrorText(err) {
|
|
4647
|
+
const e = err;
|
|
4648
|
+
const stderr = e?.stderr ? e.stderr.toString().trim() : "";
|
|
4649
|
+
return stderr || e?.message || String(err);
|
|
4650
|
+
}
|
|
4545
4651
|
function syncRemoteUrl(repoDir, remoteUrl, env) {
|
|
4546
4652
|
try {
|
|
4547
|
-
const cur =
|
|
4653
|
+
const cur = execFileSync6("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
|
|
4548
4654
|
if (cur === remoteUrl)
|
|
4549
4655
|
return;
|
|
4550
|
-
|
|
4656
|
+
execFileSync6("git", ["-C", repoDir, "remote", "set-url", "origin", remoteUrl], { env, stdio: "pipe" });
|
|
4551
4657
|
} catch {
|
|
4552
4658
|
}
|
|
4553
4659
|
}
|
|
4554
4660
|
function isLinkedWorktree(repoDir) {
|
|
4555
4661
|
try {
|
|
4556
|
-
const gitDir =
|
|
4557
|
-
return !!gitDir &&
|
|
4662
|
+
const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4663
|
+
return !!gitDir && existsSync8(join11(gitDir, "commondir"));
|
|
4558
4664
|
} catch {
|
|
4559
4665
|
return false;
|
|
4560
4666
|
}
|
|
@@ -4562,7 +4668,7 @@ function isLinkedWorktree(repoDir) {
|
|
|
4562
4668
|
function enableWorktreeConfig(repoDir) {
|
|
4563
4669
|
const getLocal = () => {
|
|
4564
4670
|
try {
|
|
4565
|
-
return
|
|
4671
|
+
return execFileSync6("git", ["-C", repoDir, "config", "--local", "--get", "extensions.worktreeConfig"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4566
4672
|
} catch {
|
|
4567
4673
|
return null;
|
|
4568
4674
|
}
|
|
@@ -4570,7 +4676,7 @@ function enableWorktreeConfig(repoDir) {
|
|
|
4570
4676
|
if (getLocal() === "true")
|
|
4571
4677
|
return true;
|
|
4572
4678
|
try {
|
|
4573
|
-
|
|
4679
|
+
execFileSync6("git", ["-C", repoDir, "config", "--local", "extensions.worktreeConfig", "true"], { stdio: "pipe" });
|
|
4574
4680
|
return true;
|
|
4575
4681
|
} catch {
|
|
4576
4682
|
return getLocal() === "true";
|
|
@@ -4581,41 +4687,41 @@ function ensureGitIdentity(repoDir, personaName) {
|
|
|
4581
4687
|
const email = `${personaName.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}@morula.local`;
|
|
4582
4688
|
if (isLinkedWorktree(repoDir)) {
|
|
4583
4689
|
if (enableWorktreeConfig(repoDir)) {
|
|
4584
|
-
|
|
4585
|
-
|
|
4690
|
+
execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.name", name], { stdio: "pipe" });
|
|
4691
|
+
execFileSync6("git", ["-C", repoDir, "config", "--worktree", "user.email", email], { stdio: "pipe" });
|
|
4586
4692
|
return;
|
|
4587
4693
|
}
|
|
4588
4694
|
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
4695
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4696
|
+
execFileSync6("git", ["-C", repoDir, "config", "user.name", name], { stdio: "pipe" });
|
|
4697
|
+
execFileSync6("git", ["-C", repoDir, "config", "user.email", email], { stdio: "pipe" });
|
|
4592
4698
|
}
|
|
4593
4699
|
|
|
4594
4700
|
// src/daemon/outbox.ts
|
|
4595
|
-
import { existsSync as
|
|
4596
|
-
import { homedir as
|
|
4597
|
-
import { join as
|
|
4701
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4702
|
+
import { homedir as homedir9 } from "node:os";
|
|
4703
|
+
import { join as join12 } from "node:path";
|
|
4598
4704
|
var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
|
|
4599
4705
|
var OUTBOX_MAX_AGE_MS = 24 * 36e5;
|
|
4600
4706
|
function outboxDir(configDir) {
|
|
4601
|
-
const base = configDir ?? process.env.MORULA_CONFIG_DIR ??
|
|
4602
|
-
return
|
|
4707
|
+
const base = configDir ?? process.env.MORULA_CONFIG_DIR ?? join12(homedir9(), ".morula-cli");
|
|
4708
|
+
return join12(base, "daemon", "outbox");
|
|
4603
4709
|
}
|
|
4604
4710
|
function writeOutbox(entry, configDir) {
|
|
4605
4711
|
const dir = outboxDir(configDir);
|
|
4606
|
-
|
|
4607
|
-
|
|
4712
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
4713
|
+
writeFileSync5(join12(dir, `${entry.taskId}.json`), JSON.stringify(entry), { mode: 384 });
|
|
4608
4714
|
}
|
|
4609
4715
|
function readOutboxEntries(configDir) {
|
|
4610
4716
|
const dir = outboxDir(configDir);
|
|
4611
|
-
if (!
|
|
4717
|
+
if (!existsSync9(dir))
|
|
4612
4718
|
return [];
|
|
4613
4719
|
const entries = [];
|
|
4614
4720
|
for (const file of readdirSync2(dir)) {
|
|
4615
4721
|
if (!file.endsWith(".json"))
|
|
4616
4722
|
continue;
|
|
4617
4723
|
try {
|
|
4618
|
-
const raw = readFileSync6(
|
|
4724
|
+
const raw = readFileSync6(join12(dir, file), "utf8");
|
|
4619
4725
|
const parsed = JSON.parse(raw);
|
|
4620
4726
|
if (parsed && typeof parsed.taskId === "string")
|
|
4621
4727
|
entries.push(parsed);
|
|
@@ -4625,17 +4731,17 @@ function readOutboxEntries(configDir) {
|
|
|
4625
4731
|
return entries;
|
|
4626
4732
|
}
|
|
4627
4733
|
function removeOutbox(taskId, configDir) {
|
|
4628
|
-
|
|
4734
|
+
rmSync6(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
|
|
4629
4735
|
}
|
|
4630
4736
|
function staleDir(configDir) {
|
|
4631
|
-
return
|
|
4737
|
+
return join12(outboxDir(configDir), "stale");
|
|
4632
4738
|
}
|
|
4633
4739
|
function archiveOutbox(entry, reason, configDir) {
|
|
4634
4740
|
const dir = staleDir(configDir);
|
|
4635
|
-
|
|
4741
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
4636
4742
|
try {
|
|
4637
|
-
|
|
4638
|
-
|
|
4743
|
+
writeFileSync5(
|
|
4744
|
+
join12(dir, `${entry.taskId}.json`),
|
|
4639
4745
|
JSON.stringify({ ...entry, archivedReason: reason, archivedAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
4640
4746
|
{ mode: 384 }
|
|
4641
4747
|
);
|
|
@@ -5622,8 +5728,8 @@ var DimAgentAdapter = class {
|
|
|
5622
5728
|
}
|
|
5623
5729
|
}
|
|
5624
5730
|
async probe() {
|
|
5625
|
-
const { existsSync:
|
|
5626
|
-
const installed =
|
|
5731
|
+
const { existsSync: existsSync19 } = await import("node:fs");
|
|
5732
|
+
const installed = existsSync19(this.dimPath) || this.dimPath === "dim";
|
|
5627
5733
|
let authStatus = installed ? "unknown" : "error";
|
|
5628
5734
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
5629
5735
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -5914,68 +6020,95 @@ function pickLegacyDimModels(inventory) {
|
|
|
5914
6020
|
}
|
|
5915
6021
|
|
|
5916
6022
|
// src/daemon/repo-cache.ts
|
|
5917
|
-
import { execFileSync as
|
|
5918
|
-
import { existsSync as
|
|
5919
|
-
import { dirname as
|
|
6023
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
6024
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
|
|
6025
|
+
import { dirname as dirname6, join as join14 } from "node:path";
|
|
5920
6026
|
function ownerOf(repoFullName) {
|
|
5921
6027
|
const idx = repoFullName.indexOf("/");
|
|
5922
6028
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
5923
6029
|
}
|
|
5924
6030
|
function buildRepoCachePath(workdir, tenantId, repoFullName) {
|
|
5925
|
-
return
|
|
6031
|
+
return join14(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
|
|
5926
6032
|
}
|
|
5927
|
-
function openRepoCache(workdir, tenantId, repoFullName, env,
|
|
6033
|
+
function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
|
|
5928
6034
|
const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
|
|
5929
|
-
const
|
|
6035
|
+
const currentOrigin = () => {
|
|
6036
|
+
if (!existsSync11(join14(repoCachePath, ".git")))
|
|
6037
|
+
return null;
|
|
6038
|
+
try {
|
|
6039
|
+
return execFileSync7("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
|
|
6040
|
+
} catch {
|
|
6041
|
+
return null;
|
|
6042
|
+
}
|
|
6043
|
+
};
|
|
6044
|
+
const fetchUrl = () => daemonRemoteUrl ?? currentOrigin() ?? agentRemoteUrl ?? `https://github.com/${repoFullName}.git`;
|
|
6045
|
+
const originUrl = () => agentRemoteUrl ?? fetchUrl();
|
|
5930
6046
|
return {
|
|
5931
6047
|
repoCachePath,
|
|
5932
6048
|
ensure() {
|
|
5933
|
-
if (
|
|
5934
|
-
|
|
5935
|
-
syncRemoteUrl(repoCachePath, url, env);
|
|
6049
|
+
if (existsSync11(join14(repoCachePath, ".git"))) {
|
|
6050
|
+
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
5936
6051
|
return;
|
|
5937
6052
|
}
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
stdio: "pipe"
|
|
5942
|
-
})
|
|
6053
|
+
mkdirSync8(dirname6(repoCachePath), { recursive: true });
|
|
6054
|
+
const cloneUrl = fetchUrl();
|
|
6055
|
+
try {
|
|
6056
|
+
execFileSync7("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
|
|
6057
|
+
} catch (err) {
|
|
6058
|
+
throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
|
|
6059
|
+
}
|
|
6060
|
+
if (originUrl() !== cloneUrl)
|
|
6061
|
+
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
5943
6062
|
},
|
|
5944
6063
|
fetch() {
|
|
5945
6064
|
try {
|
|
5946
|
-
|
|
5947
|
-
|
|
6065
|
+
if (daemonRemoteUrl) {
|
|
6066
|
+
execFileSync7(
|
|
6067
|
+
"git",
|
|
6068
|
+
["-C", repoCachePath, "fetch", "--prune", daemonRemoteUrl, "+refs/heads/*:refs/remotes/origin/*"],
|
|
6069
|
+
{ env, stdio: "pipe" }
|
|
6070
|
+
);
|
|
6071
|
+
} else {
|
|
6072
|
+
execFileSync7("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
|
|
6073
|
+
}
|
|
6074
|
+
} catch (err) {
|
|
6075
|
+
throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
|
|
5948
6076
|
}
|
|
5949
6077
|
}
|
|
5950
6078
|
};
|
|
5951
6079
|
}
|
|
6080
|
+
function gitErrorText2(err) {
|
|
6081
|
+
const e = err;
|
|
6082
|
+
const stderr = e?.stderr ? e.stderr.toString().trim() : "";
|
|
6083
|
+
return stderr || e?.message || String(err);
|
|
6084
|
+
}
|
|
5952
6085
|
|
|
5953
6086
|
// src/daemon/repo-checkout-service.ts
|
|
5954
6087
|
import { createServer as createServer3 } from "node:http";
|
|
5955
|
-
import { execFileSync as
|
|
5956
|
-
import { existsSync as
|
|
5957
|
-
import { basename as basename2, join as
|
|
6088
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
6089
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6090
|
+
import { basename as basename2, join as join17 } from "node:path";
|
|
5958
6091
|
|
|
5959
6092
|
// src/daemon/worktree-manager.ts
|
|
5960
|
-
import { execFileSync as
|
|
5961
|
-
import { existsSync as
|
|
5962
|
-
import { basename, join as
|
|
6093
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
6094
|
+
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";
|
|
6095
|
+
import { basename, join as join16, sep as sep2 } from "node:path";
|
|
5963
6096
|
|
|
5964
6097
|
// src/daemon/workspace-marker.ts
|
|
5965
|
-
import { existsSync as
|
|
5966
|
-
import { join as
|
|
6098
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6099
|
+
import { join as join15 } from "node:path";
|
|
5967
6100
|
var WORKTREE_MARKER_FILE = ".morula-worktree.json";
|
|
5968
6101
|
var WORKTREE_MARKER_SCHEMA = 1;
|
|
5969
6102
|
function markerPath(worktreePath) {
|
|
5970
|
-
return
|
|
6103
|
+
return join15(worktreePath, WORKTREE_MARKER_FILE);
|
|
5971
6104
|
}
|
|
5972
6105
|
function writeWorktreeMarker(worktreePath, marker) {
|
|
5973
|
-
|
|
6106
|
+
writeFileSync6(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
|
|
5974
6107
|
`, "utf8");
|
|
5975
6108
|
}
|
|
5976
6109
|
function readWorktreeMarker(worktreePath) {
|
|
5977
6110
|
const p = markerPath(worktreePath);
|
|
5978
|
-
if (!
|
|
6111
|
+
if (!existsSync12(p))
|
|
5979
6112
|
return null;
|
|
5980
6113
|
try {
|
|
5981
6114
|
const raw = JSON.parse(readFileSync8(p, "utf8"));
|
|
@@ -6002,42 +6135,42 @@ function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
|
|
|
6002
6135
|
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
6136
|
}
|
|
6004
6137
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6005
|
-
return
|
|
6138
|
+
return join16(workdir, "worktrees", tenantId, runtimeSegment, dirName);
|
|
6006
6139
|
}
|
|
6007
6140
|
function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
|
|
6008
|
-
return
|
|
6141
|
+
return join16(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
|
|
6009
6142
|
}
|
|
6010
6143
|
function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
|
|
6011
6144
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6012
|
-
return
|
|
6145
|
+
return join16(workdir, "worktrees", tenantId, dirName);
|
|
6013
6146
|
}
|
|
6014
6147
|
var legacyPathWarned = /* @__PURE__ */ new Set();
|
|
6015
6148
|
function warnLegacyWorktreeResidue(legacyPath) {
|
|
6016
|
-
if (legacyPathWarned.has(legacyPath) || !
|
|
6149
|
+
if (legacyPathWarned.has(legacyPath) || !existsSync13(legacyPath))
|
|
6017
6150
|
return;
|
|
6018
6151
|
legacyPathWarned.add(legacyPath);
|
|
6019
6152
|
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
6153
|
}
|
|
6021
6154
|
var legacyLayoutWarned = /* @__PURE__ */ new Set();
|
|
6022
6155
|
function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
|
|
6023
|
-
if (legacyLayoutWarned.has(taskRoot) ||
|
|
6156
|
+
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join16(taskRoot, WORKTREE_MARKER_FILE)))
|
|
6024
6157
|
return;
|
|
6025
6158
|
legacyLayoutWarned.add(taskRoot);
|
|
6026
6159
|
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
6160
|
}
|
|
6028
6161
|
function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
6029
|
-
const gitignorePath =
|
|
6030
|
-
const existing =
|
|
6162
|
+
const gitignorePath = join16(worktreePath, ".gitignore");
|
|
6163
|
+
const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
|
|
6031
6164
|
const requiredLines = [WORKTREE_MARKER_FILE];
|
|
6032
6165
|
const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
|
|
6033
6166
|
if (missing.length > 0) {
|
|
6034
6167
|
const next = `${existing.trimEnd() ? `${existing.trimEnd()}
|
|
6035
6168
|
` : ""}${missing.join("\n")}
|
|
6036
6169
|
`;
|
|
6037
|
-
|
|
6170
|
+
writeFileSync7(gitignorePath, next, "utf8");
|
|
6038
6171
|
try {
|
|
6039
|
-
|
|
6040
|
-
|
|
6172
|
+
execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
|
|
6173
|
+
execFileSync8(
|
|
6041
6174
|
"git",
|
|
6042
6175
|
["-C", worktreePath, "-c", "user.name=morula-daemon", "-c", "user.email=morula-daemon@morula.local", "commit", "-q", "-m", "chore: track morula worktree marker"],
|
|
6043
6176
|
{ env, stdio: "pipe" }
|
|
@@ -6048,7 +6181,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
|
6048
6181
|
}
|
|
6049
6182
|
function currentBranch(repoPath) {
|
|
6050
6183
|
try {
|
|
6051
|
-
const out =
|
|
6184
|
+
const out = execFileSync8("git", ["-C", repoPath, "symbolic-ref", "--short", "HEAD"], {
|
|
6052
6185
|
encoding: "utf8",
|
|
6053
6186
|
stdio: "pipe"
|
|
6054
6187
|
}).trim();
|
|
@@ -6059,13 +6192,13 @@ function currentBranch(repoPath) {
|
|
|
6059
6192
|
}
|
|
6060
6193
|
function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
|
|
6061
6194
|
try {
|
|
6062
|
-
const current =
|
|
6195
|
+
const current = execFileSync8("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf-8", env }).trim();
|
|
6063
6196
|
if (current)
|
|
6064
6197
|
return current;
|
|
6065
6198
|
} catch {
|
|
6066
6199
|
}
|
|
6067
6200
|
try {
|
|
6068
|
-
const out =
|
|
6201
|
+
const out = execFileSync8("git", ["-C", repoPath, "branch", "-r", "--contains", "HEAD"], { encoding: "utf-8", env }).trim();
|
|
6069
6202
|
const first = out.split("\n").map((l) => l.trim()).find((l) => l && !l.includes("->"));
|
|
6070
6203
|
if (first)
|
|
6071
6204
|
return first.replace(/^origin\//, "");
|
|
@@ -6076,7 +6209,7 @@ function resolveDeliveryBranch(repoPath, env, snapshotBranch) {
|
|
|
6076
6209
|
}
|
|
6077
6210
|
function headCommitSubject(repoPath, env) {
|
|
6078
6211
|
try {
|
|
6079
|
-
const out =
|
|
6212
|
+
const out = execFileSync8("git", ["-C", repoPath, "log", "-1", "--format=%s"], {
|
|
6080
6213
|
encoding: "utf8",
|
|
6081
6214
|
stdio: "pipe",
|
|
6082
6215
|
...env ? { env } : {}
|
|
@@ -6088,7 +6221,7 @@ function headCommitSubject(repoPath, env) {
|
|
|
6088
6221
|
}
|
|
6089
6222
|
function hasLocalChanges(repoPath, env) {
|
|
6090
6223
|
try {
|
|
6091
|
-
const out =
|
|
6224
|
+
const out = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], {
|
|
6092
6225
|
encoding: "utf8",
|
|
6093
6226
|
stdio: "pipe",
|
|
6094
6227
|
...env ? { env } : {}
|
|
@@ -6114,27 +6247,27 @@ function sleepSync2(ms) {
|
|
|
6114
6247
|
function backoffMs(baseMs, random = Math.random) {
|
|
6115
6248
|
return Math.round(baseMs * (0.5 + random() * 0.5));
|
|
6116
6249
|
}
|
|
6117
|
-
function
|
|
6250
|
+
function gitErrorText3(err) {
|
|
6118
6251
|
const e = err;
|
|
6119
6252
|
const stderr = e?.stderr ? e.stderr.toString() : "";
|
|
6120
6253
|
return `${stderr}${stderr ? "\n" : ""}${e?.message ?? String(err)}`;
|
|
6121
6254
|
}
|
|
6122
6255
|
function isIndexLockConflict(err) {
|
|
6123
|
-
const text =
|
|
6256
|
+
const text = gitErrorText3(err);
|
|
6124
6257
|
return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
|
|
6125
6258
|
}
|
|
6126
6259
|
function worktreeIndexLockPath(repoCachePath, worktreePath) {
|
|
6127
|
-
return
|
|
6260
|
+
return join16(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
|
|
6128
6261
|
}
|
|
6129
6262
|
function indexLockPathFromError(text) {
|
|
6130
6263
|
const m = text.match(/[^\s'‘’"]*index\.lock/);
|
|
6131
6264
|
return m && m[0].length > "index.lock".length ? m[0] : null;
|
|
6132
6265
|
}
|
|
6133
6266
|
function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, pollMs = INDEX_LOCK_POLL_MS) {
|
|
6134
|
-
if (!
|
|
6267
|
+
if (!existsSync13(lockPath))
|
|
6135
6268
|
return true;
|
|
6136
6269
|
const deadline = Date.now() + timeoutMs;
|
|
6137
|
-
while (
|
|
6270
|
+
while (existsSync13(lockPath)) {
|
|
6138
6271
|
const remain = deadline - Date.now();
|
|
6139
6272
|
if (remain <= 0)
|
|
6140
6273
|
return false;
|
|
@@ -6144,7 +6277,7 @@ function waitForIndexLockRelease(lockPath, timeoutMs = INDEX_LOCK_WAIT_MAX_MS, p
|
|
|
6144
6277
|
}
|
|
6145
6278
|
function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
|
|
6146
6279
|
const runAdd = deps.runAdd ?? ((argv2) => {
|
|
6147
|
-
|
|
6280
|
+
execFileSync8("git", argv2, { stdio: "pipe" });
|
|
6148
6281
|
});
|
|
6149
6282
|
const argv = ["-C", repoCachePath, "worktree", "add", "--detach", worktreePath, ref];
|
|
6150
6283
|
const waitMs = deps.indexLockWaitMs ?? INDEX_LOCK_WAIT_MAX_MS;
|
|
@@ -6161,7 +6294,7 @@ function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
|
|
|
6161
6294
|
throw err;
|
|
6162
6295
|
if (attempt === WORKTREE_ADD_MAX_ATTEMPTS)
|
|
6163
6296
|
break;
|
|
6164
|
-
const lockPath = indexLockPathFromError(
|
|
6297
|
+
const lockPath = indexLockPathFromError(gitErrorText3(err)) ?? worktreeIndexLockPath(repoCachePath, worktreePath);
|
|
6165
6298
|
const released = waitForIndexLockRelease(lockPath, waitMs);
|
|
6166
6299
|
const backoff = backoffMs(WORKTREE_ADD_BACKOFF_MS[attempt - 1]);
|
|
6167
6300
|
console.warn(
|
|
@@ -6170,7 +6303,7 @@ function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
|
|
|
6170
6303
|
sleepSync2(backoff);
|
|
6171
6304
|
}
|
|
6172
6305
|
}
|
|
6173
|
-
throw new Error(`WORKTREE_ADD_FAILED: worktree add \u8FDE\u7EED ${WORKTREE_ADD_MAX_ATTEMPTS} \u6B21\u5931\u8D25\uFF08${
|
|
6306
|
+
throw new Error(`WORKTREE_ADD_FAILED: worktree add \u8FDE\u7EED ${WORKTREE_ADD_MAX_ATTEMPTS} \u6B21\u5931\u8D25\uFF08${gitErrorText3(lastErr).trim()}\uFF09`);
|
|
6174
6307
|
}
|
|
6175
6308
|
var FOREIGN_RESIDUE_MAX_ENTRIES = 64;
|
|
6176
6309
|
var FOREIGN_RESIDUE_SCAN_LIMIT = 2e3;
|
|
@@ -6200,7 +6333,7 @@ function inspectForeignDirectory(dir) {
|
|
|
6200
6333
|
if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
|
|
6201
6334
|
return;
|
|
6202
6335
|
count++;
|
|
6203
|
-
const full =
|
|
6336
|
+
const full = join16(p, e.name);
|
|
6204
6337
|
try {
|
|
6205
6338
|
const st = statSync2(full);
|
|
6206
6339
|
bytes += st.size;
|
|
@@ -6232,13 +6365,13 @@ function reportForeignResidue(worktreePath, forensics, cleaned) {
|
|
|
6232
6365
|
console.warn(`[daemon] ${detail}`);
|
|
6233
6366
|
}
|
|
6234
6367
|
function prepareWorktree(input) {
|
|
6235
|
-
const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl);
|
|
6368
|
+
const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
|
|
6236
6369
|
cache.ensure();
|
|
6237
6370
|
cache.fetch();
|
|
6238
6371
|
const requestedBase = input.baseBranch?.trim();
|
|
6239
6372
|
const hasRef = (ref) => {
|
|
6240
6373
|
try {
|
|
6241
|
-
|
|
6374
|
+
execFileSync8("git", ["-C", cache.repoCachePath, "rev-parse", "--verify", `origin/${ref}`], { stdio: "pipe" });
|
|
6242
6375
|
return true;
|
|
6243
6376
|
} catch {
|
|
6244
6377
|
return false;
|
|
@@ -6250,14 +6383,14 @@ function prepareWorktree(input) {
|
|
|
6250
6383
|
} else {
|
|
6251
6384
|
const remoteHead = (() => {
|
|
6252
6385
|
try {
|
|
6253
|
-
const out =
|
|
6386
|
+
const out = execFileSync8("git", ["-C", cache.repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
6254
6387
|
const name = out.replace(/^refs\/remotes\/origin\//, "");
|
|
6255
6388
|
if (name && hasRef(name))
|
|
6256
6389
|
return name;
|
|
6257
6390
|
} catch {
|
|
6258
6391
|
}
|
|
6259
6392
|
try {
|
|
6260
|
-
const out =
|
|
6393
|
+
const out = execFileSync8("git", ["-C", cache.repoCachePath, "ls-remote", "--symref", "origin", "HEAD"], { encoding: "utf-8", stdio: "pipe" });
|
|
6261
6394
|
const m = out.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD/m);
|
|
6262
6395
|
return m?.[1] ?? null;
|
|
6263
6396
|
} catch {
|
|
@@ -6276,10 +6409,10 @@ function prepareWorktree(input) {
|
|
|
6276
6409
|
const worktreePath = buildWorktreePath(input.workdir, input.tenantId, input.runtimeId, input.workContextId, input.code, input.repoFullName);
|
|
6277
6410
|
warnLegacyWorktreeResidue(buildLegacyWorktreePath(input.workdir, input.tenantId, input.workContextId, input.code));
|
|
6278
6411
|
warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath);
|
|
6279
|
-
if (
|
|
6412
|
+
if (existsSync13(worktreePath)) {
|
|
6280
6413
|
const isGitRepo = (() => {
|
|
6281
6414
|
try {
|
|
6282
|
-
|
|
6415
|
+
execFileSync8("git", ["-C", worktreePath, "rev-parse", "--git-dir"], { stdio: "pipe" });
|
|
6283
6416
|
return true;
|
|
6284
6417
|
} catch {
|
|
6285
6418
|
return false;
|
|
@@ -6288,7 +6421,7 @@ function prepareWorktree(input) {
|
|
|
6288
6421
|
if (!isGitRepo) {
|
|
6289
6422
|
const forensics = inspectForeignDirectory(worktreePath);
|
|
6290
6423
|
try {
|
|
6291
|
-
|
|
6424
|
+
rmSync7(worktreePath, { recursive: true, force: true });
|
|
6292
6425
|
} catch {
|
|
6293
6426
|
reportForeignResidue(worktreePath, forensics, false);
|
|
6294
6427
|
throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
|
|
@@ -6306,11 +6439,11 @@ function prepareWorktree(input) {
|
|
|
6306
6439
|
}
|
|
6307
6440
|
const branch = currentBranch(worktreePath);
|
|
6308
6441
|
if (!branch) {
|
|
6309
|
-
const dirtyOut =
|
|
6442
|
+
const dirtyOut = execFileSync8("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6310
6443
|
const dirty = dirtyOut.split("\n").filter(
|
|
6311
6444
|
(l) => l.trim() && !l.trim().startsWith("?? .agents") && !l.trim().startsWith("?? .agent_context") && !l.trim().startsWith("?? .morula")
|
|
6312
6445
|
).join("\n");
|
|
6313
|
-
const extraCommits = Number(
|
|
6446
|
+
const extraCommits = Number(execFileSync8(
|
|
6314
6447
|
"git",
|
|
6315
6448
|
["-C", worktreePath, "rev-list", "--count", `origin/${input.baseBranch}..HEAD`],
|
|
6316
6449
|
{ encoding: "utf8", stdio: "pipe" }
|
|
@@ -6318,7 +6451,7 @@ function prepareWorktree(input) {
|
|
|
6318
6451
|
const rescueTag = buildRescueTag(input.workContextId, input.code);
|
|
6319
6452
|
if (dirty.length > 0) {
|
|
6320
6453
|
try {
|
|
6321
|
-
|
|
6454
|
+
execFileSync8(
|
|
6322
6455
|
"git",
|
|
6323
6456
|
["-C", worktreePath, "stash", "push", "-u", "-m", `morula-rescue ${rescueTag}`],
|
|
6324
6457
|
{ stdio: "pipe" }
|
|
@@ -6329,9 +6462,9 @@ function prepareWorktree(input) {
|
|
|
6329
6462
|
}
|
|
6330
6463
|
}
|
|
6331
6464
|
if (extraCommits > 1) {
|
|
6332
|
-
const headSha2 =
|
|
6465
|
+
const headSha2 = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6333
6466
|
try {
|
|
6334
|
-
|
|
6467
|
+
execFileSync8("git", ["-C", cache.repoCachePath, "update-ref", `refs/morula-rescue/${rescueTag}`, headSha2], { stdio: "pipe" });
|
|
6335
6468
|
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
6469
|
} catch {
|
|
6337
6470
|
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 +6476,8 @@ function prepareWorktree(input) {
|
|
|
6343
6476
|
}
|
|
6344
6477
|
}
|
|
6345
6478
|
}
|
|
6346
|
-
if (!
|
|
6347
|
-
|
|
6479
|
+
if (!existsSync13(worktreePath)) {
|
|
6480
|
+
mkdirSync9(join16(worktreePath, ".."), { recursive: true });
|
|
6348
6481
|
addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
|
|
6349
6482
|
writeWorktreeMarker(worktreePath, {
|
|
6350
6483
|
schemaVersion: 1,
|
|
@@ -6360,9 +6493,9 @@ function prepareWorktree(input) {
|
|
|
6360
6493
|
}
|
|
6361
6494
|
{
|
|
6362
6495
|
const dotMorulaRescue = `${worktreePath}.morula-rescue`;
|
|
6363
|
-
if (
|
|
6364
|
-
const dotMorulaDir =
|
|
6365
|
-
if (!
|
|
6496
|
+
if (existsSync13(dotMorulaRescue)) {
|
|
6497
|
+
const dotMorulaDir = join16(worktreePath, ".morula");
|
|
6498
|
+
if (!existsSync13(dotMorulaDir)) {
|
|
6366
6499
|
try {
|
|
6367
6500
|
renameSync2(dotMorulaRescue, dotMorulaDir);
|
|
6368
6501
|
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 +6503,11 @@ function prepareWorktree(input) {
|
|
|
6370
6503
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
|
|
6371
6504
|
}
|
|
6372
6505
|
} else {
|
|
6373
|
-
|
|
6506
|
+
rmSync7(dotMorulaRescue, { recursive: true, force: true });
|
|
6374
6507
|
}
|
|
6375
6508
|
}
|
|
6376
6509
|
}
|
|
6377
|
-
const headSha =
|
|
6510
|
+
const headSha = execFileSync8("git", ["-C", worktreePath, "rev-parse", "HEAD"], {
|
|
6378
6511
|
encoding: "utf8",
|
|
6379
6512
|
stdio: "pipe"
|
|
6380
6513
|
}).trim();
|
|
@@ -6390,10 +6523,10 @@ function prepareWorktree(input) {
|
|
|
6390
6523
|
};
|
|
6391
6524
|
}
|
|
6392
6525
|
function resolveWorkspaceLayout(root, expected) {
|
|
6393
|
-
const primaryDir =
|
|
6394
|
-
if (
|
|
6526
|
+
const primaryDir = join16(root, safeDirName(expected.repoFullName));
|
|
6527
|
+
if (existsSync13(join16(primaryDir, WORKTREE_MARKER_FILE)))
|
|
6395
6528
|
return { primaryDir, legacy: false };
|
|
6396
|
-
if (
|
|
6529
|
+
if (existsSync13(join16(root, WORKTREE_MARKER_FILE)))
|
|
6397
6530
|
return { primaryDir: root, legacy: true };
|
|
6398
6531
|
return null;
|
|
6399
6532
|
}
|
|
@@ -6408,7 +6541,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6408
6541
|
return "MARKER_MISMATCH";
|
|
6409
6542
|
const { primaryDir, legacy } = layout;
|
|
6410
6543
|
try {
|
|
6411
|
-
const status3 =
|
|
6544
|
+
const status3 = execFileSync8("git", ["-C", primaryDir, "status", "--porcelain"], {
|
|
6412
6545
|
encoding: "utf8",
|
|
6413
6546
|
stdio: "pipe"
|
|
6414
6547
|
}).trim();
|
|
@@ -6417,11 +6550,11 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6417
6550
|
).join("\n");
|
|
6418
6551
|
if (statusRelevant.length > 0)
|
|
6419
6552
|
return "WORKTREE_DIRTY";
|
|
6420
|
-
const additionalActivity = legacy ? findActivityInReposDir(
|
|
6553
|
+
const additionalActivity = legacy ? findActivityInReposDir(join16(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
|
|
6421
6554
|
if (additionalActivity)
|
|
6422
6555
|
return "WORKTREE_DIRTY";
|
|
6423
6556
|
try {
|
|
6424
|
-
const unpushed =
|
|
6557
|
+
const unpushed = execFileSync8("git", ["-C", primaryDir, "log", "--oneline", `${expected.branchName}@{upstream}..HEAD`], {
|
|
6425
6558
|
encoding: "utf8",
|
|
6426
6559
|
stdio: "pipe"
|
|
6427
6560
|
}).trim();
|
|
@@ -6437,13 +6570,13 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6437
6570
|
}
|
|
6438
6571
|
function repoHasActivity(repoPath) {
|
|
6439
6572
|
try {
|
|
6440
|
-
const status3 =
|
|
6573
|
+
const status3 = execFileSync8("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6441
6574
|
if (status3)
|
|
6442
6575
|
return true;
|
|
6443
|
-
const branches =
|
|
6576
|
+
const branches = execFileSync8("git", ["-C", repoPath, "for-each-ref", "--format=%(refname:short)", "refs/heads/"], { encoding: "utf8", stdio: "pipe" }).trim().split("\n").filter(Boolean);
|
|
6444
6577
|
for (const b of branches) {
|
|
6445
6578
|
try {
|
|
6446
|
-
const unpushed =
|
|
6579
|
+
const unpushed = execFileSync8("git", ["-C", repoPath, "log", "--oneline", b, "--not", "--remotes=origin"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6447
6580
|
if (unpushed)
|
|
6448
6581
|
return true;
|
|
6449
6582
|
} catch {
|
|
@@ -6456,7 +6589,7 @@ function repoHasActivity(repoPath) {
|
|
|
6456
6589
|
return false;
|
|
6457
6590
|
}
|
|
6458
6591
|
function findActivityInReposDir(reposDir) {
|
|
6459
|
-
if (!
|
|
6592
|
+
if (!existsSync13(reposDir))
|
|
6460
6593
|
return null;
|
|
6461
6594
|
let names = [];
|
|
6462
6595
|
try {
|
|
@@ -6465,8 +6598,8 @@ function findActivityInReposDir(reposDir) {
|
|
|
6465
6598
|
return null;
|
|
6466
6599
|
}
|
|
6467
6600
|
for (const name of names) {
|
|
6468
|
-
const repoPath =
|
|
6469
|
-
if (!
|
|
6601
|
+
const repoPath = join16(reposDir, name);
|
|
6602
|
+
if (!existsSync13(join16(repoPath, ".git")))
|
|
6470
6603
|
continue;
|
|
6471
6604
|
if (repoHasActivity(repoPath))
|
|
6472
6605
|
return repoPath;
|
|
@@ -6481,16 +6614,16 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6481
6614
|
} catch {
|
|
6482
6615
|
return null;
|
|
6483
6616
|
}
|
|
6484
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
6485
|
-
const legacyReposDir =
|
|
6486
|
-
if (
|
|
6617
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join16(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
|
|
6618
|
+
const legacyReposDir = join16(taskRoot, ".morula", "repos");
|
|
6619
|
+
if (existsSync13(legacyReposDir)) {
|
|
6487
6620
|
try {
|
|
6488
|
-
candidates.push(...readdirSync3(legacyReposDir).map((n) =>
|
|
6621
|
+
candidates.push(...readdirSync3(legacyReposDir).map((n) => join16(legacyReposDir, n)));
|
|
6489
6622
|
} catch {
|
|
6490
6623
|
}
|
|
6491
6624
|
}
|
|
6492
6625
|
for (const repoPath of candidates) {
|
|
6493
|
-
if (!
|
|
6626
|
+
if (!existsSync13(join16(repoPath, ".git")))
|
|
6494
6627
|
continue;
|
|
6495
6628
|
if (repoHasActivity(repoPath))
|
|
6496
6629
|
return repoPath;
|
|
@@ -6498,18 +6631,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6498
6631
|
return null;
|
|
6499
6632
|
}
|
|
6500
6633
|
function cleanupInjectedSkills(dir) {
|
|
6501
|
-
const injectedSkillsRoot =
|
|
6502
|
-
if (!
|
|
6634
|
+
const injectedSkillsRoot = join16(dir, ".agents", "skills");
|
|
6635
|
+
if (!existsSync13(injectedSkillsRoot))
|
|
6503
6636
|
return;
|
|
6504
6637
|
try {
|
|
6505
6638
|
for (const name of readdirSync3(injectedSkillsRoot)) {
|
|
6506
6639
|
if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
|
|
6507
|
-
|
|
6640
|
+
rmSync7(join16(injectedSkillsRoot, name), { recursive: true, force: true });
|
|
6508
6641
|
}
|
|
6509
6642
|
}
|
|
6510
6643
|
if (readdirSync3(injectedSkillsRoot).length === 0) {
|
|
6511
|
-
|
|
6512
|
-
|
|
6644
|
+
rmSync7(join16(dir, ".agents", "skills"), { recursive: true, force: true });
|
|
6645
|
+
rmSync7(join16(dir, ".agents"), { recursive: true, force: true });
|
|
6513
6646
|
}
|
|
6514
6647
|
} catch {
|
|
6515
6648
|
}
|
|
@@ -6525,11 +6658,11 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
|
|
|
6525
6658
|
for (const e of entries) {
|
|
6526
6659
|
if (!e.isDirectory() || e.name.startsWith("."))
|
|
6527
6660
|
continue;
|
|
6528
|
-
const repoPath =
|
|
6529
|
-
if (repoPath === excludeDir || !
|
|
6661
|
+
const repoPath = join16(taskRoot, e.name);
|
|
6662
|
+
if (repoPath === excludeDir || !existsSync13(join16(repoPath, ".git")))
|
|
6530
6663
|
continue;
|
|
6531
6664
|
try {
|
|
6532
|
-
const commonDir =
|
|
6665
|
+
const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
6533
6666
|
if (commonDir)
|
|
6534
6667
|
dirs.add(commonDir);
|
|
6535
6668
|
} catch {
|
|
@@ -6543,37 +6676,37 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
|
|
|
6543
6676
|
throw new Error("MARKER_MISMATCH");
|
|
6544
6677
|
const { primaryDir, legacy } = layout;
|
|
6545
6678
|
if (!legacy) {
|
|
6546
|
-
if (primaryDir !==
|
|
6679
|
+
if (primaryDir !== join16(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
|
|
6547
6680
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6548
6681
|
}
|
|
6549
6682
|
cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
|
|
6550
6683
|
const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
|
|
6551
6684
|
try {
|
|
6552
|
-
|
|
6685
|
+
rmSync7(taskRoot, { recursive: true, force: true });
|
|
6553
6686
|
} catch {
|
|
6554
6687
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6555
6688
|
}
|
|
6556
6689
|
for (const dir of [repoCachePath, ...siblingCommonDirs]) {
|
|
6557
6690
|
try {
|
|
6558
|
-
|
|
6691
|
+
execFileSync8("git", ["-C", dir, "worktree", "prune"], { stdio: "pipe" });
|
|
6559
6692
|
} catch {
|
|
6560
6693
|
}
|
|
6561
6694
|
}
|
|
6562
6695
|
}
|
|
6563
6696
|
function removeWorktree(repoCachePath, worktreePath) {
|
|
6564
|
-
if (!
|
|
6697
|
+
if (!existsSync13(worktreePath))
|
|
6565
6698
|
return;
|
|
6566
6699
|
try {
|
|
6567
6700
|
cleanupInjectedSkills(worktreePath);
|
|
6568
|
-
|
|
6569
|
-
|
|
6701
|
+
execFileSync8("git", ["-C", repoCachePath, "worktree", "remove", "--force", worktreePath], { stdio: "pipe" });
|
|
6702
|
+
execFileSync8("git", ["-C", repoCachePath, "worktree", "prune"], { stdio: "pipe" });
|
|
6570
6703
|
} catch {
|
|
6571
6704
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6572
6705
|
}
|
|
6573
6706
|
}
|
|
6574
6707
|
|
|
6575
6708
|
// src/daemon/repo-checkout-service.ts
|
|
6576
|
-
var CHECKOUT_JSON_FILE =
|
|
6709
|
+
var CHECKOUT_JSON_FILE = join17(".morula", "checkout.json");
|
|
6577
6710
|
var LocalRepoCheckoutService = class {
|
|
6578
6711
|
constructor(opts) {
|
|
6579
6712
|
this.opts = opts;
|
|
@@ -6661,9 +6794,9 @@ var LocalRepoCheckoutService = class {
|
|
|
6661
6794
|
return;
|
|
6662
6795
|
this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
|
|
6663
6796
|
try {
|
|
6664
|
-
const dir =
|
|
6665
|
-
|
|
6666
|
-
|
|
6797
|
+
const dir = join17(workdir, ".morula");
|
|
6798
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
6799
|
+
writeFileSync8(join17(dir, "checkout.json"), JSON.stringify({
|
|
6667
6800
|
taskId,
|
|
6668
6801
|
port: this.boundPort,
|
|
6669
6802
|
checkToken
|
|
@@ -6717,7 +6850,8 @@ var LocalRepoCheckoutService = class {
|
|
|
6717
6850
|
this.opts.tenantId,
|
|
6718
6851
|
repoFullName,
|
|
6719
6852
|
env,
|
|
6720
|
-
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0
|
|
6853
|
+
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
|
|
6854
|
+
baseUrl ? buildDaemonRepoUrl(repoFullName, baseUrl) : void 0
|
|
6721
6855
|
);
|
|
6722
6856
|
cache.ensure();
|
|
6723
6857
|
cache.fetch();
|
|
@@ -6725,14 +6859,16 @@ var LocalRepoCheckoutService = class {
|
|
|
6725
6859
|
repoCachePath: cache.repoCachePath,
|
|
6726
6860
|
taskRoot: task.workdir,
|
|
6727
6861
|
repoFullName,
|
|
6862
|
+
baseBranch: serverRes.baseBranch,
|
|
6863
|
+
ref: req.ref,
|
|
6728
6864
|
env
|
|
6729
6865
|
});
|
|
6730
|
-
const headSha =
|
|
6866
|
+
const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
6731
6867
|
return {
|
|
6732
6868
|
ok: true,
|
|
6733
6869
|
path: worktreePath,
|
|
6734
6870
|
headSha,
|
|
6735
|
-
branchName:
|
|
6871
|
+
branchName: execFileSync9("git", ["-C", worktreePath, "branch", "--show-current"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null
|
|
6736
6872
|
};
|
|
6737
6873
|
} catch (e) {
|
|
6738
6874
|
return { ok: false, code: "CHECKOUT_FAILED", error: e instanceof Error ? e.message : String(e) };
|
|
@@ -6778,7 +6914,8 @@ var LocalRepoCheckoutService = class {
|
|
|
6778
6914
|
this.opts.tenantId,
|
|
6779
6915
|
repoFullName,
|
|
6780
6916
|
env,
|
|
6781
|
-
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0
|
|
6917
|
+
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
|
|
6918
|
+
baseUrl ? buildDaemonRepoUrl(repoFullName, baseUrl) : void 0
|
|
6782
6919
|
);
|
|
6783
6920
|
cache.ensure();
|
|
6784
6921
|
cache.fetch();
|
|
@@ -6792,7 +6929,7 @@ var LocalRepoCheckoutService = class {
|
|
|
6792
6929
|
env
|
|
6793
6930
|
});
|
|
6794
6931
|
syncGitIdentity(task.primaryRepoDir ?? task.workdir, targetPath, env);
|
|
6795
|
-
const headSha =
|
|
6932
|
+
const headSha = execFileSync9("git", ["-C", targetPath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
6796
6933
|
this.recordPreparedRepo(req.taskId, repoFullName, {
|
|
6797
6934
|
workContextId: serverRes.workContextId,
|
|
6798
6935
|
baseBranch: baseBranch || void 0,
|
|
@@ -6823,38 +6960,90 @@ function isReadOnlyRepoDir(dirName) {
|
|
|
6823
6960
|
return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
|
|
6824
6961
|
}
|
|
6825
6962
|
function resolveRepoTargetDir(taskRoot, base) {
|
|
6826
|
-
const direct =
|
|
6827
|
-
if (!
|
|
6963
|
+
const direct = join17(taskRoot, base);
|
|
6964
|
+
if (!existsSync14(direct) || existsSync14(join17(direct, ".git")))
|
|
6828
6965
|
return direct;
|
|
6829
6966
|
for (let i = 2; i < 100; i++) {
|
|
6830
|
-
const candidate =
|
|
6831
|
-
if (!
|
|
6967
|
+
const candidate = join17(taskRoot, `${base}-${i}`);
|
|
6968
|
+
if (!existsSync14(candidate))
|
|
6832
6969
|
return candidate;
|
|
6833
6970
|
}
|
|
6834
6971
|
throw new Error(`REPO_DIR_CONFLICT: ${taskRoot} \u4E0B\u65E0\u6CD5\u4E3A ${base} \u5206\u914D\u4ED3\u5E93\u76EE\u5F55`);
|
|
6835
6972
|
}
|
|
6836
6973
|
function materializeReadWorktree(input) {
|
|
6837
6974
|
const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
|
|
6838
|
-
|
|
6839
|
-
|
|
6975
|
+
const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
|
|
6976
|
+
if (existsSync14(join17(worktreePath, ".git"))) {
|
|
6977
|
+
const current = currentBranchName(worktreePath);
|
|
6978
|
+
if (current)
|
|
6979
|
+
throw new Error(`READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5DF2\u5207\u5230\u5206\u652F ${current}\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09`);
|
|
6980
|
+
if (hasLocalChanges2(worktreePath, input.env))
|
|
6981
|
+
throw new Error("READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5B58\u5728\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09");
|
|
6982
|
+
execFileSync9("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
6840
6983
|
} else {
|
|
6841
|
-
|
|
6842
|
-
|
|
6984
|
+
mkdirSync10(join17(worktreePath, ".."), { recursive: true });
|
|
6985
|
+
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
|
|
6843
6986
|
env: input.env,
|
|
6844
6987
|
stdio: "pipe"
|
|
6845
6988
|
});
|
|
6846
6989
|
}
|
|
6847
6990
|
return worktreePath;
|
|
6848
6991
|
}
|
|
6849
|
-
function
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6992
|
+
function resolveReadRef(repoCachePath, env, requestedRef, baseBranch) {
|
|
6993
|
+
const requested = requestedRef?.trim();
|
|
6994
|
+
if (requested) {
|
|
6995
|
+
const candidates = requested.startsWith("refs/") ? [requested] : [`origin/${requested}`, requested];
|
|
6996
|
+
for (const ref of candidates) {
|
|
6997
|
+
if (refExists(repoCachePath, ref, env))
|
|
6998
|
+
return ref;
|
|
6999
|
+
}
|
|
7000
|
+
throw new Error(`REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528 ${requested}`);
|
|
7001
|
+
}
|
|
7002
|
+
const explicitBase = baseBranch?.trim();
|
|
7003
|
+
if (explicitBase) {
|
|
7004
|
+
const ref = `origin/${explicitBase}`;
|
|
7005
|
+
if (refExists(repoCachePath, ref, env))
|
|
6853
7006
|
return ref;
|
|
7007
|
+
throw new Error(`BASE_REF_NOT_FOUND: repo cache \u65E0\u9ED8\u8BA4\u5206\u652F ${explicitBase}`);
|
|
7008
|
+
}
|
|
7009
|
+
const remoteHead = (() => {
|
|
7010
|
+
try {
|
|
7011
|
+
const out = execFileSync9("git", ["-C", repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
7012
|
+
return out.replace(/^refs\/remotes\//, "");
|
|
6854
7013
|
} catch {
|
|
7014
|
+
return null;
|
|
6855
7015
|
}
|
|
7016
|
+
})();
|
|
7017
|
+
for (const ref of [remoteHead, "origin/main", "origin/master"]) {
|
|
7018
|
+
if (!ref || !refExists(repoCachePath, ref, env))
|
|
7019
|
+
continue;
|
|
7020
|
+
return ref;
|
|
7021
|
+
}
|
|
7022
|
+
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/HEAD / origin/main / origin/master\uFF09");
|
|
7023
|
+
}
|
|
7024
|
+
function refExists(repoPath, ref, env) {
|
|
7025
|
+
try {
|
|
7026
|
+
execFileSync9("git", ["-C", repoPath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
|
|
7027
|
+
return true;
|
|
7028
|
+
} catch {
|
|
7029
|
+
return false;
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
function currentBranchName(repoPath) {
|
|
7033
|
+
try {
|
|
7034
|
+
const out = execFileSync9("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
7035
|
+
return out || null;
|
|
7036
|
+
} catch {
|
|
7037
|
+
return null;
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
function hasLocalChanges2(repoPath, env) {
|
|
7041
|
+
try {
|
|
7042
|
+
const out = execFileSync9("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
7043
|
+
return out.length > 0;
|
|
7044
|
+
} catch {
|
|
7045
|
+
return true;
|
|
6856
7046
|
}
|
|
6857
|
-
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/main / origin/master / HEAD\uFF09");
|
|
6858
7047
|
}
|
|
6859
7048
|
function listMaterializedRepoDirs(taskRoot, options = {}) {
|
|
6860
7049
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
@@ -6864,7 +7053,7 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
|
|
|
6864
7053
|
} catch {
|
|
6865
7054
|
return [];
|
|
6866
7055
|
}
|
|
6867
|
-
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
7056
|
+
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
7057
|
}
|
|
6869
7058
|
function parseRepoFullNameFromRemoteUrl(url) {
|
|
6870
7059
|
const raw = url?.trim();
|
|
@@ -6886,7 +7075,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
|
|
|
6886
7075
|
}
|
|
6887
7076
|
function collectMaterializedRepoDeliveryTargets(input) {
|
|
6888
7077
|
const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
|
|
6889
|
-
const excludedDirs = [...excludedNames].map((name) =>
|
|
7078
|
+
const excludedDirs = [...excludedNames].map((name) => join17(input.taskRoot, safeRepoDirName(name)));
|
|
6890
7079
|
const recordsByDir = /* @__PURE__ */ new Map();
|
|
6891
7080
|
const recordsByDirName = /* @__PURE__ */ new Map();
|
|
6892
7081
|
for (const [repoFullName, record] of input.prepared ?? []) {
|
|
@@ -6895,7 +7084,7 @@ function collectMaterializedRepoDeliveryTargets(input) {
|
|
|
6895
7084
|
recordsByDir.set(record.dir, entry);
|
|
6896
7085
|
recordsByDirName.set(safeRepoDirName(repoFullName), entry);
|
|
6897
7086
|
}
|
|
6898
|
-
const runGit = input.gitRunner ?? ((dir, args) =>
|
|
7087
|
+
const runGit = input.gitRunner ?? ((dir, args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", env: input.env, stdio: "pipe" }).trim());
|
|
6899
7088
|
const targets = [];
|
|
6900
7089
|
for (const dir of listMaterializedRepoDirs(input.taskRoot, { excludeDirs: excludedDirs })) {
|
|
6901
7090
|
const known = recordsByDir.get(dir) ?? recordsByDirName.get(basename2(dir));
|
|
@@ -6932,7 +7121,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
6932
7121
|
const read = (key) => {
|
|
6933
7122
|
for (const scope of ["--worktree", "--local"]) {
|
|
6934
7123
|
try {
|
|
6935
|
-
const out =
|
|
7124
|
+
const out = execFileSync9("git", ["-C", primaryDir, "config", scope, key], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
6936
7125
|
if (out)
|
|
6937
7126
|
return out;
|
|
6938
7127
|
} catch {
|
|
@@ -6943,30 +7132,34 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
6943
7132
|
const name = read("user.name") ?? "morula-daemon[bot]";
|
|
6944
7133
|
const email = read("user.email") ?? "morula-daemon@morula.local";
|
|
6945
7134
|
try {
|
|
6946
|
-
|
|
6947
|
-
|
|
7135
|
+
execFileSync9("git", ["-C", targetDir, "config", "user.name", name], { env, stdio: "pipe" });
|
|
7136
|
+
execFileSync9("git", ["-C", targetDir, "config", "user.email", email], { env, stdio: "pipe" });
|
|
6948
7137
|
} catch {
|
|
6949
7138
|
}
|
|
6950
7139
|
}
|
|
6951
7140
|
function materializeAdditionalRepo(input) {
|
|
6952
|
-
if (
|
|
6953
|
-
|
|
7141
|
+
if (existsSync14(join17(input.targetPath, ".git"))) {
|
|
7142
|
+
const current = currentBranchName(input.targetPath);
|
|
7143
|
+
if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
|
|
7144
|
+
const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
|
|
7145
|
+
execFileSync9("git", ["-C", input.targetPath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
7146
|
+
}
|
|
6954
7147
|
return input.targetPath;
|
|
6955
7148
|
}
|
|
6956
|
-
const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master"
|
|
7149
|
+
const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master"] : ["origin/main", "origin/master"];
|
|
6957
7150
|
let baseRef = null;
|
|
6958
7151
|
for (const ref of candidates) {
|
|
6959
7152
|
try {
|
|
6960
|
-
|
|
7153
|
+
execFileSync9("git", ["-C", input.repoCachePath, "rev-parse", "--verify", ref], { env: input.env, stdio: "pipe" });
|
|
6961
7154
|
baseRef = ref;
|
|
6962
7155
|
break;
|
|
6963
7156
|
} catch {
|
|
6964
7157
|
}
|
|
6965
7158
|
}
|
|
6966
7159
|
if (!baseRef)
|
|
6967
|
-
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master
|
|
6968
|
-
|
|
6969
|
-
|
|
7160
|
+
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
|
|
7161
|
+
mkdirSync10(join17(input.targetPath, ".."), { recursive: true });
|
|
7162
|
+
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
|
|
6970
7163
|
env: input.env,
|
|
6971
7164
|
stdio: "pipe"
|
|
6972
7165
|
});
|
|
@@ -7435,6 +7628,14 @@ var DaemonRunner = class {
|
|
|
7435
7628
|
if (this.running)
|
|
7436
7629
|
return;
|
|
7437
7630
|
this.running = true;
|
|
7631
|
+
try {
|
|
7632
|
+
await this.startInner();
|
|
7633
|
+
} catch (error) {
|
|
7634
|
+
this.stop();
|
|
7635
|
+
throw error;
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
async startInner() {
|
|
7438
7639
|
ensureGitOnPath();
|
|
7439
7640
|
ensureNodeOnPath();
|
|
7440
7641
|
{
|
|
@@ -8116,6 +8317,7 @@ var DaemonRunner = class {
|
|
|
8116
8317
|
const wc = detail.workContext;
|
|
8117
8318
|
repoBaseUrl = tokenRes.baseUrl;
|
|
8118
8319
|
const remoteUrl = repoBaseUrl ? buildGitlabSshRemote(wc?.repoFullName ?? repo, repoBaseUrl) : void 0;
|
|
8320
|
+
const daemonRemoteUrl = buildDaemonRepoUrl(wc?.repoFullName ?? repo, repoBaseUrl);
|
|
8119
8321
|
env = { ...env, ...buildRepoEnv(tokenRes.token, repoBaseUrl) };
|
|
8120
8322
|
if (wc?.baseBranch?.trim())
|
|
8121
8323
|
env = { ...env, MORULA_BASE_BRANCH: wc.baseBranch.trim() };
|
|
@@ -8131,7 +8333,8 @@ var DaemonRunner = class {
|
|
|
8131
8333
|
baseBranch: wc.baseBranch || "main",
|
|
8132
8334
|
code: detail.code ?? null,
|
|
8133
8335
|
env,
|
|
8134
|
-
remoteUrl
|
|
8336
|
+
remoteUrl,
|
|
8337
|
+
daemonRemoteUrl
|
|
8135
8338
|
});
|
|
8136
8339
|
repoDir = managed.worktreePath;
|
|
8137
8340
|
worktreePath = managed.worktreePath;
|
|
@@ -8140,7 +8343,7 @@ var DaemonRunner = class {
|
|
|
8140
8343
|
});
|
|
8141
8344
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5DE5\u4F5C\u533A\u5C31\u7EEA: ${taskRoot}\uFF08\u4ED3\u5E93 ${repoDir}\uFF0Cbranch=${wc.branchName}\uFF09`);
|
|
8142
8345
|
} else {
|
|
8143
|
-
repoDir = ensureRepo(cfg.workdir, this.tenantId, repo, env, remoteUrl);
|
|
8346
|
+
repoDir = ensureRepo(cfg.workdir, this.tenantId, repo, env, remoteUrl, daemonRemoteUrl);
|
|
8144
8347
|
taskRoot = repoDir;
|
|
8145
8348
|
}
|
|
8146
8349
|
ensureGitIdentity(repoDir, detail.executor.executorName);
|
|
@@ -8157,12 +8360,12 @@ var DaemonRunner = class {
|
|
|
8157
8360
|
});
|
|
8158
8361
|
if (spaceToken?.token)
|
|
8159
8362
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
8160
|
-
repoDir =
|
|
8363
|
+
repoDir = join18(cfg.workdir, "scratch", taskId);
|
|
8161
8364
|
taskRoot = repoDir;
|
|
8162
|
-
|
|
8163
|
-
const readme =
|
|
8164
|
-
if (!
|
|
8165
|
-
|
|
8365
|
+
mkdirSync11(repoDir, { recursive: true });
|
|
8366
|
+
const readme = join18(repoDir, "README.md");
|
|
8367
|
+
if (!existsSync15(readme)) {
|
|
8368
|
+
writeFileSync9(
|
|
8166
8369
|
readme,
|
|
8167
8370
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
8168
8371
|
|
|
@@ -8448,7 +8651,7 @@ var DaemonRunner = class {
|
|
|
8448
8651
|
};
|
|
8449
8652
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
8450
8653
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
8451
|
-
const canResume = !!resumeSessionId && !!resumeWorkDir &&
|
|
8654
|
+
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
|
|
8452
8655
|
const input = {
|
|
8453
8656
|
executionId: `${taskId}:1`,
|
|
8454
8657
|
// task-workspace-layout:新会话 cwd = 任务工作区根(AGENTS.md/.agents 在根下,仓库在子目录)
|
|
@@ -8655,7 +8858,7 @@ var DaemonRunner = class {
|
|
|
8655
8858
|
const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
|
|
8656
8859
|
try {
|
|
8657
8860
|
const execEnv = mergeExecEnv(env);
|
|
8658
|
-
|
|
8861
|
+
execFileSync10("git", ["-C", repoDir, "push", "-u", "origin", branch], { encoding: "utf-8", env: execEnv, stdio: "pipe" });
|
|
8659
8862
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5206\u652F\u5DF2\u8865\u63A8\u9001: ${branch}`);
|
|
8660
8863
|
const listRes = await fetch(
|
|
8661
8864
|
`${baseUrl}/api/v4/projects/${projectPath}/merge_requests?source_branch=${encodeURIComponent(branch)}&state=opened`,
|
|
@@ -8722,7 +8925,7 @@ var DaemonRunner = class {
|
|
|
8722
8925
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
8723
8926
|
return;
|
|
8724
8927
|
}
|
|
8725
|
-
const managedRoot =
|
|
8928
|
+
const managedRoot = join18(cfg.workdir, "worktrees", this.tenantId);
|
|
8726
8929
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
8727
8930
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
8728
8931
|
ok: false,
|
|
@@ -8770,38 +8973,38 @@ function isDebugLogEnabled() {
|
|
|
8770
8973
|
var spaceTokenFailureLogged = false;
|
|
8771
8974
|
function writeCustomSkills(targetDir, skills, log) {
|
|
8772
8975
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
8773
|
-
const base =
|
|
8976
|
+
const base = join18(targetDir, ".agents", "skills");
|
|
8774
8977
|
for (const skill of skills) {
|
|
8775
8978
|
const name = skill.name.trim();
|
|
8776
8979
|
if (!NAME_RE.test(name)) {
|
|
8777
8980
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
8778
8981
|
continue;
|
|
8779
8982
|
}
|
|
8780
|
-
const dir =
|
|
8781
|
-
if (
|
|
8983
|
+
const dir = join18(base, name);
|
|
8984
|
+
if (dirname7(dir) !== base) {
|
|
8782
8985
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
8783
8986
|
continue;
|
|
8784
8987
|
}
|
|
8785
|
-
|
|
8988
|
+
mkdirSync11(dir, { recursive: true });
|
|
8786
8989
|
const meta = ["---", `name: ${name}`];
|
|
8787
8990
|
if (skill.description?.trim()) {
|
|
8788
8991
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
8789
8992
|
}
|
|
8790
8993
|
meta.push("---", "");
|
|
8791
|
-
|
|
8994
|
+
writeFileSync9(join18(dir, "SKILL.md"), `${meta.join("\n")}
|
|
8792
8995
|
${skill.content ?? ""}`, "utf8");
|
|
8793
8996
|
}
|
|
8794
8997
|
}
|
|
8795
8998
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
8796
|
-
const dir =
|
|
8797
|
-
const refsDir =
|
|
8798
|
-
|
|
8999
|
+
const dir = join18(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
9000
|
+
const refsDir = join18(dir, "references");
|
|
9001
|
+
mkdirSync11(refsDir, { recursive: true });
|
|
8799
9002
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
8800
9003
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
8801
9004
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
8802
|
-
|
|
9005
|
+
writeFileSync9(join18(dir, "SKILL.md"), `${front}
|
|
8803
9006
|
${body}`, "utf8");
|
|
8804
|
-
|
|
9007
|
+
writeFileSync9(join18(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
8805
9008
|
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
9009
|
}
|
|
8807
9010
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -8832,8 +9035,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
8832
9035
|
};
|
|
8833
9036
|
}
|
|
8834
9037
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
8835
|
-
const briefDir =
|
|
8836
|
-
|
|
9038
|
+
const briefDir = join18(targetDir, ".agent_context");
|
|
9039
|
+
mkdirSync11(briefDir, { recursive: true });
|
|
8837
9040
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
8838
9041
|
|
|
8839
9042
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
@@ -8842,7 +9045,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
|
8842
9045
|
---
|
|
8843
9046
|
|
|
8844
9047
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
8845
|
-
|
|
9048
|
+
writeFileSync9(join18(briefDir, "task-context.md"), brief, "utf8");
|
|
8846
9049
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
8847
9050
|
}
|
|
8848
9051
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -8856,15 +9059,15 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
8856
9059
|
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
9060
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
8858
9061
|
}
|
|
8859
|
-
|
|
8860
|
-
|
|
9062
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
9063
|
+
writeFileSync9(join18(targetDir, "AGENTS.md"), `${content}
|
|
8861
9064
|
`, "utf8");
|
|
8862
9065
|
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
9066
|
return true;
|
|
8864
9067
|
}
|
|
8865
9068
|
function gitHead(repoDir) {
|
|
8866
9069
|
try {
|
|
8867
|
-
return
|
|
9070
|
+
return execFileSync10("git", ["-C", repoDir, "rev-parse", "HEAD"], { stdio: "pipe" }).toString().trim();
|
|
8868
9071
|
} catch {
|
|
8869
9072
|
return null;
|
|
8870
9073
|
}
|
|
@@ -8955,7 +9158,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
8955
9158
|
}
|
|
8956
9159
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
8957
9160
|
const execEnv = mergeExecEnv(env);
|
|
8958
|
-
if (!
|
|
9161
|
+
if (!existsSync15(target.dir) || !existsSync15(join18(target.dir, ".git"))) {
|
|
8959
9162
|
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
9163
|
return;
|
|
8961
9164
|
}
|
|
@@ -8970,7 +9173,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
|
8970
9173
|
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
9174
|
return;
|
|
8972
9175
|
}
|
|
8973
|
-
const headSha =
|
|
9176
|
+
const headSha = execFileSync10("git", ["-C", target.dir, "rev-parse", "HEAD"], { encoding: "utf-8", env: execEnv }).trim();
|
|
8974
9177
|
const headSubject = headCommitSubject(target.dir, execEnv);
|
|
8975
9178
|
const probePushState = ops.pushState ?? resolveRemotePushState;
|
|
8976
9179
|
let dirty = hasLocalChanges(target.dir, execEnv);
|
|
@@ -9010,7 +9213,7 @@ async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
|
9010
9213
|
}
|
|
9011
9214
|
}
|
|
9012
9215
|
function execGit(repoPath, args, env) {
|
|
9013
|
-
return
|
|
9216
|
+
return execFileSync10("git", ["-C", repoPath, ...args], {
|
|
9014
9217
|
encoding: "utf8",
|
|
9015
9218
|
stdio: "pipe",
|
|
9016
9219
|
env,
|
|
@@ -9051,7 +9254,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
|
|
|
9051
9254
|
if (!taskCode)
|
|
9052
9255
|
return;
|
|
9053
9256
|
const execEnv = mergeExecEnv(env);
|
|
9054
|
-
const branch =
|
|
9257
|
+
const branch = execFileSync10("git", ["branch", "--show-current"], { cwd: repoDir, encoding: "utf-8", env: execEnv }).trim();
|
|
9055
9258
|
if (!branch || branch === "main" || branch === "master")
|
|
9056
9259
|
return;
|
|
9057
9260
|
if (env.MORULA_GITLAB_URL && env.MORULA_GITLAB_TOKEN) {
|
|
@@ -9060,7 +9263,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
|
|
|
9060
9263
|
}
|
|
9061
9264
|
let prList;
|
|
9062
9265
|
try {
|
|
9063
|
-
prList =
|
|
9266
|
+
prList = execFileSync10("gh", ["pr", "list", "--head", branch, "--json", "number,title,body", "--limit", "1"], {
|
|
9064
9267
|
cwd: repoDir,
|
|
9065
9268
|
env: execEnv,
|
|
9066
9269
|
encoding: "utf-8"
|
|
@@ -9080,7 +9283,7 @@ async function ensurePullRequestTaskCode(repoDir, repo, code, env) {
|
|
|
9080
9283
|
const newBody = `${pr.body ?? ""}
|
|
9081
9284
|
|
|
9082
9285
|
Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4E8E PR \u5173\u8054\u4E0E\u5173\u95ED\u8054\u52A8\uFF09`;
|
|
9083
|
-
|
|
9286
|
+
execFileSync10("gh", ["api", "--method", "PATCH", `repos/${repo}/pulls/${pr.number}`, "-f", `title=${newTitle}`, "-f", `body=${newBody}`], {
|
|
9084
9287
|
cwd: repoDir,
|
|
9085
9288
|
env: execEnv,
|
|
9086
9289
|
encoding: "utf-8"
|
|
@@ -9123,12 +9326,12 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
9123
9326
|
|
|
9124
9327
|
// src/commands/doctor.ts
|
|
9125
9328
|
init_dist2();
|
|
9329
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
9330
|
+
import { homedir as homedir11 } from "node:os";
|
|
9331
|
+
import { dirname as dirname8, join as join19 } from "node:path";
|
|
9126
9332
|
init_daemon_config();
|
|
9127
9333
|
init_daemon_lock();
|
|
9128
9334
|
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
9335
|
async function doctorCommand(context) {
|
|
9133
9336
|
const probe = context.parsed.flags.probe === true || context.parsed.flags.probe === "true";
|
|
9134
9337
|
const { facts, checks } = await collectDoctor(context, probe);
|
|
@@ -9145,16 +9348,16 @@ async function doctorCommand(context) {
|
|
|
9145
9348
|
async function collectDoctor(context, probe) {
|
|
9146
9349
|
const checks = [];
|
|
9147
9350
|
const env = context.env;
|
|
9148
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
9149
|
-
const configPath = env.MORULA_CONFIG ??
|
|
9150
|
-
const daemonPath =
|
|
9151
|
-
const logFile =
|
|
9351
|
+
const configDir = env.MORULA_CONFIG_DIR ?? join19(homedir11(), ".morula-cli");
|
|
9352
|
+
const configPath = env.MORULA_CONFIG ?? join19(configDir, "config.yaml");
|
|
9353
|
+
const daemonPath = join19(dirname8(configPath), "daemon");
|
|
9354
|
+
const logFile = join19(daemonPath, "daemon.log");
|
|
9152
9355
|
const contextName = context.runtime.context ?? null;
|
|
9153
9356
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
9154
9357
|
const tokenPresent = Boolean(context.runtime.token);
|
|
9155
9358
|
const tokenSource = context.runtime.tokenSource ?? null;
|
|
9156
9359
|
let configReadable = false;
|
|
9157
|
-
if (
|
|
9360
|
+
if (existsSync16(configPath)) {
|
|
9158
9361
|
try {
|
|
9159
9362
|
readFileSync10(configPath, "utf8");
|
|
9160
9363
|
configReadable = true;
|
|
@@ -9162,10 +9365,10 @@ async function collectDoctor(context, probe) {
|
|
|
9162
9365
|
configReadable = false;
|
|
9163
9366
|
}
|
|
9164
9367
|
}
|
|
9165
|
-
const lockPath =
|
|
9368
|
+
const lockPath = join19(daemonPath, DAEMON_LOCK_FILE);
|
|
9166
9369
|
const lock = probeDaemonLock(lockPath);
|
|
9167
|
-
const pidFile =
|
|
9168
|
-
const filePid =
|
|
9370
|
+
const pidFile = join19(daemonPath, "daemon.pid");
|
|
9371
|
+
const filePid = existsSync16(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim() || 0) : 0;
|
|
9169
9372
|
const lockPid = lock.state === "active" ? lock.record.pid : 0;
|
|
9170
9373
|
const daemonPid = lockPid || (isProcessAlive(filePid) ? filePid : 0) || null;
|
|
9171
9374
|
const cfg = loadDaemonConfig(context.store);
|
|
@@ -9200,7 +9403,8 @@ async function collectDoctor(context, probe) {
|
|
|
9200
9403
|
logFile,
|
|
9201
9404
|
dimPath,
|
|
9202
9405
|
dimFound,
|
|
9203
|
-
envOverrides
|
|
9406
|
+
envOverrides,
|
|
9407
|
+
daemonAutostart: process.platform === "darwin" ? daemonAutostartEnabled() : null
|
|
9204
9408
|
};
|
|
9205
9409
|
checks.push(lock.state === "stale" ? {
|
|
9206
9410
|
id: "daemon.lock",
|
|
@@ -9226,7 +9430,7 @@ async function collectDoctor(context, probe) {
|
|
|
9226
9430
|
hint: "morula-runtime daemon doctor --probe\uFF08\u8054\u7F51\u786E\u8BA4\u767B\u5F55\u6001\uFF09"
|
|
9227
9431
|
});
|
|
9228
9432
|
}
|
|
9229
|
-
checks.push(configReadable ? { id: "config.readable", status: "ok", message: `\u914D\u7F6E\u53EF\u8BFB\uFF1A${configPath}` } :
|
|
9433
|
+
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
9434
|
if (!dimFound) {
|
|
9231
9435
|
checks.push({
|
|
9232
9436
|
id: "dim.present",
|
|
@@ -9247,6 +9451,18 @@ async function collectDoctor(context, probe) {
|
|
|
9247
9451
|
} else {
|
|
9248
9452
|
checks.push({ id: "daemon.running", status: "ok", message: `daemon \u8FD0\u884C\u4E2D\uFF08pid=${daemonPid}\uFF09` });
|
|
9249
9453
|
}
|
|
9454
|
+
if (facts.daemonAutostart !== null) {
|
|
9455
|
+
if (facts.daemonAutostart) {
|
|
9456
|
+
checks.push({
|
|
9457
|
+
id: "daemon.autostart",
|
|
9458
|
+
status: "ok",
|
|
9459
|
+
message: `daemon \u5F00\u673A\u81EA\u542F\uFF1Aon\uFF08${daemonAutostartPlistPath()}\uFF09`,
|
|
9460
|
+
...facts.daemonRunning ? {} : { hint: `tail -n 50 ${logFile}\uFF08\u81EA\u542F\u5B9E\u4F8B\u53EF\u80FD\u6CE8\u518C\u5931\u8D25\u540E\u5DF2\u9000\u51FA\uFF09` }
|
|
9461
|
+
});
|
|
9462
|
+
} else {
|
|
9463
|
+
checks.push({ id: "daemon.autostart", status: "ok", message: "daemon \u5F00\u673A\u81EA\u542F\uFF1Aoff" });
|
|
9464
|
+
}
|
|
9465
|
+
}
|
|
9250
9466
|
if (envOverrides.includes("MORULA_CONFIG_DIR") || envOverrides.includes("MORULA_CONFIG")) {
|
|
9251
9467
|
checks.push({
|
|
9252
9468
|
id: "config.override",
|
|
@@ -9322,10 +9538,10 @@ function detectInstallMode() {
|
|
|
9322
9538
|
|
|
9323
9539
|
// src/commands/daemon.ts
|
|
9324
9540
|
function configFilePath(env) {
|
|
9325
|
-
return env.MORULA_CONFIG ??
|
|
9541
|
+
return env.MORULA_CONFIG ?? join20(env.MORULA_CONFIG_DIR ?? join20(homedir12(), ".morula-cli"), "config.yaml");
|
|
9326
9542
|
}
|
|
9327
9543
|
function daemonDir(configPath) {
|
|
9328
|
-
return
|
|
9544
|
+
return join20(dirname9(configPath), "daemon");
|
|
9329
9545
|
}
|
|
9330
9546
|
var PID_FILE = "daemon.pid";
|
|
9331
9547
|
var LOG_FILE = "daemon.log";
|
|
@@ -9343,9 +9559,11 @@ async function daemonCommand(context) {
|
|
|
9343
9559
|
return models(context);
|
|
9344
9560
|
if (action === "portal")
|
|
9345
9561
|
return portal(context);
|
|
9562
|
+
if (action === "autostart")
|
|
9563
|
+
return autostart(context);
|
|
9346
9564
|
if (action === "doctor")
|
|
9347
9565
|
return doctorCommand(context);
|
|
9348
|
-
throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|doctor>", {
|
|
9566
|
+
throw usage("usage: morula-runtime daemon <run|start|stop|status|models|portal|autostart|doctor>", {
|
|
9349
9567
|
hint: "morula-runtime daemon --help"
|
|
9350
9568
|
});
|
|
9351
9569
|
}
|
|
@@ -9356,29 +9574,29 @@ async function portal(context) {
|
|
|
9356
9574
|
const { PortalServer: PortalServer2 } = await Promise.resolve().then(() => (init_portal_server(), portal_server_exports));
|
|
9357
9575
|
const { createPortalEnvironmentResolver: createPortalEnvironmentResolver2 } = await Promise.resolve().then(() => (init_portal_instances(), portal_instances_exports));
|
|
9358
9576
|
const port = Number(flagString(context.parsed.flags, "port") ?? process.env.MORULA_PORTAL_PORT ?? String(DEFAULT_PORTAL_PORT));
|
|
9359
|
-
const
|
|
9577
|
+
const autostart2 = flagString(context.parsed.flags, "autostart");
|
|
9360
9578
|
const dryRun = isDryRun(context);
|
|
9361
|
-
if (
|
|
9579
|
+
if (autostart2 === "on" || autostart2 === "off") {
|
|
9362
9580
|
const { setupAutostart: setupAutostart2, removeAutostart: removeAutostart2 } = await Promise.resolve().then(() => (init_portal_autostart(), portal_autostart_exports));
|
|
9363
9581
|
if (dryRun) {
|
|
9364
9582
|
return {
|
|
9365
9583
|
ok: true,
|
|
9366
9584
|
dryRun: true,
|
|
9367
|
-
autostart,
|
|
9585
|
+
autostart: autostart2,
|
|
9368
9586
|
planned: {
|
|
9369
|
-
action:
|
|
9587
|
+
action: autostart2 === "on" ? "register LaunchAgent (portal autostart)" : "remove LaunchAgent (portal autostart)",
|
|
9370
9588
|
note: "no file was written"
|
|
9371
9589
|
}
|
|
9372
9590
|
};
|
|
9373
9591
|
}
|
|
9374
|
-
if (
|
|
9592
|
+
if (autostart2 === "on") {
|
|
9375
9593
|
const installed = await setupAutostart2(context);
|
|
9376
9594
|
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
9595
|
} else {
|
|
9378
9596
|
await removeAutostart2(context);
|
|
9379
9597
|
context.err("[portal] \u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F");
|
|
9380
9598
|
}
|
|
9381
|
-
return { ok: true, autostart };
|
|
9599
|
+
return { ok: true, autostart: autostart2 };
|
|
9382
9600
|
}
|
|
9383
9601
|
if (dryRun) {
|
|
9384
9602
|
return {
|
|
@@ -9425,6 +9643,75 @@ async function portal(context) {
|
|
|
9425
9643
|
});
|
|
9426
9644
|
return { running: true };
|
|
9427
9645
|
}
|
|
9646
|
+
async function autostart(context) {
|
|
9647
|
+
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9648
|
+
const state = context.parsed.positionals[0];
|
|
9649
|
+
if (state !== "on" && state !== "off") {
|
|
9650
|
+
throw usage("usage: morula-runtime daemon autostart <on|off>", {
|
|
9651
|
+
hint: "morula-runtime daemon autostart --help"
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
const dryRun = isDryRun(context);
|
|
9655
|
+
if (process.platform !== "darwin") {
|
|
9656
|
+
context.err("[autostart] \u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\uFF08\u4EC5 macOS LaunchAgent\uFF09");
|
|
9657
|
+
return { ok: true, autostart: state, supported: false };
|
|
9658
|
+
}
|
|
9659
|
+
const envOverridden = ["MORULA_ENV", "MORULA_CONFIG", "MORULA_CONFIG_DIR", "MORULA_API_URL", "MORULA_TOKEN", "MORULA_TASK_TOKEN"].filter((name) => Boolean(context.env[name]));
|
|
9660
|
+
const flagOverridden = ["env", "url", "token"].filter((name) => context.parsed.flags[name] !== void 0);
|
|
9661
|
+
if (envOverridden.length || flagOverridden.length) {
|
|
9662
|
+
throw new CliError(
|
|
9663
|
+
"USAGE",
|
|
9664
|
+
EXIT.USAGE,
|
|
9665
|
+
`\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")}`,
|
|
9666
|
+
false,
|
|
9667
|
+
{ 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" }
|
|
9668
|
+
);
|
|
9669
|
+
}
|
|
9670
|
+
let identityError = null;
|
|
9671
|
+
if (dryRun) {
|
|
9672
|
+
try {
|
|
9673
|
+
resolveDaemonIdentity(context, { quiet: true });
|
|
9674
|
+
} catch (error) {
|
|
9675
|
+
identityError = error instanceof Error ? error.message : String(error);
|
|
9676
|
+
}
|
|
9677
|
+
} else {
|
|
9678
|
+
resolveDaemonIdentity(context, { quiet: true });
|
|
9679
|
+
}
|
|
9680
|
+
if (state === "on") {
|
|
9681
|
+
const plan = planDaemonAutostart();
|
|
9682
|
+
if (dryRun) {
|
|
9683
|
+
return {
|
|
9684
|
+
ok: true,
|
|
9685
|
+
dryRun: true,
|
|
9686
|
+
autostart: "on",
|
|
9687
|
+
planned: {
|
|
9688
|
+
action: "register LaunchAgent (daemon boot autostart)",
|
|
9689
|
+
...plan,
|
|
9690
|
+
note: "no file was written"
|
|
9691
|
+
},
|
|
9692
|
+
...identityError ? { blockedBy: { code: "NOT_LOGGED_IN", message: identityError, hint: "morula-runtime auth login" } } : {}
|
|
9693
|
+
};
|
|
9694
|
+
}
|
|
9695
|
+
const installed = await setupDaemonAutostart((line) => context.err(line));
|
|
9696
|
+
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");
|
|
9697
|
+
return { ok: true, autostart: "on", installed };
|
|
9698
|
+
}
|
|
9699
|
+
if (dryRun) {
|
|
9700
|
+
return {
|
|
9701
|
+
ok: true,
|
|
9702
|
+
dryRun: true,
|
|
9703
|
+
autostart: "off",
|
|
9704
|
+
planned: {
|
|
9705
|
+
action: "remove LaunchAgent (daemon boot autostart)",
|
|
9706
|
+
plistPath: daemonAutostartPlistPath(),
|
|
9707
|
+
note: "no file was written"
|
|
9708
|
+
}
|
|
9709
|
+
};
|
|
9710
|
+
}
|
|
9711
|
+
await removeDaemonAutostart((line) => context.err(line));
|
|
9712
|
+
context.err("[autostart] \u5DF2\u79FB\u9664 daemon \u5F00\u673A\u81EA\u542F\uFF08\u6B63\u5728\u8FD0\u884C\u7684 daemon \u4E0D\u53D7\u5F71\u54CD\uFF09");
|
|
9713
|
+
return { ok: true, autostart: "off" };
|
|
9714
|
+
}
|
|
9428
9715
|
async function models(context) {
|
|
9429
9716
|
rejectUnknownFlags(context.parsed.flags, ["provider"]);
|
|
9430
9717
|
const { listDimModels: listDimModels2, DIM_DEFAULT_PROVIDER: DIM_DEFAULT_PROVIDER2 } = await Promise.resolve().then(() => (init_dim_model(), dim_model_exports));
|
|
@@ -9448,11 +9735,15 @@ async function run2(context) {
|
|
|
9448
9735
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
9449
9736
|
}
|
|
9450
9737
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
9451
|
-
|
|
9452
|
-
const lockPath =
|
|
9738
|
+
mkdirSync12(resolvedWorkdir, { recursive: true });
|
|
9739
|
+
const lockPath = join20(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
|
|
9453
9740
|
const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
9454
9741
|
if (!acquired.ok) {
|
|
9455
9742
|
const pid = acquired.existing?.pid;
|
|
9743
|
+
if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
|
|
9744
|
+
context.err(`[daemon] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08pid=${pid ?? "\u672A\u77E5"}\uFF09\uFF0C\u81EA\u542F\u5B9E\u4F8B\u793C\u8BA9\u9000\u51FA`);
|
|
9745
|
+
return { running: false, yieldedTo: pid ?? null };
|
|
9746
|
+
}
|
|
9456
9747
|
throw new CliError(
|
|
9457
9748
|
"ALREADY_RUNNING",
|
|
9458
9749
|
EXIT.CONFLICT,
|
|
@@ -9487,11 +9778,21 @@ async function run2(context) {
|
|
|
9487
9778
|
context.err(`[daemon] morula-runtime daemon \u524D\u53F0\u8FD0\u884C\u4E2D\uFF08Ctrl+C \u9000\u51FA\uFF09`);
|
|
9488
9779
|
try {
|
|
9489
9780
|
await runner.start();
|
|
9781
|
+
} catch (error) {
|
|
9782
|
+
if (isAuthRejected(error)) {
|
|
9783
|
+
context.err(`[daemon] \u6CE8\u518C\u88AB\u62D2\uFF08\u767B\u5F55\u6001\u5931\u6548\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
9784
|
+
context.err("[daemon] \u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u6B21\u542F\u52A8\uFF1Amorula-runtime auth login && morula-runtime daemon start");
|
|
9785
|
+
return { running: false, stoppedReason: "auth" };
|
|
9786
|
+
}
|
|
9787
|
+
throw error;
|
|
9490
9788
|
} finally {
|
|
9491
9789
|
releaseLock();
|
|
9492
9790
|
}
|
|
9493
9791
|
return { running: false };
|
|
9494
9792
|
}
|
|
9793
|
+
function isAuthRejected(error) {
|
|
9794
|
+
return error instanceof MorulaApiError && (error.code === "UNAUTHENTICATED" || error.status === 401);
|
|
9795
|
+
}
|
|
9495
9796
|
async function start(context) {
|
|
9496
9797
|
rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent", "dry-run"]);
|
|
9497
9798
|
const name = flagString(context.parsed.flags, "name") ?? void 0;
|
|
@@ -9509,10 +9810,10 @@ async function start(context) {
|
|
|
9509
9810
|
}
|
|
9510
9811
|
const dir = daemonDir(configFilePath(context.env));
|
|
9511
9812
|
if (!dryRun)
|
|
9512
|
-
|
|
9513
|
-
const pidFile =
|
|
9514
|
-
const logFile =
|
|
9515
|
-
const lockPath =
|
|
9813
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
9814
|
+
const pidFile = join20(dir, PID_FILE);
|
|
9815
|
+
const logFile = join20(dir, LOG_FILE);
|
|
9816
|
+
const lockPath = join20(dir, DAEMON_LOCK_FILE);
|
|
9516
9817
|
const lock = probeDaemonLock(lockPath);
|
|
9517
9818
|
if (lock.state === "active") {
|
|
9518
9819
|
throw new CliError(
|
|
@@ -9523,7 +9824,7 @@ async function start(context) {
|
|
|
9523
9824
|
{ hint: "morula-runtime daemon status" }
|
|
9524
9825
|
);
|
|
9525
9826
|
}
|
|
9526
|
-
if (
|
|
9827
|
+
if (existsSync17(pidFile)) {
|
|
9527
9828
|
const pid = Number(readFileSync11(pidFile, "utf8").trim());
|
|
9528
9829
|
if (isProcessAlive(pid)) {
|
|
9529
9830
|
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 +9832,7 @@ async function start(context) {
|
|
|
9531
9832
|
});
|
|
9532
9833
|
}
|
|
9533
9834
|
if (!dryRun)
|
|
9534
|
-
|
|
9835
|
+
rmSync8(pidFile, { force: true });
|
|
9535
9836
|
}
|
|
9536
9837
|
const env = {
|
|
9537
9838
|
...process.env,
|
|
@@ -9569,12 +9870,12 @@ async function start(context) {
|
|
|
9569
9870
|
const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
|
|
9570
9871
|
const result = spawnDaemonProcess2({ logFile, env });
|
|
9571
9872
|
if (result.error || result.pid <= 0) {
|
|
9572
|
-
|
|
9873
|
+
rmSync8(pidFile, { force: true });
|
|
9573
9874
|
throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
|
|
9574
9875
|
hint: `tail -n 50 ${logFile}`
|
|
9575
9876
|
});
|
|
9576
9877
|
}
|
|
9577
|
-
|
|
9878
|
+
writeFileSync10(pidFile, String(result.pid), { mode: 384 });
|
|
9578
9879
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
9579
9880
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
9580
9881
|
}
|
|
@@ -9582,11 +9883,11 @@ async function stop(context) {
|
|
|
9582
9883
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9583
9884
|
const dryRun = isDryRun(context);
|
|
9584
9885
|
const dir = daemonDir(configFilePath(context.env));
|
|
9585
|
-
const pidFile =
|
|
9586
|
-
const lockPath =
|
|
9886
|
+
const pidFile = join20(dir, PID_FILE);
|
|
9887
|
+
const lockPath = join20(dir, DAEMON_LOCK_FILE);
|
|
9587
9888
|
const lock = probeDaemonLock(lockPath);
|
|
9588
9889
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
9589
|
-
const filePid =
|
|
9890
|
+
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
|
|
9590
9891
|
const pid = [lockPid, filePid].find((p) => p !== null && isProcessAlive(p)) ?? null;
|
|
9591
9892
|
if (dryRun) {
|
|
9592
9893
|
return {
|
|
@@ -9596,7 +9897,7 @@ async function stop(context) {
|
|
|
9596
9897
|
};
|
|
9597
9898
|
}
|
|
9598
9899
|
if (!pid) {
|
|
9599
|
-
|
|
9900
|
+
rmSync8(pidFile, { force: true });
|
|
9600
9901
|
releaseDaemonLock(lockPath);
|
|
9601
9902
|
return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
|
|
9602
9903
|
}
|
|
@@ -9605,7 +9906,7 @@ async function stop(context) {
|
|
|
9605
9906
|
releaseDaemonLock(lockPath);
|
|
9606
9907
|
else
|
|
9607
9908
|
releaseDaemonLock(lockPath, pid);
|
|
9608
|
-
|
|
9909
|
+
rmSync8(pidFile, { force: true });
|
|
9609
9910
|
if (result.outcome === "killed")
|
|
9610
9911
|
context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
|
|
9611
9912
|
return { running: false, stoppedPid: pid, outcome: result.outcome };
|
|
@@ -9614,11 +9915,11 @@ async function status2(context) {
|
|
|
9614
9915
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
9615
9916
|
const cfg = loadDaemonConfig(context.store);
|
|
9616
9917
|
const dir = daemonDir(configFilePath(context.env));
|
|
9617
|
-
const pidFile =
|
|
9618
|
-
const lockPath =
|
|
9918
|
+
const pidFile = join20(dir, PID_FILE);
|
|
9919
|
+
const lockPath = join20(dir, DAEMON_LOCK_FILE);
|
|
9619
9920
|
const lock = probeDaemonLock(lockPath);
|
|
9620
9921
|
const lockPid = lock.state === "active" ? lock.record.pid : null;
|
|
9621
|
-
const filePid =
|
|
9922
|
+
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
|
|
9622
9923
|
const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
|
|
9623
9924
|
return {
|
|
9624
9925
|
running: pid !== null,
|
|
@@ -9628,7 +9929,7 @@ async function status2(context) {
|
|
|
9628
9929
|
name: cfg?.name ?? null,
|
|
9629
9930
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
9630
9931
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
9631
|
-
logFile:
|
|
9932
|
+
logFile: join20(dir, LOG_FILE),
|
|
9632
9933
|
lockFile: lockPath
|
|
9633
9934
|
};
|
|
9634
9935
|
}
|
|
@@ -9658,32 +9959,32 @@ function parseMaxConcurrent(context) {
|
|
|
9658
9959
|
init_dist2();
|
|
9659
9960
|
|
|
9660
9961
|
// src/daemon/plugin-register.ts
|
|
9661
|
-
import { chmodSync as chmodSync3, cpSync, existsSync as
|
|
9662
|
-
import { homedir as
|
|
9663
|
-
import { dirname as
|
|
9962
|
+
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";
|
|
9963
|
+
import { homedir as homedir13 } from "node:os";
|
|
9964
|
+
import { dirname as dirname10, join as join21 } from "node:path";
|
|
9664
9965
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9665
9966
|
var PLUGIN_NAME = "morula-cli";
|
|
9666
9967
|
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
9667
|
-
function legacyAgentsPluginDir(home =
|
|
9668
|
-
return
|
|
9968
|
+
function legacyAgentsPluginDir(home = homedir13()) {
|
|
9969
|
+
return join21(home, ".agents", "plugins", PLUGIN_NAME);
|
|
9669
9970
|
}
|
|
9670
|
-
function resolveDimcodeHome(env = process.env, home =
|
|
9971
|
+
function resolveDimcodeHome(env = process.env, home = homedir13()) {
|
|
9671
9972
|
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
9672
|
-
return fromEnv ||
|
|
9973
|
+
return fromEnv || join21(home, ".dimcode", "v2");
|
|
9673
9974
|
}
|
|
9674
|
-
function defaultPluginsRoot(env = process.env, home =
|
|
9675
|
-
return
|
|
9975
|
+
function defaultPluginsRoot(env = process.env, home = homedir13()) {
|
|
9976
|
+
return join21(resolveDimcodeHome(env, home), "plugins");
|
|
9676
9977
|
}
|
|
9677
9978
|
function resolvePackagePaths(entryUrl) {
|
|
9678
|
-
const distDir =
|
|
9979
|
+
const distDir = dirname10(fileURLToPath2(entryUrl));
|
|
9679
9980
|
return {
|
|
9680
|
-
packageRoot:
|
|
9981
|
+
packageRoot: dirname10(distDir),
|
|
9681
9982
|
distDir,
|
|
9682
|
-
assetsDir:
|
|
9983
|
+
assetsDir: join21(distDir, "plugin")
|
|
9683
9984
|
};
|
|
9684
9985
|
}
|
|
9685
9986
|
function buildLauncherScript(opts) {
|
|
9686
|
-
const entry =
|
|
9987
|
+
const entry = join21(opts.packageRoot, "dist", "morula.js");
|
|
9687
9988
|
return `#!/bin/sh
|
|
9688
9989
|
# 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
9990
|
# \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 +9998,15 @@ exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
|
9697
9998
|
`;
|
|
9698
9999
|
}
|
|
9699
10000
|
function assetsReady(paths) {
|
|
9700
|
-
return
|
|
10001
|
+
return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
|
|
9701
10002
|
}
|
|
9702
10003
|
function listSkillNames(assetsDir) {
|
|
9703
|
-
const dir =
|
|
9704
|
-
if (!
|
|
10004
|
+
const dir = join21(assetsDir, "skills");
|
|
10005
|
+
if (!existsSync18(dir))
|
|
9705
10006
|
return [];
|
|
9706
10007
|
return readdirSync5(dir).filter((name) => {
|
|
9707
10008
|
try {
|
|
9708
|
-
return statSync4(
|
|
10009
|
+
return statSync4(join21(dir, name)).isDirectory();
|
|
9709
10010
|
} catch {
|
|
9710
10011
|
return false;
|
|
9711
10012
|
}
|
|
@@ -9714,24 +10015,24 @@ function listSkillNames(assetsDir) {
|
|
|
9714
10015
|
function registerPlugin(opts) {
|
|
9715
10016
|
const { pluginsRoot, paths, version } = opts;
|
|
9716
10017
|
const nodePath = opts.nodePath ?? process.execPath;
|
|
9717
|
-
const pluginDir =
|
|
10018
|
+
const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
|
|
9718
10019
|
if (!assetsReady(paths))
|
|
9719
10020
|
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
9720
|
-
const skillsSrc =
|
|
10021
|
+
const skillsSrc = join21(paths.assetsDir, "skills");
|
|
9721
10022
|
const skills = listSkillNames(paths.assetsDir);
|
|
9722
|
-
const manifestDir =
|
|
9723
|
-
|
|
9724
|
-
const manifest = JSON.parse(readFileSync12(
|
|
9725
|
-
|
|
10023
|
+
const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
|
|
10024
|
+
mkdirSync13(manifestDir, { recursive: true });
|
|
10025
|
+
const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
|
|
10026
|
+
writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
9726
10027
|
`);
|
|
9727
|
-
const skillsDest =
|
|
9728
|
-
|
|
9729
|
-
|
|
10028
|
+
const skillsDest = join21(pluginDir, "skills");
|
|
10029
|
+
rmSync9(skillsDest, { recursive: true, force: true });
|
|
10030
|
+
mkdirSync13(skillsDest, { recursive: true });
|
|
9730
10031
|
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
9731
|
-
const binDir =
|
|
9732
|
-
|
|
9733
|
-
const binPath =
|
|
9734
|
-
|
|
10032
|
+
const binDir = join21(pluginDir, "bin");
|
|
10033
|
+
mkdirSync13(binDir, { recursive: true });
|
|
10034
|
+
const binPath = join21(binDir, "morula");
|
|
10035
|
+
writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
9735
10036
|
chmodSync3(binPath, 493);
|
|
9736
10037
|
const legacy = legacyAgentsPluginDir(opts.home);
|
|
9737
10038
|
return {
|
|
@@ -9740,7 +10041,7 @@ function registerPlugin(opts) {
|
|
|
9740
10041
|
skills,
|
|
9741
10042
|
version,
|
|
9742
10043
|
node: nodePath,
|
|
9743
|
-
legacyDir:
|
|
10044
|
+
legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
9744
10045
|
};
|
|
9745
10046
|
}
|
|
9746
10047
|
|
|
@@ -9917,6 +10218,21 @@ var SPEC = {
|
|
|
9917
10218
|
idempotent: true,
|
|
9918
10219
|
emits: ["running", "autostart", "ok"]
|
|
9919
10220
|
},
|
|
10221
|
+
{
|
|
10222
|
+
action: "autostart",
|
|
10223
|
+
summary: "register/remove the macOS LaunchAgent so the daemon starts at login and is relaunched by launchd on abnormal exit (unattended executor machines)",
|
|
10224
|
+
args: [
|
|
10225
|
+
{ 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)" }
|
|
10226
|
+
],
|
|
10227
|
+
flags: [
|
|
10228
|
+
{ name: "dry-run", type: "boolean", description: "print the LaunchAgent plan without writing anything (read-only)" }
|
|
10229
|
+
],
|
|
10230
|
+
examples: ["morula-runtime daemon autostart on", "morula-runtime daemon autostart off"],
|
|
10231
|
+
writes: "local",
|
|
10232
|
+
interactive: false,
|
|
10233
|
+
idempotent: true,
|
|
10234
|
+
emits: ["ok", "autostart", "installed", "dryRun", "planned", "blockedBy"]
|
|
10235
|
+
},
|
|
9920
10236
|
{
|
|
9921
10237
|
action: "doctor",
|
|
9922
10238
|
summary: "read-only diagnostics: node/cli/config/auth/daemon/dim facts plus checks[] with hints (offline by default; --probe goes online)",
|