@adhdev/daemon-core 0.9.82-rc.171 → 0.9.82-rc.173
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/config/mesh-config.d.ts +1 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.js +775 -693
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +772 -692
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/beads-db.d.ts +1 -1
- package/dist/mesh/mesh-events.d.ts +1 -0
- package/dist/mesh/mesh-fast-forward.d.ts +2 -0
- package/dist/mesh/mesh-work-queue.d.ts +10 -1
- package/dist/repo-mesh-types.d.ts +2 -0
- package/package.json +1 -1
- package/src/config/mesh-config.ts +16 -0
- package/src/index.ts +1 -3
- package/src/mesh/beads-db.ts +20 -13
- package/src/mesh/mesh-events.ts +88 -10
- package/src/mesh/mesh-fast-forward.ts +11 -3
- package/src/mesh/mesh-work-queue.ts +49 -3
- package/src/repo-mesh-types.ts +2 -0
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/mesh/mesh-sync.ts +0 -111
package/dist/index.mjs
CHANGED
|
@@ -250,6 +250,263 @@ var init_git_executor = __esm({
|
|
|
250
250
|
}
|
|
251
251
|
});
|
|
252
252
|
|
|
253
|
+
// src/git/git-status.ts
|
|
254
|
+
async function getGitRepoStatus(workspace, options = {}) {
|
|
255
|
+
const lastCheckedAt = Date.now();
|
|
256
|
+
const includeSubmodules = options.includeSubmodules !== false;
|
|
257
|
+
try {
|
|
258
|
+
const repo = await resolveGitRepository(workspace, options);
|
|
259
|
+
let parsed = await readPorcelainStatus(repo, options);
|
|
260
|
+
let upstreamProbe = getInitialUpstreamProbe(parsed);
|
|
261
|
+
if (options.refreshUpstream) {
|
|
262
|
+
upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
|
|
263
|
+
if (upstreamProbe.upstreamStatus === "fresh") {
|
|
264
|
+
parsed = await readPorcelainStatus(repo, options);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const head = await readHead(repo, options);
|
|
268
|
+
const stashCount = await readStashCount(repo, options);
|
|
269
|
+
let submodules;
|
|
270
|
+
if (includeSubmodules) {
|
|
271
|
+
submodules = await getSubmoduleStatuses(repo, options);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
workspace: repo.workspace,
|
|
275
|
+
repoRoot: repo.repoRoot,
|
|
276
|
+
isGitRepo: true,
|
|
277
|
+
branch: parsed.branch,
|
|
278
|
+
headCommit: head.commit,
|
|
279
|
+
headMessage: head.message,
|
|
280
|
+
upstream: parsed.upstream,
|
|
281
|
+
upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
|
|
282
|
+
upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
|
|
283
|
+
upstreamFetchError: upstreamProbe.upstreamFetchError,
|
|
284
|
+
ahead: parsed.ahead,
|
|
285
|
+
behind: parsed.behind,
|
|
286
|
+
staged: parsed.staged,
|
|
287
|
+
modified: parsed.modified,
|
|
288
|
+
untracked: parsed.untracked,
|
|
289
|
+
deleted: parsed.deleted,
|
|
290
|
+
renamed: parsed.renamed,
|
|
291
|
+
hasConflicts: parsed.conflictFiles.length > 0,
|
|
292
|
+
conflictFiles: parsed.conflictFiles,
|
|
293
|
+
stashCount,
|
|
294
|
+
lastCheckedAt,
|
|
295
|
+
submodules
|
|
296
|
+
};
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (error instanceof GitCommandError) {
|
|
299
|
+
return emptyStatus(workspace, lastCheckedAt, error);
|
|
300
|
+
}
|
|
301
|
+
return emptyStatus(
|
|
302
|
+
workspace,
|
|
303
|
+
lastCheckedAt,
|
|
304
|
+
new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function readPorcelainStatus(repo, options) {
|
|
309
|
+
const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
|
|
310
|
+
return parsePorcelainV2Status(statusOutput.stdout);
|
|
311
|
+
}
|
|
312
|
+
function getInitialUpstreamProbe(parsed) {
|
|
313
|
+
return {
|
|
314
|
+
upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async function refreshTrackedUpstream(repo, parsed, options) {
|
|
318
|
+
if (!parsed.upstream || !parsed.branch) {
|
|
319
|
+
return { upstreamStatus: "no_upstream" };
|
|
320
|
+
}
|
|
321
|
+
const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
|
|
322
|
+
if (!remoteName) {
|
|
323
|
+
return {
|
|
324
|
+
upstreamStatus: "stale",
|
|
325
|
+
upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
|
|
330
|
+
return {
|
|
331
|
+
upstreamStatus: "fresh",
|
|
332
|
+
upstreamFetchedAt: Date.now()
|
|
333
|
+
};
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
upstreamStatus: "stale",
|
|
337
|
+
upstreamFetchError: formatGitError(error)
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function readBranchRemote(repo, branch, options) {
|
|
342
|
+
try {
|
|
343
|
+
const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
|
|
344
|
+
return result.stdout.trim() || null;
|
|
345
|
+
} catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function inferRemoteName(upstream) {
|
|
350
|
+
const [remoteName] = upstream.split("/");
|
|
351
|
+
return remoteName?.trim() || null;
|
|
352
|
+
}
|
|
353
|
+
function formatGitError(error) {
|
|
354
|
+
if (error instanceof GitCommandError) {
|
|
355
|
+
return error.stderr || error.message;
|
|
356
|
+
}
|
|
357
|
+
if (error instanceof Error) {
|
|
358
|
+
return error.message;
|
|
359
|
+
}
|
|
360
|
+
return String(error);
|
|
361
|
+
}
|
|
362
|
+
function parsePorcelainV2Status(output) {
|
|
363
|
+
const parsed = {
|
|
364
|
+
branch: null,
|
|
365
|
+
upstream: null,
|
|
366
|
+
ahead: 0,
|
|
367
|
+
behind: 0,
|
|
368
|
+
staged: 0,
|
|
369
|
+
modified: 0,
|
|
370
|
+
untracked: 0,
|
|
371
|
+
deleted: 0,
|
|
372
|
+
renamed: 0,
|
|
373
|
+
conflictFiles: []
|
|
374
|
+
};
|
|
375
|
+
for (const line of output.split("\n")) {
|
|
376
|
+
if (!line) continue;
|
|
377
|
+
if (line.startsWith("# branch.head ")) {
|
|
378
|
+
const branch = line.slice("# branch.head ".length).trim();
|
|
379
|
+
parsed.branch = branch && branch !== "(detached)" ? branch : null;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (line.startsWith("# branch.upstream ")) {
|
|
383
|
+
parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (line.startsWith("# branch.ab ")) {
|
|
387
|
+
const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
|
|
388
|
+
if (match) {
|
|
389
|
+
parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
|
|
390
|
+
parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
|
|
391
|
+
}
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (line.startsWith("? ")) {
|
|
395
|
+
parsed.untracked += 1;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (line.startsWith("u ")) {
|
|
399
|
+
const fields = line.split(" ");
|
|
400
|
+
const filePath = fields.slice(10).join(" ");
|
|
401
|
+
if (filePath) parsed.conflictFiles.push(filePath);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (line.startsWith("1 ") || line.startsWith("2 ")) {
|
|
405
|
+
const fields = line.split(" ");
|
|
406
|
+
const xy = fields[1] ?? "..";
|
|
407
|
+
const indexStatus = xy[0] ?? ".";
|
|
408
|
+
const worktreeStatus = xy[1] ?? ".";
|
|
409
|
+
if (isStagedStatus(indexStatus)) parsed.staged += 1;
|
|
410
|
+
if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
|
|
411
|
+
if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
|
|
412
|
+
if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
|
|
413
|
+
if (xy.includes("U")) {
|
|
414
|
+
const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
|
|
415
|
+
if (filePath) parsed.conflictFiles.push(filePath);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
|
|
420
|
+
return parsed;
|
|
421
|
+
}
|
|
422
|
+
async function readHead(repo, options) {
|
|
423
|
+
try {
|
|
424
|
+
const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
|
|
425
|
+
const text = result.stdout.trimEnd();
|
|
426
|
+
if (!text) return { commit: null, message: null };
|
|
427
|
+
const [commit, ...messageParts] = text.split("\0");
|
|
428
|
+
return {
|
|
429
|
+
commit: commit || null,
|
|
430
|
+
message: messageParts.join("\0") || null
|
|
431
|
+
};
|
|
432
|
+
} catch {
|
|
433
|
+
return { commit: null, message: null };
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function readStashCount(repo, options) {
|
|
437
|
+
try {
|
|
438
|
+
const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
|
|
439
|
+
return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
440
|
+
} catch {
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function isStagedStatus(status) {
|
|
445
|
+
return status !== "." && status !== "?" && status !== "U";
|
|
446
|
+
}
|
|
447
|
+
function emptyStatus(workspace, lastCheckedAt, error) {
|
|
448
|
+
return {
|
|
449
|
+
workspace,
|
|
450
|
+
repoRoot: null,
|
|
451
|
+
isGitRepo: false,
|
|
452
|
+
branch: null,
|
|
453
|
+
headCommit: null,
|
|
454
|
+
headMessage: null,
|
|
455
|
+
upstream: null,
|
|
456
|
+
upstreamStatus: "unavailable",
|
|
457
|
+
ahead: 0,
|
|
458
|
+
behind: 0,
|
|
459
|
+
staged: 0,
|
|
460
|
+
modified: 0,
|
|
461
|
+
untracked: 0,
|
|
462
|
+
deleted: 0,
|
|
463
|
+
renamed: 0,
|
|
464
|
+
hasConflicts: false,
|
|
465
|
+
conflictFiles: [],
|
|
466
|
+
stashCount: 0,
|
|
467
|
+
lastCheckedAt,
|
|
468
|
+
error: error.stderr || error.message,
|
|
469
|
+
reason: error.reason
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
async function getSubmoduleStatuses(repo, options) {
|
|
473
|
+
if (!repo.repoRoot) return [];
|
|
474
|
+
try {
|
|
475
|
+
const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
|
|
476
|
+
return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
|
|
477
|
+
} catch {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
|
|
482
|
+
const submodules = [];
|
|
483
|
+
const ignoreSet = new Set(ignorePaths || []);
|
|
484
|
+
for (const line of output.split("\n")) {
|
|
485
|
+
if (!line.trim()) continue;
|
|
486
|
+
const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
|
|
487
|
+
if (!match) continue;
|
|
488
|
+
const prefix = match[1];
|
|
489
|
+
const commit = match[2];
|
|
490
|
+
const path40 = match[3];
|
|
491
|
+
if (ignoreSet.has(path40)) continue;
|
|
492
|
+
submodules.push({
|
|
493
|
+
path: path40,
|
|
494
|
+
commit,
|
|
495
|
+
repoPath: repoRoot + "/" + path40,
|
|
496
|
+
dirty: prefix === "+",
|
|
497
|
+
outOfSync: prefix === "-",
|
|
498
|
+
lastCheckedAt: Date.now()
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return submodules;
|
|
502
|
+
}
|
|
503
|
+
var init_git_status = __esm({
|
|
504
|
+
"src/git/git-status.ts"() {
|
|
505
|
+
"use strict";
|
|
506
|
+
init_git_executor();
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
253
510
|
// src/git/git-worktree.ts
|
|
254
511
|
var git_worktree_exports = {};
|
|
255
512
|
__export(git_worktree_exports, {
|
|
@@ -778,6 +1035,16 @@ function loadMeshConfig() {
|
|
|
778
1035
|
return { meshes: [] };
|
|
779
1036
|
}
|
|
780
1037
|
}
|
|
1038
|
+
function normalizeCapabilityTags(value) {
|
|
1039
|
+
if (!Array.isArray(value)) return void 0;
|
|
1040
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1041
|
+
const tags = value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
|
|
1042
|
+
if (seen.has(tag)) return false;
|
|
1043
|
+
seen.add(tag);
|
|
1044
|
+
return true;
|
|
1045
|
+
});
|
|
1046
|
+
return tags.length ? tags : void 0;
|
|
1047
|
+
}
|
|
781
1048
|
function saveMeshConfig(config) {
|
|
782
1049
|
const path40 = getMeshConfigPath();
|
|
783
1050
|
writeFileSync2(path40, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
@@ -1063,6 +1330,7 @@ function addNode(meshId, opts) {
|
|
|
1063
1330
|
repoRoot: opts.repoRoot,
|
|
1064
1331
|
daemonId: opts.daemonId,
|
|
1065
1332
|
machineId: opts.machineId,
|
|
1333
|
+
capabilities: normalizeCapabilityTags(opts.capabilities),
|
|
1066
1334
|
userOverrides: opts.userOverrides || {},
|
|
1067
1335
|
policy: opts.policy || {},
|
|
1068
1336
|
isLocalWorktree: opts.isLocalWorktree,
|
|
@@ -2545,6 +2813,369 @@ var init_mesh_ledger = __esm({
|
|
|
2545
2813
|
}
|
|
2546
2814
|
});
|
|
2547
2815
|
|
|
2816
|
+
// src/mesh/mesh-fast-forward.ts
|
|
2817
|
+
async function fastForwardMeshNode(args) {
|
|
2818
|
+
const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
|
|
2819
|
+
const nodeId = normalizeOptionalString(args.nodeId);
|
|
2820
|
+
const meshId = normalizeOptionalString(args.meshId);
|
|
2821
|
+
const requestedBranch = normalizeOptionalString(args.branch);
|
|
2822
|
+
const trigger = normalizeOptionalString(args.trigger) || "manual";
|
|
2823
|
+
const updateSubmodules = args.updateSubmodules === true;
|
|
2824
|
+
const dryRun = args.dryRun === true || args.execute !== true;
|
|
2825
|
+
const plannedSteps = buildPlannedSteps(updateSubmodules);
|
|
2826
|
+
const base = {
|
|
2827
|
+
...nodeId ? { nodeId } : {},
|
|
2828
|
+
...meshId ? { meshId } : {},
|
|
2829
|
+
workspace,
|
|
2830
|
+
dryRun,
|
|
2831
|
+
updateSubmodules,
|
|
2832
|
+
plannedSteps,
|
|
2833
|
+
trigger
|
|
2834
|
+
};
|
|
2835
|
+
if (!workspace) {
|
|
2836
|
+
return block(base, "invalid_workspace", ["workspace_required"]);
|
|
2837
|
+
}
|
|
2838
|
+
const current = await getGitRepoStatus(workspace, {
|
|
2839
|
+
...STATUS_OPTIONS,
|
|
2840
|
+
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
2841
|
+
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
2842
|
+
});
|
|
2843
|
+
const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
|
|
2844
|
+
if (earlyBlockers.length > 0) {
|
|
2845
|
+
const result2 = {
|
|
2846
|
+
...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
|
|
2847
|
+
current,
|
|
2848
|
+
finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
|
|
2849
|
+
};
|
|
2850
|
+
await appendFastForwardLedger(result2, "blocked");
|
|
2851
|
+
return result2;
|
|
2852
|
+
}
|
|
2853
|
+
if (current.behind === 0) {
|
|
2854
|
+
const result2 = {
|
|
2855
|
+
...base,
|
|
2856
|
+
success: true,
|
|
2857
|
+
code: "already_up_to_date",
|
|
2858
|
+
allowed: true,
|
|
2859
|
+
willRun: false,
|
|
2860
|
+
executed: false,
|
|
2861
|
+
blockingReasons: [],
|
|
2862
|
+
current,
|
|
2863
|
+
preStatus: current,
|
|
2864
|
+
postStatus: current,
|
|
2865
|
+
finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
|
|
2866
|
+
};
|
|
2867
|
+
await appendFastForwardLedger(result2, "noop");
|
|
2868
|
+
return result2;
|
|
2869
|
+
}
|
|
2870
|
+
const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
|
|
2871
|
+
if (!ancestorCheck.ok) {
|
|
2872
|
+
const result2 = {
|
|
2873
|
+
...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
|
|
2874
|
+
current,
|
|
2875
|
+
preStatus: current,
|
|
2876
|
+
operationError: ancestorCheck.error,
|
|
2877
|
+
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
2878
|
+
};
|
|
2879
|
+
await appendFastForwardLedger(result2, "blocked");
|
|
2880
|
+
return result2;
|
|
2881
|
+
}
|
|
2882
|
+
if (dryRun) {
|
|
2883
|
+
const result2 = {
|
|
2884
|
+
...base,
|
|
2885
|
+
success: true,
|
|
2886
|
+
code: "fast_forward_available",
|
|
2887
|
+
allowed: true,
|
|
2888
|
+
willRun: false,
|
|
2889
|
+
executed: false,
|
|
2890
|
+
blockingReasons: [],
|
|
2891
|
+
current,
|
|
2892
|
+
preStatus: current,
|
|
2893
|
+
finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
|
|
2894
|
+
};
|
|
2895
|
+
await appendFastForwardLedger(result2, "dry_run");
|
|
2896
|
+
return result2;
|
|
2897
|
+
}
|
|
2898
|
+
try {
|
|
2899
|
+
await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
|
|
2900
|
+
} catch (error) {
|
|
2901
|
+
const result2 = {
|
|
2902
|
+
...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
|
|
2903
|
+
current,
|
|
2904
|
+
preStatus: current,
|
|
2905
|
+
operationError: formatGitError2(error),
|
|
2906
|
+
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
2907
|
+
};
|
|
2908
|
+
await appendFastForwardLedger(result2, "failed");
|
|
2909
|
+
return result2;
|
|
2910
|
+
}
|
|
2911
|
+
let postStatus = await getGitRepoStatus(workspace, {
|
|
2912
|
+
...STATUS_OPTIONS,
|
|
2913
|
+
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
2914
|
+
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
2915
|
+
});
|
|
2916
|
+
const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
|
|
2917
|
+
let submoduleFollowUpRequired = false;
|
|
2918
|
+
let operationError;
|
|
2919
|
+
if (submoduleIssues.length > 0) {
|
|
2920
|
+
if (updateSubmodules) {
|
|
2921
|
+
try {
|
|
2922
|
+
await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
|
|
2923
|
+
postStatus = await getGitRepoStatus(workspace, {
|
|
2924
|
+
...STATUS_OPTIONS,
|
|
2925
|
+
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
2926
|
+
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
2927
|
+
});
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
operationError = formatGitError2(error);
|
|
2930
|
+
}
|
|
2931
|
+
} else {
|
|
2932
|
+
submoduleFollowUpRequired = true;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
const postBlockers = collectPostExecutionBlockers(postStatus);
|
|
2936
|
+
if (operationError) postBlockers.push("submodule_update_failed");
|
|
2937
|
+
if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
|
|
2938
|
+
const success = postBlockers.length === 0 || submoduleFollowUpRequired;
|
|
2939
|
+
const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
|
|
2940
|
+
const result = {
|
|
2941
|
+
...base,
|
|
2942
|
+
success,
|
|
2943
|
+
code,
|
|
2944
|
+
allowed: true,
|
|
2945
|
+
willRun: true,
|
|
2946
|
+
executed: true,
|
|
2947
|
+
blockingReasons: postBlockers,
|
|
2948
|
+
current,
|
|
2949
|
+
preStatus: current,
|
|
2950
|
+
postStatus,
|
|
2951
|
+
...operationError ? { operationError } : {},
|
|
2952
|
+
finalBranchConvergenceState: buildConvergenceState(
|
|
2953
|
+
postStatus,
|
|
2954
|
+
postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
|
|
2955
|
+
)
|
|
2956
|
+
};
|
|
2957
|
+
await appendFastForwardLedger(result, success ? "executed" : "failed");
|
|
2958
|
+
return result;
|
|
2959
|
+
}
|
|
2960
|
+
function buildPlannedSteps(updateSubmodules) {
|
|
2961
|
+
const steps = [
|
|
2962
|
+
{
|
|
2963
|
+
operation: "refresh_upstream",
|
|
2964
|
+
description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
|
|
2965
|
+
safe: true,
|
|
2966
|
+
willMutateWorktree: false
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
operation: "verify_clean_worktree",
|
|
2970
|
+
description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
|
|
2971
|
+
safe: true,
|
|
2972
|
+
willMutateWorktree: false
|
|
2973
|
+
},
|
|
2974
|
+
{
|
|
2975
|
+
operation: "verify_fast_forward",
|
|
2976
|
+
description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
|
|
2977
|
+
safe: true,
|
|
2978
|
+
willMutateWorktree: false
|
|
2979
|
+
},
|
|
2980
|
+
{
|
|
2981
|
+
operation: "merge_ff_only",
|
|
2982
|
+
description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
|
|
2983
|
+
safe: true,
|
|
2984
|
+
willMutateWorktree: true
|
|
2985
|
+
}
|
|
2986
|
+
];
|
|
2987
|
+
if (updateSubmodules) {
|
|
2988
|
+
steps.push({
|
|
2989
|
+
operation: "submodule_update",
|
|
2990
|
+
description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
|
|
2991
|
+
safe: true,
|
|
2992
|
+
willMutateWorktree: true
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
steps.push({
|
|
2996
|
+
operation: "verify_post_status",
|
|
2997
|
+
description: "Re-read daemon-owned git status and report final branch convergence state.",
|
|
2998
|
+
safe: true,
|
|
2999
|
+
willMutateWorktree: false
|
|
3000
|
+
});
|
|
3001
|
+
return steps;
|
|
3002
|
+
}
|
|
3003
|
+
function collectPreflightBlockers(status, requestedBranch) {
|
|
3004
|
+
const blockers = [];
|
|
3005
|
+
if (!status.isGitRepo) blockers.push("not_git_repo");
|
|
3006
|
+
if (!status.branch) blockers.push("detached_head_or_unknown_branch");
|
|
3007
|
+
if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
|
|
3008
|
+
if (!status.upstream) blockers.push("upstream_missing");
|
|
3009
|
+
if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
|
|
3010
|
+
if (status.hasConflicts) blockers.push("conflicts_present");
|
|
3011
|
+
if (status.staged > 0) blockers.push("staged_changes_present");
|
|
3012
|
+
if (status.modified > 0) blockers.push("modified_changes_present");
|
|
3013
|
+
if (status.untracked > 0) blockers.push("untracked_changes_present");
|
|
3014
|
+
if (status.deleted > 0) blockers.push("deleted_changes_present");
|
|
3015
|
+
if (status.renamed > 0) blockers.push("renamed_changes_present");
|
|
3016
|
+
if (status.stashCount > 0) blockers.push("stash_entries_present");
|
|
3017
|
+
blockers.push(...collectSubmoduleBlockers(status, "pre"));
|
|
3018
|
+
if (status.ahead > 0 && status.behind > 0) {
|
|
3019
|
+
blockers.push("branch_diverged_from_upstream");
|
|
3020
|
+
blockers.push("branch_has_local_commits");
|
|
3021
|
+
} else if (status.ahead > 0) blockers.push("branch_has_local_commits");
|
|
3022
|
+
return blockers;
|
|
3023
|
+
}
|
|
3024
|
+
function collectPostExecutionBlockers(status) {
|
|
3025
|
+
const blockers = [];
|
|
3026
|
+
if (!status.isGitRepo) blockers.push("post_not_git_repo");
|
|
3027
|
+
if (status.hasConflicts) blockers.push("post_conflicts_present");
|
|
3028
|
+
if (status.ahead !== 0) blockers.push("post_branch_ahead");
|
|
3029
|
+
if (status.behind !== 0) blockers.push("post_branch_still_behind");
|
|
3030
|
+
if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
|
|
3031
|
+
blockers.push("post_working_tree_not_clean");
|
|
3032
|
+
}
|
|
3033
|
+
if (status.stashCount > 0) blockers.push("post_stash_entries_present");
|
|
3034
|
+
blockers.push(...collectSubmoduleBlockers(status, "post"));
|
|
3035
|
+
return blockers;
|
|
3036
|
+
}
|
|
3037
|
+
function collectSubmoduleBlockers(status, phase) {
|
|
3038
|
+
const submodules = Array.isArray(status.submodules) ? status.submodules : [];
|
|
3039
|
+
const blockers = [];
|
|
3040
|
+
for (const submodule of submodules) {
|
|
3041
|
+
if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
|
|
3042
|
+
if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
|
|
3043
|
+
if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
|
|
3044
|
+
}
|
|
3045
|
+
return blockers;
|
|
3046
|
+
}
|
|
3047
|
+
function chooseBlockCode(status, blockers) {
|
|
3048
|
+
if (blockers.includes("not_git_repo")) return "not_git_repo";
|
|
3049
|
+
if (blockers.includes("branch_mismatch")) return "branch_mismatch";
|
|
3050
|
+
if (blockers.includes("upstream_missing")) return "upstream_missing";
|
|
3051
|
+
if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
|
|
3052
|
+
if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
|
|
3053
|
+
if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
|
|
3054
|
+
if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
|
|
3055
|
+
if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
|
|
3056
|
+
return "preflight_blocked";
|
|
3057
|
+
}
|
|
3058
|
+
function codeToConvergenceStatus(code) {
|
|
3059
|
+
if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
|
|
3060
|
+
if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
|
|
3061
|
+
return "blocked";
|
|
3062
|
+
}
|
|
3063
|
+
async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
|
|
3064
|
+
if (!upstream) return { ok: false, error: "missing upstream" };
|
|
3065
|
+
try {
|
|
3066
|
+
await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
|
|
3067
|
+
return { ok: true };
|
|
3068
|
+
} catch (error) {
|
|
3069
|
+
return { ok: false, error: formatGitError2(error) };
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
function block(base, code, blockingReasons) {
|
|
3073
|
+
const normalizedReasons = normalizeBlockingReasons(blockingReasons);
|
|
3074
|
+
return {
|
|
3075
|
+
...base,
|
|
3076
|
+
success: false,
|
|
3077
|
+
code,
|
|
3078
|
+
allowed: false,
|
|
3079
|
+
willRun: false,
|
|
3080
|
+
executed: false,
|
|
3081
|
+
blockingReasons: normalizedReasons
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
function normalizeBlockingReasons(reasons) {
|
|
3085
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
3086
|
+
for (const reason of reasons) {
|
|
3087
|
+
normalized.add(reason);
|
|
3088
|
+
}
|
|
3089
|
+
if ([
|
|
3090
|
+
"conflicts_present",
|
|
3091
|
+
"staged_changes_present",
|
|
3092
|
+
"modified_changes_present",
|
|
3093
|
+
"untracked_changes_present",
|
|
3094
|
+
"deleted_changes_present",
|
|
3095
|
+
"renamed_changes_present"
|
|
3096
|
+
].some((reason) => normalized.has(reason))) {
|
|
3097
|
+
normalized.add("working_tree_not_clean");
|
|
3098
|
+
}
|
|
3099
|
+
return Array.from(normalized);
|
|
3100
|
+
}
|
|
3101
|
+
function buildConvergenceState(status, convergenceStatus) {
|
|
3102
|
+
return {
|
|
3103
|
+
status: convergenceStatus,
|
|
3104
|
+
branch: status.branch,
|
|
3105
|
+
headCommit: status.headCommit,
|
|
3106
|
+
upstream: status.upstream,
|
|
3107
|
+
ahead: status.ahead,
|
|
3108
|
+
behind: status.behind,
|
|
3109
|
+
dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
|
|
3110
|
+
stashCount: status.stashCount,
|
|
3111
|
+
submodules: summarizeSubmodules(status.submodules)
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
function summarizeSubmodules(submodules) {
|
|
3115
|
+
return (submodules || []).map((submodule) => ({
|
|
3116
|
+
path: submodule.path,
|
|
3117
|
+
commit: submodule.commit,
|
|
3118
|
+
dirty: submodule.dirty,
|
|
3119
|
+
outOfSync: submodule.outOfSync,
|
|
3120
|
+
...submodule.error ? { error: submodule.error } : {}
|
|
3121
|
+
}));
|
|
3122
|
+
}
|
|
3123
|
+
function normalizeOptionalString(value) {
|
|
3124
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
3125
|
+
}
|
|
3126
|
+
function formatGitError2(error) {
|
|
3127
|
+
if (error instanceof GitCommandError) {
|
|
3128
|
+
return error.stderr || error.stdout || error.message;
|
|
3129
|
+
}
|
|
3130
|
+
if (error instanceof Error) return error.message;
|
|
3131
|
+
return String(error);
|
|
3132
|
+
}
|
|
3133
|
+
async function appendFastForwardLedger(result, outcome) {
|
|
3134
|
+
if (!result.meshId) return;
|
|
3135
|
+
try {
|
|
3136
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
3137
|
+
appendLedgerEntry2(result.meshId, {
|
|
3138
|
+
kind: "direct_fast_forward",
|
|
3139
|
+
...result.nodeId ? { nodeId: result.nodeId } : {},
|
|
3140
|
+
payload: {
|
|
3141
|
+
operation: "mesh_fast_forward_node",
|
|
3142
|
+
trigger: result.trigger || "manual",
|
|
3143
|
+
outcome,
|
|
3144
|
+
code: result.code,
|
|
3145
|
+
workspace: result.workspace,
|
|
3146
|
+
allowed: result.allowed,
|
|
3147
|
+
dryRun: result.dryRun,
|
|
3148
|
+
willRun: result.willRun,
|
|
3149
|
+
executed: result.executed,
|
|
3150
|
+
branch: result.postStatus?.branch ?? result.current?.branch,
|
|
3151
|
+
upstream: result.postStatus?.upstream ?? result.current?.upstream,
|
|
3152
|
+
before: result.current ? {
|
|
3153
|
+
headCommit: result.current.headCommit,
|
|
3154
|
+
ahead: result.current.ahead,
|
|
3155
|
+
behind: result.current.behind
|
|
3156
|
+
} : void 0,
|
|
3157
|
+
after: result.postStatus ? {
|
|
3158
|
+
headCommit: result.postStatus.headCommit,
|
|
3159
|
+
ahead: result.postStatus.ahead,
|
|
3160
|
+
behind: result.postStatus.behind
|
|
3161
|
+
} : void 0,
|
|
3162
|
+
blockingReasons: result.blockingReasons
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
} catch (error) {
|
|
3166
|
+
result.ledgerError = error instanceof Error ? error.message : String(error);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
var STATUS_OPTIONS;
|
|
3170
|
+
var init_mesh_fast_forward = __esm({
|
|
3171
|
+
"src/mesh/mesh-fast-forward.ts"() {
|
|
3172
|
+
"use strict";
|
|
3173
|
+
init_git_status();
|
|
3174
|
+
init_git_executor();
|
|
3175
|
+
STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
|
|
2548
3179
|
// src/mesh/beads-db.ts
|
|
2549
3180
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
|
|
2550
3181
|
import { dirname as dirname2, join as join12 } from "path";
|
|
@@ -2566,6 +3197,7 @@ var init_beads_db = __esm({
|
|
|
2566
3197
|
"src/mesh/beads-db.ts"() {
|
|
2567
3198
|
"use strict";
|
|
2568
3199
|
init_mesh_ledger();
|
|
3200
|
+
init_mesh_work_queue();
|
|
2569
3201
|
BeadsDB = class _BeadsDB {
|
|
2570
3202
|
static instance;
|
|
2571
3203
|
db;
|
|
@@ -2786,25 +3418,29 @@ var init_beads_db = __esm({
|
|
|
2786
3418
|
return row !== void 0;
|
|
2787
3419
|
}
|
|
2788
3420
|
// O(1) claim: transaction ensures only one session claims a pending task
|
|
2789
|
-
claimNextQueueTask(meshId, nodeId, sessionId) {
|
|
3421
|
+
claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags = []) {
|
|
2790
3422
|
return this.transaction(() => {
|
|
2791
3423
|
this.ensureLegacyQueueMigrated(meshId);
|
|
2792
3424
|
if (this.hasActiveAssignment(meshId, sessionId, nodeId)) return null;
|
|
2793
|
-
const
|
|
3425
|
+
const rows = [
|
|
3426
|
+
...this.db.prepare(`
|
|
2794
3427
|
SELECT payload FROM mesh_queue
|
|
2795
3428
|
WHERE mesh_id = ? AND status = 'pending' AND target_session_id = ?
|
|
2796
|
-
ORDER BY created_at ASC
|
|
2797
|
-
`).
|
|
3429
|
+
ORDER BY created_at ASC
|
|
3430
|
+
`).all(meshId, sessionId),
|
|
3431
|
+
...this.db.prepare(`
|
|
2798
3432
|
SELECT payload FROM mesh_queue
|
|
2799
3433
|
WHERE mesh_id = ? AND status = 'pending' AND target_node_id = ? AND target_session_id IS NULL
|
|
2800
|
-
ORDER BY created_at ASC
|
|
2801
|
-
`).
|
|
3434
|
+
ORDER BY created_at ASC
|
|
3435
|
+
`).all(meshId, nodeId),
|
|
3436
|
+
...this.db.prepare(`
|
|
2802
3437
|
SELECT payload FROM mesh_queue
|
|
2803
3438
|
WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
|
|
2804
|
-
ORDER BY created_at ASC
|
|
2805
|
-
`).
|
|
2806
|
-
|
|
2807
|
-
const entry = JSON.parse(row.payload);
|
|
3439
|
+
ORDER BY created_at ASC
|
|
3440
|
+
`).all(meshId)
|
|
3441
|
+
];
|
|
3442
|
+
const entry = rows.map((row) => JSON.parse(row.payload)).find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags));
|
|
3443
|
+
if (!entry) return null;
|
|
2808
3444
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2809
3445
|
entry.status = "assigned";
|
|
2810
3446
|
entry.assignedNodeId = nodeId;
|
|
@@ -2952,6 +3588,7 @@ __export(mesh_work_queue_exports, {
|
|
|
2952
3588
|
__clearMeshQueueForTests: () => __clearMeshQueueForTests,
|
|
2953
3589
|
__replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
|
|
2954
3590
|
__resetBeadsDBForTests: () => __resetBeadsDBForTests,
|
|
3591
|
+
buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
|
|
2955
3592
|
cancelTask: () => cancelTask,
|
|
2956
3593
|
claimNextTask: () => claimNextTask,
|
|
2957
3594
|
cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
|
|
@@ -2962,6 +3599,8 @@ __export(mesh_work_queue_exports, {
|
|
|
2962
3599
|
getQueue: () => getQueue,
|
|
2963
3600
|
insertDirectDispatch: () => insertDirectDispatch,
|
|
2964
3601
|
markStaleDirectDispatches: () => markStaleDirectDispatches,
|
|
3602
|
+
nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
|
|
3603
|
+
normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
|
|
2965
3604
|
normalizeMeshTaskMode: () => normalizeMeshTaskMode,
|
|
2966
3605
|
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
2967
3606
|
requeueTask: () => requeueTask,
|
|
@@ -2997,6 +3636,35 @@ function validateMeshTaskModeRequest(mode, message) {
|
|
|
2997
3636
|
]
|
|
2998
3637
|
};
|
|
2999
3638
|
}
|
|
3639
|
+
function normalizeMeshCapabilityTags(value) {
|
|
3640
|
+
if (!Array.isArray(value)) return [];
|
|
3641
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3642
|
+
return value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
|
|
3643
|
+
if (seen.has(tag)) return false;
|
|
3644
|
+
seen.add(tag);
|
|
3645
|
+
return true;
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
function firstProviderPriority(policy) {
|
|
3649
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
3650
|
+
if (!Array.isArray(raw)) return void 0;
|
|
3651
|
+
return raw.find((type) => typeof type === "string" && type.trim())?.trim();
|
|
3652
|
+
}
|
|
3653
|
+
function buildMeshNodeCapabilityTags(node, providerType) {
|
|
3654
|
+
const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
|
|
3655
|
+
return normalizeMeshCapabilityTags([
|
|
3656
|
+
...Array.isArray(node?.capabilities) ? node.capabilities : [],
|
|
3657
|
+
`os=${process.platform}`,
|
|
3658
|
+
`arch=${process.arch}`,
|
|
3659
|
+
...provider ? [`provider=${provider}`] : []
|
|
3660
|
+
]);
|
|
3661
|
+
}
|
|
3662
|
+
function nodeSatisfiesRequiredTags(requiredTags, capabilityTags) {
|
|
3663
|
+
const required = normalizeMeshCapabilityTags(requiredTags);
|
|
3664
|
+
if (required.length === 0) return true;
|
|
3665
|
+
const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
|
|
3666
|
+
return required.every((tag) => available.has(tag));
|
|
3667
|
+
}
|
|
3000
3668
|
function withQueueLock(_meshId, fn) {
|
|
3001
3669
|
return BeadsDB.getInstance().transaction(fn);
|
|
3002
3670
|
}
|
|
@@ -3014,6 +3682,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
3014
3682
|
taskMode: modeValidation.taskMode,
|
|
3015
3683
|
targetNodeId: opts?.targetNodeId,
|
|
3016
3684
|
targetSessionId: opts?.targetSessionId,
|
|
3685
|
+
requiredTags: normalizeMeshCapabilityTags(opts?.requiredTags),
|
|
3017
3686
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3018
3687
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3019
3688
|
};
|
|
@@ -3026,8 +3695,8 @@ function getQueue(meshId, opts) {
|
|
|
3026
3695
|
function getMeshQueueRevision(meshId) {
|
|
3027
3696
|
return BeadsDB.getInstance().getQueueRevision(meshId);
|
|
3028
3697
|
}
|
|
3029
|
-
function claimNextTask(meshId, nodeId, sessionId) {
|
|
3030
|
-
return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId);
|
|
3698
|
+
function claimNextTask(meshId, nodeId, sessionId, capabilityTags) {
|
|
3699
|
+
return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags);
|
|
3031
3700
|
}
|
|
3032
3701
|
function updateTaskStatus(meshId, taskId, status, opts) {
|
|
3033
3702
|
requireMeshHostQueueOwner(opts);
|
|
@@ -3318,6 +3987,7 @@ var init_cli_detector = __esm({
|
|
|
3318
3987
|
// src/mesh/mesh-events.ts
|
|
3319
3988
|
var mesh_events_exports = {};
|
|
3320
3989
|
__export(mesh_events_exports, {
|
|
3990
|
+
__resetIdleAutoFastForwardForTests: () => __resetIdleAutoFastForwardForTests,
|
|
3321
3991
|
clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
|
|
3322
3992
|
drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
|
|
3323
3993
|
getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
|
|
@@ -3340,6 +4010,9 @@ function getCachedMeshByWorkspace(workspace) {
|
|
|
3340
4010
|
function readWorkerResultMetadata(event) {
|
|
3341
4011
|
return readRecord2(event.workerResult) || readRecord2(event.meshWorkerResult) || readRecord2(event.structuredResult);
|
|
3342
4012
|
}
|
|
4013
|
+
function __resetIdleAutoFastForwardForTests() {
|
|
4014
|
+
idleAutoFastForwardLastAttempt.clear();
|
|
4015
|
+
}
|
|
3343
4016
|
function sweepExpiredRemoteIdleSessions() {
|
|
3344
4017
|
const now = Date.now();
|
|
3345
4018
|
for (const [key, session] of remoteIdleSessions) {
|
|
@@ -3765,13 +4438,14 @@ function buildLongGeneratingCompletionReconciliation(args) {
|
|
|
3765
4438
|
};
|
|
3766
4439
|
}
|
|
3767
4440
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
3768
|
-
const
|
|
4441
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
4442
|
+
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
4443
|
+
const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
|
|
4444
|
+
const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags);
|
|
3769
4445
|
if (!task) {
|
|
3770
4446
|
return false;
|
|
3771
4447
|
}
|
|
3772
4448
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
3773
|
-
const mesh = getMeshWithCache(components, meshId);
|
|
3774
|
-
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
3775
4449
|
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
3776
4450
|
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
3777
4451
|
if (!isLocalNode) {
|
|
@@ -4077,6 +4751,55 @@ async function triggerMeshQueue(components, meshId) {
|
|
|
4077
4751
|
}
|
|
4078
4752
|
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
4079
4753
|
}
|
|
4754
|
+
async function maybeAutoFastForwardIdleNode(components, args) {
|
|
4755
|
+
const mesh = getMeshWithCache(components, args.meshId);
|
|
4756
|
+
const node = mesh?.nodes?.find((candidate) => candidate?.id === args.nodeId || candidate?.nodeId === args.nodeId);
|
|
4757
|
+
const workspace = readNonEmptyString2(node?.workspace);
|
|
4758
|
+
if (!workspace) return;
|
|
4759
|
+
if (!existsSync13(workspace)) return;
|
|
4760
|
+
const throttleKey = `${args.meshId}:${args.nodeId}`;
|
|
4761
|
+
const now = Date.now();
|
|
4762
|
+
const lastAttempt = idleAutoFastForwardLastAttempt.get(throttleKey) || 0;
|
|
4763
|
+
if (now - lastAttempt < IDLE_AUTO_FAST_FORWARD_THROTTLE_MS) return;
|
|
4764
|
+
idleAutoFastForwardLastAttempt.set(throttleKey, now);
|
|
4765
|
+
const submoduleIgnorePaths = Array.isArray(node?.policy?.submoduleIgnorePaths) ? node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
|
|
4766
|
+
try {
|
|
4767
|
+
const dryRun = await fastForwardMeshNode({
|
|
4768
|
+
meshId: args.meshId,
|
|
4769
|
+
nodeId: args.nodeId,
|
|
4770
|
+
workspace,
|
|
4771
|
+
execute: false,
|
|
4772
|
+
dryRun: true,
|
|
4773
|
+
updateSubmodules: false,
|
|
4774
|
+
submoduleIgnorePaths,
|
|
4775
|
+
trigger: "idle_auto"
|
|
4776
|
+
});
|
|
4777
|
+
if (!dryRun || dryRun.code !== "fast_forward_available" || dryRun.allowed !== true) return;
|
|
4778
|
+
await fastForwardMeshNode({
|
|
4779
|
+
meshId: args.meshId,
|
|
4780
|
+
nodeId: args.nodeId,
|
|
4781
|
+
workspace,
|
|
4782
|
+
execute: true,
|
|
4783
|
+
dryRun: false,
|
|
4784
|
+
updateSubmodules: false,
|
|
4785
|
+
submoduleIgnorePaths,
|
|
4786
|
+
trigger: "idle_auto"
|
|
4787
|
+
});
|
|
4788
|
+
} catch (e) {
|
|
4789
|
+
LOG.warn("MeshFastForward", `Idle auto fast-forward check failed for ${args.nodeId}: ${e?.message || e}`);
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
function runIdleMaintenanceThenAssignQueue(components, args) {
|
|
4793
|
+
setImmediate(() => {
|
|
4794
|
+
maybeAutoFastForwardIdleNode(components, args).finally(() => {
|
|
4795
|
+
try {
|
|
4796
|
+
tryAssignQueueTask(components, args.meshId, args.nodeId, args.sessionId, args.providerType);
|
|
4797
|
+
} catch (e) {
|
|
4798
|
+
LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${args.nodeId}: ${e?.message || e}`);
|
|
4799
|
+
}
|
|
4800
|
+
});
|
|
4801
|
+
});
|
|
4802
|
+
}
|
|
4080
4803
|
function buildMeshSystemMessage(args) {
|
|
4081
4804
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
4082
4805
|
if (args.event === "agent:generating_completed") {
|
|
@@ -4298,9 +5021,7 @@ function injectMeshSystemMessage(components, args) {
|
|
|
4298
5021
|
updateDirectDispatchStatus(args.meshId, sessionId, "completed");
|
|
4299
5022
|
setImmediate(() => cleanupTerminalDirectDispatches());
|
|
4300
5023
|
if (nodeId && providerType) {
|
|
4301
|
-
|
|
4302
|
-
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
4303
|
-
});
|
|
5024
|
+
runIdleMaintenanceThenAssignQueue(components, { meshId: args.meshId, nodeId, sessionId, providerType });
|
|
4304
5025
|
}
|
|
4305
5026
|
}
|
|
4306
5027
|
} else if (args.event === "agent:ready") {
|
|
@@ -4354,8 +5075,14 @@ function injectMeshSystemMessage(components, args) {
|
|
|
4354
5075
|
expiresAt: Date.now() + REMOTE_IDLE_SESSION_TTL_MS
|
|
4355
5076
|
});
|
|
4356
5077
|
setImmediate(() => {
|
|
4357
|
-
|
|
4358
|
-
|
|
5078
|
+
maybeAutoFastForwardIdleNode(components, { meshId: args.meshId, nodeId, sessionId, providerType }).finally(() => {
|
|
5079
|
+
try {
|
|
5080
|
+
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
5081
|
+
if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
5082
|
+
} catch (e) {
|
|
5083
|
+
LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${nodeId}: ${e?.message || e}`);
|
|
5084
|
+
}
|
|
5085
|
+
});
|
|
4359
5086
|
});
|
|
4360
5087
|
}
|
|
4361
5088
|
} else if (args.event === "agent:generating_started") {
|
|
@@ -4631,7 +5358,7 @@ function setupMeshEventForwarding(components) {
|
|
|
4631
5358
|
});
|
|
4632
5359
|
});
|
|
4633
5360
|
}
|
|
4634
|
-
var REMOTE_IDLE_SESSION_TTL_MS, remoteIdleSessions, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, REFINE_TERMINAL_EVENTS, MAX_PENDING_EVENTS_BYTES, MAX_PENDING_EVENTS_KEEP, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
5361
|
+
var REMOTE_IDLE_SESSION_TTL_MS, remoteIdleSessions, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, REFINE_TERMINAL_EVENTS, MAX_PENDING_EVENTS_BYTES, MAX_PENDING_EVENTS_KEEP, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
4635
5362
|
var init_mesh_events = __esm({
|
|
4636
5363
|
"src/mesh/mesh-events.ts"() {
|
|
4637
5364
|
"use strict";
|
|
@@ -4642,10 +5369,13 @@ var init_mesh_events = __esm({
|
|
|
4642
5369
|
init_mesh_ledger();
|
|
4643
5370
|
init_mesh_work_queue();
|
|
4644
5371
|
init_beads_db();
|
|
5372
|
+
init_mesh_fast_forward();
|
|
4645
5373
|
REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1e3;
|
|
4646
5374
|
remoteIdleSessions = /* @__PURE__ */ new Map();
|
|
4647
5375
|
meshByWorkspaceCache = /* @__PURE__ */ new Map();
|
|
4648
5376
|
MESH_WORKSPACE_CACHE_TTL_MS = 5e3;
|
|
5377
|
+
IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
|
|
5378
|
+
idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
|
|
4649
5379
|
REFINE_TERMINAL_EVENTS = /* @__PURE__ */ new Set(["refine:completed", "refine:failed"]);
|
|
4650
5380
|
MAX_PENDING_EVENTS_BYTES = 100 * 1024;
|
|
4651
5381
|
MAX_PENDING_EVENTS_KEEP = 50;
|
|
@@ -12274,258 +13004,7 @@ init_repo_mesh_types();
|
|
|
12274
13004
|
|
|
12275
13005
|
// src/git/index.ts
|
|
12276
13006
|
init_git_executor();
|
|
12277
|
-
|
|
12278
|
-
// src/git/git-status.ts
|
|
12279
|
-
init_git_executor();
|
|
12280
|
-
async function getGitRepoStatus(workspace, options = {}) {
|
|
12281
|
-
const lastCheckedAt = Date.now();
|
|
12282
|
-
const includeSubmodules = options.includeSubmodules !== false;
|
|
12283
|
-
try {
|
|
12284
|
-
const repo = await resolveGitRepository(workspace, options);
|
|
12285
|
-
let parsed = await readPorcelainStatus(repo, options);
|
|
12286
|
-
let upstreamProbe = getInitialUpstreamProbe(parsed);
|
|
12287
|
-
if (options.refreshUpstream) {
|
|
12288
|
-
upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
|
|
12289
|
-
if (upstreamProbe.upstreamStatus === "fresh") {
|
|
12290
|
-
parsed = await readPorcelainStatus(repo, options);
|
|
12291
|
-
}
|
|
12292
|
-
}
|
|
12293
|
-
const head = await readHead(repo, options);
|
|
12294
|
-
const stashCount = await readStashCount(repo, options);
|
|
12295
|
-
let submodules;
|
|
12296
|
-
if (includeSubmodules) {
|
|
12297
|
-
submodules = await getSubmoduleStatuses(repo, options);
|
|
12298
|
-
}
|
|
12299
|
-
return {
|
|
12300
|
-
workspace: repo.workspace,
|
|
12301
|
-
repoRoot: repo.repoRoot,
|
|
12302
|
-
isGitRepo: true,
|
|
12303
|
-
branch: parsed.branch,
|
|
12304
|
-
headCommit: head.commit,
|
|
12305
|
-
headMessage: head.message,
|
|
12306
|
-
upstream: parsed.upstream,
|
|
12307
|
-
upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
|
|
12308
|
-
upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
|
|
12309
|
-
upstreamFetchError: upstreamProbe.upstreamFetchError,
|
|
12310
|
-
ahead: parsed.ahead,
|
|
12311
|
-
behind: parsed.behind,
|
|
12312
|
-
staged: parsed.staged,
|
|
12313
|
-
modified: parsed.modified,
|
|
12314
|
-
untracked: parsed.untracked,
|
|
12315
|
-
deleted: parsed.deleted,
|
|
12316
|
-
renamed: parsed.renamed,
|
|
12317
|
-
hasConflicts: parsed.conflictFiles.length > 0,
|
|
12318
|
-
conflictFiles: parsed.conflictFiles,
|
|
12319
|
-
stashCount,
|
|
12320
|
-
lastCheckedAt,
|
|
12321
|
-
submodules
|
|
12322
|
-
};
|
|
12323
|
-
} catch (error) {
|
|
12324
|
-
if (error instanceof GitCommandError) {
|
|
12325
|
-
return emptyStatus(workspace, lastCheckedAt, error);
|
|
12326
|
-
}
|
|
12327
|
-
return emptyStatus(
|
|
12328
|
-
workspace,
|
|
12329
|
-
lastCheckedAt,
|
|
12330
|
-
new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
|
|
12331
|
-
);
|
|
12332
|
-
}
|
|
12333
|
-
}
|
|
12334
|
-
async function readPorcelainStatus(repo, options) {
|
|
12335
|
-
const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
|
|
12336
|
-
return parsePorcelainV2Status(statusOutput.stdout);
|
|
12337
|
-
}
|
|
12338
|
-
function getInitialUpstreamProbe(parsed) {
|
|
12339
|
-
return {
|
|
12340
|
-
upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
|
|
12341
|
-
};
|
|
12342
|
-
}
|
|
12343
|
-
async function refreshTrackedUpstream(repo, parsed, options) {
|
|
12344
|
-
if (!parsed.upstream || !parsed.branch) {
|
|
12345
|
-
return { upstreamStatus: "no_upstream" };
|
|
12346
|
-
}
|
|
12347
|
-
const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
|
|
12348
|
-
if (!remoteName) {
|
|
12349
|
-
return {
|
|
12350
|
-
upstreamStatus: "stale",
|
|
12351
|
-
upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
|
|
12352
|
-
};
|
|
12353
|
-
}
|
|
12354
|
-
try {
|
|
12355
|
-
await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
|
|
12356
|
-
return {
|
|
12357
|
-
upstreamStatus: "fresh",
|
|
12358
|
-
upstreamFetchedAt: Date.now()
|
|
12359
|
-
};
|
|
12360
|
-
} catch (error) {
|
|
12361
|
-
return {
|
|
12362
|
-
upstreamStatus: "stale",
|
|
12363
|
-
upstreamFetchError: formatGitError(error)
|
|
12364
|
-
};
|
|
12365
|
-
}
|
|
12366
|
-
}
|
|
12367
|
-
async function readBranchRemote(repo, branch, options) {
|
|
12368
|
-
try {
|
|
12369
|
-
const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
|
|
12370
|
-
return result.stdout.trim() || null;
|
|
12371
|
-
} catch {
|
|
12372
|
-
return null;
|
|
12373
|
-
}
|
|
12374
|
-
}
|
|
12375
|
-
function inferRemoteName(upstream) {
|
|
12376
|
-
const [remoteName] = upstream.split("/");
|
|
12377
|
-
return remoteName?.trim() || null;
|
|
12378
|
-
}
|
|
12379
|
-
function formatGitError(error) {
|
|
12380
|
-
if (error instanceof GitCommandError) {
|
|
12381
|
-
return error.stderr || error.message;
|
|
12382
|
-
}
|
|
12383
|
-
if (error instanceof Error) {
|
|
12384
|
-
return error.message;
|
|
12385
|
-
}
|
|
12386
|
-
return String(error);
|
|
12387
|
-
}
|
|
12388
|
-
function parsePorcelainV2Status(output) {
|
|
12389
|
-
const parsed = {
|
|
12390
|
-
branch: null,
|
|
12391
|
-
upstream: null,
|
|
12392
|
-
ahead: 0,
|
|
12393
|
-
behind: 0,
|
|
12394
|
-
staged: 0,
|
|
12395
|
-
modified: 0,
|
|
12396
|
-
untracked: 0,
|
|
12397
|
-
deleted: 0,
|
|
12398
|
-
renamed: 0,
|
|
12399
|
-
conflictFiles: []
|
|
12400
|
-
};
|
|
12401
|
-
for (const line of output.split("\n")) {
|
|
12402
|
-
if (!line) continue;
|
|
12403
|
-
if (line.startsWith("# branch.head ")) {
|
|
12404
|
-
const branch = line.slice("# branch.head ".length).trim();
|
|
12405
|
-
parsed.branch = branch && branch !== "(detached)" ? branch : null;
|
|
12406
|
-
continue;
|
|
12407
|
-
}
|
|
12408
|
-
if (line.startsWith("# branch.upstream ")) {
|
|
12409
|
-
parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
|
|
12410
|
-
continue;
|
|
12411
|
-
}
|
|
12412
|
-
if (line.startsWith("# branch.ab ")) {
|
|
12413
|
-
const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
|
|
12414
|
-
if (match) {
|
|
12415
|
-
parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
|
|
12416
|
-
parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
|
|
12417
|
-
}
|
|
12418
|
-
continue;
|
|
12419
|
-
}
|
|
12420
|
-
if (line.startsWith("? ")) {
|
|
12421
|
-
parsed.untracked += 1;
|
|
12422
|
-
continue;
|
|
12423
|
-
}
|
|
12424
|
-
if (line.startsWith("u ")) {
|
|
12425
|
-
const fields = line.split(" ");
|
|
12426
|
-
const filePath = fields.slice(10).join(" ");
|
|
12427
|
-
if (filePath) parsed.conflictFiles.push(filePath);
|
|
12428
|
-
continue;
|
|
12429
|
-
}
|
|
12430
|
-
if (line.startsWith("1 ") || line.startsWith("2 ")) {
|
|
12431
|
-
const fields = line.split(" ");
|
|
12432
|
-
const xy = fields[1] ?? "..";
|
|
12433
|
-
const indexStatus = xy[0] ?? ".";
|
|
12434
|
-
const worktreeStatus = xy[1] ?? ".";
|
|
12435
|
-
if (isStagedStatus(indexStatus)) parsed.staged += 1;
|
|
12436
|
-
if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
|
|
12437
|
-
if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
|
|
12438
|
-
if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
|
|
12439
|
-
if (xy.includes("U")) {
|
|
12440
|
-
const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
|
|
12441
|
-
if (filePath) parsed.conflictFiles.push(filePath);
|
|
12442
|
-
}
|
|
12443
|
-
}
|
|
12444
|
-
}
|
|
12445
|
-
parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
|
|
12446
|
-
return parsed;
|
|
12447
|
-
}
|
|
12448
|
-
async function readHead(repo, options) {
|
|
12449
|
-
try {
|
|
12450
|
-
const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
|
|
12451
|
-
const text = result.stdout.trimEnd();
|
|
12452
|
-
if (!text) return { commit: null, message: null };
|
|
12453
|
-
const [commit, ...messageParts] = text.split("\0");
|
|
12454
|
-
return {
|
|
12455
|
-
commit: commit || null,
|
|
12456
|
-
message: messageParts.join("\0") || null
|
|
12457
|
-
};
|
|
12458
|
-
} catch {
|
|
12459
|
-
return { commit: null, message: null };
|
|
12460
|
-
}
|
|
12461
|
-
}
|
|
12462
|
-
async function readStashCount(repo, options) {
|
|
12463
|
-
try {
|
|
12464
|
-
const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
|
|
12465
|
-
return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
12466
|
-
} catch {
|
|
12467
|
-
return 0;
|
|
12468
|
-
}
|
|
12469
|
-
}
|
|
12470
|
-
function isStagedStatus(status) {
|
|
12471
|
-
return status !== "." && status !== "?" && status !== "U";
|
|
12472
|
-
}
|
|
12473
|
-
function emptyStatus(workspace, lastCheckedAt, error) {
|
|
12474
|
-
return {
|
|
12475
|
-
workspace,
|
|
12476
|
-
repoRoot: null,
|
|
12477
|
-
isGitRepo: false,
|
|
12478
|
-
branch: null,
|
|
12479
|
-
headCommit: null,
|
|
12480
|
-
headMessage: null,
|
|
12481
|
-
upstream: null,
|
|
12482
|
-
upstreamStatus: "unavailable",
|
|
12483
|
-
ahead: 0,
|
|
12484
|
-
behind: 0,
|
|
12485
|
-
staged: 0,
|
|
12486
|
-
modified: 0,
|
|
12487
|
-
untracked: 0,
|
|
12488
|
-
deleted: 0,
|
|
12489
|
-
renamed: 0,
|
|
12490
|
-
hasConflicts: false,
|
|
12491
|
-
conflictFiles: [],
|
|
12492
|
-
stashCount: 0,
|
|
12493
|
-
lastCheckedAt,
|
|
12494
|
-
error: error.stderr || error.message,
|
|
12495
|
-
reason: error.reason
|
|
12496
|
-
};
|
|
12497
|
-
}
|
|
12498
|
-
async function getSubmoduleStatuses(repo, options) {
|
|
12499
|
-
if (!repo.repoRoot) return [];
|
|
12500
|
-
try {
|
|
12501
|
-
const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
|
|
12502
|
-
return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
|
|
12503
|
-
} catch {
|
|
12504
|
-
return [];
|
|
12505
|
-
}
|
|
12506
|
-
}
|
|
12507
|
-
function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
|
|
12508
|
-
const submodules = [];
|
|
12509
|
-
const ignoreSet = new Set(ignorePaths || []);
|
|
12510
|
-
for (const line of output.split("\n")) {
|
|
12511
|
-
if (!line.trim()) continue;
|
|
12512
|
-
const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
|
|
12513
|
-
if (!match) continue;
|
|
12514
|
-
const prefix = match[1];
|
|
12515
|
-
const commit = match[2];
|
|
12516
|
-
const path40 = match[3];
|
|
12517
|
-
if (ignoreSet.has(path40)) continue;
|
|
12518
|
-
submodules.push({
|
|
12519
|
-
path: path40,
|
|
12520
|
-
commit,
|
|
12521
|
-
repoPath: repoRoot + "/" + path40,
|
|
12522
|
-
dirty: prefix === "+",
|
|
12523
|
-
outOfSync: prefix === "-",
|
|
12524
|
-
lastCheckedAt: Date.now()
|
|
12525
|
-
});
|
|
12526
|
-
}
|
|
12527
|
-
return submodules;
|
|
12528
|
-
}
|
|
13007
|
+
init_git_status();
|
|
12529
13008
|
|
|
12530
13009
|
// src/git/git-diff.ts
|
|
12531
13010
|
init_git_executor();
|
|
@@ -12941,6 +13420,7 @@ function createGitSnapshotStore(options = {}) {
|
|
|
12941
13420
|
}
|
|
12942
13421
|
|
|
12943
13422
|
// src/git/git-monitor.ts
|
|
13423
|
+
init_git_status();
|
|
12944
13424
|
var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
|
|
12945
13425
|
var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
|
|
12946
13426
|
function defaultStatusProvider(workspace) {
|
|
@@ -13063,6 +13543,7 @@ function createGitWorkspaceMonitor(options = {}) {
|
|
|
13063
13543
|
// src/git/git-commands.ts
|
|
13064
13544
|
import * as path3 from "path";
|
|
13065
13545
|
init_git_executor();
|
|
13546
|
+
init_git_status();
|
|
13066
13547
|
var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
13067
13548
|
"git_status",
|
|
13068
13549
|
"git_diff_summary",
|
|
@@ -14543,414 +15024,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
|
|
|
14543
15024
|
return state;
|
|
14544
15025
|
}
|
|
14545
15026
|
|
|
14546
|
-
// src/mesh/mesh-sync.ts
|
|
14547
|
-
init_mesh_config();
|
|
14548
|
-
async function syncMeshes(transport) {
|
|
14549
|
-
const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
|
|
14550
|
-
let remoteMeshes;
|
|
14551
|
-
try {
|
|
14552
|
-
const res = await transport.listRemoteMeshes();
|
|
14553
|
-
remoteMeshes = res.meshes;
|
|
14554
|
-
} catch (e) {
|
|
14555
|
-
result.errors.push(`Failed to list remote meshes: ${e.message}`);
|
|
14556
|
-
return result;
|
|
14557
|
-
}
|
|
14558
|
-
const localMeshes = listMeshes();
|
|
14559
|
-
const remoteByIdentity = new Map(remoteMeshes.map((m) => [m.repo_identity, m]));
|
|
14560
|
-
const localByIdentity = new Map(localMeshes.map((m) => [m.repoIdentity, m]));
|
|
14561
|
-
for (const local of localMeshes) {
|
|
14562
|
-
if (!remoteByIdentity.has(local.repoIdentity)) {
|
|
14563
|
-
try {
|
|
14564
|
-
await transport.createRemoteMesh({
|
|
14565
|
-
name: local.name,
|
|
14566
|
-
repo_identity: local.repoIdentity,
|
|
14567
|
-
repo_remote_url: local.repoRemoteUrl,
|
|
14568
|
-
default_branch: local.defaultBranch,
|
|
14569
|
-
policy: JSON.stringify(local.policy)
|
|
14570
|
-
});
|
|
14571
|
-
result.pushed++;
|
|
14572
|
-
} catch (e) {
|
|
14573
|
-
result.errors.push(`Push failed for "${local.name}": ${e.message}`);
|
|
14574
|
-
}
|
|
14575
|
-
}
|
|
14576
|
-
}
|
|
14577
|
-
for (const remote of remoteMeshes) {
|
|
14578
|
-
if (!localByIdentity.has(remote.repo_identity)) {
|
|
14579
|
-
try {
|
|
14580
|
-
let policy;
|
|
14581
|
-
try {
|
|
14582
|
-
policy = JSON.parse(remote.policy);
|
|
14583
|
-
} catch {
|
|
14584
|
-
policy = void 0;
|
|
14585
|
-
}
|
|
14586
|
-
createMesh({
|
|
14587
|
-
name: remote.name,
|
|
14588
|
-
repoIdentity: remote.repo_identity,
|
|
14589
|
-
repoRemoteUrl: remote.repo_remote_url || void 0,
|
|
14590
|
-
defaultBranch: remote.default_branch || void 0,
|
|
14591
|
-
policy
|
|
14592
|
-
});
|
|
14593
|
-
result.pulled++;
|
|
14594
|
-
} catch (e) {
|
|
14595
|
-
result.errors.push(`Pull failed for "${remote.name}": ${e.message}`);
|
|
14596
|
-
}
|
|
14597
|
-
}
|
|
14598
|
-
}
|
|
14599
|
-
return result;
|
|
14600
|
-
}
|
|
14601
|
-
|
|
14602
15027
|
// src/index.ts
|
|
14603
15028
|
init_mesh_ledger();
|
|
14604
|
-
|
|
14605
|
-
// src/mesh/mesh-fast-forward.ts
|
|
14606
|
-
init_git_executor();
|
|
14607
|
-
var STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
|
|
14608
|
-
async function fastForwardMeshNode(args) {
|
|
14609
|
-
const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
|
|
14610
|
-
const nodeId = normalizeOptionalString(args.nodeId);
|
|
14611
|
-
const meshId = normalizeOptionalString(args.meshId);
|
|
14612
|
-
const requestedBranch = normalizeOptionalString(args.branch);
|
|
14613
|
-
const updateSubmodules = args.updateSubmodules === true;
|
|
14614
|
-
const dryRun = args.dryRun === true || args.execute !== true;
|
|
14615
|
-
const plannedSteps = buildPlannedSteps(updateSubmodules);
|
|
14616
|
-
const base = {
|
|
14617
|
-
...nodeId ? { nodeId } : {},
|
|
14618
|
-
...meshId ? { meshId } : {},
|
|
14619
|
-
workspace,
|
|
14620
|
-
dryRun,
|
|
14621
|
-
updateSubmodules,
|
|
14622
|
-
plannedSteps
|
|
14623
|
-
};
|
|
14624
|
-
if (!workspace) {
|
|
14625
|
-
return block(base, "invalid_workspace", ["workspace_required"]);
|
|
14626
|
-
}
|
|
14627
|
-
const current = await getGitRepoStatus(workspace, {
|
|
14628
|
-
...STATUS_OPTIONS,
|
|
14629
|
-
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
14630
|
-
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
14631
|
-
});
|
|
14632
|
-
const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
|
|
14633
|
-
if (earlyBlockers.length > 0) {
|
|
14634
|
-
return {
|
|
14635
|
-
...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
|
|
14636
|
-
current,
|
|
14637
|
-
finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
|
|
14638
|
-
};
|
|
14639
|
-
}
|
|
14640
|
-
if (current.behind === 0) {
|
|
14641
|
-
const result2 = {
|
|
14642
|
-
...base,
|
|
14643
|
-
success: true,
|
|
14644
|
-
code: "already_up_to_date",
|
|
14645
|
-
allowed: true,
|
|
14646
|
-
willRun: false,
|
|
14647
|
-
executed: false,
|
|
14648
|
-
blockingReasons: [],
|
|
14649
|
-
current,
|
|
14650
|
-
preStatus: current,
|
|
14651
|
-
postStatus: current,
|
|
14652
|
-
finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
|
|
14653
|
-
};
|
|
14654
|
-
await appendFastForwardLedger(result2, "noop");
|
|
14655
|
-
return result2;
|
|
14656
|
-
}
|
|
14657
|
-
const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
|
|
14658
|
-
if (!ancestorCheck.ok) {
|
|
14659
|
-
const result2 = {
|
|
14660
|
-
...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
|
|
14661
|
-
current,
|
|
14662
|
-
preStatus: current,
|
|
14663
|
-
operationError: ancestorCheck.error,
|
|
14664
|
-
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
14665
|
-
};
|
|
14666
|
-
await appendFastForwardLedger(result2, "blocked");
|
|
14667
|
-
return result2;
|
|
14668
|
-
}
|
|
14669
|
-
if (dryRun) {
|
|
14670
|
-
const result2 = {
|
|
14671
|
-
...base,
|
|
14672
|
-
success: true,
|
|
14673
|
-
code: "fast_forward_available",
|
|
14674
|
-
allowed: true,
|
|
14675
|
-
willRun: false,
|
|
14676
|
-
executed: false,
|
|
14677
|
-
blockingReasons: [],
|
|
14678
|
-
current,
|
|
14679
|
-
preStatus: current,
|
|
14680
|
-
finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
|
|
14681
|
-
};
|
|
14682
|
-
return result2;
|
|
14683
|
-
}
|
|
14684
|
-
try {
|
|
14685
|
-
await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
|
|
14686
|
-
} catch (error) {
|
|
14687
|
-
const result2 = {
|
|
14688
|
-
...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
|
|
14689
|
-
current,
|
|
14690
|
-
preStatus: current,
|
|
14691
|
-
operationError: formatGitError2(error),
|
|
14692
|
-
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
14693
|
-
};
|
|
14694
|
-
await appendFastForwardLedger(result2, "failed");
|
|
14695
|
-
return result2;
|
|
14696
|
-
}
|
|
14697
|
-
let postStatus = await getGitRepoStatus(workspace, {
|
|
14698
|
-
...STATUS_OPTIONS,
|
|
14699
|
-
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
14700
|
-
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
14701
|
-
});
|
|
14702
|
-
const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
|
|
14703
|
-
let submoduleFollowUpRequired = false;
|
|
14704
|
-
let operationError;
|
|
14705
|
-
if (submoduleIssues.length > 0) {
|
|
14706
|
-
if (updateSubmodules) {
|
|
14707
|
-
try {
|
|
14708
|
-
await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
|
|
14709
|
-
postStatus = await getGitRepoStatus(workspace, {
|
|
14710
|
-
...STATUS_OPTIONS,
|
|
14711
|
-
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
14712
|
-
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
14713
|
-
});
|
|
14714
|
-
} catch (error) {
|
|
14715
|
-
operationError = formatGitError2(error);
|
|
14716
|
-
}
|
|
14717
|
-
} else {
|
|
14718
|
-
submoduleFollowUpRequired = true;
|
|
14719
|
-
}
|
|
14720
|
-
}
|
|
14721
|
-
const postBlockers = collectPostExecutionBlockers(postStatus);
|
|
14722
|
-
if (operationError) postBlockers.push("submodule_update_failed");
|
|
14723
|
-
if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
|
|
14724
|
-
const success = postBlockers.length === 0 || submoduleFollowUpRequired;
|
|
14725
|
-
const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
|
|
14726
|
-
const result = {
|
|
14727
|
-
...base,
|
|
14728
|
-
success,
|
|
14729
|
-
code,
|
|
14730
|
-
allowed: true,
|
|
14731
|
-
willRun: true,
|
|
14732
|
-
executed: true,
|
|
14733
|
-
blockingReasons: postBlockers,
|
|
14734
|
-
current,
|
|
14735
|
-
preStatus: current,
|
|
14736
|
-
postStatus,
|
|
14737
|
-
...operationError ? { operationError } : {},
|
|
14738
|
-
finalBranchConvergenceState: buildConvergenceState(
|
|
14739
|
-
postStatus,
|
|
14740
|
-
postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
|
|
14741
|
-
)
|
|
14742
|
-
};
|
|
14743
|
-
await appendFastForwardLedger(result, success ? "executed" : "failed");
|
|
14744
|
-
return result;
|
|
14745
|
-
}
|
|
14746
|
-
function buildPlannedSteps(updateSubmodules) {
|
|
14747
|
-
const steps = [
|
|
14748
|
-
{
|
|
14749
|
-
operation: "refresh_upstream",
|
|
14750
|
-
description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
|
|
14751
|
-
safe: true,
|
|
14752
|
-
willMutateWorktree: false
|
|
14753
|
-
},
|
|
14754
|
-
{
|
|
14755
|
-
operation: "verify_clean_worktree",
|
|
14756
|
-
description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
|
|
14757
|
-
safe: true,
|
|
14758
|
-
willMutateWorktree: false
|
|
14759
|
-
},
|
|
14760
|
-
{
|
|
14761
|
-
operation: "verify_fast_forward",
|
|
14762
|
-
description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
|
|
14763
|
-
safe: true,
|
|
14764
|
-
willMutateWorktree: false
|
|
14765
|
-
},
|
|
14766
|
-
{
|
|
14767
|
-
operation: "merge_ff_only",
|
|
14768
|
-
description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
|
|
14769
|
-
safe: true,
|
|
14770
|
-
willMutateWorktree: true
|
|
14771
|
-
}
|
|
14772
|
-
];
|
|
14773
|
-
if (updateSubmodules) {
|
|
14774
|
-
steps.push({
|
|
14775
|
-
operation: "submodule_update",
|
|
14776
|
-
description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
|
|
14777
|
-
safe: true,
|
|
14778
|
-
willMutateWorktree: true
|
|
14779
|
-
});
|
|
14780
|
-
}
|
|
14781
|
-
steps.push({
|
|
14782
|
-
operation: "verify_post_status",
|
|
14783
|
-
description: "Re-read daemon-owned git status and report final branch convergence state.",
|
|
14784
|
-
safe: true,
|
|
14785
|
-
willMutateWorktree: false
|
|
14786
|
-
});
|
|
14787
|
-
return steps;
|
|
14788
|
-
}
|
|
14789
|
-
function collectPreflightBlockers(status, requestedBranch) {
|
|
14790
|
-
const blockers = [];
|
|
14791
|
-
if (!status.isGitRepo) blockers.push("not_git_repo");
|
|
14792
|
-
if (!status.branch) blockers.push("detached_head_or_unknown_branch");
|
|
14793
|
-
if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
|
|
14794
|
-
if (!status.upstream) blockers.push("upstream_missing");
|
|
14795
|
-
if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
|
|
14796
|
-
if (status.hasConflicts) blockers.push("conflicts_present");
|
|
14797
|
-
if (status.staged > 0) blockers.push("staged_changes_present");
|
|
14798
|
-
if (status.modified > 0) blockers.push("modified_changes_present");
|
|
14799
|
-
if (status.untracked > 0) blockers.push("untracked_changes_present");
|
|
14800
|
-
if (status.deleted > 0) blockers.push("deleted_changes_present");
|
|
14801
|
-
if (status.renamed > 0) blockers.push("renamed_changes_present");
|
|
14802
|
-
if (status.stashCount > 0) blockers.push("stash_entries_present");
|
|
14803
|
-
blockers.push(...collectSubmoduleBlockers(status, "pre"));
|
|
14804
|
-
if (status.ahead > 0 && status.behind > 0) {
|
|
14805
|
-
blockers.push("branch_diverged_from_upstream");
|
|
14806
|
-
blockers.push("branch_has_local_commits");
|
|
14807
|
-
} else if (status.ahead > 0) blockers.push("branch_has_local_commits");
|
|
14808
|
-
return blockers;
|
|
14809
|
-
}
|
|
14810
|
-
function collectPostExecutionBlockers(status) {
|
|
14811
|
-
const blockers = [];
|
|
14812
|
-
if (!status.isGitRepo) blockers.push("post_not_git_repo");
|
|
14813
|
-
if (status.hasConflicts) blockers.push("post_conflicts_present");
|
|
14814
|
-
if (status.ahead !== 0) blockers.push("post_branch_ahead");
|
|
14815
|
-
if (status.behind !== 0) blockers.push("post_branch_still_behind");
|
|
14816
|
-
if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
|
|
14817
|
-
blockers.push("post_working_tree_not_clean");
|
|
14818
|
-
}
|
|
14819
|
-
if (status.stashCount > 0) blockers.push("post_stash_entries_present");
|
|
14820
|
-
blockers.push(...collectSubmoduleBlockers(status, "post"));
|
|
14821
|
-
return blockers;
|
|
14822
|
-
}
|
|
14823
|
-
function collectSubmoduleBlockers(status, phase) {
|
|
14824
|
-
const submodules = Array.isArray(status.submodules) ? status.submodules : [];
|
|
14825
|
-
const blockers = [];
|
|
14826
|
-
for (const submodule of submodules) {
|
|
14827
|
-
if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
|
|
14828
|
-
if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
|
|
14829
|
-
if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
|
|
14830
|
-
}
|
|
14831
|
-
return blockers;
|
|
14832
|
-
}
|
|
14833
|
-
function chooseBlockCode(status, blockers) {
|
|
14834
|
-
if (blockers.includes("not_git_repo")) return "not_git_repo";
|
|
14835
|
-
if (blockers.includes("branch_mismatch")) return "branch_mismatch";
|
|
14836
|
-
if (blockers.includes("upstream_missing")) return "upstream_missing";
|
|
14837
|
-
if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
|
|
14838
|
-
if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
|
|
14839
|
-
if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
|
|
14840
|
-
if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
|
|
14841
|
-
if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
|
|
14842
|
-
return "preflight_blocked";
|
|
14843
|
-
}
|
|
14844
|
-
function codeToConvergenceStatus(code) {
|
|
14845
|
-
if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
|
|
14846
|
-
if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
|
|
14847
|
-
return "blocked";
|
|
14848
|
-
}
|
|
14849
|
-
async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
|
|
14850
|
-
if (!upstream) return { ok: false, error: "missing upstream" };
|
|
14851
|
-
try {
|
|
14852
|
-
await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
|
|
14853
|
-
return { ok: true };
|
|
14854
|
-
} catch (error) {
|
|
14855
|
-
return { ok: false, error: formatGitError2(error) };
|
|
14856
|
-
}
|
|
14857
|
-
}
|
|
14858
|
-
function block(base, code, blockingReasons) {
|
|
14859
|
-
const normalizedReasons = normalizeBlockingReasons(blockingReasons);
|
|
14860
|
-
return {
|
|
14861
|
-
...base,
|
|
14862
|
-
success: false,
|
|
14863
|
-
code,
|
|
14864
|
-
allowed: false,
|
|
14865
|
-
willRun: false,
|
|
14866
|
-
executed: false,
|
|
14867
|
-
blockingReasons: normalizedReasons
|
|
14868
|
-
};
|
|
14869
|
-
}
|
|
14870
|
-
function normalizeBlockingReasons(reasons) {
|
|
14871
|
-
const normalized = /* @__PURE__ */ new Set();
|
|
14872
|
-
for (const reason of reasons) {
|
|
14873
|
-
normalized.add(reason);
|
|
14874
|
-
}
|
|
14875
|
-
if ([
|
|
14876
|
-
"conflicts_present",
|
|
14877
|
-
"staged_changes_present",
|
|
14878
|
-
"modified_changes_present",
|
|
14879
|
-
"untracked_changes_present",
|
|
14880
|
-
"deleted_changes_present",
|
|
14881
|
-
"renamed_changes_present"
|
|
14882
|
-
].some((reason) => normalized.has(reason))) {
|
|
14883
|
-
normalized.add("working_tree_not_clean");
|
|
14884
|
-
}
|
|
14885
|
-
return Array.from(normalized);
|
|
14886
|
-
}
|
|
14887
|
-
function buildConvergenceState(status, convergenceStatus) {
|
|
14888
|
-
return {
|
|
14889
|
-
status: convergenceStatus,
|
|
14890
|
-
branch: status.branch,
|
|
14891
|
-
headCommit: status.headCommit,
|
|
14892
|
-
upstream: status.upstream,
|
|
14893
|
-
ahead: status.ahead,
|
|
14894
|
-
behind: status.behind,
|
|
14895
|
-
dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
|
|
14896
|
-
stashCount: status.stashCount,
|
|
14897
|
-
submodules: summarizeSubmodules(status.submodules)
|
|
14898
|
-
};
|
|
14899
|
-
}
|
|
14900
|
-
function summarizeSubmodules(submodules) {
|
|
14901
|
-
return (submodules || []).map((submodule) => ({
|
|
14902
|
-
path: submodule.path,
|
|
14903
|
-
commit: submodule.commit,
|
|
14904
|
-
dirty: submodule.dirty,
|
|
14905
|
-
outOfSync: submodule.outOfSync,
|
|
14906
|
-
...submodule.error ? { error: submodule.error } : {}
|
|
14907
|
-
}));
|
|
14908
|
-
}
|
|
14909
|
-
function normalizeOptionalString(value) {
|
|
14910
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
14911
|
-
}
|
|
14912
|
-
function formatGitError2(error) {
|
|
14913
|
-
if (error instanceof GitCommandError) {
|
|
14914
|
-
return error.stderr || error.stdout || error.message;
|
|
14915
|
-
}
|
|
14916
|
-
if (error instanceof Error) return error.message;
|
|
14917
|
-
return String(error);
|
|
14918
|
-
}
|
|
14919
|
-
async function appendFastForwardLedger(result, outcome) {
|
|
14920
|
-
if (!result.meshId) return;
|
|
14921
|
-
try {
|
|
14922
|
-
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
14923
|
-
appendLedgerEntry2(result.meshId, {
|
|
14924
|
-
kind: "direct_fast_forward",
|
|
14925
|
-
...result.nodeId ? { nodeId: result.nodeId } : {},
|
|
14926
|
-
payload: {
|
|
14927
|
-
operation: "mesh_fast_forward_node",
|
|
14928
|
-
outcome,
|
|
14929
|
-
code: result.code,
|
|
14930
|
-
workspace: result.workspace,
|
|
14931
|
-
allowed: result.allowed,
|
|
14932
|
-
dryRun: result.dryRun,
|
|
14933
|
-
willRun: result.willRun,
|
|
14934
|
-
executed: result.executed,
|
|
14935
|
-
branch: result.postStatus?.branch ?? result.current?.branch,
|
|
14936
|
-
upstream: result.postStatus?.upstream ?? result.current?.upstream,
|
|
14937
|
-
before: result.current ? {
|
|
14938
|
-
headCommit: result.current.headCommit,
|
|
14939
|
-
ahead: result.current.ahead,
|
|
14940
|
-
behind: result.current.behind
|
|
14941
|
-
} : void 0,
|
|
14942
|
-
after: result.postStatus ? {
|
|
14943
|
-
headCommit: result.postStatus.headCommit,
|
|
14944
|
-
ahead: result.postStatus.ahead,
|
|
14945
|
-
behind: result.postStatus.behind
|
|
14946
|
-
} : void 0,
|
|
14947
|
-
blockingReasons: result.blockingReasons
|
|
14948
|
-
}
|
|
14949
|
-
});
|
|
14950
|
-
} catch (error) {
|
|
14951
|
-
result.ledgerError = error instanceof Error ? error.message : String(error);
|
|
14952
|
-
}
|
|
14953
|
-
}
|
|
15029
|
+
init_mesh_fast_forward();
|
|
14954
15030
|
|
|
14955
15031
|
// src/mesh/mesh-ledger-reconciliation.ts
|
|
14956
15032
|
function lastTimestamp(slice) {
|
|
@@ -34416,6 +34492,7 @@ function getAvailableIdeIds() {
|
|
|
34416
34492
|
// src/commands/router.ts
|
|
34417
34493
|
init_config();
|
|
34418
34494
|
init_cli_detector();
|
|
34495
|
+
init_git_status();
|
|
34419
34496
|
init_logger();
|
|
34420
34497
|
|
|
34421
34498
|
// src/logging/command-log.ts
|
|
@@ -34568,6 +34645,7 @@ import * as yaml3 from "js-yaml";
|
|
|
34568
34645
|
init_mesh_coordinator();
|
|
34569
34646
|
init_mesh_events();
|
|
34570
34647
|
init_mesh_host_ownership();
|
|
34648
|
+
init_mesh_fast_forward();
|
|
34571
34649
|
|
|
34572
34650
|
// src/mesh/preview-freshness.ts
|
|
34573
34651
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -37222,8 +37300,8 @@ var DaemonCommandRouter = class {
|
|
|
37222
37300
|
}
|
|
37223
37301
|
}
|
|
37224
37302
|
try {
|
|
37225
|
-
const { getMesh:
|
|
37226
|
-
const mesh =
|
|
37303
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
37304
|
+
const mesh = getMesh2(meshId);
|
|
37227
37305
|
if (mesh) return { mesh, inline: false, source: "local_config" };
|
|
37228
37306
|
} catch {
|
|
37229
37307
|
}
|
|
@@ -39148,8 +39226,8 @@ var DaemonCommandRouter = class {
|
|
|
39148
39226
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
39149
39227
|
if (!meshId) return { success: false, error: "meshId required" };
|
|
39150
39228
|
try {
|
|
39151
|
-
const { deleteMesh:
|
|
39152
|
-
const deleted =
|
|
39229
|
+
const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39230
|
+
const deleted = deleteMesh2(meshId);
|
|
39153
39231
|
return { success: true, deleted };
|
|
39154
39232
|
} catch (e) {
|
|
39155
39233
|
return { success: false, error: e.message };
|
|
@@ -39267,7 +39345,7 @@ var DaemonCommandRouter = class {
|
|
|
39267
39345
|
const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
|
|
39268
39346
|
if (ownerFailure) return ownerFailure;
|
|
39269
39347
|
try {
|
|
39270
|
-
const { addNode:
|
|
39348
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39271
39349
|
const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
39272
39350
|
const readOnly = args?.readOnly === true;
|
|
39273
39351
|
const policy = {
|
|
@@ -39278,7 +39356,7 @@ var DaemonCommandRouter = class {
|
|
|
39278
39356
|
const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
|
|
39279
39357
|
const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
|
|
39280
39358
|
const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
|
|
39281
|
-
const node =
|
|
39359
|
+
const node = addNode2(meshId, {
|
|
39282
39360
|
workspace,
|
|
39283
39361
|
...repoRoot ? { repoRoot } : {},
|
|
39284
39362
|
...daemonId ? { daemonId } : {},
|
|
@@ -39469,8 +39547,8 @@ var DaemonCommandRouter = class {
|
|
|
39469
39547
|
if (meshRecord?.inline) {
|
|
39470
39548
|
removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
|
|
39471
39549
|
} else {
|
|
39472
|
-
const { removeNode:
|
|
39473
|
-
removed =
|
|
39550
|
+
const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39551
|
+
removed = removeNode2(meshId, nodeId);
|
|
39474
39552
|
if (removed) this.invalidateAggregateMeshStatus(meshId);
|
|
39475
39553
|
}
|
|
39476
39554
|
if (removed) {
|
|
@@ -39539,8 +39617,8 @@ var DaemonCommandRouter = class {
|
|
|
39539
39617
|
};
|
|
39540
39618
|
this.updateInlineMeshNode(meshId, mesh, node);
|
|
39541
39619
|
} else {
|
|
39542
|
-
const { addNode:
|
|
39543
|
-
node =
|
|
39620
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39621
|
+
node = addNode2(meshId, {
|
|
39544
39622
|
workspace: result.worktreePath,
|
|
39545
39623
|
repoRoot: result.worktreePath,
|
|
39546
39624
|
daemonId: sourceNode.daemonId,
|
|
@@ -39695,8 +39773,8 @@ var DaemonCommandRouter = class {
|
|
|
39695
39773
|
mesh = args.inlineMesh;
|
|
39696
39774
|
this.inlineMeshCache.set(meshId, mesh);
|
|
39697
39775
|
} else {
|
|
39698
|
-
const { getMesh:
|
|
39699
|
-
mesh =
|
|
39776
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39777
|
+
mesh = getMesh2(meshId);
|
|
39700
39778
|
}
|
|
39701
39779
|
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
39702
39780
|
const meshHost = resolveMeshHostStatus(mesh);
|
|
@@ -49074,6 +49152,7 @@ export {
|
|
|
49074
49152
|
buildMeshHostRequiredFailure,
|
|
49075
49153
|
buildMeshLedgerReconciliationEvidence,
|
|
49076
49154
|
buildMeshLedgerReplicaEvidence,
|
|
49155
|
+
buildMeshNodeCapabilityTags,
|
|
49077
49156
|
buildP2pRelayFailurePayload,
|
|
49078
49157
|
buildPinnedGlobalInstallCommand,
|
|
49079
49158
|
buildRuntimeSystemChatMessage,
|
|
@@ -49201,6 +49280,7 @@ export {
|
|
|
49201
49280
|
maybeRunDaemonUpgradeHelperFromEnv,
|
|
49202
49281
|
namedKeyToAnsi,
|
|
49203
49282
|
namedKeysToAnsi,
|
|
49283
|
+
nodeSatisfiesRequiredTags,
|
|
49204
49284
|
normalizeActiveChatData,
|
|
49205
49285
|
normalizeChatMessage,
|
|
49206
49286
|
normalizeChatMessageKind,
|
|
@@ -49212,6 +49292,7 @@ export {
|
|
|
49212
49292
|
normalizeInteractivePrompt,
|
|
49213
49293
|
normalizeInteractivePromptResponse,
|
|
49214
49294
|
normalizeManagedStatus,
|
|
49295
|
+
normalizeMeshCapabilityTags,
|
|
49215
49296
|
normalizeMeshDaemonRole,
|
|
49216
49297
|
normalizeMeshTaskMode,
|
|
49217
49298
|
normalizeMeshWorkerResult,
|
|
@@ -49270,7 +49351,6 @@ export {
|
|
|
49270
49351
|
startLocalIpcServer,
|
|
49271
49352
|
suggestMeshRefineConfig,
|
|
49272
49353
|
summarizeGitStatus,
|
|
49273
|
-
syncMeshes,
|
|
49274
49354
|
triggerMeshQueue,
|
|
49275
49355
|
unregisterMeshCoordinator,
|
|
49276
49356
|
updateConfig,
|