@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.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;
|
|
@@ -10655,13 +11385,15 @@ function executeJsonl(src, input) {
|
|
|
10655
11385
|
try {
|
|
10656
11386
|
stat2 = fs13.statSync(resolved);
|
|
10657
11387
|
} catch {
|
|
10658
|
-
return null;
|
|
10659
11388
|
}
|
|
10660
|
-
if (stat2.isFile()) {
|
|
11389
|
+
if (stat2 && stat2.isFile()) {
|
|
10661
11390
|
sourcePath = resolved;
|
|
10662
|
-
} else if (stat2.isDirectory()) {
|
|
11391
|
+
} else if (stat2 && stat2.isDirectory()) {
|
|
10663
11392
|
sourcePath = newestRecentFile(resolved, filePat, windowMs, sessionFloor);
|
|
10664
11393
|
}
|
|
11394
|
+
if (!sourcePath && hasDateTemplateSegment(src.path)) {
|
|
11395
|
+
sourcePath = newestRecentFileAcrossDateWindow(src.path, input, filePat, windowMs, sessionFloor);
|
|
11396
|
+
}
|
|
10665
11397
|
}
|
|
10666
11398
|
if (!sourcePath) return null;
|
|
10667
11399
|
const mtime = safeMtimeMs(sourcePath);
|
|
@@ -10884,6 +11616,69 @@ function newestRecentFileAcrossGlob(template, pattern, windowMs, sessionFloorMs
|
|
|
10884
11616
|
}
|
|
10885
11617
|
return best ? best.p : null;
|
|
10886
11618
|
}
|
|
11619
|
+
function hasDateTemplateSegment(template) {
|
|
11620
|
+
return /\{yyyy\}|\{mm\}|\{dd\}/.test(template);
|
|
11621
|
+
}
|
|
11622
|
+
function newestRecentFileAcrossDateWindow(template, input, pattern, windowMs, sessionFloorMs) {
|
|
11623
|
+
const cutoff = Math.max(Date.now() - windowMs, sessionFloorMs);
|
|
11624
|
+
let best = null;
|
|
11625
|
+
for (let dayOffset = 0; dayOffset < 3; dayOffset += 1) {
|
|
11626
|
+
const dayMs = Date.now() - dayOffset * 24 * 60 * 60 * 1e3;
|
|
11627
|
+
const dayInput = { ...input, sessionStartedAtMs: sessionFloorMs };
|
|
11628
|
+
const resolved = expandPathForDate(template, dayInput, new Date(dayMs));
|
|
11629
|
+
if (!resolved) continue;
|
|
11630
|
+
let entries;
|
|
11631
|
+
try {
|
|
11632
|
+
entries = fs13.readdirSync(resolved, { withFileTypes: true });
|
|
11633
|
+
} catch {
|
|
11634
|
+
continue;
|
|
11635
|
+
}
|
|
11636
|
+
for (const e of entries) {
|
|
11637
|
+
if (!e.isFile() || !pattern.test(e.name)) continue;
|
|
11638
|
+
const p = path25.join(resolved, e.name);
|
|
11639
|
+
const mtime = safeMtimeMs(p);
|
|
11640
|
+
if (mtime < cutoff) continue;
|
|
11641
|
+
if (!best || mtime > best.mtime) best = { p, mtime };
|
|
11642
|
+
}
|
|
11643
|
+
}
|
|
11644
|
+
return best ? best.p : null;
|
|
11645
|
+
}
|
|
11646
|
+
function expandPathForDate(template, input, day) {
|
|
11647
|
+
if (!template) return null;
|
|
11648
|
+
let out = template;
|
|
11649
|
+
if (out.startsWith("~/") || out === "~") {
|
|
11650
|
+
out = path25.join(os18.homedir(), out.slice(2));
|
|
11651
|
+
}
|
|
11652
|
+
out = out.replace(/\$\{([A-Z_][A-Z0-9_]*)(?::-(.*?))?\}/g, (_m, name, fallback) => {
|
|
11653
|
+
const v = input.envOverrides?.[name] ?? process.env[name];
|
|
11654
|
+
return v != null && v !== "" ? v : fallback ?? "";
|
|
11655
|
+
});
|
|
11656
|
+
if (out.startsWith("~/")) out = path25.join(os18.homedir(), out.slice(2));
|
|
11657
|
+
const workspaceRaw = input.workspace ?? "";
|
|
11658
|
+
let workspaceResolved = workspaceRaw;
|
|
11659
|
+
if (workspaceRaw) {
|
|
11660
|
+
try {
|
|
11661
|
+
workspaceResolved = fs13.realpathSync(workspaceRaw);
|
|
11662
|
+
} catch {
|
|
11663
|
+
}
|
|
11664
|
+
}
|
|
11665
|
+
const vars = {
|
|
11666
|
+
cwd: workspaceResolved,
|
|
11667
|
+
cwd_dashed: workspaceResolved.replace(/\//g, "-"),
|
|
11668
|
+
session_id: input.providerSessionId || input.sessionId || input.historySessionId || "",
|
|
11669
|
+
yyyy: String(day.getUTCFullYear()),
|
|
11670
|
+
mm: String(day.getUTCMonth() + 1).padStart(2, "0"),
|
|
11671
|
+
dd: String(day.getUTCDate()).padStart(2, "0")
|
|
11672
|
+
};
|
|
11673
|
+
let missing = false;
|
|
11674
|
+
out = out.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_m, name) => {
|
|
11675
|
+
const v = vars[name] ?? "";
|
|
11676
|
+
if (!v) missing = true;
|
|
11677
|
+
return v;
|
|
11678
|
+
});
|
|
11679
|
+
if (missing) return null;
|
|
11680
|
+
return out;
|
|
11681
|
+
}
|
|
10887
11682
|
function newestRecentFile(dir, pattern, windowMs, sessionFloorMs = 0) {
|
|
10888
11683
|
let entries;
|
|
10889
11684
|
try {
|
|
@@ -12143,6 +12938,7 @@ __export(index_exports, {
|
|
|
12143
12938
|
buildMeshHostRequiredFailure: () => buildMeshHostRequiredFailure,
|
|
12144
12939
|
buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
|
|
12145
12940
|
buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
|
|
12941
|
+
buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
|
|
12146
12942
|
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
12147
12943
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
12148
12944
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
@@ -12270,6 +13066,7 @@ __export(index_exports, {
|
|
|
12270
13066
|
maybeRunDaemonUpgradeHelperFromEnv: () => maybeRunDaemonUpgradeHelperFromEnv,
|
|
12271
13067
|
namedKeyToAnsi: () => namedKeyToAnsi,
|
|
12272
13068
|
namedKeysToAnsi: () => namedKeysToAnsi,
|
|
13069
|
+
nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
|
|
12273
13070
|
normalizeActiveChatData: () => normalizeActiveChatData,
|
|
12274
13071
|
normalizeChatMessage: () => normalizeChatMessage,
|
|
12275
13072
|
normalizeChatMessageKind: () => normalizeChatMessageKind,
|
|
@@ -12281,6 +13078,7 @@ __export(index_exports, {
|
|
|
12281
13078
|
normalizeInteractivePrompt: () => normalizeInteractivePrompt,
|
|
12282
13079
|
normalizeInteractivePromptResponse: () => normalizeInteractivePromptResponse,
|
|
12283
13080
|
normalizeManagedStatus: () => normalizeManagedStatus,
|
|
13081
|
+
normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
|
|
12284
13082
|
normalizeMeshDaemonRole: () => normalizeMeshDaemonRole,
|
|
12285
13083
|
normalizeMeshTaskMode: () => normalizeMeshTaskMode,
|
|
12286
13084
|
normalizeMeshWorkerResult: () => normalizeMeshWorkerResult,
|
|
@@ -12339,7 +13137,6 @@ __export(index_exports, {
|
|
|
12339
13137
|
startLocalIpcServer: () => startLocalIpcServer,
|
|
12340
13138
|
suggestMeshRefineConfig: () => suggestMeshRefineConfig,
|
|
12341
13139
|
summarizeGitStatus: () => summarizeGitStatus,
|
|
12342
|
-
syncMeshes: () => syncMeshes,
|
|
12343
13140
|
triggerMeshQueue: () => triggerMeshQueue,
|
|
12344
13141
|
unregisterMeshCoordinator: () => unregisterMeshCoordinator,
|
|
12345
13142
|
updateConfig: () => updateConfig,
|
|
@@ -12582,258 +13379,7 @@ init_repo_mesh_types();
|
|
|
12582
13379
|
|
|
12583
13380
|
// src/git/index.ts
|
|
12584
13381
|
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
|
-
}
|
|
13382
|
+
init_git_status();
|
|
12837
13383
|
|
|
12838
13384
|
// src/git/git-diff.ts
|
|
12839
13385
|
var import_promises2 = require("fs/promises");
|
|
@@ -13249,6 +13795,7 @@ function createGitSnapshotStore(options = {}) {
|
|
|
13249
13795
|
}
|
|
13250
13796
|
|
|
13251
13797
|
// src/git/git-monitor.ts
|
|
13798
|
+
init_git_status();
|
|
13252
13799
|
var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
|
|
13253
13800
|
var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
|
|
13254
13801
|
function defaultStatusProvider(workspace) {
|
|
@@ -13371,6 +13918,7 @@ function createGitWorkspaceMonitor(options = {}) {
|
|
|
13371
13918
|
// src/git/git-commands.ts
|
|
13372
13919
|
var path3 = __toESM(require("path"));
|
|
13373
13920
|
init_git_executor();
|
|
13921
|
+
init_git_status();
|
|
13374
13922
|
var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
13375
13923
|
"git_status",
|
|
13376
13924
|
"git_diff_summary",
|
|
@@ -14851,414 +15399,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
|
|
|
14851
15399
|
return state;
|
|
14852
15400
|
}
|
|
14853
15401
|
|
|
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
15402
|
// src/index.ts
|
|
14911
15403
|
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
|
-
}
|
|
15404
|
+
init_mesh_fast_forward();
|
|
15262
15405
|
|
|
15263
15406
|
// src/mesh/mesh-ledger-reconciliation.ts
|
|
15264
15407
|
function lastTimestamp(slice) {
|
|
@@ -34719,6 +34862,7 @@ function getAvailableIdeIds() {
|
|
|
34719
34862
|
// src/commands/router.ts
|
|
34720
34863
|
init_config();
|
|
34721
34864
|
init_cli_detector();
|
|
34865
|
+
init_git_status();
|
|
34722
34866
|
init_logger();
|
|
34723
34867
|
|
|
34724
34868
|
// src/logging/command-log.ts
|
|
@@ -34871,6 +35015,7 @@ init_logger();
|
|
|
34871
35015
|
init_mesh_coordinator();
|
|
34872
35016
|
init_mesh_events();
|
|
34873
35017
|
init_mesh_host_ownership();
|
|
35018
|
+
init_mesh_fast_forward();
|
|
34874
35019
|
|
|
34875
35020
|
// src/mesh/preview-freshness.ts
|
|
34876
35021
|
var import_node_child_process4 = require("child_process");
|
|
@@ -37525,8 +37670,8 @@ var DaemonCommandRouter = class {
|
|
|
37525
37670
|
}
|
|
37526
37671
|
}
|
|
37527
37672
|
try {
|
|
37528
|
-
const { getMesh:
|
|
37529
|
-
const mesh =
|
|
37673
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
37674
|
+
const mesh = getMesh2(meshId);
|
|
37530
37675
|
if (mesh) return { mesh, inline: false, source: "local_config" };
|
|
37531
37676
|
} catch {
|
|
37532
37677
|
}
|
|
@@ -39451,8 +39596,8 @@ var DaemonCommandRouter = class {
|
|
|
39451
39596
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
39452
39597
|
if (!meshId) return { success: false, error: "meshId required" };
|
|
39453
39598
|
try {
|
|
39454
|
-
const { deleteMesh:
|
|
39455
|
-
const deleted =
|
|
39599
|
+
const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39600
|
+
const deleted = deleteMesh2(meshId);
|
|
39456
39601
|
return { success: true, deleted };
|
|
39457
39602
|
} catch (e) {
|
|
39458
39603
|
return { success: false, error: e.message };
|
|
@@ -39570,7 +39715,7 @@ var DaemonCommandRouter = class {
|
|
|
39570
39715
|
const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
|
|
39571
39716
|
if (ownerFailure) return ownerFailure;
|
|
39572
39717
|
try {
|
|
39573
|
-
const { addNode:
|
|
39718
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39574
39719
|
const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
39575
39720
|
const readOnly = args?.readOnly === true;
|
|
39576
39721
|
const policy = {
|
|
@@ -39581,7 +39726,7 @@ var DaemonCommandRouter = class {
|
|
|
39581
39726
|
const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
|
|
39582
39727
|
const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
|
|
39583
39728
|
const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
|
|
39584
|
-
const node =
|
|
39729
|
+
const node = addNode2(meshId, {
|
|
39585
39730
|
workspace,
|
|
39586
39731
|
...repoRoot ? { repoRoot } : {},
|
|
39587
39732
|
...daemonId ? { daemonId } : {},
|
|
@@ -39772,8 +39917,8 @@ var DaemonCommandRouter = class {
|
|
|
39772
39917
|
if (meshRecord?.inline) {
|
|
39773
39918
|
removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
|
|
39774
39919
|
} else {
|
|
39775
|
-
const { removeNode:
|
|
39776
|
-
removed =
|
|
39920
|
+
const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39921
|
+
removed = removeNode2(meshId, nodeId);
|
|
39777
39922
|
if (removed) this.invalidateAggregateMeshStatus(meshId);
|
|
39778
39923
|
}
|
|
39779
39924
|
if (removed) {
|
|
@@ -39842,8 +39987,8 @@ var DaemonCommandRouter = class {
|
|
|
39842
39987
|
};
|
|
39843
39988
|
this.updateInlineMeshNode(meshId, mesh, node);
|
|
39844
39989
|
} else {
|
|
39845
|
-
const { addNode:
|
|
39846
|
-
node =
|
|
39990
|
+
const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
39991
|
+
node = addNode2(meshId, {
|
|
39847
39992
|
workspace: result.worktreePath,
|
|
39848
39993
|
repoRoot: result.worktreePath,
|
|
39849
39994
|
daemonId: sourceNode.daemonId,
|
|
@@ -39998,8 +40143,8 @@ var DaemonCommandRouter = class {
|
|
|
39998
40143
|
mesh = args.inlineMesh;
|
|
39999
40144
|
this.inlineMeshCache.set(meshId, mesh);
|
|
40000
40145
|
} else {
|
|
40001
|
-
const { getMesh:
|
|
40002
|
-
mesh =
|
|
40146
|
+
const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
40147
|
+
mesh = getMesh2(meshId);
|
|
40003
40148
|
}
|
|
40004
40149
|
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
40005
40150
|
const meshHost = resolveMeshHostStatus(mesh);
|
|
@@ -49371,6 +49516,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49371
49516
|
buildMeshHostRequiredFailure,
|
|
49372
49517
|
buildMeshLedgerReconciliationEvidence,
|
|
49373
49518
|
buildMeshLedgerReplicaEvidence,
|
|
49519
|
+
buildMeshNodeCapabilityTags,
|
|
49374
49520
|
buildP2pRelayFailurePayload,
|
|
49375
49521
|
buildPinnedGlobalInstallCommand,
|
|
49376
49522
|
buildRuntimeSystemChatMessage,
|
|
@@ -49498,6 +49644,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49498
49644
|
maybeRunDaemonUpgradeHelperFromEnv,
|
|
49499
49645
|
namedKeyToAnsi,
|
|
49500
49646
|
namedKeysToAnsi,
|
|
49647
|
+
nodeSatisfiesRequiredTags,
|
|
49501
49648
|
normalizeActiveChatData,
|
|
49502
49649
|
normalizeChatMessage,
|
|
49503
49650
|
normalizeChatMessageKind,
|
|
@@ -49509,6 +49656,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49509
49656
|
normalizeInteractivePrompt,
|
|
49510
49657
|
normalizeInteractivePromptResponse,
|
|
49511
49658
|
normalizeManagedStatus,
|
|
49659
|
+
normalizeMeshCapabilityTags,
|
|
49512
49660
|
normalizeMeshDaemonRole,
|
|
49513
49661
|
normalizeMeshTaskMode,
|
|
49514
49662
|
normalizeMeshWorkerResult,
|
|
@@ -49567,7 +49715,6 @@ var V1_CONTRACT_VERSION = "1.0.0";
|
|
|
49567
49715
|
startLocalIpcServer,
|
|
49568
49716
|
suggestMeshRefineConfig,
|
|
49569
49717
|
summarizeGitStatus,
|
|
49570
|
-
syncMeshes,
|
|
49571
49718
|
triggerMeshQueue,
|
|
49572
49719
|
unregisterMeshCoordinator,
|
|
49573
49720
|
updateConfig,
|