@adhdev/daemon-core 0.9.82-rc.292 → 0.9.82-rc.294
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +418 -55
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +420 -57
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-missions.d.ts +25 -1
- package/dist/mesh/mesh-runtime-store.d.ts +15 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
- package/dist/providers/spec/cli-adapter.d.ts +14 -0
- package/dist/providers/types/interactive-prompt.d.ts +19 -0
- package/package.json +2 -2
- package/src/commands/router.ts +15 -3
- package/src/config/chat-history.ts +255 -10
- package/src/git/git-status.ts +46 -11
- package/src/mesh/coordinator-prompt.ts +3 -2
- package/src/mesh/mesh-events-coordinator.ts +51 -6
- package/src/mesh/mesh-missions.ts +41 -3
- package/src/mesh/mesh-reconcile-loop.ts +55 -0
- package/src/mesh/mesh-runtime-store.ts +30 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
- package/src/providers/cli-provider-instance.ts +57 -30
- package/src/providers/extension-provider-instance.ts +5 -1
- package/src/providers/ide-provider-instance.ts +6 -1
- package/src/providers/spec/cli-adapter.ts +40 -0
- package/src/providers/types/interactive-prompt.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -275,10 +275,10 @@ function readInjected(value) {
|
|
|
275
275
|
}
|
|
276
276
|
function getDaemonBuildInfo() {
|
|
277
277
|
if (cached) return cached;
|
|
278
|
-
const commit = readInjected(true ? "
|
|
279
|
-
const commitShort = readInjected(true ? "
|
|
280
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
281
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
278
|
+
const commit = readInjected(true ? "6ee0e52b0b7347870577ebe598054d87127078fe" : void 0) ?? "unknown";
|
|
279
|
+
const commitShort = readInjected(true ? "6ee0e52b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
280
|
+
const version = readInjected(true ? "0.9.82-rc.294" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
281
|
+
const builtAt = readInjected(true ? "2026-06-16T12:15:42.290Z" : void 0);
|
|
282
282
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
283
283
|
return cached;
|
|
284
284
|
}
|
|
@@ -349,6 +349,15 @@ async function getGitRepoStatus(workspace, options = {}) {
|
|
|
349
349
|
);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
+
function isNonRuntimeRootFile(file) {
|
|
353
|
+
const base = file.slice(file.lastIndexOf("/") + 1);
|
|
354
|
+
if (/^\.(?:verify|marker|converge|ff-verify|patch-equiv|live-verify)\b/i.test(base)) return true;
|
|
355
|
+
if (/(?:^|\/)docs\//i.test(file)) return true;
|
|
356
|
+
if (/^(?:README|CHANGELOG|LICENSE|NOTICE|AUTHORS|CONTRIBUTING|CODEOWNERS)(?:\.[A-Za-z0-9]+)?$/i.test(base)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
352
361
|
async function classifyDaemonBuildChange(repoPath, buildCommit, options) {
|
|
353
362
|
try {
|
|
354
363
|
const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
|
|
@@ -357,18 +366,18 @@ async function classifyDaemonBuildChange(repoPath, buildCommit, options) {
|
|
|
357
366
|
return { isDaemonAffecting: true, affectedPackages: [] };
|
|
358
367
|
}
|
|
359
368
|
const pkgs = /* @__PURE__ */ new Set();
|
|
360
|
-
let
|
|
369
|
+
let sawRuntimeAmbiguousNonPackage = false;
|
|
361
370
|
for (const file of files) {
|
|
362
371
|
const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
|
|
363
372
|
if (!match) {
|
|
364
|
-
|
|
373
|
+
if (!isNonRuntimeRootFile(file)) sawRuntimeAmbiguousNonPackage = true;
|
|
365
374
|
continue;
|
|
366
375
|
}
|
|
367
376
|
pkgs.add(match[1]);
|
|
368
377
|
}
|
|
369
378
|
const affectedPackages = [...pkgs].sort();
|
|
370
|
-
const
|
|
371
|
-
return { isDaemonAffecting: !
|
|
379
|
+
const allBenign = !sawRuntimeAmbiguousNonPackage && affectedPackages.every((p) => WEB_ONLY_PACKAGES.has(p) && !DAEMON_RUNTIME_PACKAGES.has(p));
|
|
380
|
+
return { isDaemonAffecting: !allBenign, affectedPackages };
|
|
372
381
|
} catch {
|
|
373
382
|
return { isDaemonAffecting: true, affectedPackages: [] };
|
|
374
383
|
}
|
|
@@ -395,7 +404,8 @@ async function detectDaemonBuildBehind(repo, submodules, options) {
|
|
|
395
404
|
options
|
|
396
405
|
);
|
|
397
406
|
const scopeLabel = scope === "root" ? "workspace" : scope;
|
|
398
|
-
const
|
|
407
|
+
const benignDetail = affectedPackages.length > 0 ? `only web packages changed (${affectedPackages.join(", ")})` : "only non-runtime files changed (markers/docs)";
|
|
408
|
+
const warning = isDaemonAffecting ? `Live daemon was built from ${build.commitShort} which is behind ${scopeLabel} HEAD ${head.slice(0, 7)}. Merged code is NOT live until the daemon is rebuilt/redeployed and restarted \u2014 a local dist rebuild alone does not update a cloud daemon.` : `Live daemon was built from ${build.commitShort} which is behind ${scopeLabel} HEAD ${head.slice(0, 7)}, but ${benignDetail}. Daemon restart NOT required \u2014 redeploy the web app to reflect the change.`;
|
|
399
409
|
return {
|
|
400
410
|
buildCommit: build.commit,
|
|
401
411
|
buildCommitShort: build.commitShort,
|
|
@@ -1997,8 +2007,9 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
1997
2007
|
- **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start fresh only for independent work, provider mismatch, transcript contamination, or required worktree isolation.
|
|
1998
2008
|
- **Respect explicit provider requests.** Map: Hermes \u2192 \`hermes-cli\`, Claude/Claude Code \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`, Antigravity \u2192 \`antigravity-cli\`. Never substitute the coordinator's own runtime.
|
|
1999
2009
|
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
2000
|
-
- **Limit parallelism.** Start with 1\u20132 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing.
|
|
2010
|
+
- **Limit parallelism.** Start with 1\u20132 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing. This caps *concurrent* load \u2014 it does not mean serialize independent work: when a new, independent request arrives and there is headroom under \`maxParallelTasks\`, dispatch it right away rather than waiting for an in-flight task or a user nudge (read-only diagnosis especially, since it has no merge cost).
|
|
2001
2011
|
- **Check history first.** Call \`mesh_task_history\` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.
|
|
2012
|
+
- **Sequence shared-base-moving merges.** Parallel dispatch is encouraged, but merging one worktree can advance another in-flight worktree's base \u2014 especially the oss submodule pointer \u2014 turning a clean fast-forward into a diverged rebase (patch-equivalence correctly blocks this). Before merging an in-flight worktree while siblings are also in flight, land in an intentional order, re-clone long-running worktrees from the advanced base, or expect to manually rebase + ff-only the laggards; merging an independent fix mid-flight can strand siblings into a rebase.
|
|
2002
2013
|
- **Converge branches.** After worktree tasks: refine/fast-forward, or classify as \`pushed_feature_branch_needs_merge\` / \`blocked_review\` / \`cleanup_candidate\` / \`not_mergeable\`. Clean up with \`mesh_remove_node\`.
|
|
2003
2014
|
- **Refinery is config-driven.** \`mesh_refine_node\` must run validation from \`.adhdev/refine.{json,yaml,yml}\` or \`repo-mesh.refine.*\`. Heuristics are scaffolding only.
|
|
2004
2015
|
- **Submodule reachability = publish-needed.** \`submodule_reachability_failed\` \u2192 classify as \`blocked_review\`, request user approval to push to submodule main, then rerun \`mesh_refine_node\`.
|
|
@@ -2051,7 +2062,7 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
2051
2062
|
c. **Targeted Tasks**: Use \`mesh_send_task\` only when you need to bypass the queue and force a specific node to execute a task immediately.
|
|
2052
2063
|
d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
|
|
2053
2064
|
e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.
|
|
2054
|
-
4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Do **not** repeatedly call \`mesh_status\` or \`mesh_view_queue\` just to wait for assigned/generating work. After dispatching a direct or queued task, send one progress update with the task/session handle, then stop. Wait for \`pendingCoordinatorEvents\` or another completion/approval/status signal, an explicit user status request, or a real timeout/stall signal before reading status/chat/queue again. Use at most one compact \`mesh_read_chat\` check after a terminal signal. Handle approvals via \`mesh_approve\`.
|
|
2065
|
+
4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Do **not** repeatedly call \`mesh_status\` or \`mesh_view_queue\` just to wait for assigned/generating work. After dispatching a direct or queued task, send one progress update with the task/session handle, then stop. Wait for \`pendingCoordinatorEvents\` or another completion/approval/status signal, an explicit user status request, or a real timeout/stall signal before reading status/chat/queue again. Use at most one compact \`mesh_read_chat\` check after a terminal signal. Handle approvals via \`mesh_approve\`. **Proactively parallelize new work.** When the user reports a new bug or asks for new work, start it immediately if it is independent of in-flight tasks and there is headroom under \`maxParallelTasks\` \u2014 do not wait for a current task to finish or for the user to prompt you to parallelize. Read-only diagnosis (\`live_debug_readonly\`) has no isolation or merge cost, so dispatch it in parallel right away. The no-polling / concurrency-limit rules constrain *re-checking or duplicating already-dispatched work*; they are **not** a reason to defer starting a new, independent task.
|
|
2055
2066
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
2056
2067
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
2057
2068
|
7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from the configured submodule remote main branch, not merely present on a feature ref or local checkout. If \`mesh_refine_node\` returns \`submodule_reachability_failed\` or publish-required evidence, keep the public convergence bucket as \`blocked_review\`; unless \`allowAutoPublishSubmoduleMainCommits\` is explicitly enabled and Refinery reports successful non-force publish plus post-publish verification, ask the user for explicit approval to push/publish the unreachable submodule commit(s) to submodule main, then rerun \`mesh_refine_node\`. Do not merge the root branch until the submodule commit(s) are reachable from submodule origin/main. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
@@ -4395,6 +4406,34 @@ var init_mesh_runtime_store = __esm({
|
|
|
4395
4406
|
).get(meshId);
|
|
4396
4407
|
return row?.cnt ?? 0;
|
|
4397
4408
|
}
|
|
4409
|
+
/**
|
|
4410
|
+
* Mark specific pending-event rows drained by id (ack). Used by the
|
|
4411
|
+
* unresolved-delegate durable-forward outbox: an event is peeked (not drained)
|
|
4412
|
+
* while its push to the coordinator is unconfirmed, then marked drained ONLY
|
|
4413
|
+
* after the push is acked. A failed push leaves the row undrained so the next
|
|
4414
|
+
* reconcile tick retries it. Returns the number of rows newly marked drained.
|
|
4415
|
+
*/
|
|
4416
|
+
markPendingEventsDrainedById(ids) {
|
|
4417
|
+
const idList = ids.filter((id) => typeof id === "string" && id.length > 0);
|
|
4418
|
+
if (idList.length === 0) return 0;
|
|
4419
|
+
const now = Date.now();
|
|
4420
|
+
return this.db.prepare(
|
|
4421
|
+
`UPDATE mesh_pending_events SET drained = 1, drained_at = ? WHERE drained = 0 AND id IN (${idList.map(() => "?").join(",")})`
|
|
4422
|
+
).run(now, ...idList).changes;
|
|
4423
|
+
}
|
|
4424
|
+
/**
|
|
4425
|
+
* Hard-delete pending-event rows by id (including the dedup fingerprint history).
|
|
4426
|
+
* Used to expire an unresolved-delegate outbox entry that has exhausted its retry
|
|
4427
|
+
* budget — fully removing it frees the fingerprint so a genuinely new completion
|
|
4428
|
+
* for the same task could be re-queued later. Returns the number of rows deleted.
|
|
4429
|
+
*/
|
|
4430
|
+
deletePendingEventsById(ids) {
|
|
4431
|
+
const idList = ids.filter((id) => typeof id === "string" && id.length > 0);
|
|
4432
|
+
if (idList.length === 0) return 0;
|
|
4433
|
+
return this.db.prepare(
|
|
4434
|
+
`DELETE FROM mesh_pending_events WHERE id IN (${idList.map(() => "?").join(",")})`
|
|
4435
|
+
).run(...idList).changes;
|
|
4436
|
+
}
|
|
4398
4437
|
};
|
|
4399
4438
|
}
|
|
4400
4439
|
});
|
|
@@ -4402,6 +4441,7 @@ var init_mesh_runtime_store = __esm({
|
|
|
4402
4441
|
// src/mesh/mesh-missions.ts
|
|
4403
4442
|
var mesh_missions_exports = {};
|
|
4404
4443
|
__export(mesh_missions_exports, {
|
|
4444
|
+
GOAL_PREVIEW_MAX: () => GOAL_PREVIEW_MAX,
|
|
4405
4445
|
MESH_MISSION_STATUSES: () => MESH_MISSION_STATUSES,
|
|
4406
4446
|
buildMissionPromptSection: () => buildMissionPromptSection,
|
|
4407
4447
|
getActiveMeshMissionSummaries: () => getActiveMeshMissionSummaries,
|
|
@@ -4473,12 +4513,23 @@ function summarizeMeshMission(meshId, mission) {
|
|
|
4473
4513
|
function getActiveMeshMissionSummaries(meshId) {
|
|
4474
4514
|
return getMeshMissions(meshId, ["active"]).map((mission) => summarizeMeshMission(meshId, mission));
|
|
4475
4515
|
}
|
|
4516
|
+
function slimMissionSummary(summary) {
|
|
4517
|
+
const goal = typeof summary.goal === "string" ? summary.goal : "";
|
|
4518
|
+
const goalTruncated = goal.length > GOAL_PREVIEW_MAX;
|
|
4519
|
+
const { goal: _omitGoal, ...rest } = summary;
|
|
4520
|
+
return {
|
|
4521
|
+
...rest,
|
|
4522
|
+
goalPreview: goalTruncated ? goal.slice(0, GOAL_PREVIEW_MAX) : goal,
|
|
4523
|
+
goalTruncated
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4476
4526
|
function getMeshStatusMissionSummaries(meshId, options) {
|
|
4477
4527
|
const historyLimit = Math.max(0, options?.historyLimit ?? 10);
|
|
4478
4528
|
const all = getMeshMissions(meshId);
|
|
4479
4529
|
const live = all.filter((m) => m.status === "active" || m.status === "paused");
|
|
4480
4530
|
const history = all.filter((m) => m.status === "completed" || m.status === "abandoned").sort((a, b) => (b.updatedAt || "").localeCompare(a.updatedAt || "")).slice(0, historyLimit);
|
|
4481
|
-
|
|
4531
|
+
const full = [...live, ...history].map((mission) => summarizeMeshMission(meshId, mission));
|
|
4532
|
+
return options?.verbose ? full : full.map(slimMissionSummary);
|
|
4482
4533
|
}
|
|
4483
4534
|
function buildMissionPromptSection(meshId) {
|
|
4484
4535
|
const summaries = getActiveMeshMissionSummaries(meshId);
|
|
@@ -4498,7 +4549,7 @@ function buildMissionPromptSection(meshId) {
|
|
|
4498
4549
|
);
|
|
4499
4550
|
return lines.join("\n");
|
|
4500
4551
|
}
|
|
4501
|
-
var import_crypto6, MESH_MISSION_STATUSES;
|
|
4552
|
+
var import_crypto6, MESH_MISSION_STATUSES, GOAL_PREVIEW_MAX;
|
|
4502
4553
|
var init_mesh_missions = __esm({
|
|
4503
4554
|
"src/mesh/mesh-missions.ts"() {
|
|
4504
4555
|
"use strict";
|
|
@@ -4506,6 +4557,7 @@ var init_mesh_missions = __esm({
|
|
|
4506
4557
|
init_mesh_runtime_store();
|
|
4507
4558
|
init_mesh_work_queue();
|
|
4508
4559
|
MESH_MISSION_STATUSES = ["active", "paused", "completed", "abandoned"];
|
|
4560
|
+
GOAL_PREVIEW_MAX = 120;
|
|
4509
4561
|
}
|
|
4510
4562
|
});
|
|
4511
4563
|
|
|
@@ -7604,6 +7656,111 @@ var init_mesh_routing = __esm({
|
|
|
7604
7656
|
}
|
|
7605
7657
|
});
|
|
7606
7658
|
|
|
7659
|
+
// src/mesh/mesh-unresolved-forward-outbox.ts
|
|
7660
|
+
function getStore() {
|
|
7661
|
+
try {
|
|
7662
|
+
return MeshRuntimeStore.getInstance();
|
|
7663
|
+
} catch {
|
|
7664
|
+
return void 0;
|
|
7665
|
+
}
|
|
7666
|
+
}
|
|
7667
|
+
function enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, forwardPayload) {
|
|
7668
|
+
const target = readNonEmptyString2(coordinatorDaemonId);
|
|
7669
|
+
const event = readNonEmptyString2(eventName);
|
|
7670
|
+
if (!target || !event) return false;
|
|
7671
|
+
const store = getStore();
|
|
7672
|
+
if (!store) return false;
|
|
7673
|
+
const queuedAt = Date.now();
|
|
7674
|
+
const fingerprintSource = {
|
|
7675
|
+
event,
|
|
7676
|
+
meshId: UNRESOLVED_FORWARD_OUTBOX_MESH_ID,
|
|
7677
|
+
nodeLabel: readNonEmptyString2(forwardPayload.nodeId) || readNonEmptyString2(forwardPayload.workspace) || "unresolved-delegate",
|
|
7678
|
+
nodeId: readNonEmptyString2(forwardPayload.nodeId) || void 0,
|
|
7679
|
+
workspace: readNonEmptyString2(forwardPayload.workspace) || void 0,
|
|
7680
|
+
metadataEvent: forwardPayload,
|
|
7681
|
+
queuedAt,
|
|
7682
|
+
targetCoordinatorDaemonId: target
|
|
7683
|
+
};
|
|
7684
|
+
const fingerprint = `${target}::${buildPendingEventFingerprint(fingerprintSource)}`;
|
|
7685
|
+
try {
|
|
7686
|
+
const inserted = store.insertPendingEvent({
|
|
7687
|
+
id: (0, import_crypto9.randomUUID)(),
|
|
7688
|
+
meshId: UNRESOLVED_FORWARD_OUTBOX_MESH_ID,
|
|
7689
|
+
coordinatorDaemonId: target,
|
|
7690
|
+
event,
|
|
7691
|
+
// Store the flat forward payload + the queue timestamp so the retry tick can
|
|
7692
|
+
// rebuild the push args and apply age-based expiry without a schema change.
|
|
7693
|
+
payload: { forwardPayload, coordinatorDaemonId: target, queuedAt },
|
|
7694
|
+
fingerprint,
|
|
7695
|
+
queuedAt
|
|
7696
|
+
});
|
|
7697
|
+
if (inserted) {
|
|
7698
|
+
LOG.info("MeshEvents", `Durably queued unresolved-delegate ${event} for coordinator ${target} (outbox)`);
|
|
7699
|
+
}
|
|
7700
|
+
return true;
|
|
7701
|
+
} catch (e) {
|
|
7702
|
+
LOG.warn("MeshEvents", `Failed to persist unresolved-delegate forward to outbox: ${e?.message || e}`);
|
|
7703
|
+
return false;
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
function peekUnresolvedDelegateForwards() {
|
|
7707
|
+
const store = getStore();
|
|
7708
|
+
if (!store) return [];
|
|
7709
|
+
let rows;
|
|
7710
|
+
try {
|
|
7711
|
+
rows = store.peekPendingEvents(UNRESOLVED_FORWARD_OUTBOX_MESH_ID);
|
|
7712
|
+
} catch {
|
|
7713
|
+
return [];
|
|
7714
|
+
}
|
|
7715
|
+
const out = [];
|
|
7716
|
+
for (const row of rows) {
|
|
7717
|
+
const stored = row.payload && typeof row.payload === "object" ? row.payload : {};
|
|
7718
|
+
const coordinatorDaemonId = readNonEmptyString2(stored.coordinatorDaemonId);
|
|
7719
|
+
const forwardPayload = stored.forwardPayload && typeof stored.forwardPayload === "object" ? stored.forwardPayload : void 0;
|
|
7720
|
+
if (!coordinatorDaemonId || !forwardPayload) continue;
|
|
7721
|
+
const queuedAt = typeof stored.queuedAt === "number" ? stored.queuedAt : 0;
|
|
7722
|
+
out.push({ id: row.id, coordinatorDaemonId, payload: forwardPayload, queuedAt });
|
|
7723
|
+
}
|
|
7724
|
+
return out;
|
|
7725
|
+
}
|
|
7726
|
+
function ackUnresolvedDelegateForward(id) {
|
|
7727
|
+
const store = getStore();
|
|
7728
|
+
if (!store) return;
|
|
7729
|
+
try {
|
|
7730
|
+
store.markPendingEventsDrainedById([id]);
|
|
7731
|
+
} catch {
|
|
7732
|
+
}
|
|
7733
|
+
}
|
|
7734
|
+
function expireStaleUnresolvedDelegateForwards(nowMs = Date.now()) {
|
|
7735
|
+
const entries = peekUnresolvedDelegateForwards();
|
|
7736
|
+
const staleIds = entries.filter((e) => e.queuedAt > 0 && nowMs - e.queuedAt >= UNRESOLVED_FORWARD_MAX_AGE_MS).map((e) => e.id);
|
|
7737
|
+
if (staleIds.length === 0) return 0;
|
|
7738
|
+
const store = getStore();
|
|
7739
|
+
if (!store) return 0;
|
|
7740
|
+
try {
|
|
7741
|
+
const removed = store.deletePendingEventsById(staleIds);
|
|
7742
|
+
if (removed > 0) {
|
|
7743
|
+
LOG.warn("MeshEvents", `Expired ${removed} unresolved-delegate forward(s) after ${Math.round(UNRESOLVED_FORWARD_MAX_AGE_MS / 6e4)}m of failed retries \u2014 coordinator unreachable, completion dropped`);
|
|
7744
|
+
}
|
|
7745
|
+
return removed;
|
|
7746
|
+
} catch {
|
|
7747
|
+
return 0;
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
var import_crypto9, UNRESOLVED_FORWARD_OUTBOX_MESH_ID, UNRESOLVED_FORWARD_MAX_AGE_MS;
|
|
7751
|
+
var init_mesh_unresolved_forward_outbox = __esm({
|
|
7752
|
+
"src/mesh/mesh-unresolved-forward-outbox.ts"() {
|
|
7753
|
+
"use strict";
|
|
7754
|
+
import_crypto9 = require("crypto");
|
|
7755
|
+
init_logger();
|
|
7756
|
+
init_mesh_runtime_store();
|
|
7757
|
+
init_mesh_events_pending();
|
|
7758
|
+
init_mesh_events_utils();
|
|
7759
|
+
UNRESOLVED_FORWARD_OUTBOX_MESH_ID = "__unresolved_forward_outbox__";
|
|
7760
|
+
UNRESOLVED_FORWARD_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
7761
|
+
}
|
|
7762
|
+
});
|
|
7763
|
+
|
|
7607
7764
|
// src/mesh/mesh-events-coordinator.ts
|
|
7608
7765
|
function resolveCoordinatorDrainDaemonIds(components) {
|
|
7609
7766
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -8738,12 +8895,25 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
|
|
|
8738
8895
|
nodeId: readNonEmptyString2(routing.nodeId) || readNonEmptyString2(event.meshNodeId) || void 0,
|
|
8739
8896
|
workspace: readNonEmptyString2(routing.workspace) || readNonEmptyString2(event.workspace) || void 0
|
|
8740
8897
|
};
|
|
8741
|
-
|
|
8742
|
-
|
|
8898
|
+
const persisted = enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, payload);
|
|
8899
|
+
Promise.resolve(components.dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
|
|
8900
|
+
if (result && result.success === false) {
|
|
8901
|
+
LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued for retry`);
|
|
8902
|
+
return;
|
|
8903
|
+
}
|
|
8904
|
+
if (persisted) ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload);
|
|
8905
|
+
}).catch((e) => {
|
|
8906
|
+
LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued for retry`);
|
|
8743
8907
|
});
|
|
8744
|
-
LOG.info("MeshEvents", `
|
|
8908
|
+
LOG.info("MeshEvents", `Durably forwarded ${eventName} for unresolved-mesh worker at ${routing.workspace || "(no workspace)"} to coordinator daemon ${coordinatorDaemonId}`);
|
|
8745
8909
|
return true;
|
|
8746
8910
|
}
|
|
8911
|
+
function ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload) {
|
|
8912
|
+
const match = peekUnresolvedDelegateForwards().find(
|
|
8913
|
+
(entry) => entry.coordinatorDaemonId === coordinatorDaemonId && readNonEmptyString2(entry.payload.event) === eventName && readNonEmptyString2(entry.payload.targetSessionId || entry.payload.sessionId || entry.payload.instanceId) === readNonEmptyString2(payload.targetSessionId || payload.sessionId || payload.instanceId) && readNonEmptyString2(entry.payload.workspace) === readNonEmptyString2(payload.workspace)
|
|
8914
|
+
);
|
|
8915
|
+
if (match) ackUnresolvedDelegateForward(match.id);
|
|
8916
|
+
}
|
|
8747
8917
|
function setupMeshEventForwarding(components) {
|
|
8748
8918
|
components.instanceManager.onEvent((event) => {
|
|
8749
8919
|
if (event.event === "agent:ready" || event.event === "agent:generating_completed") {
|
|
@@ -8825,6 +8995,7 @@ var init_mesh_events_coordinator = __esm({
|
|
|
8825
8995
|
init_mesh_runtime_store();
|
|
8826
8996
|
init_mesh_events_pending();
|
|
8827
8997
|
init_mesh_routing();
|
|
8998
|
+
init_mesh_unresolved_forward_outbox();
|
|
8828
8999
|
init_repo_mesh_types();
|
|
8829
9000
|
init_dist();
|
|
8830
9001
|
init_mesh_events_stale();
|
|
@@ -8940,6 +9111,13 @@ async function runMeshReconcileTick(components) {
|
|
|
8940
9111
|
return void 0;
|
|
8941
9112
|
}
|
|
8942
9113
|
})();
|
|
9114
|
+
if (dispatchMeshCommand) {
|
|
9115
|
+
try {
|
|
9116
|
+
await retryUnresolvedDelegateForwards(components);
|
|
9117
|
+
} catch (e) {
|
|
9118
|
+
LOG.warn("MeshReconcile", `Unresolved-delegate forward retry failed: ${e?.message || e}`);
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
8943
9121
|
if (dispatchMeshCommand) {
|
|
8944
9122
|
for (const mesh of listMeshes()) {
|
|
8945
9123
|
const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
|
|
@@ -8993,6 +9171,28 @@ async function runMeshReconcileTick(components) {
|
|
|
8993
9171
|
}
|
|
8994
9172
|
}
|
|
8995
9173
|
}
|
|
9174
|
+
async function retryUnresolvedDelegateForwards(components) {
|
|
9175
|
+
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
9176
|
+
if (!dispatchMeshCommand) return;
|
|
9177
|
+
expireStaleUnresolvedDelegateForwards();
|
|
9178
|
+
const entries = peekUnresolvedDelegateForwards();
|
|
9179
|
+
if (entries.length === 0) return;
|
|
9180
|
+
for (const entry of entries) {
|
|
9181
|
+
let result;
|
|
9182
|
+
try {
|
|
9183
|
+
result = await dispatchMeshCommand(entry.coordinatorDaemonId, "mesh_forward_event", entry.payload);
|
|
9184
|
+
} catch (e) {
|
|
9185
|
+
LOG.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued`);
|
|
9186
|
+
continue;
|
|
9187
|
+
}
|
|
9188
|
+
if (result && result.success === false) {
|
|
9189
|
+
LOG.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued`);
|
|
9190
|
+
continue;
|
|
9191
|
+
}
|
|
9192
|
+
ackUnresolvedDelegateForward(entry.id);
|
|
9193
|
+
LOG.info("MeshReconcile", `Retried+delivered unresolved-delegate ${readNonEmptyString2(entry.payload.event)} to coordinator ${entry.coordinatorDaemonId}`);
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
8996
9196
|
async function pullRemoteNodeQueues(components, mesh, localDaemonId, candidateDaemonIds) {
|
|
8997
9197
|
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
8998
9198
|
if (!dispatchMeshCommand) return;
|
|
@@ -9069,6 +9269,7 @@ var init_mesh_reconcile_loop = __esm({
|
|
|
9069
9269
|
init_mesh_events_pending();
|
|
9070
9270
|
init_mesh_runtime_store();
|
|
9071
9271
|
init_mesh_events_coordinator();
|
|
9272
|
+
init_mesh_unresolved_forward_outbox();
|
|
9072
9273
|
init_mesh_events_utils();
|
|
9073
9274
|
DEFAULT_RECONCILE_INTERVAL_MS = 4e3;
|
|
9074
9275
|
}
|
|
@@ -21712,22 +21913,160 @@ function isBoundedTailRequest(limit, offset, excludeRecentCount) {
|
|
|
21712
21913
|
if (!Number.isFinite(numericOffset) || !Number.isFinite(numericExclude)) return false;
|
|
21713
21914
|
return true;
|
|
21714
21915
|
}
|
|
21916
|
+
var REVERSE_TAIL_SMALL_FILE_BYTES = 64 * 1024;
|
|
21917
|
+
var REVERSE_TAIL_CHUNK_BYTES = 64 * 1024;
|
|
21918
|
+
var TAIL_LINES_RETAINED = BOUNDED_TAIL_MAX_LIMIT + 2 * BOUNDED_TAIL_SLACK;
|
|
21919
|
+
var INCREMENTAL_TAIL_CACHE_MAX_ENTRIES = 64;
|
|
21920
|
+
var incrementalTailCache = /* @__PURE__ */ new Map();
|
|
21921
|
+
function evictIncrementalTailCache() {
|
|
21922
|
+
while (incrementalTailCache.size > INCREMENTAL_TAIL_CACHE_MAX_ENTRIES) {
|
|
21923
|
+
const oldest = incrementalTailCache.keys().next().value;
|
|
21924
|
+
if (oldest === void 0) break;
|
|
21925
|
+
incrementalTailCache.delete(oldest);
|
|
21926
|
+
}
|
|
21927
|
+
}
|
|
21928
|
+
function splitBufferLines(buf) {
|
|
21929
|
+
const lines = [];
|
|
21930
|
+
let lineEnd = buf.length;
|
|
21931
|
+
let firstNewline = -1;
|
|
21932
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
21933
|
+
if (buf[i] !== 10) continue;
|
|
21934
|
+
if (i + 1 < lineEnd) {
|
|
21935
|
+
lines.push(buf.toString("utf-8", i + 1, lineEnd));
|
|
21936
|
+
}
|
|
21937
|
+
lineEnd = i;
|
|
21938
|
+
firstNewline = i;
|
|
21939
|
+
}
|
|
21940
|
+
lines.reverse();
|
|
21941
|
+
const head = firstNewline >= 0 ? buf.subarray(0, firstNewline) : buf;
|
|
21942
|
+
return { head, lines };
|
|
21943
|
+
}
|
|
21944
|
+
function readReverseTailLines(filePath, needed) {
|
|
21945
|
+
const fd = fs5.openSync(filePath, "r");
|
|
21946
|
+
try {
|
|
21947
|
+
const stat2 = fs5.fstatSync(fd);
|
|
21948
|
+
const size = stat2.size;
|
|
21949
|
+
let position = size;
|
|
21950
|
+
let carry = Buffer.alloc(0);
|
|
21951
|
+
const collected = [];
|
|
21952
|
+
while (position > 0 && collected.length < needed) {
|
|
21953
|
+
const chunkSize = Math.min(REVERSE_TAIL_CHUNK_BYTES, position);
|
|
21954
|
+
position -= chunkSize;
|
|
21955
|
+
const chunk = Buffer.alloc(chunkSize);
|
|
21956
|
+
fs5.readSync(fd, chunk, 0, chunkSize, position);
|
|
21957
|
+
const combined = carry.length ? Buffer.concat([chunk, carry]) : chunk;
|
|
21958
|
+
const { head, lines } = splitBufferLines(combined);
|
|
21959
|
+
carry = head;
|
|
21960
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
21961
|
+
collected.push(lines[i]);
|
|
21962
|
+
}
|
|
21963
|
+
}
|
|
21964
|
+
const reachedStart = position <= 0;
|
|
21965
|
+
if (reachedStart && carry.length) {
|
|
21966
|
+
collected.push(carry.toString("utf-8"));
|
|
21967
|
+
}
|
|
21968
|
+
collected.reverse();
|
|
21969
|
+
return { lines: collected, coversWholeFile: reachedStart, size, mtimeMs: stat2.mtimeMs };
|
|
21970
|
+
} finally {
|
|
21971
|
+
fs5.closeSync(fd);
|
|
21972
|
+
}
|
|
21973
|
+
}
|
|
21974
|
+
function readFileTailLines(filePath, needed) {
|
|
21975
|
+
let stat2;
|
|
21976
|
+
try {
|
|
21977
|
+
stat2 = fs5.statSync(filePath);
|
|
21978
|
+
} catch {
|
|
21979
|
+
return { lines: [], coversWholeFile: true };
|
|
21980
|
+
}
|
|
21981
|
+
const size = stat2.size;
|
|
21982
|
+
const mtimeMs = stat2.mtimeMs;
|
|
21983
|
+
if (size === 0) {
|
|
21984
|
+
incrementalTailCache.delete(filePath);
|
|
21985
|
+
return { lines: [], coversWholeFile: true };
|
|
21986
|
+
}
|
|
21987
|
+
const cached2 = incrementalTailCache.get(filePath);
|
|
21988
|
+
if (cached2) {
|
|
21989
|
+
if (cached2.size === size && cached2.mtimeMs === mtimeMs) {
|
|
21990
|
+
incrementalTailCache.delete(filePath);
|
|
21991
|
+
incrementalTailCache.set(filePath, cached2);
|
|
21992
|
+
if (cached2.coversWholeFile || cached2.lines.length >= needed) {
|
|
21993
|
+
return { lines: cached2.lines, coversWholeFile: cached2.coversWholeFile };
|
|
21994
|
+
}
|
|
21995
|
+
} else if (size > cached2.size) {
|
|
21996
|
+
const incremental = tryIncrementalTailGrowth(filePath, cached2, size, mtimeMs, needed);
|
|
21997
|
+
if (incremental) return { lines: incremental.lines, coversWholeFile: incremental.coversWholeFile };
|
|
21998
|
+
}
|
|
21999
|
+
incrementalTailCache.delete(filePath);
|
|
22000
|
+
}
|
|
22001
|
+
if (size <= REVERSE_TAIL_SMALL_FILE_BYTES) {
|
|
22002
|
+
let content;
|
|
22003
|
+
try {
|
|
22004
|
+
content = fs5.readFileSync(filePath, "utf-8");
|
|
22005
|
+
} catch {
|
|
22006
|
+
return { lines: [], coversWholeFile: true };
|
|
22007
|
+
}
|
|
22008
|
+
const lines = content.split("\n");
|
|
22009
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
22010
|
+
storeIncrementalTailCache(filePath, size, mtimeMs, lines, true);
|
|
22011
|
+
return { lines, coversWholeFile: true };
|
|
22012
|
+
}
|
|
22013
|
+
let result;
|
|
22014
|
+
try {
|
|
22015
|
+
result = readReverseTailLines(filePath, needed);
|
|
22016
|
+
} catch {
|
|
22017
|
+
return { lines: [], coversWholeFile: true };
|
|
22018
|
+
}
|
|
22019
|
+
storeIncrementalTailCache(filePath, result.size, result.mtimeMs, result.lines, result.coversWholeFile);
|
|
22020
|
+
return { lines: result.lines, coversWholeFile: result.coversWholeFile };
|
|
22021
|
+
}
|
|
22022
|
+
function tryIncrementalTailGrowth(filePath, cached2, size, mtimeMs, needed) {
|
|
22023
|
+
const fd = fs5.openSync(filePath, "r");
|
|
22024
|
+
try {
|
|
22025
|
+
if (cached2.size > 0) {
|
|
22026
|
+
const boundary = Buffer.alloc(1);
|
|
22027
|
+
fs5.readSync(fd, boundary, 0, 1, cached2.size - 1);
|
|
22028
|
+
if (boundary[0] !== 10) return null;
|
|
22029
|
+
}
|
|
22030
|
+
const appendedLength = size - cached2.size;
|
|
22031
|
+
const appended = Buffer.alloc(appendedLength);
|
|
22032
|
+
fs5.readSync(fd, appended, 0, appendedLength, cached2.size);
|
|
22033
|
+
const newLines = appended.toString("utf-8").split("\n");
|
|
22034
|
+
if (newLines.length && newLines[newLines.length - 1] === "") newLines.pop();
|
|
22035
|
+
const merged = cached2.lines.concat(newLines);
|
|
22036
|
+
const trimmed = merged.length > TAIL_LINES_RETAINED ? merged.slice(merged.length - TAIL_LINES_RETAINED) : merged;
|
|
22037
|
+
const coversWholeFile = cached2.coversWholeFile && trimmed.length === merged.length;
|
|
22038
|
+
storeIncrementalTailCache(filePath, size, mtimeMs, trimmed, coversWholeFile);
|
|
22039
|
+
if (coversWholeFile || trimmed.length >= needed) {
|
|
22040
|
+
return { lines: trimmed, coversWholeFile };
|
|
22041
|
+
}
|
|
22042
|
+
return { lines: trimmed, coversWholeFile };
|
|
22043
|
+
} catch {
|
|
22044
|
+
return null;
|
|
22045
|
+
} finally {
|
|
22046
|
+
fs5.closeSync(fd);
|
|
22047
|
+
}
|
|
22048
|
+
}
|
|
22049
|
+
function storeIncrementalTailCache(filePath, size, mtimeMs, lines, coversWholeFile) {
|
|
22050
|
+
const retained = lines.length > TAIL_LINES_RETAINED ? lines.slice(lines.length - TAIL_LINES_RETAINED) : lines;
|
|
22051
|
+
const covers = coversWholeFile && retained.length === lines.length;
|
|
22052
|
+
incrementalTailCache.delete(filePath);
|
|
22053
|
+
incrementalTailCache.set(filePath, { size, mtimeMs, lines: retained, coversWholeFile: covers });
|
|
22054
|
+
evictIncrementalTailCache();
|
|
22055
|
+
}
|
|
21715
22056
|
function readBoundedTailRecords(agentType, dir, files, needed) {
|
|
21716
22057
|
const collected = [];
|
|
21717
22058
|
const seen = /* @__PURE__ */ new Set();
|
|
21718
22059
|
let readAllFiles = true;
|
|
21719
22060
|
for (let f = 0; f < files.length; f++) {
|
|
21720
22061
|
const filePath = path12.join(dir, files[f]);
|
|
21721
|
-
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
} catch {
|
|
21725
|
-
continue;
|
|
21726
|
-
}
|
|
21727
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
22062
|
+
const remaining = Math.max(0, needed - collected.length);
|
|
22063
|
+
const perFileNeeded = Math.min(needed, remaining + BOUNDED_TAIL_SLACK);
|
|
22064
|
+
const { lines, coversWholeFile } = readFileTailLines(filePath, perFileNeeded);
|
|
21728
22065
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
22066
|
+
const line = lines[i];
|
|
22067
|
+
if (!line) continue;
|
|
21729
22068
|
try {
|
|
21730
|
-
const parsed = JSON.parse(
|
|
22069
|
+
const parsed = JSON.parse(line);
|
|
21731
22070
|
const sanitizedMessage = sanitizeHistoryMessage(agentType, parsed);
|
|
21732
22071
|
if (!sanitizedMessage) continue;
|
|
21733
22072
|
const hash = buildHistoryMessageHash(agentType, sanitizedMessage);
|
|
@@ -21737,6 +22076,10 @@ function readBoundedTailRecords(agentType, dir, files, needed) {
|
|
|
21737
22076
|
} catch {
|
|
21738
22077
|
}
|
|
21739
22078
|
}
|
|
22079
|
+
if (!coversWholeFile) {
|
|
22080
|
+
readAllFiles = false;
|
|
22081
|
+
break;
|
|
22082
|
+
}
|
|
21740
22083
|
if (collected.length >= needed && f < files.length - 1) {
|
|
21741
22084
|
readAllFiles = false;
|
|
21742
22085
|
break;
|
|
@@ -22340,7 +22683,7 @@ var ExtensionProviderInstance = class {
|
|
|
22340
22683
|
this.runtimeMessages = [];
|
|
22341
22684
|
}
|
|
22342
22685
|
updateSettings(newSettings) {
|
|
22343
|
-
this.settings = { ...newSettings };
|
|
22686
|
+
this.settings = { ...this.settings, ...newSettings };
|
|
22344
22687
|
this.monitor.updateConfig({
|
|
22345
22688
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
22346
22689
|
longGeneratingAlert: this.settings.longGeneratingAlert !== false,
|
|
@@ -23007,7 +23350,7 @@ var IdeProviderInstance = class {
|
|
|
23007
23350
|
this.extensions.clear();
|
|
23008
23351
|
}
|
|
23009
23352
|
updateSettings(newSettings) {
|
|
23010
|
-
this.settings = { ...newSettings };
|
|
23353
|
+
this.settings = { ...this.settings, ...newSettings };
|
|
23011
23354
|
this.monitor.updateConfig({
|
|
23012
23355
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
23013
23356
|
longGeneratingAlert: this.settings.longGeneratingAlert !== false,
|
|
@@ -31693,12 +32036,14 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
31693
32036
|
}
|
|
31694
32037
|
this.maybeClearResolvedClaudeTuiPrompt();
|
|
31695
32038
|
this.maybeCaptureClaudeTuiPrompt();
|
|
32039
|
+
this.maybeUpgradeClaudeTuiMultiSelect();
|
|
31696
32040
|
this.statusCallback?.();
|
|
31697
32041
|
return;
|
|
31698
32042
|
case "pty_data":
|
|
31699
32043
|
this.detectInteractivePromptFromPtyChunk(ev.chunk);
|
|
31700
32044
|
this.maybeClearResolvedClaudeTuiPrompt();
|
|
31701
32045
|
this.maybeCaptureClaudeTuiPrompt();
|
|
32046
|
+
this.maybeUpgradeClaudeTuiMultiSelect();
|
|
31702
32047
|
try {
|
|
31703
32048
|
this.ptyDataCallback?.(ev.chunk);
|
|
31704
32049
|
} catch {
|
|
@@ -31847,6 +32192,35 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
31847
32192
|
this.claudeTuiPromptCaptureInFlight = false;
|
|
31848
32193
|
});
|
|
31849
32194
|
}
|
|
32195
|
+
/**
|
|
32196
|
+
* The TUI prompt is captured on the FIRST frame that renders the
|
|
32197
|
+
* "Enter to select" footer. At that instant the option rows' checkbox
|
|
32198
|
+
* column may not have drawn yet, so `detectClaudeTuiMultiSelect` returns
|
|
32199
|
+
* false and the prompt is frozen as single-select — the dashboard then
|
|
32200
|
+
* renders radio buttons even though the picker is multi-select.
|
|
32201
|
+
*
|
|
32202
|
+
* While the same TUI prompt is still on screen, re-check the live snapshot:
|
|
32203
|
+
* if checkbox glyphs have since appeared, promote any single-select
|
|
32204
|
+
* question to multi-select and re-emit status. Promotion is one-way
|
|
32205
|
+
* (false→true only) — once a question is known multi-select we never demote
|
|
32206
|
+
* it, since the glyph column can scroll out of view on later frames.
|
|
32207
|
+
*/
|
|
32208
|
+
maybeUpgradeClaudeTuiMultiSelect() {
|
|
32209
|
+
if (this.cliType !== "claude-cli" || this.interactivePromptTransport !== "tui" || !this.activeInteractivePrompt) return;
|
|
32210
|
+
const questions = this.activeInteractivePrompt.questions;
|
|
32211
|
+
if (questions.length !== 1) return;
|
|
32212
|
+
if (questions[0].multiSelect) return;
|
|
32213
|
+
let screenText = "";
|
|
32214
|
+
try {
|
|
32215
|
+
screenText = this.driver.snapshot();
|
|
32216
|
+
} catch {
|
|
32217
|
+
return;
|
|
32218
|
+
}
|
|
32219
|
+
if (!screenText.includes("Enter to select")) return;
|
|
32220
|
+
if (!detectClaudeTuiMultiSelect(screenText)) return;
|
|
32221
|
+
questions[0].multiSelect = true;
|
|
32222
|
+
this.statusCallback?.();
|
|
32223
|
+
}
|
|
31850
32224
|
readClaudeTuiHeaders(screenText) {
|
|
31851
32225
|
const navLine = screenText.split(/\r?\n/).find((line) => line.includes("\u2714 Submit") && /[☐☒]/.test(line));
|
|
31852
32226
|
if (!navLine) return [];
|
|
@@ -31996,6 +32370,7 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
31996
32370
|
}
|
|
31997
32371
|
|
|
31998
32372
|
// src/providers/cli-provider-instance.ts
|
|
32373
|
+
var STATUS_HYDRATION_TAIL_LIMIT = 200;
|
|
31999
32374
|
function isIdleStatus(value) {
|
|
32000
32375
|
const status = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
32001
32376
|
return !status || status === "idle" || status === "ready";
|
|
@@ -32555,22 +32930,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
32555
32930
|
};
|
|
32556
32931
|
}
|
|
32557
32932
|
updateSettings(newSettings) {
|
|
32558
|
-
|
|
32559
|
-
for (const key of [
|
|
32560
|
-
"meshNodeFor",
|
|
32561
|
-
"meshNodeId",
|
|
32562
|
-
"meshActiveTaskId",
|
|
32563
|
-
"meshCoordinatorFor",
|
|
32564
|
-
"meshCoordinatorDaemonId",
|
|
32565
|
-
"meshCoordinatorNodeId",
|
|
32566
|
-
"spawnedSessionVisibility",
|
|
32567
|
-
"launchedByCoordinator"
|
|
32568
|
-
]) {
|
|
32569
|
-
if (this.settings[key] !== void 0 && newSettings[key] === void 0) {
|
|
32570
|
-
runtimeMeshSettings[key] = this.settings[key];
|
|
32571
|
-
}
|
|
32572
|
-
}
|
|
32573
|
-
this.settings = { ...newSettings, ...runtimeMeshSettings };
|
|
32933
|
+
this.settings = { ...this.settings, ...newSettings };
|
|
32574
32934
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
32575
32935
|
this.monitor.updateConfig({
|
|
32576
32936
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
@@ -33673,12 +34033,14 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33673
34033
|
const newestMessageAt = parsedMessages.reduce((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
33674
34034
|
return newestMessageAt === 0;
|
|
33675
34035
|
}
|
|
33676
|
-
syncCanonicalSavedHistoryIfNeeded() {
|
|
34036
|
+
syncCanonicalSavedHistoryIfNeeded(options = {}) {
|
|
33677
34037
|
if (!this.providerSessionId) return false;
|
|
33678
34038
|
const canonicalHistory = this.provider.nativeHistory;
|
|
33679
34039
|
if (!canonicalHistory) return false;
|
|
34040
|
+
const limit = options.full ? Number.MAX_SAFE_INTEGER : STATUS_HYDRATION_TAIL_LIMIT;
|
|
34041
|
+
const windowTag = options.full ? "full" : `tail:${STATUS_HYDRATION_TAIL_LIMIT}`;
|
|
33680
34042
|
if (isNativeSourceCanonicalHistory(canonicalHistory)) {
|
|
33681
|
-
const cacheKey = [this.type, this.providerSessionId, this.workingDir].join("\0");
|
|
34043
|
+
const cacheKey = [this.type, this.providerSessionId, this.workingDir, windowTag].join("\0");
|
|
33682
34044
|
const now = Date.now();
|
|
33683
34045
|
if (cacheKey === this.lastNativeSourceCanonicalCacheKey && now - this.lastNativeSourceCanonicalCheckAt < 2e3) {
|
|
33684
34046
|
return true;
|
|
@@ -33690,7 +34052,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33690
34052
|
historySessionId: this.providerSessionId,
|
|
33691
34053
|
workspace: this.workingDir,
|
|
33692
34054
|
offset: 0,
|
|
33693
|
-
limit
|
|
34055
|
+
limit,
|
|
33694
34056
|
historyBehavior: this.provider.historyBehavior,
|
|
33695
34057
|
scripts: this.provider.scripts
|
|
33696
34058
|
});
|
|
@@ -33706,7 +34068,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33706
34068
|
return true;
|
|
33707
34069
|
}
|
|
33708
34070
|
try {
|
|
33709
|
-
const cacheKey = [this.type, this.providerSessionId, this.workingDir, canonicalHistory.mode || "materialized-mirror"].join("\0");
|
|
34071
|
+
const cacheKey = [this.type, this.providerSessionId, this.workingDir, canonicalHistory.mode || "materialized-mirror", windowTag].join("\0");
|
|
33710
34072
|
const now = Date.now();
|
|
33711
34073
|
if (cacheKey === this.lastNativeSourceCanonicalCacheKey && now - this.lastNativeSourceCanonicalCheckAt < 2e3) {
|
|
33712
34074
|
return true;
|
|
@@ -33716,7 +34078,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33716
34078
|
if (!materializeProviderNativeHistory(this.type, canonicalHistory, this.providerSessionId, this.workingDir, this.provider.scripts)) {
|
|
33717
34079
|
return false;
|
|
33718
34080
|
}
|
|
33719
|
-
const restoredHistory = readChatHistory(this.type, 0,
|
|
34081
|
+
const restoredHistory = readChatHistory(this.type, 0, limit, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
33720
34082
|
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
33721
34083
|
role: message.role,
|
|
33722
34084
|
content: message.content,
|
|
@@ -33731,7 +34093,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33731
34093
|
}
|
|
33732
34094
|
restorePersistedHistoryFromCurrentSession() {
|
|
33733
34095
|
if (!this.providerSessionId) return;
|
|
33734
|
-
this.syncCanonicalSavedHistoryIfNeeded();
|
|
34096
|
+
this.syncCanonicalSavedHistoryIfNeeded({ full: true });
|
|
33735
34097
|
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory) ? readProviderChatHistory(this.type, {
|
|
33736
34098
|
canonicalHistory: this.provider.nativeHistory,
|
|
33737
34099
|
historySessionId: this.providerSessionId,
|
|
@@ -46628,9 +46990,9 @@ ${hintLines.join("\n")}` : "",
|
|
|
46628
46990
|
});
|
|
46629
46991
|
let node;
|
|
46630
46992
|
if (meshRecord.inline) {
|
|
46631
|
-
const { randomUUID:
|
|
46993
|
+
const { randomUUID: randomUUID15 } = await import("crypto");
|
|
46632
46994
|
node = {
|
|
46633
|
-
id: `node_${
|
|
46995
|
+
id: `node_${randomUUID15().replace(/-/g, "")}`,
|
|
46634
46996
|
workspace: result.worktreePath,
|
|
46635
46997
|
repoRoot: result.worktreePath,
|
|
46636
46998
|
daemonId: sourceNode.daemonId,
|
|
@@ -47404,10 +47766,11 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47404
47766
|
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
47405
47767
|
const meshHost = resolveMeshHostStatus(mesh);
|
|
47406
47768
|
const refreshRequested = args?.refresh === true || args?.forceRefresh === true;
|
|
47769
|
+
const verboseMissions = args?.verbose === true || args?.compact === false;
|
|
47407
47770
|
const peekScope = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : this.deps.statusInstanceId || void 0;
|
|
47408
47771
|
const pendingCoordinatorEventCount = getPendingMeshCoordinatorEvents(meshId, peekScope).length;
|
|
47409
47772
|
const hadAggregateCache = this.aggregateMeshStatusCache.has(meshId);
|
|
47410
|
-
if (!refreshRequested && pendingCoordinatorEventCount === 0) {
|
|
47773
|
+
if (!refreshRequested && !verboseMissions && pendingCoordinatorEventCount === 0) {
|
|
47411
47774
|
const cachedStatus = this.getCachedAggregateMeshStatus(meshId, mesh, { requireDirectPeerTruth: args?.requireDirectPeerTruth === true });
|
|
47412
47775
|
if (cachedStatus) {
|
|
47413
47776
|
logRepoMeshStatusDebug("return_cached", {
|
|
@@ -47710,7 +48073,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47710
48073
|
liveSessionRecords: liveMeshSessions
|
|
47711
48074
|
});
|
|
47712
48075
|
const { getMeshStatusMissionSummaries: getMeshStatusMissionSummaries2 } = await Promise.resolve().then(() => (init_mesh_missions(), mesh_missions_exports));
|
|
47713
|
-
const missions = getMeshStatusMissionSummaries2(meshId);
|
|
48076
|
+
const missions = getMeshStatusMissionSummaries2(meshId, { verbose: verboseMissions });
|
|
47714
48077
|
const statusResult = {
|
|
47715
48078
|
success: true,
|
|
47716
48079
|
meshId: mesh.id,
|
|
@@ -47764,7 +48127,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47764
48127
|
}))
|
|
47765
48128
|
};
|
|
47766
48129
|
const { pendingCoordinatorEvents: _pendingCoordinatorEvents, unroutableDeliveries: _unroutableDeliveries, ...cacheableStatusResult } = statusResult;
|
|
47767
|
-
const rememberedStatus = this.rememberAggregateMeshStatus(meshId, cacheableStatusResult, refreshReason);
|
|
48130
|
+
const rememberedStatus = verboseMissions ? cacheableStatusResult : this.rememberAggregateMeshStatus(meshId, cacheableStatusResult, refreshReason);
|
|
47768
48131
|
const returnedStatus = {
|
|
47769
48132
|
...rememberedStatus,
|
|
47770
48133
|
...pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {},
|
|
@@ -55318,7 +55681,7 @@ var SessionHostPtyTransportFactory = class {
|
|
|
55318
55681
|
};
|
|
55319
55682
|
|
|
55320
55683
|
// src/cli-adapters/raw-terminal-io.ts
|
|
55321
|
-
var
|
|
55684
|
+
var import_crypto10 = require("crypto");
|
|
55322
55685
|
var import_session_host_core10 = require("@adhdev/session-host-core");
|
|
55323
55686
|
var BASE_KEY_SEQUENCES = {
|
|
55324
55687
|
enter: "\r",
|
|
@@ -55416,7 +55779,7 @@ var RawTerminalAttachment = class _RawTerminalAttachment {
|
|
|
55416
55779
|
const sessionId = String(options.sessionId || "").trim();
|
|
55417
55780
|
if (!sessionId) throw new Error("sessionId is required");
|
|
55418
55781
|
const mode = options.mode || "read";
|
|
55419
|
-
const clientId = options.clientId || `raw-terminal-${process.pid}-${(0,
|
|
55782
|
+
const clientId = options.clientId || `raw-terminal-${process.pid}-${(0, import_crypto10.randomUUID)().slice(0, 8)}`;
|
|
55420
55783
|
const client = options.client || new import_session_host_core10.SessionHostClient({ endpoint: options.endpoint });
|
|
55421
55784
|
await client.connect();
|
|
55422
55785
|
const attachResponse = await client.request({
|