@adhdev/daemon-core 0.9.82-rc.172 → 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.js
CHANGED
|
@@ -255,6 +255,263 @@ var init_git_executor = __esm({
|
|
|
255
255
|
}
|
|
256
256
|
});
|
|
257
257
|
|
|
258
|
+
// src/git/git-status.ts
|
|
259
|
+
async function getGitRepoStatus(workspace, options = {}) {
|
|
260
|
+
const lastCheckedAt = Date.now();
|
|
261
|
+
const includeSubmodules = options.includeSubmodules !== false;
|
|
262
|
+
try {
|
|
263
|
+
const repo = await resolveGitRepository(workspace, options);
|
|
264
|
+
let parsed = await readPorcelainStatus(repo, options);
|
|
265
|
+
let upstreamProbe = getInitialUpstreamProbe(parsed);
|
|
266
|
+
if (options.refreshUpstream) {
|
|
267
|
+
upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
|
|
268
|
+
if (upstreamProbe.upstreamStatus === "fresh") {
|
|
269
|
+
parsed = await readPorcelainStatus(repo, options);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const head = await readHead(repo, options);
|
|
273
|
+
const stashCount = await readStashCount(repo, options);
|
|
274
|
+
let submodules;
|
|
275
|
+
if (includeSubmodules) {
|
|
276
|
+
submodules = await getSubmoduleStatuses(repo, options);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
workspace: repo.workspace,
|
|
280
|
+
repoRoot: repo.repoRoot,
|
|
281
|
+
isGitRepo: true,
|
|
282
|
+
branch: parsed.branch,
|
|
283
|
+
headCommit: head.commit,
|
|
284
|
+
headMessage: head.message,
|
|
285
|
+
upstream: parsed.upstream,
|
|
286
|
+
upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
|
|
287
|
+
upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
|
|
288
|
+
upstreamFetchError: upstreamProbe.upstreamFetchError,
|
|
289
|
+
ahead: parsed.ahead,
|
|
290
|
+
behind: parsed.behind,
|
|
291
|
+
staged: parsed.staged,
|
|
292
|
+
modified: parsed.modified,
|
|
293
|
+
untracked: parsed.untracked,
|
|
294
|
+
deleted: parsed.deleted,
|
|
295
|
+
renamed: parsed.renamed,
|
|
296
|
+
hasConflicts: parsed.conflictFiles.length > 0,
|
|
297
|
+
conflictFiles: parsed.conflictFiles,
|
|
298
|
+
stashCount,
|
|
299
|
+
lastCheckedAt,
|
|
300
|
+
submodules
|
|
301
|
+
};
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error instanceof GitCommandError) {
|
|
304
|
+
return emptyStatus(workspace, lastCheckedAt, error);
|
|
305
|
+
}
|
|
306
|
+
return emptyStatus(
|
|
307
|
+
workspace,
|
|
308
|
+
lastCheckedAt,
|
|
309
|
+
new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function readPorcelainStatus(repo, options) {
|
|
314
|
+
const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
|
|
315
|
+
return parsePorcelainV2Status(statusOutput.stdout);
|
|
316
|
+
}
|
|
317
|
+
function getInitialUpstreamProbe(parsed) {
|
|
318
|
+
return {
|
|
319
|
+
upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function refreshTrackedUpstream(repo, parsed, options) {
|
|
323
|
+
if (!parsed.upstream || !parsed.branch) {
|
|
324
|
+
return { upstreamStatus: "no_upstream" };
|
|
325
|
+
}
|
|
326
|
+
const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
|
|
327
|
+
if (!remoteName) {
|
|
328
|
+
return {
|
|
329
|
+
upstreamStatus: "stale",
|
|
330
|
+
upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
|
|
335
|
+
return {
|
|
336
|
+
upstreamStatus: "fresh",
|
|
337
|
+
upstreamFetchedAt: Date.now()
|
|
338
|
+
};
|
|
339
|
+
} catch (error) {
|
|
340
|
+
return {
|
|
341
|
+
upstreamStatus: "stale",
|
|
342
|
+
upstreamFetchError: formatGitError(error)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function readBranchRemote(repo, branch, options) {
|
|
347
|
+
try {
|
|
348
|
+
const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
|
|
349
|
+
return result.stdout.trim() || null;
|
|
350
|
+
} catch {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function inferRemoteName(upstream) {
|
|
355
|
+
const [remoteName] = upstream.split("/");
|
|
356
|
+
return remoteName?.trim() || null;
|
|
357
|
+
}
|
|
358
|
+
function formatGitError(error) {
|
|
359
|
+
if (error instanceof GitCommandError) {
|
|
360
|
+
return error.stderr || error.message;
|
|
361
|
+
}
|
|
362
|
+
if (error instanceof Error) {
|
|
363
|
+
return error.message;
|
|
364
|
+
}
|
|
365
|
+
return String(error);
|
|
366
|
+
}
|
|
367
|
+
function parsePorcelainV2Status(output) {
|
|
368
|
+
const parsed = {
|
|
369
|
+
branch: null,
|
|
370
|
+
upstream: null,
|
|
371
|
+
ahead: 0,
|
|
372
|
+
behind: 0,
|
|
373
|
+
staged: 0,
|
|
374
|
+
modified: 0,
|
|
375
|
+
untracked: 0,
|
|
376
|
+
deleted: 0,
|
|
377
|
+
renamed: 0,
|
|
378
|
+
conflictFiles: []
|
|
379
|
+
};
|
|
380
|
+
for (const line of output.split("\n")) {
|
|
381
|
+
if (!line) continue;
|
|
382
|
+
if (line.startsWith("# branch.head ")) {
|
|
383
|
+
const branch = line.slice("# branch.head ".length).trim();
|
|
384
|
+
parsed.branch = branch && branch !== "(detached)" ? branch : null;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (line.startsWith("# branch.upstream ")) {
|
|
388
|
+
parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (line.startsWith("# branch.ab ")) {
|
|
392
|
+
const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
|
|
393
|
+
if (match) {
|
|
394
|
+
parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
|
|
395
|
+
parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
|
|
396
|
+
}
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (line.startsWith("? ")) {
|
|
400
|
+
parsed.untracked += 1;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (line.startsWith("u ")) {
|
|
404
|
+
const fields = line.split(" ");
|
|
405
|
+
const filePath = fields.slice(10).join(" ");
|
|
406
|
+
if (filePath) parsed.conflictFiles.push(filePath);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (line.startsWith("1 ") || line.startsWith("2 ")) {
|
|
410
|
+
const fields = line.split(" ");
|
|
411
|
+
const xy = fields[1] ?? "..";
|
|
412
|
+
const indexStatus = xy[0] ?? ".";
|
|
413
|
+
const worktreeStatus = xy[1] ?? ".";
|
|
414
|
+
if (isStagedStatus(indexStatus)) parsed.staged += 1;
|
|
415
|
+
if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
|
|
416
|
+
if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
|
|
417
|
+
if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
|
|
418
|
+
if (xy.includes("U")) {
|
|
419
|
+
const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
|
|
420
|
+
if (filePath) parsed.conflictFiles.push(filePath);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
|
|
425
|
+
return parsed;
|
|
426
|
+
}
|
|
427
|
+
async function readHead(repo, options) {
|
|
428
|
+
try {
|
|
429
|
+
const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
|
|
430
|
+
const text = result.stdout.trimEnd();
|
|
431
|
+
if (!text) return { commit: null, message: null };
|
|
432
|
+
const [commit, ...messageParts] = text.split("\0");
|
|
433
|
+
return {
|
|
434
|
+
commit: commit || null,
|
|
435
|
+
message: messageParts.join("\0") || null
|
|
436
|
+
};
|
|
437
|
+
} catch {
|
|
438
|
+
return { commit: null, message: null };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function readStashCount(repo, options) {
|
|
442
|
+
try {
|
|
443
|
+
const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
|
|
444
|
+
return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
445
|
+
} catch {
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function isStagedStatus(status) {
|
|
450
|
+
return status !== "." && status !== "?" && status !== "U";
|
|
451
|
+
}
|
|
452
|
+
function emptyStatus(workspace, lastCheckedAt, error) {
|
|
453
|
+
return {
|
|
454
|
+
workspace,
|
|
455
|
+
repoRoot: null,
|
|
456
|
+
isGitRepo: false,
|
|
457
|
+
branch: null,
|
|
458
|
+
headCommit: null,
|
|
459
|
+
headMessage: null,
|
|
460
|
+
upstream: null,
|
|
461
|
+
upstreamStatus: "unavailable",
|
|
462
|
+
ahead: 0,
|
|
463
|
+
behind: 0,
|
|
464
|
+
staged: 0,
|
|
465
|
+
modified: 0,
|
|
466
|
+
untracked: 0,
|
|
467
|
+
deleted: 0,
|
|
468
|
+
renamed: 0,
|
|
469
|
+
hasConflicts: false,
|
|
470
|
+
conflictFiles: [],
|
|
471
|
+
stashCount: 0,
|
|
472
|
+
lastCheckedAt,
|
|
473
|
+
error: error.stderr || error.message,
|
|
474
|
+
reason: error.reason
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
async function getSubmoduleStatuses(repo, options) {
|
|
478
|
+
if (!repo.repoRoot) return [];
|
|
479
|
+
try {
|
|
480
|
+
const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
|
|
481
|
+
return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
|
|
482
|
+
} catch {
|
|
483
|
+
return [];
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
|
|
487
|
+
const submodules = [];
|
|
488
|
+
const ignoreSet = new Set(ignorePaths || []);
|
|
489
|
+
for (const line of output.split("\n")) {
|
|
490
|
+
if (!line.trim()) continue;
|
|
491
|
+
const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
|
|
492
|
+
if (!match) continue;
|
|
493
|
+
const prefix = match[1];
|
|
494
|
+
const commit = match[2];
|
|
495
|
+
const path40 = match[3];
|
|
496
|
+
if (ignoreSet.has(path40)) continue;
|
|
497
|
+
submodules.push({
|
|
498
|
+
path: path40,
|
|
499
|
+
commit,
|
|
500
|
+
repoPath: repoRoot + "/" + path40,
|
|
501
|
+
dirty: prefix === "+",
|
|
502
|
+
outOfSync: prefix === "-",
|
|
503
|
+
lastCheckedAt: Date.now()
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return submodules;
|
|
507
|
+
}
|
|
508
|
+
var init_git_status = __esm({
|
|
509
|
+
"src/git/git-status.ts"() {
|
|
510
|
+
"use strict";
|
|
511
|
+
init_git_executor();
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
258
515
|
// src/git/git-worktree.ts
|
|
259
516
|
var git_worktree_exports = {};
|
|
260
517
|
__export(git_worktree_exports, {
|
|
@@ -780,6 +1037,16 @@ function loadMeshConfig() {
|
|
|
780
1037
|
return { meshes: [] };
|
|
781
1038
|
}
|
|
782
1039
|
}
|
|
1040
|
+
function normalizeCapabilityTags(value) {
|
|
1041
|
+
if (!Array.isArray(value)) return void 0;
|
|
1042
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1043
|
+
const tags = value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
|
|
1044
|
+
if (seen.has(tag)) return false;
|
|
1045
|
+
seen.add(tag);
|
|
1046
|
+
return true;
|
|
1047
|
+
});
|
|
1048
|
+
return tags.length ? tags : void 0;
|
|
1049
|
+
}
|
|
783
1050
|
function saveMeshConfig(config) {
|
|
784
1051
|
const path40 = getMeshConfigPath();
|
|
785
1052
|
(0, import_fs2.writeFileSync)(path40, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
@@ -1065,6 +1332,7 @@ function addNode(meshId, opts) {
|
|
|
1065
1332
|
repoRoot: opts.repoRoot,
|
|
1066
1333
|
daemonId: opts.daemonId,
|
|
1067
1334
|
machineId: opts.machineId,
|
|
1335
|
+
capabilities: normalizeCapabilityTags(opts.capabilities),
|
|
1068
1336
|
userOverrides: opts.userOverrides || {},
|
|
1069
1337
|
policy: opts.policy || {},
|
|
1070
1338
|
isLocalWorktree: opts.isLocalWorktree,
|
|
@@ -2550,6 +2818,369 @@ var init_mesh_ledger = __esm({
|
|
|
2550
2818
|
}
|
|
2551
2819
|
});
|
|
2552
2820
|
|
|
2821
|
+
// src/mesh/mesh-fast-forward.ts
|
|
2822
|
+
async function fastForwardMeshNode(args) {
|
|
2823
|
+
const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
|
|
2824
|
+
const nodeId = normalizeOptionalString(args.nodeId);
|
|
2825
|
+
const meshId = normalizeOptionalString(args.meshId);
|
|
2826
|
+
const requestedBranch = normalizeOptionalString(args.branch);
|
|
2827
|
+
const trigger = normalizeOptionalString(args.trigger) || "manual";
|
|
2828
|
+
const updateSubmodules = args.updateSubmodules === true;
|
|
2829
|
+
const dryRun = args.dryRun === true || args.execute !== true;
|
|
2830
|
+
const plannedSteps = buildPlannedSteps(updateSubmodules);
|
|
2831
|
+
const base = {
|
|
2832
|
+
...nodeId ? { nodeId } : {},
|
|
2833
|
+
...meshId ? { meshId } : {},
|
|
2834
|
+
workspace,
|
|
2835
|
+
dryRun,
|
|
2836
|
+
updateSubmodules,
|
|
2837
|
+
plannedSteps,
|
|
2838
|
+
trigger
|
|
2839
|
+
};
|
|
2840
|
+
if (!workspace) {
|
|
2841
|
+
return block(base, "invalid_workspace", ["workspace_required"]);
|
|
2842
|
+
}
|
|
2843
|
+
const current = await getGitRepoStatus(workspace, {
|
|
2844
|
+
...STATUS_OPTIONS,
|
|
2845
|
+
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
2846
|
+
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
2847
|
+
});
|
|
2848
|
+
const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
|
|
2849
|
+
if (earlyBlockers.length > 0) {
|
|
2850
|
+
const result2 = {
|
|
2851
|
+
...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
|
|
2852
|
+
current,
|
|
2853
|
+
finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
|
|
2854
|
+
};
|
|
2855
|
+
await appendFastForwardLedger(result2, "blocked");
|
|
2856
|
+
return result2;
|
|
2857
|
+
}
|
|
2858
|
+
if (current.behind === 0) {
|
|
2859
|
+
const result2 = {
|
|
2860
|
+
...base,
|
|
2861
|
+
success: true,
|
|
2862
|
+
code: "already_up_to_date",
|
|
2863
|
+
allowed: true,
|
|
2864
|
+
willRun: false,
|
|
2865
|
+
executed: false,
|
|
2866
|
+
blockingReasons: [],
|
|
2867
|
+
current,
|
|
2868
|
+
preStatus: current,
|
|
2869
|
+
postStatus: current,
|
|
2870
|
+
finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
|
|
2871
|
+
};
|
|
2872
|
+
await appendFastForwardLedger(result2, "noop");
|
|
2873
|
+
return result2;
|
|
2874
|
+
}
|
|
2875
|
+
const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
|
|
2876
|
+
if (!ancestorCheck.ok) {
|
|
2877
|
+
const result2 = {
|
|
2878
|
+
...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
|
|
2879
|
+
current,
|
|
2880
|
+
preStatus: current,
|
|
2881
|
+
operationError: ancestorCheck.error,
|
|
2882
|
+
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
2883
|
+
};
|
|
2884
|
+
await appendFastForwardLedger(result2, "blocked");
|
|
2885
|
+
return result2;
|
|
2886
|
+
}
|
|
2887
|
+
if (dryRun) {
|
|
2888
|
+
const result2 = {
|
|
2889
|
+
...base,
|
|
2890
|
+
success: true,
|
|
2891
|
+
code: "fast_forward_available",
|
|
2892
|
+
allowed: true,
|
|
2893
|
+
willRun: false,
|
|
2894
|
+
executed: false,
|
|
2895
|
+
blockingReasons: [],
|
|
2896
|
+
current,
|
|
2897
|
+
preStatus: current,
|
|
2898
|
+
finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
|
|
2899
|
+
};
|
|
2900
|
+
await appendFastForwardLedger(result2, "dry_run");
|
|
2901
|
+
return result2;
|
|
2902
|
+
}
|
|
2903
|
+
try {
|
|
2904
|
+
await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
|
|
2905
|
+
} catch (error) {
|
|
2906
|
+
const result2 = {
|
|
2907
|
+
...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
|
|
2908
|
+
current,
|
|
2909
|
+
preStatus: current,
|
|
2910
|
+
operationError: formatGitError2(error),
|
|
2911
|
+
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
2912
|
+
};
|
|
2913
|
+
await appendFastForwardLedger(result2, "failed");
|
|
2914
|
+
return result2;
|
|
2915
|
+
}
|
|
2916
|
+
let postStatus = await getGitRepoStatus(workspace, {
|
|
2917
|
+
...STATUS_OPTIONS,
|
|
2918
|
+
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
2919
|
+
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
2920
|
+
});
|
|
2921
|
+
const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
|
|
2922
|
+
let submoduleFollowUpRequired = false;
|
|
2923
|
+
let operationError;
|
|
2924
|
+
if (submoduleIssues.length > 0) {
|
|
2925
|
+
if (updateSubmodules) {
|
|
2926
|
+
try {
|
|
2927
|
+
await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
|
|
2928
|
+
postStatus = await getGitRepoStatus(workspace, {
|
|
2929
|
+
...STATUS_OPTIONS,
|
|
2930
|
+
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
2931
|
+
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
2932
|
+
});
|
|
2933
|
+
} catch (error) {
|
|
2934
|
+
operationError = formatGitError2(error);
|
|
2935
|
+
}
|
|
2936
|
+
} else {
|
|
2937
|
+
submoduleFollowUpRequired = true;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
const postBlockers = collectPostExecutionBlockers(postStatus);
|
|
2941
|
+
if (operationError) postBlockers.push("submodule_update_failed");
|
|
2942
|
+
if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
|
|
2943
|
+
const success = postBlockers.length === 0 || submoduleFollowUpRequired;
|
|
2944
|
+
const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
|
|
2945
|
+
const result = {
|
|
2946
|
+
...base,
|
|
2947
|
+
success,
|
|
2948
|
+
code,
|
|
2949
|
+
allowed: true,
|
|
2950
|
+
willRun: true,
|
|
2951
|
+
executed: true,
|
|
2952
|
+
blockingReasons: postBlockers,
|
|
2953
|
+
current,
|
|
2954
|
+
preStatus: current,
|
|
2955
|
+
postStatus,
|
|
2956
|
+
...operationError ? { operationError } : {},
|
|
2957
|
+
finalBranchConvergenceState: buildConvergenceState(
|
|
2958
|
+
postStatus,
|
|
2959
|
+
postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
|
|
2960
|
+
)
|
|
2961
|
+
};
|
|
2962
|
+
await appendFastForwardLedger(result, success ? "executed" : "failed");
|
|
2963
|
+
return result;
|
|
2964
|
+
}
|
|
2965
|
+
function buildPlannedSteps(updateSubmodules) {
|
|
2966
|
+
const steps = [
|
|
2967
|
+
{
|
|
2968
|
+
operation: "refresh_upstream",
|
|
2969
|
+
description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
|
|
2970
|
+
safe: true,
|
|
2971
|
+
willMutateWorktree: false
|
|
2972
|
+
},
|
|
2973
|
+
{
|
|
2974
|
+
operation: "verify_clean_worktree",
|
|
2975
|
+
description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
|
|
2976
|
+
safe: true,
|
|
2977
|
+
willMutateWorktree: false
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
operation: "verify_fast_forward",
|
|
2981
|
+
description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
|
|
2982
|
+
safe: true,
|
|
2983
|
+
willMutateWorktree: false
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
operation: "merge_ff_only",
|
|
2987
|
+
description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
|
|
2988
|
+
safe: true,
|
|
2989
|
+
willMutateWorktree: true
|
|
2990
|
+
}
|
|
2991
|
+
];
|
|
2992
|
+
if (updateSubmodules) {
|
|
2993
|
+
steps.push({
|
|
2994
|
+
operation: "submodule_update",
|
|
2995
|
+
description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
|
|
2996
|
+
safe: true,
|
|
2997
|
+
willMutateWorktree: true
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
steps.push({
|
|
3001
|
+
operation: "verify_post_status",
|
|
3002
|
+
description: "Re-read daemon-owned git status and report final branch convergence state.",
|
|
3003
|
+
safe: true,
|
|
3004
|
+
willMutateWorktree: false
|
|
3005
|
+
});
|
|
3006
|
+
return steps;
|
|
3007
|
+
}
|
|
3008
|
+
function collectPreflightBlockers(status, requestedBranch) {
|
|
3009
|
+
const blockers = [];
|
|
3010
|
+
if (!status.isGitRepo) blockers.push("not_git_repo");
|
|
3011
|
+
if (!status.branch) blockers.push("detached_head_or_unknown_branch");
|
|
3012
|
+
if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
|
|
3013
|
+
if (!status.upstream) blockers.push("upstream_missing");
|
|
3014
|
+
if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
|
|
3015
|
+
if (status.hasConflicts) blockers.push("conflicts_present");
|
|
3016
|
+
if (status.staged > 0) blockers.push("staged_changes_present");
|
|
3017
|
+
if (status.modified > 0) blockers.push("modified_changes_present");
|
|
3018
|
+
if (status.untracked > 0) blockers.push("untracked_changes_present");
|
|
3019
|
+
if (status.deleted > 0) blockers.push("deleted_changes_present");
|
|
3020
|
+
if (status.renamed > 0) blockers.push("renamed_changes_present");
|
|
3021
|
+
if (status.stashCount > 0) blockers.push("stash_entries_present");
|
|
3022
|
+
blockers.push(...collectSubmoduleBlockers(status, "pre"));
|
|
3023
|
+
if (status.ahead > 0 && status.behind > 0) {
|
|
3024
|
+
blockers.push("branch_diverged_from_upstream");
|
|
3025
|
+
blockers.push("branch_has_local_commits");
|
|
3026
|
+
} else if (status.ahead > 0) blockers.push("branch_has_local_commits");
|
|
3027
|
+
return blockers;
|
|
3028
|
+
}
|
|
3029
|
+
function collectPostExecutionBlockers(status) {
|
|
3030
|
+
const blockers = [];
|
|
3031
|
+
if (!status.isGitRepo) blockers.push("post_not_git_repo");
|
|
3032
|
+
if (status.hasConflicts) blockers.push("post_conflicts_present");
|
|
3033
|
+
if (status.ahead !== 0) blockers.push("post_branch_ahead");
|
|
3034
|
+
if (status.behind !== 0) blockers.push("post_branch_still_behind");
|
|
3035
|
+
if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
|
|
3036
|
+
blockers.push("post_working_tree_not_clean");
|
|
3037
|
+
}
|
|
3038
|
+
if (status.stashCount > 0) blockers.push("post_stash_entries_present");
|
|
3039
|
+
blockers.push(...collectSubmoduleBlockers(status, "post"));
|
|
3040
|
+
return blockers;
|
|
3041
|
+
}
|
|
3042
|
+
function collectSubmoduleBlockers(status, phase) {
|
|
3043
|
+
const submodules = Array.isArray(status.submodules) ? status.submodules : [];
|
|
3044
|
+
const blockers = [];
|
|
3045
|
+
for (const submodule of submodules) {
|
|
3046
|
+
if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
|
|
3047
|
+
if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
|
|
3048
|
+
if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
|
|
3049
|
+
}
|
|
3050
|
+
return blockers;
|
|
3051
|
+
}
|
|
3052
|
+
function chooseBlockCode(status, blockers) {
|
|
3053
|
+
if (blockers.includes("not_git_repo")) return "not_git_repo";
|
|
3054
|
+
if (blockers.includes("branch_mismatch")) return "branch_mismatch";
|
|
3055
|
+
if (blockers.includes("upstream_missing")) return "upstream_missing";
|
|
3056
|
+
if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
|
|
3057
|
+
if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
|
|
3058
|
+
if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
|
|
3059
|
+
if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
|
|
3060
|
+
if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
|
|
3061
|
+
return "preflight_blocked";
|
|
3062
|
+
}
|
|
3063
|
+
function codeToConvergenceStatus(code) {
|
|
3064
|
+
if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
|
|
3065
|
+
if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
|
|
3066
|
+
return "blocked";
|
|
3067
|
+
}
|
|
3068
|
+
async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
|
|
3069
|
+
if (!upstream) return { ok: false, error: "missing upstream" };
|
|
3070
|
+
try {
|
|
3071
|
+
await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
|
|
3072
|
+
return { ok: true };
|
|
3073
|
+
} catch (error) {
|
|
3074
|
+
return { ok: false, error: formatGitError2(error) };
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
function block(base, code, blockingReasons) {
|
|
3078
|
+
const normalizedReasons = normalizeBlockingReasons(blockingReasons);
|
|
3079
|
+
return {
|
|
3080
|
+
...base,
|
|
3081
|
+
success: false,
|
|
3082
|
+
code,
|
|
3083
|
+
allowed: false,
|
|
3084
|
+
willRun: false,
|
|
3085
|
+
executed: false,
|
|
3086
|
+
blockingReasons: normalizedReasons
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
function normalizeBlockingReasons(reasons) {
|
|
3090
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
3091
|
+
for (const reason of reasons) {
|
|
3092
|
+
normalized.add(reason);
|
|
3093
|
+
}
|
|
3094
|
+
if ([
|
|
3095
|
+
"conflicts_present",
|
|
3096
|
+
"staged_changes_present",
|
|
3097
|
+
"modified_changes_present",
|
|
3098
|
+
"untracked_changes_present",
|
|
3099
|
+
"deleted_changes_present",
|
|
3100
|
+
"renamed_changes_present"
|
|
3101
|
+
].some((reason) => normalized.has(reason))) {
|
|
3102
|
+
normalized.add("working_tree_not_clean");
|
|
3103
|
+
}
|
|
3104
|
+
return Array.from(normalized);
|
|
3105
|
+
}
|
|
3106
|
+
function buildConvergenceState(status, convergenceStatus) {
|
|
3107
|
+
return {
|
|
3108
|
+
status: convergenceStatus,
|
|
3109
|
+
branch: status.branch,
|
|
3110
|
+
headCommit: status.headCommit,
|
|
3111
|
+
upstream: status.upstream,
|
|
3112
|
+
ahead: status.ahead,
|
|
3113
|
+
behind: status.behind,
|
|
3114
|
+
dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
|
|
3115
|
+
stashCount: status.stashCount,
|
|
3116
|
+
submodules: summarizeSubmodules(status.submodules)
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
function summarizeSubmodules(submodules) {
|
|
3120
|
+
return (submodules || []).map((submodule) => ({
|
|
3121
|
+
path: submodule.path,
|
|
3122
|
+
commit: submodule.commit,
|
|
3123
|
+
dirty: submodule.dirty,
|
|
3124
|
+
outOfSync: submodule.outOfSync,
|
|
3125
|
+
...submodule.error ? { error: submodule.error } : {}
|
|
3126
|
+
}));
|
|
3127
|
+
}
|
|
3128
|
+
function normalizeOptionalString(value) {
|
|
3129
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
3130
|
+
}
|
|
3131
|
+
function formatGitError2(error) {
|
|
3132
|
+
if (error instanceof GitCommandError) {
|
|
3133
|
+
return error.stderr || error.stdout || error.message;
|
|
3134
|
+
}
|
|
3135
|
+
if (error instanceof Error) return error.message;
|
|
3136
|
+
return String(error);
|
|
3137
|
+
}
|
|
3138
|
+
async function appendFastForwardLedger(result, outcome) {
|
|
3139
|
+
if (!result.meshId) return;
|
|
3140
|
+
try {
|
|
3141
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
3142
|
+
appendLedgerEntry2(result.meshId, {
|
|
3143
|
+
kind: "direct_fast_forward",
|
|
3144
|
+
...result.nodeId ? { nodeId: result.nodeId } : {},
|
|
3145
|
+
payload: {
|
|
3146
|
+
operation: "mesh_fast_forward_node",
|
|
3147
|
+
trigger: result.trigger || "manual",
|
|
3148
|
+
outcome,
|
|
3149
|
+
code: result.code,
|
|
3150
|
+
workspace: result.workspace,
|
|
3151
|
+
allowed: result.allowed,
|
|
3152
|
+
dryRun: result.dryRun,
|
|
3153
|
+
willRun: result.willRun,
|
|
3154
|
+
executed: result.executed,
|
|
3155
|
+
branch: result.postStatus?.branch ?? result.current?.branch,
|
|
3156
|
+
upstream: result.postStatus?.upstream ?? result.current?.upstream,
|
|
3157
|
+
before: result.current ? {
|
|
3158
|
+
headCommit: result.current.headCommit,
|
|
3159
|
+
ahead: result.current.ahead,
|
|
3160
|
+
behind: result.current.behind
|
|
3161
|
+
} : void 0,
|
|
3162
|
+
after: result.postStatus ? {
|
|
3163
|
+
headCommit: result.postStatus.headCommit,
|
|
3164
|
+
ahead: result.postStatus.ahead,
|
|
3165
|
+
behind: result.postStatus.behind
|
|
3166
|
+
} : void 0,
|
|
3167
|
+
blockingReasons: result.blockingReasons
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
result.ledgerError = error instanceof Error ? error.message : String(error);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
var STATUS_OPTIONS;
|
|
3175
|
+
var init_mesh_fast_forward = __esm({
|
|
3176
|
+
"src/mesh/mesh-fast-forward.ts"() {
|
|
3177
|
+
"use strict";
|
|
3178
|
+
init_git_status();
|
|
3179
|
+
init_git_executor();
|
|
3180
|
+
STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
|
|
2553
3184
|
// src/mesh/beads-db.ts
|
|
2554
3185
|
function loadDatabaseCtor() {
|
|
2555
3186
|
if (DatabaseCtor) return DatabaseCtor;
|
|
@@ -2571,6 +3202,7 @@ var init_beads_db = __esm({
|
|
|
2571
3202
|
import_path7 = require("path");
|
|
2572
3203
|
import_module = require("module");
|
|
2573
3204
|
init_mesh_ledger();
|
|
3205
|
+
init_mesh_work_queue();
|
|
2574
3206
|
import_meta = {};
|
|
2575
3207
|
BeadsDB = class _BeadsDB {
|
|
2576
3208
|
static instance;
|
|
@@ -2792,25 +3424,29 @@ var init_beads_db = __esm({
|
|
|
2792
3424
|
return row !== void 0;
|
|
2793
3425
|
}
|
|
2794
3426
|
// O(1) claim: transaction ensures only one session claims a pending task
|
|
2795
|
-
claimNextQueueTask(meshId, nodeId, sessionId) {
|
|
3427
|
+
claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags = []) {
|
|
2796
3428
|
return this.transaction(() => {
|
|
2797
3429
|
this.ensureLegacyQueueMigrated(meshId);
|
|
2798
3430
|
if (this.hasActiveAssignment(meshId, sessionId, nodeId)) return null;
|
|
2799
|
-
const
|
|
3431
|
+
const rows = [
|
|
3432
|
+
...this.db.prepare(`
|
|
2800
3433
|
SELECT payload FROM mesh_queue
|
|
2801
3434
|
WHERE mesh_id = ? AND status = 'pending' AND target_session_id = ?
|
|
2802
|
-
ORDER BY created_at ASC
|
|
2803
|
-
`).
|
|
3435
|
+
ORDER BY created_at ASC
|
|
3436
|
+
`).all(meshId, sessionId),
|
|
3437
|
+
...this.db.prepare(`
|
|
2804
3438
|
SELECT payload FROM mesh_queue
|
|
2805
3439
|
WHERE mesh_id = ? AND status = 'pending' AND target_node_id = ? AND target_session_id IS NULL
|
|
2806
|
-
ORDER BY created_at ASC
|
|
2807
|
-
`).
|
|
3440
|
+
ORDER BY created_at ASC
|
|
3441
|
+
`).all(meshId, nodeId),
|
|
3442
|
+
...this.db.prepare(`
|
|
2808
3443
|
SELECT payload FROM mesh_queue
|
|
2809
3444
|
WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
|
|
2810
|
-
ORDER BY created_at ASC
|
|
2811
|
-
`).
|
|
2812
|
-
|
|
2813
|
-
const entry = JSON.parse(row.payload);
|
|
3445
|
+
ORDER BY created_at ASC
|
|
3446
|
+
`).all(meshId)
|
|
3447
|
+
];
|
|
3448
|
+
const entry = rows.map((row) => JSON.parse(row.payload)).find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags));
|
|
3449
|
+
if (!entry) return null;
|
|
2814
3450
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2815
3451
|
entry.status = "assigned";
|
|
2816
3452
|
entry.assignedNodeId = nodeId;
|
|
@@ -2958,6 +3594,7 @@ __export(mesh_work_queue_exports, {
|
|
|
2958
3594
|
__clearMeshQueueForTests: () => __clearMeshQueueForTests,
|
|
2959
3595
|
__replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
|
|
2960
3596
|
__resetBeadsDBForTests: () => __resetBeadsDBForTests,
|
|
3597
|
+
buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
|
|
2961
3598
|
cancelTask: () => cancelTask,
|
|
2962
3599
|
claimNextTask: () => claimNextTask,
|
|
2963
3600
|
cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
|
|
@@ -2968,6 +3605,8 @@ __export(mesh_work_queue_exports, {
|
|
|
2968
3605
|
getQueue: () => getQueue,
|
|
2969
3606
|
insertDirectDispatch: () => insertDirectDispatch,
|
|
2970
3607
|
markStaleDirectDispatches: () => markStaleDirectDispatches,
|
|
3608
|
+
nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
|
|
3609
|
+
normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
|
|
2971
3610
|
normalizeMeshTaskMode: () => normalizeMeshTaskMode,
|
|
2972
3611
|
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
2973
3612
|
requeueTask: () => requeueTask,
|
|
@@ -3002,6 +3641,35 @@ function validateMeshTaskModeRequest(mode, message) {
|
|
|
3002
3641
|
]
|
|
3003
3642
|
};
|
|
3004
3643
|
}
|
|
3644
|
+
function normalizeMeshCapabilityTags(value) {
|
|
3645
|
+
if (!Array.isArray(value)) return [];
|
|
3646
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3647
|
+
return value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
|
|
3648
|
+
if (seen.has(tag)) return false;
|
|
3649
|
+
seen.add(tag);
|
|
3650
|
+
return true;
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
function firstProviderPriority(policy) {
|
|
3654
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
3655
|
+
if (!Array.isArray(raw)) return void 0;
|
|
3656
|
+
return raw.find((type) => typeof type === "string" && type.trim())?.trim();
|
|
3657
|
+
}
|
|
3658
|
+
function buildMeshNodeCapabilityTags(node, providerType) {
|
|
3659
|
+
const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
|
|
3660
|
+
return normalizeMeshCapabilityTags([
|
|
3661
|
+
...Array.isArray(node?.capabilities) ? node.capabilities : [],
|
|
3662
|
+
`os=${process.platform}`,
|
|
3663
|
+
`arch=${process.arch}`,
|
|
3664
|
+
...provider ? [`provider=${provider}`] : []
|
|
3665
|
+
]);
|
|
3666
|
+
}
|
|
3667
|
+
function nodeSatisfiesRequiredTags(requiredTags, capabilityTags) {
|
|
3668
|
+
const required = normalizeMeshCapabilityTags(requiredTags);
|
|
3669
|
+
if (required.length === 0) return true;
|
|
3670
|
+
const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
|
|
3671
|
+
return required.every((tag) => available.has(tag));
|
|
3672
|
+
}
|
|
3005
3673
|
function withQueueLock(_meshId, fn) {
|
|
3006
3674
|
return BeadsDB.getInstance().transaction(fn);
|
|
3007
3675
|
}
|
|
@@ -3019,6 +3687,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
3019
3687
|
taskMode: modeValidation.taskMode,
|
|
3020
3688
|
targetNodeId: opts?.targetNodeId,
|
|
3021
3689
|
targetSessionId: opts?.targetSessionId,
|
|
3690
|
+
requiredTags: normalizeMeshCapabilityTags(opts?.requiredTags),
|
|
3022
3691
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3023
3692
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3024
3693
|
};
|
|
@@ -3031,8 +3700,8 @@ function getQueue(meshId, opts) {
|
|
|
3031
3700
|
function getMeshQueueRevision(meshId) {
|
|
3032
3701
|
return BeadsDB.getInstance().getQueueRevision(meshId);
|
|
3033
3702
|
}
|
|
3034
|
-
function claimNextTask(meshId, nodeId, sessionId) {
|
|
3035
|
-
return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId);
|
|
3703
|
+
function claimNextTask(meshId, nodeId, sessionId, capabilityTags) {
|
|
3704
|
+
return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags);
|
|
3036
3705
|
}
|
|
3037
3706
|
function updateTaskStatus(meshId, taskId, status, opts) {
|
|
3038
3707
|
requireMeshHostQueueOwner(opts);
|
|
@@ -3325,6 +3994,7 @@ var init_cli_detector = __esm({
|
|
|
3325
3994
|
// src/mesh/mesh-events.ts
|
|
3326
3995
|
var mesh_events_exports = {};
|
|
3327
3996
|
__export(mesh_events_exports, {
|
|
3997
|
+
__resetIdleAutoFastForwardForTests: () => __resetIdleAutoFastForwardForTests,
|
|
3328
3998
|
clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
|
|
3329
3999
|
drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
|
|
3330
4000
|
getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
|
|
@@ -3345,6 +4015,9 @@ function getCachedMeshByWorkspace(workspace) {
|
|
|
3345
4015
|
function readWorkerResultMetadata(event) {
|
|
3346
4016
|
return readRecord2(event.workerResult) || readRecord2(event.meshWorkerResult) || readRecord2(event.structuredResult);
|
|
3347
4017
|
}
|
|
4018
|
+
function __resetIdleAutoFastForwardForTests() {
|
|
4019
|
+
idleAutoFastForwardLastAttempt.clear();
|
|
4020
|
+
}
|
|
3348
4021
|
function sweepExpiredRemoteIdleSessions() {
|
|
3349
4022
|
const now = Date.now();
|
|
3350
4023
|
for (const [key, session] of remoteIdleSessions) {
|
|
@@ -3770,13 +4443,14 @@ function buildLongGeneratingCompletionReconciliation(args) {
|
|
|
3770
4443
|
};
|
|
3771
4444
|
}
|
|
3772
4445
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
3773
|
-
const
|
|
4446
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
4447
|
+
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
4448
|
+
const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
|
|
4449
|
+
const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags);
|
|
3774
4450
|
if (!task) {
|
|
3775
4451
|
return false;
|
|
3776
4452
|
}
|
|
3777
4453
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
3778
|
-
const mesh = getMeshWithCache(components, meshId);
|
|
3779
|
-
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
3780
4454
|
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
3781
4455
|
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
3782
4456
|
if (!isLocalNode) {
|
|
@@ -4082,6 +4756,55 @@ async function triggerMeshQueue(components, meshId) {
|
|
|
4082
4756
|
}
|
|
4083
4757
|
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
4084
4758
|
}
|
|
4759
|
+
async function maybeAutoFastForwardIdleNode(components, args) {
|
|
4760
|
+
const mesh = getMeshWithCache(components, args.meshId);
|
|
4761
|
+
const node = mesh?.nodes?.find((candidate) => candidate?.id === args.nodeId || candidate?.nodeId === args.nodeId);
|
|
4762
|
+
const workspace = readNonEmptyString2(node?.workspace);
|
|
4763
|
+
if (!workspace) return;
|
|
4764
|
+
if (!(0, import_fs9.existsSync)(workspace)) return;
|
|
4765
|
+
const throttleKey = `${args.meshId}:${args.nodeId}`;
|
|
4766
|
+
const now = Date.now();
|
|
4767
|
+
const lastAttempt = idleAutoFastForwardLastAttempt.get(throttleKey) || 0;
|
|
4768
|
+
if (now - lastAttempt < IDLE_AUTO_FAST_FORWARD_THROTTLE_MS) return;
|
|
4769
|
+
idleAutoFastForwardLastAttempt.set(throttleKey, now);
|
|
4770
|
+
const submoduleIgnorePaths = Array.isArray(node?.policy?.submoduleIgnorePaths) ? node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
|
|
4771
|
+
try {
|
|
4772
|
+
const dryRun = await fastForwardMeshNode({
|
|
4773
|
+
meshId: args.meshId,
|
|
4774
|
+
nodeId: args.nodeId,
|
|
4775
|
+
workspace,
|
|
4776
|
+
execute: false,
|
|
4777
|
+
dryRun: true,
|
|
4778
|
+
updateSubmodules: false,
|
|
4779
|
+
submoduleIgnorePaths,
|
|
4780
|
+
trigger: "idle_auto"
|
|
4781
|
+
});
|
|
4782
|
+
if (!dryRun || dryRun.code !== "fast_forward_available" || dryRun.allowed !== true) return;
|
|
4783
|
+
await fastForwardMeshNode({
|
|
4784
|
+
meshId: args.meshId,
|
|
4785
|
+
nodeId: args.nodeId,
|
|
4786
|
+
workspace,
|
|
4787
|
+
execute: true,
|
|
4788
|
+
dryRun: false,
|
|
4789
|
+
updateSubmodules: false,
|
|
4790
|
+
submoduleIgnorePaths,
|
|
4791
|
+
trigger: "idle_auto"
|
|
4792
|
+
});
|
|
4793
|
+
} catch (e) {
|
|
4794
|
+
LOG.warn("MeshFastForward", `Idle auto fast-forward check failed for ${args.nodeId}: ${e?.message || e}`);
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
function runIdleMaintenanceThenAssignQueue(components, args) {
|
|
4798
|
+
setImmediate(() => {
|
|
4799
|
+
maybeAutoFastForwardIdleNode(components, args).finally(() => {
|
|
4800
|
+
try {
|
|
4801
|
+
tryAssignQueueTask(components, args.meshId, args.nodeId, args.sessionId, args.providerType);
|
|
4802
|
+
} catch (e) {
|
|
4803
|
+
LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${args.nodeId}: ${e?.message || e}`);
|
|
4804
|
+
}
|
|
4805
|
+
});
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4085
4808
|
function buildMeshSystemMessage(args) {
|
|
4086
4809
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
4087
4810
|
if (args.event === "agent:generating_completed") {
|
|
@@ -4303,9 +5026,7 @@ function injectMeshSystemMessage(components, args) {
|
|
|
4303
5026
|
updateDirectDispatchStatus(args.meshId, sessionId, "completed");
|
|
4304
5027
|
setImmediate(() => cleanupTerminalDirectDispatches());
|
|
4305
5028
|
if (nodeId && providerType) {
|
|
4306
|
-
|
|
4307
|
-
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
4308
|
-
});
|
|
5029
|
+
runIdleMaintenanceThenAssignQueue(components, { meshId: args.meshId, nodeId, sessionId, providerType });
|
|
4309
5030
|
}
|
|
4310
5031
|
}
|
|
4311
5032
|
} else if (args.event === "agent:ready") {
|
|
@@ -4359,8 +5080,14 @@ function injectMeshSystemMessage(components, args) {
|
|
|
4359
5080
|
expiresAt: Date.now() + REMOTE_IDLE_SESSION_TTL_MS
|
|
4360
5081
|
});
|
|
4361
5082
|
setImmediate(() => {
|
|
4362
|
-
|
|
4363
|
-
|
|
5083
|
+
maybeAutoFastForwardIdleNode(components, { meshId: args.meshId, nodeId, sessionId, providerType }).finally(() => {
|
|
5084
|
+
try {
|
|
5085
|
+
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
5086
|
+
if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
5087
|
+
} catch (e) {
|
|
5088
|
+
LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${nodeId}: ${e?.message || e}`);
|
|
5089
|
+
}
|
|
5090
|
+
});
|
|
4364
5091
|
});
|
|
4365
5092
|
}
|
|
4366
5093
|
} else if (args.event === "agent:generating_started") {
|
|
@@ -4636,7 +5363,7 @@ function setupMeshEventForwarding(components) {
|
|
|
4636
5363
|
});
|
|
4637
5364
|
});
|
|
4638
5365
|
}
|
|
4639
|
-
var import_fs9, import_path8, 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;
|
|
5366
|
+
var import_fs9, import_path8, 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;
|
|
4640
5367
|
var init_mesh_events = __esm({
|
|
4641
5368
|
"src/mesh/mesh-events.ts"() {
|
|
4642
5369
|
"use strict";
|
|
@@ -4649,10 +5376,13 @@ var init_mesh_events = __esm({
|
|
|
4649
5376
|
init_mesh_ledger();
|
|
4650
5377
|
init_mesh_work_queue();
|
|
4651
5378
|
init_beads_db();
|
|
5379
|
+
init_mesh_fast_forward();
|
|
4652
5380
|
REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1e3;
|
|
4653
5381
|
remoteIdleSessions = /* @__PURE__ */ new Map();
|
|
4654
5382
|
meshByWorkspaceCache = /* @__PURE__ */ new Map();
|
|
4655
5383
|
MESH_WORKSPACE_CACHE_TTL_MS = 5e3;
|
|
5384
|
+
IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
|
|
5385
|
+
idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
|
|
4656
5386
|
REFINE_TERMINAL_EVENTS = /* @__PURE__ */ new Set(["refine:completed", "refine:failed"]);
|
|
4657
5387
|
MAX_PENDING_EVENTS_BYTES = 100 * 1024;
|
|
4658
5388
|
MAX_PENDING_EVENTS_KEEP = 50;
|
|
@@ -12143,6 +12873,7 @@ __export(index_exports, {
|
|
|
12143
12873
|
buildMeshHostRequiredFailure: () => buildMeshHostRequiredFailure,
|
|
12144
12874
|
buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
|
|
12145
12875
|
buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
|
|
12876
|
+
buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
|
|
12146
12877
|
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
12147
12878
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
12148
12879
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
@@ -12270,6 +13001,7 @@ __export(index_exports, {
|
|
|
12270
13001
|
maybeRunDaemonUpgradeHelperFromEnv: () => maybeRunDaemonUpgradeHelperFromEnv,
|
|
12271
13002
|
namedKeyToAnsi: () => namedKeyToAnsi,
|
|
12272
13003
|
namedKeysToAnsi: () => namedKeysToAnsi,
|
|
13004
|
+
nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
|
|
12273
13005
|
normalizeActiveChatData: () => normalizeActiveChatData,
|
|
12274
13006
|
normalizeChatMessage: () => normalizeChatMessage,
|
|
12275
13007
|
normalizeChatMessageKind: () => normalizeChatMessageKind,
|
|
@@ -12281,6 +13013,7 @@ __export(index_exports, {
|
|
|
12281
13013
|
normalizeInteractivePrompt: () => normalizeInteractivePrompt,
|
|
12282
13014
|
normalizeInteractivePromptResponse: () => normalizeInteractivePromptResponse,
|
|
12283
13015
|
normalizeManagedStatus: () => normalizeManagedStatus,
|
|
13016
|
+
normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
|
|
12284
13017
|
normalizeMeshDaemonRole: () => normalizeMeshDaemonRole,
|
|
12285
13018
|
normalizeMeshTaskMode: () => normalizeMeshTaskMode,
|
|
12286
13019
|
normalizeMeshWorkerResult: () => normalizeMeshWorkerResult,
|
|
@@ -12339,7 +13072,6 @@ __export(index_exports, {
|
|
|
12339
13072
|
startLocalIpcServer: () => startLocalIpcServer,
|
|
12340
13073
|
suggestMeshRefineConfig: () => suggestMeshRefineConfig,
|
|
12341
13074
|
summarizeGitStatus: () => summarizeGitStatus,
|
|
12342
|
-
syncMeshes: () => syncMeshes,
|
|
12343
13075
|
triggerMeshQueue: () => triggerMeshQueue,
|
|
12344
13076
|
unregisterMeshCoordinator: () => unregisterMeshCoordinator,
|
|
12345
13077
|
updateConfig: () => updateConfig,
|
|
@@ -12582,258 +13314,7 @@ init_repo_mesh_types();
|
|
|
12582
13314
|
|
|
12583
13315
|
// src/git/index.ts
|
|
12584
13316
|
init_git_executor();
|
|
12585
|
-
|
|
12586
|
-
// src/git/git-status.ts
|
|
12587
|
-
init_git_executor();
|
|
12588
|
-
async function getGitRepoStatus(workspace, options = {}) {
|
|
12589
|
-
const lastCheckedAt = Date.now();
|
|
12590
|
-
const includeSubmodules = options.includeSubmodules !== false;
|
|
12591
|
-
try {
|
|
12592
|
-
const repo = await resolveGitRepository(workspace, options);
|
|
12593
|
-
let parsed = await readPorcelainStatus(repo, options);
|
|
12594
|
-
let upstreamProbe = getInitialUpstreamProbe(parsed);
|
|
12595
|
-
if (options.refreshUpstream) {
|
|
12596
|
-
upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
|
|
12597
|
-
if (upstreamProbe.upstreamStatus === "fresh") {
|
|
12598
|
-
parsed = await readPorcelainStatus(repo, options);
|
|
12599
|
-
}
|
|
12600
|
-
}
|
|
12601
|
-
const head = await readHead(repo, options);
|
|
12602
|
-
const stashCount = await readStashCount(repo, options);
|
|
12603
|
-
let submodules;
|
|
12604
|
-
if (includeSubmodules) {
|
|
12605
|
-
submodules = await getSubmoduleStatuses(repo, options);
|
|
12606
|
-
}
|
|
12607
|
-
return {
|
|
12608
|
-
workspace: repo.workspace,
|
|
12609
|
-
repoRoot: repo.repoRoot,
|
|
12610
|
-
isGitRepo: true,
|
|
12611
|
-
branch: parsed.branch,
|
|
12612
|
-
headCommit: head.commit,
|
|
12613
|
-
headMessage: head.message,
|
|
12614
|
-
upstream: parsed.upstream,
|
|
12615
|
-
upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
|
|
12616
|
-
upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
|
|
12617
|
-
upstreamFetchError: upstreamProbe.upstreamFetchError,
|
|
12618
|
-
ahead: parsed.ahead,
|
|
12619
|
-
behind: parsed.behind,
|
|
12620
|
-
staged: parsed.staged,
|
|
12621
|
-
modified: parsed.modified,
|
|
12622
|
-
untracked: parsed.untracked,
|
|
12623
|
-
deleted: parsed.deleted,
|
|
12624
|
-
renamed: parsed.renamed,
|
|
12625
|
-
hasConflicts: parsed.conflictFiles.length > 0,
|
|
12626
|
-
conflictFiles: parsed.conflictFiles,
|
|
12627
|
-
stashCount,
|
|
12628
|
-
lastCheckedAt,
|
|
12629
|
-
submodules
|
|
12630
|
-
};
|
|
12631
|
-
} catch (error) {
|
|
12632
|
-
if (error instanceof GitCommandError) {
|
|
12633
|
-
return emptyStatus(workspace, lastCheckedAt, error);
|
|
12634
|
-
}
|
|
12635
|
-
return emptyStatus(
|
|
12636
|
-
workspace,
|
|
12637
|
-
lastCheckedAt,
|
|
12638
|
-
new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
|
|
12639
|
-
);
|
|
12640
|
-
}
|
|
12641
|
-
}
|
|
12642
|
-
async function readPorcelainStatus(repo, options) {
|
|
12643
|
-
const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
|
|
12644
|
-
return parsePorcelainV2Status(statusOutput.stdout);
|
|
12645
|
-
}
|
|
12646
|
-
function getInitialUpstreamProbe(parsed) {
|
|
12647
|
-
return {
|
|
12648
|
-
upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
|
|
12649
|
-
};
|
|
12650
|
-
}
|
|
12651
|
-
async function refreshTrackedUpstream(repo, parsed, options) {
|
|
12652
|
-
if (!parsed.upstream || !parsed.branch) {
|
|
12653
|
-
return { upstreamStatus: "no_upstream" };
|
|
12654
|
-
}
|
|
12655
|
-
const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
|
|
12656
|
-
if (!remoteName) {
|
|
12657
|
-
return {
|
|
12658
|
-
upstreamStatus: "stale",
|
|
12659
|
-
upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
|
|
12660
|
-
};
|
|
12661
|
-
}
|
|
12662
|
-
try {
|
|
12663
|
-
await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
|
|
12664
|
-
return {
|
|
12665
|
-
upstreamStatus: "fresh",
|
|
12666
|
-
upstreamFetchedAt: Date.now()
|
|
12667
|
-
};
|
|
12668
|
-
} catch (error) {
|
|
12669
|
-
return {
|
|
12670
|
-
upstreamStatus: "stale",
|
|
12671
|
-
upstreamFetchError: formatGitError(error)
|
|
12672
|
-
};
|
|
12673
|
-
}
|
|
12674
|
-
}
|
|
12675
|
-
async function readBranchRemote(repo, branch, options) {
|
|
12676
|
-
try {
|
|
12677
|
-
const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
|
|
12678
|
-
return result.stdout.trim() || null;
|
|
12679
|
-
} catch {
|
|
12680
|
-
return null;
|
|
12681
|
-
}
|
|
12682
|
-
}
|
|
12683
|
-
function inferRemoteName(upstream) {
|
|
12684
|
-
const [remoteName] = upstream.split("/");
|
|
12685
|
-
return remoteName?.trim() || null;
|
|
12686
|
-
}
|
|
12687
|
-
function formatGitError(error) {
|
|
12688
|
-
if (error instanceof GitCommandError) {
|
|
12689
|
-
return error.stderr || error.message;
|
|
12690
|
-
}
|
|
12691
|
-
if (error instanceof Error) {
|
|
12692
|
-
return error.message;
|
|
12693
|
-
}
|
|
12694
|
-
return String(error);
|
|
12695
|
-
}
|
|
12696
|
-
function parsePorcelainV2Status(output) {
|
|
12697
|
-
const parsed = {
|
|
12698
|
-
branch: null,
|
|
12699
|
-
upstream: null,
|
|
12700
|
-
ahead: 0,
|
|
12701
|
-
behind: 0,
|
|
12702
|
-
staged: 0,
|
|
12703
|
-
modified: 0,
|
|
12704
|
-
untracked: 0,
|
|
12705
|
-
deleted: 0,
|
|
12706
|
-
renamed: 0,
|
|
12707
|
-
conflictFiles: []
|
|
12708
|
-
};
|
|
12709
|
-
for (const line of output.split("\n")) {
|
|
12710
|
-
if (!line) continue;
|
|
12711
|
-
if (line.startsWith("# branch.head ")) {
|
|
12712
|
-
const branch = line.slice("# branch.head ".length).trim();
|
|
12713
|
-
parsed.branch = branch && branch !== "(detached)" ? branch : null;
|
|
12714
|
-
continue;
|
|
12715
|
-
}
|
|
12716
|
-
if (line.startsWith("# branch.upstream ")) {
|
|
12717
|
-
parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
|
|
12718
|
-
continue;
|
|
12719
|
-
}
|
|
12720
|
-
if (line.startsWith("# branch.ab ")) {
|
|
12721
|
-
const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
|
|
12722
|
-
if (match) {
|
|
12723
|
-
parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
|
|
12724
|
-
parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
|
|
12725
|
-
}
|
|
12726
|
-
continue;
|
|
12727
|
-
}
|
|
12728
|
-
if (line.startsWith("? ")) {
|
|
12729
|
-
parsed.untracked += 1;
|
|
12730
|
-
continue;
|
|
12731
|
-
}
|
|
12732
|
-
if (line.startsWith("u ")) {
|
|
12733
|
-
const fields = line.split(" ");
|
|
12734
|
-
const filePath = fields.slice(10).join(" ");
|
|
12735
|
-
if (filePath) parsed.conflictFiles.push(filePath);
|
|
12736
|
-
continue;
|
|
12737
|
-
}
|
|
12738
|
-
if (line.startsWith("1 ") || line.startsWith("2 ")) {
|
|
12739
|
-
const fields = line.split(" ");
|
|
12740
|
-
const xy = fields[1] ?? "..";
|
|
12741
|
-
const indexStatus = xy[0] ?? ".";
|
|
12742
|
-
const worktreeStatus = xy[1] ?? ".";
|
|
12743
|
-
if (isStagedStatus(indexStatus)) parsed.staged += 1;
|
|
12744
|
-
if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
|
|
12745
|
-
if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
|
|
12746
|
-
if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
|
|
12747
|
-
if (xy.includes("U")) {
|
|
12748
|
-
const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
|
|
12749
|
-
if (filePath) parsed.conflictFiles.push(filePath);
|
|
12750
|
-
}
|
|
12751
|
-
}
|
|
12752
|
-
}
|
|
12753
|
-
parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
|
|
12754
|
-
return parsed;
|
|
12755
|
-
}
|
|
12756
|
-
async function readHead(repo, options) {
|
|
12757
|
-
try {
|
|
12758
|
-
const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
|
|
12759
|
-
const text = result.stdout.trimEnd();
|
|
12760
|
-
if (!text) return { commit: null, message: null };
|
|
12761
|
-
const [commit, ...messageParts] = text.split("\0");
|
|
12762
|
-
return {
|
|
12763
|
-
commit: commit || null,
|
|
12764
|
-
message: messageParts.join("\0") || null
|
|
12765
|
-
};
|
|
12766
|
-
} catch {
|
|
12767
|
-
return { commit: null, message: null };
|
|
12768
|
-
}
|
|
12769
|
-
}
|
|
12770
|
-
async function readStashCount(repo, options) {
|
|
12771
|
-
try {
|
|
12772
|
-
const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
|
|
12773
|
-
return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
12774
|
-
} catch {
|
|
12775
|
-
return 0;
|
|
12776
|
-
}
|
|
12777
|
-
}
|
|
12778
|
-
function isStagedStatus(status) {
|
|
12779
|
-
return status !== "." && status !== "?" && status !== "U";
|
|
12780
|
-
}
|
|
12781
|
-
function emptyStatus(workspace, lastCheckedAt, error) {
|
|
12782
|
-
return {
|
|
12783
|
-
workspace,
|
|
12784
|
-
repoRoot: null,
|
|
12785
|
-
isGitRepo: false,
|
|
12786
|
-
branch: null,
|
|
12787
|
-
headCommit: null,
|
|
12788
|
-
headMessage: null,
|
|
12789
|
-
upstream: null,
|
|
12790
|
-
upstreamStatus: "unavailable",
|
|
12791
|
-
ahead: 0,
|
|
12792
|
-
behind: 0,
|
|
12793
|
-
staged: 0,
|
|
12794
|
-
modified: 0,
|
|
12795
|
-
untracked: 0,
|
|
12796
|
-
deleted: 0,
|
|
12797
|
-
renamed: 0,
|
|
12798
|
-
hasConflicts: false,
|
|
12799
|
-
conflictFiles: [],
|
|
12800
|
-
stashCount: 0,
|
|
12801
|
-
lastCheckedAt,
|
|
12802
|
-
error: error.stderr || error.message,
|
|
12803
|
-
reason: error.reason
|
|
12804
|
-
};
|
|
12805
|
-
}
|
|
12806
|
-
async function getSubmoduleStatuses(repo, options) {
|
|
12807
|
-
if (!repo.repoRoot) return [];
|
|
12808
|
-
try {
|
|
12809
|
-
const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
|
|
12810
|
-
return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
|
|
12811
|
-
} catch {
|
|
12812
|
-
return [];
|
|
12813
|
-
}
|
|
12814
|
-
}
|
|
12815
|
-
function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
|
|
12816
|
-
const submodules = [];
|
|
12817
|
-
const ignoreSet = new Set(ignorePaths || []);
|
|
12818
|
-
for (const line of output.split("\n")) {
|
|
12819
|
-
if (!line.trim()) continue;
|
|
12820
|
-
const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
|
|
12821
|
-
if (!match) continue;
|
|
12822
|
-
const prefix = match[1];
|
|
12823
|
-
const commit = match[2];
|
|
12824
|
-
const path40 = match[3];
|
|
12825
|
-
if (ignoreSet.has(path40)) continue;
|
|
12826
|
-
submodules.push({
|
|
12827
|
-
path: path40,
|
|
12828
|
-
commit,
|
|
12829
|
-
repoPath: repoRoot + "/" + path40,
|
|
12830
|
-
dirty: prefix === "+",
|
|
12831
|
-
outOfSync: prefix === "-",
|
|
12832
|
-
lastCheckedAt: Date.now()
|
|
12833
|
-
});
|
|
12834
|
-
}
|
|
12835
|
-
return submodules;
|
|
12836
|
-
}
|
|
13317
|
+
init_git_status();
|
|
12837
13318
|
|
|
12838
13319
|
// src/git/git-diff.ts
|
|
12839
13320
|
var import_promises2 = require("fs/promises");
|
|
@@ -13249,6 +13730,7 @@ function createGitSnapshotStore(options = {}) {
|
|
|
13249
13730
|
}
|
|
13250
13731
|
|
|
13251
13732
|
// src/git/git-monitor.ts
|
|
13733
|
+
init_git_status();
|
|
13252
13734
|
var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
|
|
13253
13735
|
var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
|
|
13254
13736
|
function defaultStatusProvider(workspace) {
|
|
@@ -13371,6 +13853,7 @@ function createGitWorkspaceMonitor(options = {}) {
|
|
|
13371
13853
|
// src/git/git-commands.ts
|
|
13372
13854
|
var path3 = __toESM(require("path"));
|
|
13373
13855
|
init_git_executor();
|
|
13856
|
+
init_git_status();
|
|
13374
13857
|
var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
13375
13858
|
"git_status",
|
|
13376
13859
|
"git_diff_summary",
|
|
@@ -14851,414 +15334,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
|
|
|
14851
15334
|
return state;
|
|
14852
15335
|
}
|
|
14853
15336
|
|
|
14854
|
-
// src/mesh/mesh-sync.ts
|
|
14855
|
-
init_mesh_config();
|
|
14856
|
-
async function syncMeshes(transport) {
|
|
14857
|
-
const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
|
|
14858
|
-
let remoteMeshes;
|
|
14859
|
-
try {
|
|
14860
|
-
const res = await transport.listRemoteMeshes();
|
|
14861
|
-
remoteMeshes = res.meshes;
|
|
14862
|
-
} catch (e) {
|
|
14863
|
-
result.errors.push(`Failed to list remote meshes: ${e.message}`);
|
|
14864
|
-
return result;
|
|
14865
|
-
}
|
|
14866
|
-
const localMeshes = listMeshes();
|
|
14867
|
-
const remoteByIdentity = new Map(remoteMeshes.map((m) => [m.repo_identity, m]));
|
|
14868
|
-
const localByIdentity = new Map(localMeshes.map((m) => [m.repoIdentity, m]));
|
|
14869
|
-
for (const local of localMeshes) {
|
|
14870
|
-
if (!remoteByIdentity.has(local.repoIdentity)) {
|
|
14871
|
-
try {
|
|
14872
|
-
await transport.createRemoteMesh({
|
|
14873
|
-
name: local.name,
|
|
14874
|
-
repo_identity: local.repoIdentity,
|
|
14875
|
-
repo_remote_url: local.repoRemoteUrl,
|
|
14876
|
-
default_branch: local.defaultBranch,
|
|
14877
|
-
policy: JSON.stringify(local.policy)
|
|
14878
|
-
});
|
|
14879
|
-
result.pushed++;
|
|
14880
|
-
} catch (e) {
|
|
14881
|
-
result.errors.push(`Push failed for "${local.name}": ${e.message}`);
|
|
14882
|
-
}
|
|
14883
|
-
}
|
|
14884
|
-
}
|
|
14885
|
-
for (const remote of remoteMeshes) {
|
|
14886
|
-
if (!localByIdentity.has(remote.repo_identity)) {
|
|
14887
|
-
try {
|
|
14888
|
-
let policy;
|
|
14889
|
-
try {
|
|
14890
|
-
policy = JSON.parse(remote.policy);
|
|
14891
|
-
} catch {
|
|
14892
|
-
policy = void 0;
|
|
14893
|
-
}
|
|
14894
|
-
createMesh({
|
|
14895
|
-
name: remote.name,
|
|
14896
|
-
repoIdentity: remote.repo_identity,
|
|
14897
|
-
repoRemoteUrl: remote.repo_remote_url || void 0,
|
|
14898
|
-
defaultBranch: remote.default_branch || void 0,
|
|
14899
|
-
policy
|
|
14900
|
-
});
|
|
14901
|
-
result.pulled++;
|
|
14902
|
-
} catch (e) {
|
|
14903
|
-
result.errors.push(`Pull failed for "${remote.name}": ${e.message}`);
|
|
14904
|
-
}
|
|
14905
|
-
}
|
|
14906
|
-
}
|
|
14907
|
-
return result;
|
|
14908
|
-
}
|
|
14909
|
-
|
|
14910
15337
|
// src/index.ts
|
|
14911
15338
|
init_mesh_ledger();
|
|
14912
|
-
|
|
14913
|
-
// src/mesh/mesh-fast-forward.ts
|
|
14914
|
-
init_git_executor();
|
|
14915
|
-
var STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
|
|
14916
|
-
async function fastForwardMeshNode(args) {
|
|
14917
|
-
const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
|
|
14918
|
-
const nodeId = normalizeOptionalString(args.nodeId);
|
|
14919
|
-
const meshId = normalizeOptionalString(args.meshId);
|
|
14920
|
-
const requestedBranch = normalizeOptionalString(args.branch);
|
|
14921
|
-
const updateSubmodules = args.updateSubmodules === true;
|
|
14922
|
-
const dryRun = args.dryRun === true || args.execute !== true;
|
|
14923
|
-
const plannedSteps = buildPlannedSteps(updateSubmodules);
|
|
14924
|
-
const base = {
|
|
14925
|
-
...nodeId ? { nodeId } : {},
|
|
14926
|
-
...meshId ? { meshId } : {},
|
|
14927
|
-
workspace,
|
|
14928
|
-
dryRun,
|
|
14929
|
-
updateSubmodules,
|
|
14930
|
-
plannedSteps
|
|
14931
|
-
};
|
|
14932
|
-
if (!workspace) {
|
|
14933
|
-
return block(base, "invalid_workspace", ["workspace_required"]);
|
|
14934
|
-
}
|
|
14935
|
-
const current = await getGitRepoStatus(workspace, {
|
|
14936
|
-
...STATUS_OPTIONS,
|
|
14937
|
-
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
14938
|
-
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
14939
|
-
});
|
|
14940
|
-
const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
|
|
14941
|
-
if (earlyBlockers.length > 0) {
|
|
14942
|
-
return {
|
|
14943
|
-
...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
|
|
14944
|
-
current,
|
|
14945
|
-
finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
|
|
14946
|
-
};
|
|
14947
|
-
}
|
|
14948
|
-
if (current.behind === 0) {
|
|
14949
|
-
const result2 = {
|
|
14950
|
-
...base,
|
|
14951
|
-
success: true,
|
|
14952
|
-
code: "already_up_to_date",
|
|
14953
|
-
allowed: true,
|
|
14954
|
-
willRun: false,
|
|
14955
|
-
executed: false,
|
|
14956
|
-
blockingReasons: [],
|
|
14957
|
-
current,
|
|
14958
|
-
preStatus: current,
|
|
14959
|
-
postStatus: current,
|
|
14960
|
-
finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
|
|
14961
|
-
};
|
|
14962
|
-
await appendFastForwardLedger(result2, "noop");
|
|
14963
|
-
return result2;
|
|
14964
|
-
}
|
|
14965
|
-
const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
|
|
14966
|
-
if (!ancestorCheck.ok) {
|
|
14967
|
-
const result2 = {
|
|
14968
|
-
...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
|
|
14969
|
-
current,
|
|
14970
|
-
preStatus: current,
|
|
14971
|
-
operationError: ancestorCheck.error,
|
|
14972
|
-
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
14973
|
-
};
|
|
14974
|
-
await appendFastForwardLedger(result2, "blocked");
|
|
14975
|
-
return result2;
|
|
14976
|
-
}
|
|
14977
|
-
if (dryRun) {
|
|
14978
|
-
const result2 = {
|
|
14979
|
-
...base,
|
|
14980
|
-
success: true,
|
|
14981
|
-
code: "fast_forward_available",
|
|
14982
|
-
allowed: true,
|
|
14983
|
-
willRun: false,
|
|
14984
|
-
executed: false,
|
|
14985
|
-
blockingReasons: [],
|
|
14986
|
-
current,
|
|
14987
|
-
preStatus: current,
|
|
14988
|
-
finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
|
|
14989
|
-
};
|
|
14990
|
-
return result2;
|
|
14991
|
-
}
|
|
14992
|
-
try {
|
|
14993
|
-
await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
|
|
14994
|
-
} catch (error) {
|
|
14995
|
-
const result2 = {
|
|
14996
|
-
...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
|
|
14997
|
-
current,
|
|
14998
|
-
preStatus: current,
|
|
14999
|
-
operationError: formatGitError2(error),
|
|
15000
|
-
finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
|
|
15001
|
-
};
|
|
15002
|
-
await appendFastForwardLedger(result2, "failed");
|
|
15003
|
-
return result2;
|
|
15004
|
-
}
|
|
15005
|
-
let postStatus = await getGitRepoStatus(workspace, {
|
|
15006
|
-
...STATUS_OPTIONS,
|
|
15007
|
-
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
15008
|
-
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
15009
|
-
});
|
|
15010
|
-
const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
|
|
15011
|
-
let submoduleFollowUpRequired = false;
|
|
15012
|
-
let operationError;
|
|
15013
|
-
if (submoduleIssues.length > 0) {
|
|
15014
|
-
if (updateSubmodules) {
|
|
15015
|
-
try {
|
|
15016
|
-
await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
|
|
15017
|
-
postStatus = await getGitRepoStatus(workspace, {
|
|
15018
|
-
...STATUS_OPTIONS,
|
|
15019
|
-
submoduleIgnorePaths: args.submoduleIgnorePaths,
|
|
15020
|
-
timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
|
|
15021
|
-
});
|
|
15022
|
-
} catch (error) {
|
|
15023
|
-
operationError = formatGitError2(error);
|
|
15024
|
-
}
|
|
15025
|
-
} else {
|
|
15026
|
-
submoduleFollowUpRequired = true;
|
|
15027
|
-
}
|
|
15028
|
-
}
|
|
15029
|
-
const postBlockers = collectPostExecutionBlockers(postStatus);
|
|
15030
|
-
if (operationError) postBlockers.push("submodule_update_failed");
|
|
15031
|
-
if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
|
|
15032
|
-
const success = postBlockers.length === 0 || submoduleFollowUpRequired;
|
|
15033
|
-
const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
|
|
15034
|
-
const result = {
|
|
15035
|
-
...base,
|
|
15036
|
-
success,
|
|
15037
|
-
code,
|
|
15038
|
-
allowed: true,
|
|
15039
|
-
willRun: true,
|
|
15040
|
-
executed: true,
|
|
15041
|
-
blockingReasons: postBlockers,
|
|
15042
|
-
current,
|
|
15043
|
-
preStatus: current,
|
|
15044
|
-
postStatus,
|
|
15045
|
-
...operationError ? { operationError } : {},
|
|
15046
|
-
finalBranchConvergenceState: buildConvergenceState(
|
|
15047
|
-
postStatus,
|
|
15048
|
-
postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
|
|
15049
|
-
)
|
|
15050
|
-
};
|
|
15051
|
-
await appendFastForwardLedger(result, success ? "executed" : "failed");
|
|
15052
|
-
return result;
|
|
15053
|
-
}
|
|
15054
|
-
function buildPlannedSteps(updateSubmodules) {
|
|
15055
|
-
const steps = [
|
|
15056
|
-
{
|
|
15057
|
-
operation: "refresh_upstream",
|
|
15058
|
-
description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
|
|
15059
|
-
safe: true,
|
|
15060
|
-
willMutateWorktree: false
|
|
15061
|
-
},
|
|
15062
|
-
{
|
|
15063
|
-
operation: "verify_clean_worktree",
|
|
15064
|
-
description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
|
|
15065
|
-
safe: true,
|
|
15066
|
-
willMutateWorktree: false
|
|
15067
|
-
},
|
|
15068
|
-
{
|
|
15069
|
-
operation: "verify_fast_forward",
|
|
15070
|
-
description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
|
|
15071
|
-
safe: true,
|
|
15072
|
-
willMutateWorktree: false
|
|
15073
|
-
},
|
|
15074
|
-
{
|
|
15075
|
-
operation: "merge_ff_only",
|
|
15076
|
-
description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
|
|
15077
|
-
safe: true,
|
|
15078
|
-
willMutateWorktree: true
|
|
15079
|
-
}
|
|
15080
|
-
];
|
|
15081
|
-
if (updateSubmodules) {
|
|
15082
|
-
steps.push({
|
|
15083
|
-
operation: "submodule_update",
|
|
15084
|
-
description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
|
|
15085
|
-
safe: true,
|
|
15086
|
-
willMutateWorktree: true
|
|
15087
|
-
});
|
|
15088
|
-
}
|
|
15089
|
-
steps.push({
|
|
15090
|
-
operation: "verify_post_status",
|
|
15091
|
-
description: "Re-read daemon-owned git status and report final branch convergence state.",
|
|
15092
|
-
safe: true,
|
|
15093
|
-
willMutateWorktree: false
|
|
15094
|
-
});
|
|
15095
|
-
return steps;
|
|
15096
|
-
}
|
|
15097
|
-
function collectPreflightBlockers(status, requestedBranch) {
|
|
15098
|
-
const blockers = [];
|
|
15099
|
-
if (!status.isGitRepo) blockers.push("not_git_repo");
|
|
15100
|
-
if (!status.branch) blockers.push("detached_head_or_unknown_branch");
|
|
15101
|
-
if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
|
|
15102
|
-
if (!status.upstream) blockers.push("upstream_missing");
|
|
15103
|
-
if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
|
|
15104
|
-
if (status.hasConflicts) blockers.push("conflicts_present");
|
|
15105
|
-
if (status.staged > 0) blockers.push("staged_changes_present");
|
|
15106
|
-
if (status.modified > 0) blockers.push("modified_changes_present");
|
|
15107
|
-
if (status.untracked > 0) blockers.push("untracked_changes_present");
|
|
15108
|
-
if (status.deleted > 0) blockers.push("deleted_changes_present");
|
|
15109
|
-
if (status.renamed > 0) blockers.push("renamed_changes_present");
|
|
15110
|
-
if (status.stashCount > 0) blockers.push("stash_entries_present");
|
|
15111
|
-
blockers.push(...collectSubmoduleBlockers(status, "pre"));
|
|
15112
|
-
if (status.ahead > 0 && status.behind > 0) {
|
|
15113
|
-
blockers.push("branch_diverged_from_upstream");
|
|
15114
|
-
blockers.push("branch_has_local_commits");
|
|
15115
|
-
} else if (status.ahead > 0) blockers.push("branch_has_local_commits");
|
|
15116
|
-
return blockers;
|
|
15117
|
-
}
|
|
15118
|
-
function collectPostExecutionBlockers(status) {
|
|
15119
|
-
const blockers = [];
|
|
15120
|
-
if (!status.isGitRepo) blockers.push("post_not_git_repo");
|
|
15121
|
-
if (status.hasConflicts) blockers.push("post_conflicts_present");
|
|
15122
|
-
if (status.ahead !== 0) blockers.push("post_branch_ahead");
|
|
15123
|
-
if (status.behind !== 0) blockers.push("post_branch_still_behind");
|
|
15124
|
-
if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
|
|
15125
|
-
blockers.push("post_working_tree_not_clean");
|
|
15126
|
-
}
|
|
15127
|
-
if (status.stashCount > 0) blockers.push("post_stash_entries_present");
|
|
15128
|
-
blockers.push(...collectSubmoduleBlockers(status, "post"));
|
|
15129
|
-
return blockers;
|
|
15130
|
-
}
|
|
15131
|
-
function collectSubmoduleBlockers(status, phase) {
|
|
15132
|
-
const submodules = Array.isArray(status.submodules) ? status.submodules : [];
|
|
15133
|
-
const blockers = [];
|
|
15134
|
-
for (const submodule of submodules) {
|
|
15135
|
-
if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
|
|
15136
|
-
if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
|
|
15137
|
-
if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
|
|
15138
|
-
}
|
|
15139
|
-
return blockers;
|
|
15140
|
-
}
|
|
15141
|
-
function chooseBlockCode(status, blockers) {
|
|
15142
|
-
if (blockers.includes("not_git_repo")) return "not_git_repo";
|
|
15143
|
-
if (blockers.includes("branch_mismatch")) return "branch_mismatch";
|
|
15144
|
-
if (blockers.includes("upstream_missing")) return "upstream_missing";
|
|
15145
|
-
if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
|
|
15146
|
-
if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
|
|
15147
|
-
if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
|
|
15148
|
-
if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
|
|
15149
|
-
if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
|
|
15150
|
-
return "preflight_blocked";
|
|
15151
|
-
}
|
|
15152
|
-
function codeToConvergenceStatus(code) {
|
|
15153
|
-
if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
|
|
15154
|
-
if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
|
|
15155
|
-
return "blocked";
|
|
15156
|
-
}
|
|
15157
|
-
async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
|
|
15158
|
-
if (!upstream) return { ok: false, error: "missing upstream" };
|
|
15159
|
-
try {
|
|
15160
|
-
await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
|
|
15161
|
-
return { ok: true };
|
|
15162
|
-
} catch (error) {
|
|
15163
|
-
return { ok: false, error: formatGitError2(error) };
|
|
15164
|
-
}
|
|
15165
|
-
}
|
|
15166
|
-
function block(base, code, blockingReasons) {
|
|
15167
|
-
const normalizedReasons = normalizeBlockingReasons(blockingReasons);
|
|
15168
|
-
return {
|
|
15169
|
-
...base,
|
|
15170
|
-
success: false,
|
|
15171
|
-
code,
|
|
15172
|
-
allowed: false,
|
|
15173
|
-
willRun: false,
|
|
15174
|
-
executed: false,
|
|
15175
|
-
blockingReasons: normalizedReasons
|
|
15176
|
-
};
|
|
15177
|
-
}
|
|
15178
|
-
function normalizeBlockingReasons(reasons) {
|
|
15179
|
-
const normalized = /* @__PURE__ */ new Set();
|
|
15180
|
-
for (const reason of reasons) {
|
|
15181
|
-
normalized.add(reason);
|
|
15182
|
-
}
|
|
15183
|
-
if ([
|
|
15184
|
-
"conflicts_present",
|
|
15185
|
-
"staged_changes_present",
|
|
15186
|
-
"modified_changes_present",
|
|
15187
|
-
"untracked_changes_present",
|
|
15188
|
-
"deleted_changes_present",
|
|
15189
|
-
"renamed_changes_present"
|
|
15190
|
-
].some((reason) => normalized.has(reason))) {
|
|
15191
|
-
normalized.add("working_tree_not_clean");
|
|
15192
|
-
}
|
|
15193
|
-
return Array.from(normalized);
|
|
15194
|
-
}
|
|
15195
|
-
function buildConvergenceState(status, convergenceStatus) {
|
|
15196
|
-
return {
|
|
15197
|
-
status: convergenceStatus,
|
|
15198
|
-
branch: status.branch,
|
|
15199
|
-
headCommit: status.headCommit,
|
|
15200
|
-
upstream: status.upstream,
|
|
15201
|
-
ahead: status.ahead,
|
|
15202
|
-
behind: status.behind,
|
|
15203
|
-
dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
|
|
15204
|
-
stashCount: status.stashCount,
|
|
15205
|
-
submodules: summarizeSubmodules(status.submodules)
|
|
15206
|
-
};
|
|
15207
|
-
}
|
|
15208
|
-
function summarizeSubmodules(submodules) {
|
|
15209
|
-
return (submodules || []).map((submodule) => ({
|
|
15210
|
-
path: submodule.path,
|
|
15211
|
-
commit: submodule.commit,
|
|
15212
|
-
dirty: submodule.dirty,
|
|
15213
|
-
outOfSync: submodule.outOfSync,
|
|
15214
|
-
...submodule.error ? { error: submodule.error } : {}
|
|
15215
|
-
}));
|
|
15216
|
-
}
|
|
15217
|
-
function normalizeOptionalString(value) {
|
|
15218
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
15219
|
-
}
|
|
15220
|
-
function formatGitError2(error) {
|
|
15221
|
-
if (error instanceof GitCommandError) {
|
|
15222
|
-
return error.stderr || error.stdout || error.message;
|
|
15223
|
-
}
|
|
15224
|
-
if (error instanceof Error) return error.message;
|
|
15225
|
-
return String(error);
|
|
15226
|
-
}
|
|
15227
|
-
async function appendFastForwardLedger(result, outcome) {
|
|
15228
|
-
if (!result.meshId) return;
|
|
15229
|
-
try {
|
|
15230
|
-
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
15231
|
-
appendLedgerEntry2(result.meshId, {
|
|
15232
|
-
kind: "direct_fast_forward",
|
|
15233
|
-
...result.nodeId ? { nodeId: result.nodeId } : {},
|
|
15234
|
-
payload: {
|
|
15235
|
-
operation: "mesh_fast_forward_node",
|
|
15236
|
-
outcome,
|
|
15237
|
-
code: result.code,
|
|
15238
|
-
workspace: result.workspace,
|
|
15239
|
-
allowed: result.allowed,
|
|
15240
|
-
dryRun: result.dryRun,
|
|
15241
|
-
willRun: result.willRun,
|
|
15242
|
-
executed: result.executed,
|
|
15243
|
-
branch: result.postStatus?.branch ?? result.current?.branch,
|
|
15244
|
-
upstream: result.postStatus?.upstream ?? result.current?.upstream,
|
|
15245
|
-
before: result.current ? {
|
|
15246
|
-
headCommit: result.current.headCommit,
|
|
15247
|
-
ahead: result.current.ahead,
|
|
15248
|
-
behind: result.current.behind
|
|
15249
|
-
} : void 0,
|
|
15250
|
-
after: result.postStatus ? {
|
|
15251
|
-
headCommit: result.postStatus.headCommit,
|
|
15252
|
-
ahead: result.postStatus.ahead,
|
|
15253
|
-
behind: result.postStatus.behind
|
|
15254
|
-
} : void 0,
|
|
15255
|
-
blockingReasons: result.blockingReasons
|
|
15256
|
-
}
|
|
15257
|
-
});
|
|
15258
|
-
} catch (error) {
|
|
15259
|
-
result.ledgerError = error instanceof Error ? error.message : String(error);
|
|
15260
|
-
}
|
|
15261
|
-
}
|
|
15339
|
+
init_mesh_fast_forward();
|
|
15262
15340
|
|
|
15263
15341
|
// src/mesh/mesh-ledger-reconciliation.ts
|
|
15264
15342
|
function lastTimestamp(slice) {
|
|
@@ -34719,6 +34797,7 @@ function getAvailableIdeIds() {
|
|
|
34719
34797
|
// src/commands/router.ts
|
|
34720
34798
|
init_config();
|
|
34721
34799
|
init_cli_detector();
|
|
34800
|
+
init_git_status();
|
|
34722
34801
|
init_logger();
|
|
34723
34802
|
|
|
34724
34803
|
// src/logging/command-log.ts
|
|
@@ -34871,6 +34950,7 @@ init_logger();
|
|
|
34871
34950
|
init_mesh_coordinator();
|
|
34872
34951
|
init_mesh_events();
|
|
34873
34952
|
init_mesh_host_ownership();
|
|
34953
|
+
init_mesh_fast_forward();
|
|
34874
34954
|
|
|
34875
34955
|
// src/mesh/preview-freshness.ts
|
|
34876
34956
|
var import_node_child_process4 = require("child_process");
|
|
@@ -37525,8 +37605,8 @@ var DaemonCommandRouter = class {
|
|
|
37525
37605
|
}
|
|
37526
37606
|
}
|
|
37527
37607
|
try {
|
|
37528
|
-
const { getMesh:
|
|
37529
|
-
const mesh =
|
|
37608
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
37609
|
+
const mesh = getMesh2(meshId);
|
|
37530
37610
|
if (mesh) return { mesh, inline: false, source: "local_config" };
|
|
37531
37611
|
} catch {
|
|
37532
37612
|
}
|
|
@@ -39451,8 +39531,8 @@ var DaemonCommandRouter = class {
|
|
|
39451
39531
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
39452
39532
|
if (!meshId) return { success: false, error: "meshId required" };
|
|
39453
39533
|
try {
|
|
39454
|
-
const { deleteMesh:
|
|
39455
|
-
const deleted =
|
|
39534
|
+
const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39535
|
+
const deleted = deleteMesh2(meshId);
|
|
39456
39536
|
return { success: true, deleted };
|
|
39457
39537
|
} catch (e) {
|
|
39458
39538
|
return { success: false, error: e.message };
|
|
@@ -39570,7 +39650,7 @@ var DaemonCommandRouter = class {
|
|
|
39570
39650
|
const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
|
|
39571
39651
|
if (ownerFailure) return ownerFailure;
|
|
39572
39652
|
try {
|
|
39573
|
-
const { addNode:
|
|
39653
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39574
39654
|
const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
39575
39655
|
const readOnly = args?.readOnly === true;
|
|
39576
39656
|
const policy = {
|
|
@@ -39581,7 +39661,7 @@ var DaemonCommandRouter = class {
|
|
|
39581
39661
|
const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
|
|
39582
39662
|
const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
|
|
39583
39663
|
const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
|
|
39584
|
-
const node =
|
|
39664
|
+
const node = addNode2(meshId, {
|
|
39585
39665
|
workspace,
|
|
39586
39666
|
...repoRoot ? { repoRoot } : {},
|
|
39587
39667
|
...daemonId ? { daemonId } : {},
|
|
@@ -39772,8 +39852,8 @@ var DaemonCommandRouter = class {
|
|
|
39772
39852
|
if (meshRecord?.inline) {
|
|
39773
39853
|
removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
|
|
39774
39854
|
} else {
|
|
39775
|
-
const { removeNode:
|
|
39776
|
-
removed =
|
|
39855
|
+
const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39856
|
+
removed = removeNode2(meshId, nodeId);
|
|
39777
39857
|
if (removed) this.invalidateAggregateMeshStatus(meshId);
|
|
39778
39858
|
}
|
|
39779
39859
|
if (removed) {
|
|
@@ -39842,8 +39922,8 @@ var DaemonCommandRouter = class {
|
|
|
39842
39922
|
};
|
|
39843
39923
|
this.updateInlineMeshNode(meshId, mesh, node);
|
|
39844
39924
|
} else {
|
|
39845
|
-
const { addNode:
|
|
39846
|
-
node =
|
|
39925
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39926
|
+
node = addNode2(meshId, {
|
|
39847
39927
|
workspace: result.worktreePath,
|
|
39848
39928
|
repoRoot: result.worktreePath,
|
|
39849
39929
|
daemonId: sourceNode.daemonId,
|
|
@@ -39998,8 +40078,8 @@ var DaemonCommandRouter = class {
|
|
|
39998
40078
|
mesh = args.inlineMesh;
|
|
39999
40079
|
this.inlineMeshCache.set(meshId, mesh);
|
|
40000
40080
|
} else {
|
|
40001
|
-
const { getMesh:
|
|
40002
|
-
mesh =
|
|
40081
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
40082
|
+
mesh = getMesh2(meshId);
|
|
40003
40083
|
}
|
|
40004
40084
|
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
40005
40085
|
const meshHost = resolveMeshHostStatus(mesh);
|
|
@@ -49371,6 +49451,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49371
49451
|
buildMeshHostRequiredFailure,
|
|
49372
49452
|
buildMeshLedgerReconciliationEvidence,
|
|
49373
49453
|
buildMeshLedgerReplicaEvidence,
|
|
49454
|
+
buildMeshNodeCapabilityTags,
|
|
49374
49455
|
buildP2pRelayFailurePayload,
|
|
49375
49456
|
buildPinnedGlobalInstallCommand,
|
|
49376
49457
|
buildRuntimeSystemChatMessage,
|
|
@@ -49498,6 +49579,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49498
49579
|
maybeRunDaemonUpgradeHelperFromEnv,
|
|
49499
49580
|
namedKeyToAnsi,
|
|
49500
49581
|
namedKeysToAnsi,
|
|
49582
|
+
nodeSatisfiesRequiredTags,
|
|
49501
49583
|
normalizeActiveChatData,
|
|
49502
49584
|
normalizeChatMessage,
|
|
49503
49585
|
normalizeChatMessageKind,
|
|
@@ -49509,6 +49591,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49509
49591
|
normalizeInteractivePrompt,
|
|
49510
49592
|
normalizeInteractivePromptResponse,
|
|
49511
49593
|
normalizeManagedStatus,
|
|
49594
|
+
normalizeMeshCapabilityTags,
|
|
49512
49595
|
normalizeMeshDaemonRole,
|
|
49513
49596
|
normalizeMeshTaskMode,
|
|
49514
49597
|
normalizeMeshWorkerResult,
|
|
@@ -49567,7 +49650,6 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49567
49650
|
startLocalIpcServer,
|
|
49568
49651
|
suggestMeshRefineConfig,
|
|
49569
49652
|
summarizeGitStatus,
|
|
49570
|
-
syncMeshes,
|
|
49571
49653
|
triggerMeshQueue,
|
|
49572
49654
|
unregisterMeshCoordinator,
|
|
49573
49655
|
updateConfig,
|