@adhdev/daemon-core 0.9.82-rc.172 → 0.9.82-rc.174
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 +843 -696
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +840 -695
- 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/providers/spec/native-history-executor.ts +86 -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;
|
|
@@ -10653,13 +11383,15 @@ function executeJsonl(src, input) {
|
|
|
10653
11383
|
try {
|
|
10654
11384
|
stat2 = fs13.statSync(resolved);
|
|
10655
11385
|
} catch {
|
|
10656
|
-
return null;
|
|
10657
11386
|
}
|
|
10658
|
-
if (stat2.isFile()) {
|
|
11387
|
+
if (stat2 && stat2.isFile()) {
|
|
10659
11388
|
sourcePath = resolved;
|
|
10660
|
-
} else if (stat2.isDirectory()) {
|
|
11389
|
+
} else if (stat2 && stat2.isDirectory()) {
|
|
10661
11390
|
sourcePath = newestRecentFile(resolved, filePat, windowMs, sessionFloor);
|
|
10662
11391
|
}
|
|
11392
|
+
if (!sourcePath && hasDateTemplateSegment(src.path)) {
|
|
11393
|
+
sourcePath = newestRecentFileAcrossDateWindow(src.path, input, filePat, windowMs, sessionFloor);
|
|
11394
|
+
}
|
|
10663
11395
|
}
|
|
10664
11396
|
if (!sourcePath) return null;
|
|
10665
11397
|
const mtime = safeMtimeMs(sourcePath);
|
|
@@ -10882,6 +11614,69 @@ function newestRecentFileAcrossGlob(template, pattern, windowMs, sessionFloorMs
|
|
|
10882
11614
|
}
|
|
10883
11615
|
return best ? best.p : null;
|
|
10884
11616
|
}
|
|
11617
|
+
function hasDateTemplateSegment(template) {
|
|
11618
|
+
return /\{yyyy\}|\{mm\}|\{dd\}/.test(template);
|
|
11619
|
+
}
|
|
11620
|
+
function newestRecentFileAcrossDateWindow(template, input, pattern, windowMs, sessionFloorMs) {
|
|
11621
|
+
const cutoff = Math.max(Date.now() - windowMs, sessionFloorMs);
|
|
11622
|
+
let best = null;
|
|
11623
|
+
for (let dayOffset = 0; dayOffset < 3; dayOffset += 1) {
|
|
11624
|
+
const dayMs = Date.now() - dayOffset * 24 * 60 * 60 * 1e3;
|
|
11625
|
+
const dayInput = { ...input, sessionStartedAtMs: sessionFloorMs };
|
|
11626
|
+
const resolved = expandPathForDate(template, dayInput, new Date(dayMs));
|
|
11627
|
+
if (!resolved) continue;
|
|
11628
|
+
let entries;
|
|
11629
|
+
try {
|
|
11630
|
+
entries = fs13.readdirSync(resolved, { withFileTypes: true });
|
|
11631
|
+
} catch {
|
|
11632
|
+
continue;
|
|
11633
|
+
}
|
|
11634
|
+
for (const e of entries) {
|
|
11635
|
+
if (!e.isFile() || !pattern.test(e.name)) continue;
|
|
11636
|
+
const p = path25.join(resolved, e.name);
|
|
11637
|
+
const mtime = safeMtimeMs(p);
|
|
11638
|
+
if (mtime < cutoff) continue;
|
|
11639
|
+
if (!best || mtime > best.mtime) best = { p, mtime };
|
|
11640
|
+
}
|
|
11641
|
+
}
|
|
11642
|
+
return best ? best.p : null;
|
|
11643
|
+
}
|
|
11644
|
+
function expandPathForDate(template, input, day) {
|
|
11645
|
+
if (!template) return null;
|
|
11646
|
+
let out = template;
|
|
11647
|
+
if (out.startsWith("~/") || out === "~") {
|
|
11648
|
+
out = path25.join(os18.homedir(), out.slice(2));
|
|
11649
|
+
}
|
|
11650
|
+
out = out.replace(/\$\{([A-Z_][A-Z0-9_]*)(?::-(.*?))?\}/g, (_m, name, fallback) => {
|
|
11651
|
+
const v = input.envOverrides?.[name] ?? process.env[name];
|
|
11652
|
+
return v != null && v !== "" ? v : fallback ?? "";
|
|
11653
|
+
});
|
|
11654
|
+
if (out.startsWith("~/")) out = path25.join(os18.homedir(), out.slice(2));
|
|
11655
|
+
const workspaceRaw = input.workspace ?? "";
|
|
11656
|
+
let workspaceResolved = workspaceRaw;
|
|
11657
|
+
if (workspaceRaw) {
|
|
11658
|
+
try {
|
|
11659
|
+
workspaceResolved = fs13.realpathSync(workspaceRaw);
|
|
11660
|
+
} catch {
|
|
11661
|
+
}
|
|
11662
|
+
}
|
|
11663
|
+
const vars = {
|
|
11664
|
+
cwd: workspaceResolved,
|
|
11665
|
+
cwd_dashed: workspaceResolved.replace(/\//g, "-"),
|
|
11666
|
+
session_id: input.providerSessionId || input.sessionId || input.historySessionId || "",
|
|
11667
|
+
yyyy: String(day.getUTCFullYear()),
|
|
11668
|
+
mm: String(day.getUTCMonth() + 1).padStart(2, "0"),
|
|
11669
|
+
dd: String(day.getUTCDate()).padStart(2, "0")
|
|
11670
|
+
};
|
|
11671
|
+
let missing = false;
|
|
11672
|
+
out = out.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_m, name) => {
|
|
11673
|
+
const v = vars[name] ?? "";
|
|
11674
|
+
if (!v) missing = true;
|
|
11675
|
+
return v;
|
|
11676
|
+
});
|
|
11677
|
+
if (missing) return null;
|
|
11678
|
+
return out;
|
|
11679
|
+
}
|
|
10885
11680
|
function newestRecentFile(dir, pattern, windowMs, sessionFloorMs = 0) {
|
|
10886
11681
|
let entries;
|
|
10887
11682
|
try {
|
|
@@ -12274,258 +13069,7 @@ init_repo_mesh_types();
|
|
|
12274
13069
|
|
|
12275
13070
|
// src/git/index.ts
|
|
12276
13071
|
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
|
-
}
|
|
13072
|
+
init_git_status();
|
|
12529
13073
|
|
|
12530
13074
|
// src/git/git-diff.ts
|
|
12531
13075
|
init_git_executor();
|
|
@@ -12941,6 +13485,7 @@ function createGitSnapshotStore(options = {}) {
|
|
|
12941
13485
|
}
|
|
12942
13486
|
|
|
12943
13487
|
// src/git/git-monitor.ts
|
|
13488
|
+
init_git_status();
|
|
12944
13489
|
var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
|
|
12945
13490
|
var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
|
|
12946
13491
|
function defaultStatusProvider(workspace) {
|
|
@@ -13063,6 +13608,7 @@ function createGitWorkspaceMonitor(options = {}) {
|
|
|
13063
13608
|
// src/git/git-commands.ts
|
|
13064
13609
|
import * as path3 from "path";
|
|
13065
13610
|
init_git_executor();
|
|
13611
|
+
init_git_status();
|
|
13066
13612
|
var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
13067
13613
|
"git_status",
|
|
13068
13614
|
"git_diff_summary",
|
|
@@ -14543,414 +15089,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
|
|
|
14543
15089
|
return state;
|
|
14544
15090
|
}
|
|
14545
15091
|
|
|
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
15092
|
// src/index.ts
|
|
14603
15093
|
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
|
-
}
|
|
15094
|
+
init_mesh_fast_forward();
|
|
14954
15095
|
|
|
14955
15096
|
// src/mesh/mesh-ledger-reconciliation.ts
|
|
14956
15097
|
function lastTimestamp(slice) {
|
|
@@ -34416,6 +34557,7 @@ function getAvailableIdeIds() {
|
|
|
34416
34557
|
// src/commands/router.ts
|
|
34417
34558
|
init_config();
|
|
34418
34559
|
init_cli_detector();
|
|
34560
|
+
init_git_status();
|
|
34419
34561
|
init_logger();
|
|
34420
34562
|
|
|
34421
34563
|
// src/logging/command-log.ts
|
|
@@ -34568,6 +34710,7 @@ import * as yaml3 from "js-yaml";
|
|
|
34568
34710
|
init_mesh_coordinator();
|
|
34569
34711
|
init_mesh_events();
|
|
34570
34712
|
init_mesh_host_ownership();
|
|
34713
|
+
init_mesh_fast_forward();
|
|
34571
34714
|
|
|
34572
34715
|
// src/mesh/preview-freshness.ts
|
|
34573
34716
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -37222,8 +37365,8 @@ var DaemonCommandRouter = class {
|
|
|
37222
37365
|
}
|
|
37223
37366
|
}
|
|
37224
37367
|
try {
|
|
37225
|
-
const { getMesh:
|
|
37226
|
-
const mesh =
|
|
37368
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
37369
|
+
const mesh = getMesh2(meshId);
|
|
37227
37370
|
if (mesh) return { mesh, inline: false, source: "local_config" };
|
|
37228
37371
|
} catch {
|
|
37229
37372
|
}
|
|
@@ -39148,8 +39291,8 @@ var DaemonCommandRouter = class {
|
|
|
39148
39291
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
39149
39292
|
if (!meshId) return { success: false, error: "meshId required" };
|
|
39150
39293
|
try {
|
|
39151
|
-
const { deleteMesh:
|
|
39152
|
-
const deleted =
|
|
39294
|
+
const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39295
|
+
const deleted = deleteMesh2(meshId);
|
|
39153
39296
|
return { success: true, deleted };
|
|
39154
39297
|
} catch (e) {
|
|
39155
39298
|
return { success: false, error: e.message };
|
|
@@ -39267,7 +39410,7 @@ var DaemonCommandRouter = class {
|
|
|
39267
39410
|
const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
|
|
39268
39411
|
if (ownerFailure) return ownerFailure;
|
|
39269
39412
|
try {
|
|
39270
|
-
const { addNode:
|
|
39413
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39271
39414
|
const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
39272
39415
|
const readOnly = args?.readOnly === true;
|
|
39273
39416
|
const policy = {
|
|
@@ -39278,7 +39421,7 @@ var DaemonCommandRouter = class {
|
|
|
39278
39421
|
const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
|
|
39279
39422
|
const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
|
|
39280
39423
|
const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
|
|
39281
|
-
const node =
|
|
39424
|
+
const node = addNode2(meshId, {
|
|
39282
39425
|
workspace,
|
|
39283
39426
|
...repoRoot ? { repoRoot } : {},
|
|
39284
39427
|
...daemonId ? { daemonId } : {},
|
|
@@ -39469,8 +39612,8 @@ var DaemonCommandRouter = class {
|
|
|
39469
39612
|
if (meshRecord?.inline) {
|
|
39470
39613
|
removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
|
|
39471
39614
|
} else {
|
|
39472
|
-
const { removeNode:
|
|
39473
|
-
removed =
|
|
39615
|
+
const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39616
|
+
removed = removeNode2(meshId, nodeId);
|
|
39474
39617
|
if (removed) this.invalidateAggregateMeshStatus(meshId);
|
|
39475
39618
|
}
|
|
39476
39619
|
if (removed) {
|
|
@@ -39539,8 +39682,8 @@ var DaemonCommandRouter = class {
|
|
|
39539
39682
|
};
|
|
39540
39683
|
this.updateInlineMeshNode(meshId, mesh, node);
|
|
39541
39684
|
} else {
|
|
39542
|
-
const { addNode:
|
|
39543
|
-
node =
|
|
39685
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39686
|
+
node = addNode2(meshId, {
|
|
39544
39687
|
workspace: result.worktreePath,
|
|
39545
39688
|
repoRoot: result.worktreePath,
|
|
39546
39689
|
daemonId: sourceNode.daemonId,
|
|
@@ -39695,8 +39838,8 @@ var DaemonCommandRouter = class {
|
|
|
39695
39838
|
mesh = args.inlineMesh;
|
|
39696
39839
|
this.inlineMeshCache.set(meshId, mesh);
|
|
39697
39840
|
} else {
|
|
39698
|
-
const { getMesh:
|
|
39699
|
-
mesh =
|
|
39841
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39842
|
+
mesh = getMesh2(meshId);
|
|
39700
39843
|
}
|
|
39701
39844
|
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
39702
39845
|
const meshHost = resolveMeshHostStatus(mesh);
|
|
@@ -49074,6 +49217,7 @@ export {
|
|
|
49074
49217
|
buildMeshHostRequiredFailure,
|
|
49075
49218
|
buildMeshLedgerReconciliationEvidence,
|
|
49076
49219
|
buildMeshLedgerReplicaEvidence,
|
|
49220
|
+
buildMeshNodeCapabilityTags,
|
|
49077
49221
|
buildP2pRelayFailurePayload,
|
|
49078
49222
|
buildPinnedGlobalInstallCommand,
|
|
49079
49223
|
buildRuntimeSystemChatMessage,
|
|
@@ -49201,6 +49345,7 @@ export {
|
|
|
49201
49345
|
maybeRunDaemonUpgradeHelperFromEnv,
|
|
49202
49346
|
namedKeyToAnsi,
|
|
49203
49347
|
namedKeysToAnsi,
|
|
49348
|
+
nodeSatisfiesRequiredTags,
|
|
49204
49349
|
normalizeActiveChatData,
|
|
49205
49350
|
normalizeChatMessage,
|
|
49206
49351
|
normalizeChatMessageKind,
|
|
@@ -49212,6 +49357,7 @@ export {
|
|
|
49212
49357
|
normalizeInteractivePrompt,
|
|
49213
49358
|
normalizeInteractivePromptResponse,
|
|
49214
49359
|
normalizeManagedStatus,
|
|
49360
|
+
normalizeMeshCapabilityTags,
|
|
49215
49361
|
normalizeMeshDaemonRole,
|
|
49216
49362
|
normalizeMeshTaskMode,
|
|
49217
49363
|
normalizeMeshWorkerResult,
|
|
@@ -49270,7 +49416,6 @@ export {
|
|
|
49270
49416
|
startLocalIpcServer,
|
|
49271
49417
|
suggestMeshRefineConfig,
|
|
49272
49418
|
summarizeGitStatus,
|
|
49273
|
-
syncMeshes,
|
|
49274
49419
|
triggerMeshQueue,
|
|
49275
49420
|
unregisterMeshCoordinator,
|
|
49276
49421
|
updateConfig,
|