@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.mjs
CHANGED
|
@@ -270,10 +270,10 @@ function readInjected(value) {
|
|
|
270
270
|
}
|
|
271
271
|
function getDaemonBuildInfo() {
|
|
272
272
|
if (cached) return cached;
|
|
273
|
-
const commit = readInjected(true ? "
|
|
274
|
-
const commitShort = readInjected(true ? "
|
|
275
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
276
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
273
|
+
const commit = readInjected(true ? "6ee0e52b0b7347870577ebe598054d87127078fe" : void 0) ?? "unknown";
|
|
274
|
+
const commitShort = readInjected(true ? "6ee0e52b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
275
|
+
const version = readInjected(true ? "0.9.82-rc.294" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
276
|
+
const builtAt = readInjected(true ? "2026-06-16T12:15:42.290Z" : void 0);
|
|
277
277
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
278
278
|
return cached;
|
|
279
279
|
}
|
|
@@ -344,6 +344,15 @@ async function getGitRepoStatus(workspace, options = {}) {
|
|
|
344
344
|
);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
+
function isNonRuntimeRootFile(file) {
|
|
348
|
+
const base = file.slice(file.lastIndexOf("/") + 1);
|
|
349
|
+
if (/^\.(?:verify|marker|converge|ff-verify|patch-equiv|live-verify)\b/i.test(base)) return true;
|
|
350
|
+
if (/(?:^|\/)docs\//i.test(file)) return true;
|
|
351
|
+
if (/^(?:README|CHANGELOG|LICENSE|NOTICE|AUTHORS|CONTRIBUTING|CODEOWNERS)(?:\.[A-Za-z0-9]+)?$/i.test(base)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
347
356
|
async function classifyDaemonBuildChange(repoPath, buildCommit, options) {
|
|
348
357
|
try {
|
|
349
358
|
const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
|
|
@@ -352,18 +361,18 @@ async function classifyDaemonBuildChange(repoPath, buildCommit, options) {
|
|
|
352
361
|
return { isDaemonAffecting: true, affectedPackages: [] };
|
|
353
362
|
}
|
|
354
363
|
const pkgs = /* @__PURE__ */ new Set();
|
|
355
|
-
let
|
|
364
|
+
let sawRuntimeAmbiguousNonPackage = false;
|
|
356
365
|
for (const file of files) {
|
|
357
366
|
const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
|
|
358
367
|
if (!match) {
|
|
359
|
-
|
|
368
|
+
if (!isNonRuntimeRootFile(file)) sawRuntimeAmbiguousNonPackage = true;
|
|
360
369
|
continue;
|
|
361
370
|
}
|
|
362
371
|
pkgs.add(match[1]);
|
|
363
372
|
}
|
|
364
373
|
const affectedPackages = [...pkgs].sort();
|
|
365
|
-
const
|
|
366
|
-
return { isDaemonAffecting: !
|
|
374
|
+
const allBenign = !sawRuntimeAmbiguousNonPackage && affectedPackages.every((p) => WEB_ONLY_PACKAGES.has(p) && !DAEMON_RUNTIME_PACKAGES.has(p));
|
|
375
|
+
return { isDaemonAffecting: !allBenign, affectedPackages };
|
|
367
376
|
} catch {
|
|
368
377
|
return { isDaemonAffecting: true, affectedPackages: [] };
|
|
369
378
|
}
|
|
@@ -390,7 +399,8 @@ async function detectDaemonBuildBehind(repo, submodules, options) {
|
|
|
390
399
|
options
|
|
391
400
|
);
|
|
392
401
|
const scopeLabel = scope === "root" ? "workspace" : scope;
|
|
393
|
-
const
|
|
402
|
+
const benignDetail = affectedPackages.length > 0 ? `only web packages changed (${affectedPackages.join(", ")})` : "only non-runtime files changed (markers/docs)";
|
|
403
|
+
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.`;
|
|
394
404
|
return {
|
|
395
405
|
buildCommit: build.commit,
|
|
396
406
|
buildCommitShort: build.commitShort,
|
|
@@ -1995,8 +2005,9 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
1995
2005
|
- **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.
|
|
1996
2006
|
- **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.
|
|
1997
2007
|
- **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
|
|
1998
|
-
- **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.
|
|
2008
|
+
- **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).
|
|
1999
2009
|
- **Check history first.** Call \`mesh_task_history\` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.
|
|
2010
|
+
- **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.
|
|
2000
2011
|
- **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\`.
|
|
2001
2012
|
- **Refinery is config-driven.** \`mesh_refine_node\` must run validation from \`.adhdev/refine.{json,yaml,yml}\` or \`repo-mesh.refine.*\`. Heuristics are scaffolding only.
|
|
2002
2013
|
- **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\`.
|
|
@@ -2046,7 +2057,7 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
2046
2057
|
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.
|
|
2047
2058
|
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.
|
|
2048
2059
|
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.
|
|
2049
|
-
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\`.
|
|
2060
|
+
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.
|
|
2050
2061
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
2051
2062
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
2052
2063
|
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.
|
|
@@ -4389,6 +4400,34 @@ var init_mesh_runtime_store = __esm({
|
|
|
4389
4400
|
).get(meshId);
|
|
4390
4401
|
return row?.cnt ?? 0;
|
|
4391
4402
|
}
|
|
4403
|
+
/**
|
|
4404
|
+
* Mark specific pending-event rows drained by id (ack). Used by the
|
|
4405
|
+
* unresolved-delegate durable-forward outbox: an event is peeked (not drained)
|
|
4406
|
+
* while its push to the coordinator is unconfirmed, then marked drained ONLY
|
|
4407
|
+
* after the push is acked. A failed push leaves the row undrained so the next
|
|
4408
|
+
* reconcile tick retries it. Returns the number of rows newly marked drained.
|
|
4409
|
+
*/
|
|
4410
|
+
markPendingEventsDrainedById(ids) {
|
|
4411
|
+
const idList = ids.filter((id) => typeof id === "string" && id.length > 0);
|
|
4412
|
+
if (idList.length === 0) return 0;
|
|
4413
|
+
const now = Date.now();
|
|
4414
|
+
return this.db.prepare(
|
|
4415
|
+
`UPDATE mesh_pending_events SET drained = 1, drained_at = ? WHERE drained = 0 AND id IN (${idList.map(() => "?").join(",")})`
|
|
4416
|
+
).run(now, ...idList).changes;
|
|
4417
|
+
}
|
|
4418
|
+
/**
|
|
4419
|
+
* Hard-delete pending-event rows by id (including the dedup fingerprint history).
|
|
4420
|
+
* Used to expire an unresolved-delegate outbox entry that has exhausted its retry
|
|
4421
|
+
* budget — fully removing it frees the fingerprint so a genuinely new completion
|
|
4422
|
+
* for the same task could be re-queued later. Returns the number of rows deleted.
|
|
4423
|
+
*/
|
|
4424
|
+
deletePendingEventsById(ids) {
|
|
4425
|
+
const idList = ids.filter((id) => typeof id === "string" && id.length > 0);
|
|
4426
|
+
if (idList.length === 0) return 0;
|
|
4427
|
+
return this.db.prepare(
|
|
4428
|
+
`DELETE FROM mesh_pending_events WHERE id IN (${idList.map(() => "?").join(",")})`
|
|
4429
|
+
).run(...idList).changes;
|
|
4430
|
+
}
|
|
4392
4431
|
};
|
|
4393
4432
|
}
|
|
4394
4433
|
});
|
|
@@ -4396,6 +4435,7 @@ var init_mesh_runtime_store = __esm({
|
|
|
4396
4435
|
// src/mesh/mesh-missions.ts
|
|
4397
4436
|
var mesh_missions_exports = {};
|
|
4398
4437
|
__export(mesh_missions_exports, {
|
|
4438
|
+
GOAL_PREVIEW_MAX: () => GOAL_PREVIEW_MAX,
|
|
4399
4439
|
MESH_MISSION_STATUSES: () => MESH_MISSION_STATUSES,
|
|
4400
4440
|
buildMissionPromptSection: () => buildMissionPromptSection,
|
|
4401
4441
|
getActiveMeshMissionSummaries: () => getActiveMeshMissionSummaries,
|
|
@@ -4468,12 +4508,23 @@ function summarizeMeshMission(meshId, mission) {
|
|
|
4468
4508
|
function getActiveMeshMissionSummaries(meshId) {
|
|
4469
4509
|
return getMeshMissions(meshId, ["active"]).map((mission) => summarizeMeshMission(meshId, mission));
|
|
4470
4510
|
}
|
|
4511
|
+
function slimMissionSummary(summary) {
|
|
4512
|
+
const goal = typeof summary.goal === "string" ? summary.goal : "";
|
|
4513
|
+
const goalTruncated = goal.length > GOAL_PREVIEW_MAX;
|
|
4514
|
+
const { goal: _omitGoal, ...rest } = summary;
|
|
4515
|
+
return {
|
|
4516
|
+
...rest,
|
|
4517
|
+
goalPreview: goalTruncated ? goal.slice(0, GOAL_PREVIEW_MAX) : goal,
|
|
4518
|
+
goalTruncated
|
|
4519
|
+
};
|
|
4520
|
+
}
|
|
4471
4521
|
function getMeshStatusMissionSummaries(meshId, options) {
|
|
4472
4522
|
const historyLimit = Math.max(0, options?.historyLimit ?? 10);
|
|
4473
4523
|
const all = getMeshMissions(meshId);
|
|
4474
4524
|
const live = all.filter((m) => m.status === "active" || m.status === "paused");
|
|
4475
4525
|
const history = all.filter((m) => m.status === "completed" || m.status === "abandoned").sort((a, b) => (b.updatedAt || "").localeCompare(a.updatedAt || "")).slice(0, historyLimit);
|
|
4476
|
-
|
|
4526
|
+
const full = [...live, ...history].map((mission) => summarizeMeshMission(meshId, mission));
|
|
4527
|
+
return options?.verbose ? full : full.map(slimMissionSummary);
|
|
4477
4528
|
}
|
|
4478
4529
|
function buildMissionPromptSection(meshId) {
|
|
4479
4530
|
const summaries = getActiveMeshMissionSummaries(meshId);
|
|
@@ -4493,13 +4544,14 @@ function buildMissionPromptSection(meshId) {
|
|
|
4493
4544
|
);
|
|
4494
4545
|
return lines.join("\n");
|
|
4495
4546
|
}
|
|
4496
|
-
var MESH_MISSION_STATUSES;
|
|
4547
|
+
var MESH_MISSION_STATUSES, GOAL_PREVIEW_MAX;
|
|
4497
4548
|
var init_mesh_missions = __esm({
|
|
4498
4549
|
"src/mesh/mesh-missions.ts"() {
|
|
4499
4550
|
"use strict";
|
|
4500
4551
|
init_mesh_runtime_store();
|
|
4501
4552
|
init_mesh_work_queue();
|
|
4502
4553
|
MESH_MISSION_STATUSES = ["active", "paused", "completed", "abandoned"];
|
|
4554
|
+
GOAL_PREVIEW_MAX = 120;
|
|
4503
4555
|
}
|
|
4504
4556
|
});
|
|
4505
4557
|
|
|
@@ -7597,6 +7649,111 @@ var init_mesh_routing = __esm({
|
|
|
7597
7649
|
}
|
|
7598
7650
|
});
|
|
7599
7651
|
|
|
7652
|
+
// src/mesh/mesh-unresolved-forward-outbox.ts
|
|
7653
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
7654
|
+
function getStore() {
|
|
7655
|
+
try {
|
|
7656
|
+
return MeshRuntimeStore.getInstance();
|
|
7657
|
+
} catch {
|
|
7658
|
+
return void 0;
|
|
7659
|
+
}
|
|
7660
|
+
}
|
|
7661
|
+
function enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, forwardPayload) {
|
|
7662
|
+
const target = readNonEmptyString2(coordinatorDaemonId);
|
|
7663
|
+
const event = readNonEmptyString2(eventName);
|
|
7664
|
+
if (!target || !event) return false;
|
|
7665
|
+
const store = getStore();
|
|
7666
|
+
if (!store) return false;
|
|
7667
|
+
const queuedAt = Date.now();
|
|
7668
|
+
const fingerprintSource = {
|
|
7669
|
+
event,
|
|
7670
|
+
meshId: UNRESOLVED_FORWARD_OUTBOX_MESH_ID,
|
|
7671
|
+
nodeLabel: readNonEmptyString2(forwardPayload.nodeId) || readNonEmptyString2(forwardPayload.workspace) || "unresolved-delegate",
|
|
7672
|
+
nodeId: readNonEmptyString2(forwardPayload.nodeId) || void 0,
|
|
7673
|
+
workspace: readNonEmptyString2(forwardPayload.workspace) || void 0,
|
|
7674
|
+
metadataEvent: forwardPayload,
|
|
7675
|
+
queuedAt,
|
|
7676
|
+
targetCoordinatorDaemonId: target
|
|
7677
|
+
};
|
|
7678
|
+
const fingerprint = `${target}::${buildPendingEventFingerprint(fingerprintSource)}`;
|
|
7679
|
+
try {
|
|
7680
|
+
const inserted = store.insertPendingEvent({
|
|
7681
|
+
id: randomUUID9(),
|
|
7682
|
+
meshId: UNRESOLVED_FORWARD_OUTBOX_MESH_ID,
|
|
7683
|
+
coordinatorDaemonId: target,
|
|
7684
|
+
event,
|
|
7685
|
+
// Store the flat forward payload + the queue timestamp so the retry tick can
|
|
7686
|
+
// rebuild the push args and apply age-based expiry without a schema change.
|
|
7687
|
+
payload: { forwardPayload, coordinatorDaemonId: target, queuedAt },
|
|
7688
|
+
fingerprint,
|
|
7689
|
+
queuedAt
|
|
7690
|
+
});
|
|
7691
|
+
if (inserted) {
|
|
7692
|
+
LOG.info("MeshEvents", `Durably queued unresolved-delegate ${event} for coordinator ${target} (outbox)`);
|
|
7693
|
+
}
|
|
7694
|
+
return true;
|
|
7695
|
+
} catch (e) {
|
|
7696
|
+
LOG.warn("MeshEvents", `Failed to persist unresolved-delegate forward to outbox: ${e?.message || e}`);
|
|
7697
|
+
return false;
|
|
7698
|
+
}
|
|
7699
|
+
}
|
|
7700
|
+
function peekUnresolvedDelegateForwards() {
|
|
7701
|
+
const store = getStore();
|
|
7702
|
+
if (!store) return [];
|
|
7703
|
+
let rows;
|
|
7704
|
+
try {
|
|
7705
|
+
rows = store.peekPendingEvents(UNRESOLVED_FORWARD_OUTBOX_MESH_ID);
|
|
7706
|
+
} catch {
|
|
7707
|
+
return [];
|
|
7708
|
+
}
|
|
7709
|
+
const out = [];
|
|
7710
|
+
for (const row of rows) {
|
|
7711
|
+
const stored = row.payload && typeof row.payload === "object" ? row.payload : {};
|
|
7712
|
+
const coordinatorDaemonId = readNonEmptyString2(stored.coordinatorDaemonId);
|
|
7713
|
+
const forwardPayload = stored.forwardPayload && typeof stored.forwardPayload === "object" ? stored.forwardPayload : void 0;
|
|
7714
|
+
if (!coordinatorDaemonId || !forwardPayload) continue;
|
|
7715
|
+
const queuedAt = typeof stored.queuedAt === "number" ? stored.queuedAt : 0;
|
|
7716
|
+
out.push({ id: row.id, coordinatorDaemonId, payload: forwardPayload, queuedAt });
|
|
7717
|
+
}
|
|
7718
|
+
return out;
|
|
7719
|
+
}
|
|
7720
|
+
function ackUnresolvedDelegateForward(id) {
|
|
7721
|
+
const store = getStore();
|
|
7722
|
+
if (!store) return;
|
|
7723
|
+
try {
|
|
7724
|
+
store.markPendingEventsDrainedById([id]);
|
|
7725
|
+
} catch {
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
function expireStaleUnresolvedDelegateForwards(nowMs = Date.now()) {
|
|
7729
|
+
const entries = peekUnresolvedDelegateForwards();
|
|
7730
|
+
const staleIds = entries.filter((e) => e.queuedAt > 0 && nowMs - e.queuedAt >= UNRESOLVED_FORWARD_MAX_AGE_MS).map((e) => e.id);
|
|
7731
|
+
if (staleIds.length === 0) return 0;
|
|
7732
|
+
const store = getStore();
|
|
7733
|
+
if (!store) return 0;
|
|
7734
|
+
try {
|
|
7735
|
+
const removed = store.deletePendingEventsById(staleIds);
|
|
7736
|
+
if (removed > 0) {
|
|
7737
|
+
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`);
|
|
7738
|
+
}
|
|
7739
|
+
return removed;
|
|
7740
|
+
} catch {
|
|
7741
|
+
return 0;
|
|
7742
|
+
}
|
|
7743
|
+
}
|
|
7744
|
+
var UNRESOLVED_FORWARD_OUTBOX_MESH_ID, UNRESOLVED_FORWARD_MAX_AGE_MS;
|
|
7745
|
+
var init_mesh_unresolved_forward_outbox = __esm({
|
|
7746
|
+
"src/mesh/mesh-unresolved-forward-outbox.ts"() {
|
|
7747
|
+
"use strict";
|
|
7748
|
+
init_logger();
|
|
7749
|
+
init_mesh_runtime_store();
|
|
7750
|
+
init_mesh_events_pending();
|
|
7751
|
+
init_mesh_events_utils();
|
|
7752
|
+
UNRESOLVED_FORWARD_OUTBOX_MESH_ID = "__unresolved_forward_outbox__";
|
|
7753
|
+
UNRESOLVED_FORWARD_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
7754
|
+
}
|
|
7755
|
+
});
|
|
7756
|
+
|
|
7600
7757
|
// src/mesh/mesh-events-coordinator.ts
|
|
7601
7758
|
import { existsSync as existsSync14 } from "fs";
|
|
7602
7759
|
function resolveCoordinatorDrainDaemonIds(components) {
|
|
@@ -8732,12 +8889,25 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
|
|
|
8732
8889
|
nodeId: readNonEmptyString2(routing.nodeId) || readNonEmptyString2(event.meshNodeId) || void 0,
|
|
8733
8890
|
workspace: readNonEmptyString2(routing.workspace) || readNonEmptyString2(event.workspace) || void 0
|
|
8734
8891
|
};
|
|
8735
|
-
|
|
8736
|
-
|
|
8892
|
+
const persisted = enqueueUnresolvedDelegateForward(coordinatorDaemonId, eventName, payload);
|
|
8893
|
+
Promise.resolve(components.dispatchMeshCommand(coordinatorDaemonId, "mesh_forward_event", payload)).then((result) => {
|
|
8894
|
+
if (result && result.success === false) {
|
|
8895
|
+
LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued for retry`);
|
|
8896
|
+
return;
|
|
8897
|
+
}
|
|
8898
|
+
if (persisted) ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload);
|
|
8899
|
+
}).catch((e) => {
|
|
8900
|
+
LOG.warn("MeshEvents", `Immediate forward of ${eventName} to coordinator ${coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued for retry`);
|
|
8737
8901
|
});
|
|
8738
|
-
LOG.info("MeshEvents", `
|
|
8902
|
+
LOG.info("MeshEvents", `Durably forwarded ${eventName} for unresolved-mesh worker at ${routing.workspace || "(no workspace)"} to coordinator daemon ${coordinatorDaemonId}`);
|
|
8739
8903
|
return true;
|
|
8740
8904
|
}
|
|
8905
|
+
function ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload) {
|
|
8906
|
+
const match = peekUnresolvedDelegateForwards().find(
|
|
8907
|
+
(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)
|
|
8908
|
+
);
|
|
8909
|
+
if (match) ackUnresolvedDelegateForward(match.id);
|
|
8910
|
+
}
|
|
8741
8911
|
function setupMeshEventForwarding(components) {
|
|
8742
8912
|
components.instanceManager.onEvent((event) => {
|
|
8743
8913
|
if (event.event === "agent:ready" || event.event === "agent:generating_completed") {
|
|
@@ -8818,6 +8988,7 @@ var init_mesh_events_coordinator = __esm({
|
|
|
8818
8988
|
init_mesh_runtime_store();
|
|
8819
8989
|
init_mesh_events_pending();
|
|
8820
8990
|
init_mesh_routing();
|
|
8991
|
+
init_mesh_unresolved_forward_outbox();
|
|
8821
8992
|
init_repo_mesh_types();
|
|
8822
8993
|
init_dist();
|
|
8823
8994
|
init_mesh_events_stale();
|
|
@@ -8933,6 +9104,13 @@ async function runMeshReconcileTick(components) {
|
|
|
8933
9104
|
return void 0;
|
|
8934
9105
|
}
|
|
8935
9106
|
})();
|
|
9107
|
+
if (dispatchMeshCommand) {
|
|
9108
|
+
try {
|
|
9109
|
+
await retryUnresolvedDelegateForwards(components);
|
|
9110
|
+
} catch (e) {
|
|
9111
|
+
LOG.warn("MeshReconcile", `Unresolved-delegate forward retry failed: ${e?.message || e}`);
|
|
9112
|
+
}
|
|
9113
|
+
}
|
|
8936
9114
|
if (dispatchMeshCommand) {
|
|
8937
9115
|
for (const mesh of listMeshes()) {
|
|
8938
9116
|
const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
|
|
@@ -8986,6 +9164,28 @@ async function runMeshReconcileTick(components) {
|
|
|
8986
9164
|
}
|
|
8987
9165
|
}
|
|
8988
9166
|
}
|
|
9167
|
+
async function retryUnresolvedDelegateForwards(components) {
|
|
9168
|
+
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
9169
|
+
if (!dispatchMeshCommand) return;
|
|
9170
|
+
expireStaleUnresolvedDelegateForwards();
|
|
9171
|
+
const entries = peekUnresolvedDelegateForwards();
|
|
9172
|
+
if (entries.length === 0) return;
|
|
9173
|
+
for (const entry of entries) {
|
|
9174
|
+
let result;
|
|
9175
|
+
try {
|
|
9176
|
+
result = await dispatchMeshCommand(entry.coordinatorDaemonId, "mesh_forward_event", entry.payload);
|
|
9177
|
+
} catch (e) {
|
|
9178
|
+
LOG.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} failed: ${e?.message || e} \u2014 left queued`);
|
|
9179
|
+
continue;
|
|
9180
|
+
}
|
|
9181
|
+
if (result && result.success === false) {
|
|
9182
|
+
LOG.warn("MeshReconcile", `Retry forward to coordinator ${entry.coordinatorDaemonId} rejected (${readNonEmptyString2(result.error) || "no reason"}) \u2014 left queued`);
|
|
9183
|
+
continue;
|
|
9184
|
+
}
|
|
9185
|
+
ackUnresolvedDelegateForward(entry.id);
|
|
9186
|
+
LOG.info("MeshReconcile", `Retried+delivered unresolved-delegate ${readNonEmptyString2(entry.payload.event)} to coordinator ${entry.coordinatorDaemonId}`);
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
8989
9189
|
async function pullRemoteNodeQueues(components, mesh, localDaemonId, candidateDaemonIds) {
|
|
8990
9190
|
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
8991
9191
|
if (!dispatchMeshCommand) return;
|
|
@@ -9062,6 +9262,7 @@ var init_mesh_reconcile_loop = __esm({
|
|
|
9062
9262
|
init_mesh_events_pending();
|
|
9063
9263
|
init_mesh_runtime_store();
|
|
9064
9264
|
init_mesh_events_coordinator();
|
|
9265
|
+
init_mesh_unresolved_forward_outbox();
|
|
9065
9266
|
init_mesh_events_utils();
|
|
9066
9267
|
DEFAULT_RECONCILE_INTERVAL_MS = 4e3;
|
|
9067
9268
|
}
|
|
@@ -21371,22 +21572,160 @@ function isBoundedTailRequest(limit, offset, excludeRecentCount) {
|
|
|
21371
21572
|
if (!Number.isFinite(numericOffset) || !Number.isFinite(numericExclude)) return false;
|
|
21372
21573
|
return true;
|
|
21373
21574
|
}
|
|
21575
|
+
var REVERSE_TAIL_SMALL_FILE_BYTES = 64 * 1024;
|
|
21576
|
+
var REVERSE_TAIL_CHUNK_BYTES = 64 * 1024;
|
|
21577
|
+
var TAIL_LINES_RETAINED = BOUNDED_TAIL_MAX_LIMIT + 2 * BOUNDED_TAIL_SLACK;
|
|
21578
|
+
var INCREMENTAL_TAIL_CACHE_MAX_ENTRIES = 64;
|
|
21579
|
+
var incrementalTailCache = /* @__PURE__ */ new Map();
|
|
21580
|
+
function evictIncrementalTailCache() {
|
|
21581
|
+
while (incrementalTailCache.size > INCREMENTAL_TAIL_CACHE_MAX_ENTRIES) {
|
|
21582
|
+
const oldest = incrementalTailCache.keys().next().value;
|
|
21583
|
+
if (oldest === void 0) break;
|
|
21584
|
+
incrementalTailCache.delete(oldest);
|
|
21585
|
+
}
|
|
21586
|
+
}
|
|
21587
|
+
function splitBufferLines(buf) {
|
|
21588
|
+
const lines = [];
|
|
21589
|
+
let lineEnd = buf.length;
|
|
21590
|
+
let firstNewline = -1;
|
|
21591
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
21592
|
+
if (buf[i] !== 10) continue;
|
|
21593
|
+
if (i + 1 < lineEnd) {
|
|
21594
|
+
lines.push(buf.toString("utf-8", i + 1, lineEnd));
|
|
21595
|
+
}
|
|
21596
|
+
lineEnd = i;
|
|
21597
|
+
firstNewline = i;
|
|
21598
|
+
}
|
|
21599
|
+
lines.reverse();
|
|
21600
|
+
const head = firstNewline >= 0 ? buf.subarray(0, firstNewline) : buf;
|
|
21601
|
+
return { head, lines };
|
|
21602
|
+
}
|
|
21603
|
+
function readReverseTailLines(filePath, needed) {
|
|
21604
|
+
const fd = fs5.openSync(filePath, "r");
|
|
21605
|
+
try {
|
|
21606
|
+
const stat2 = fs5.fstatSync(fd);
|
|
21607
|
+
const size = stat2.size;
|
|
21608
|
+
let position = size;
|
|
21609
|
+
let carry = Buffer.alloc(0);
|
|
21610
|
+
const collected = [];
|
|
21611
|
+
while (position > 0 && collected.length < needed) {
|
|
21612
|
+
const chunkSize = Math.min(REVERSE_TAIL_CHUNK_BYTES, position);
|
|
21613
|
+
position -= chunkSize;
|
|
21614
|
+
const chunk = Buffer.alloc(chunkSize);
|
|
21615
|
+
fs5.readSync(fd, chunk, 0, chunkSize, position);
|
|
21616
|
+
const combined = carry.length ? Buffer.concat([chunk, carry]) : chunk;
|
|
21617
|
+
const { head, lines } = splitBufferLines(combined);
|
|
21618
|
+
carry = head;
|
|
21619
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
21620
|
+
collected.push(lines[i]);
|
|
21621
|
+
}
|
|
21622
|
+
}
|
|
21623
|
+
const reachedStart = position <= 0;
|
|
21624
|
+
if (reachedStart && carry.length) {
|
|
21625
|
+
collected.push(carry.toString("utf-8"));
|
|
21626
|
+
}
|
|
21627
|
+
collected.reverse();
|
|
21628
|
+
return { lines: collected, coversWholeFile: reachedStart, size, mtimeMs: stat2.mtimeMs };
|
|
21629
|
+
} finally {
|
|
21630
|
+
fs5.closeSync(fd);
|
|
21631
|
+
}
|
|
21632
|
+
}
|
|
21633
|
+
function readFileTailLines(filePath, needed) {
|
|
21634
|
+
let stat2;
|
|
21635
|
+
try {
|
|
21636
|
+
stat2 = fs5.statSync(filePath);
|
|
21637
|
+
} catch {
|
|
21638
|
+
return { lines: [], coversWholeFile: true };
|
|
21639
|
+
}
|
|
21640
|
+
const size = stat2.size;
|
|
21641
|
+
const mtimeMs = stat2.mtimeMs;
|
|
21642
|
+
if (size === 0) {
|
|
21643
|
+
incrementalTailCache.delete(filePath);
|
|
21644
|
+
return { lines: [], coversWholeFile: true };
|
|
21645
|
+
}
|
|
21646
|
+
const cached2 = incrementalTailCache.get(filePath);
|
|
21647
|
+
if (cached2) {
|
|
21648
|
+
if (cached2.size === size && cached2.mtimeMs === mtimeMs) {
|
|
21649
|
+
incrementalTailCache.delete(filePath);
|
|
21650
|
+
incrementalTailCache.set(filePath, cached2);
|
|
21651
|
+
if (cached2.coversWholeFile || cached2.lines.length >= needed) {
|
|
21652
|
+
return { lines: cached2.lines, coversWholeFile: cached2.coversWholeFile };
|
|
21653
|
+
}
|
|
21654
|
+
} else if (size > cached2.size) {
|
|
21655
|
+
const incremental = tryIncrementalTailGrowth(filePath, cached2, size, mtimeMs, needed);
|
|
21656
|
+
if (incremental) return { lines: incremental.lines, coversWholeFile: incremental.coversWholeFile };
|
|
21657
|
+
}
|
|
21658
|
+
incrementalTailCache.delete(filePath);
|
|
21659
|
+
}
|
|
21660
|
+
if (size <= REVERSE_TAIL_SMALL_FILE_BYTES) {
|
|
21661
|
+
let content;
|
|
21662
|
+
try {
|
|
21663
|
+
content = fs5.readFileSync(filePath, "utf-8");
|
|
21664
|
+
} catch {
|
|
21665
|
+
return { lines: [], coversWholeFile: true };
|
|
21666
|
+
}
|
|
21667
|
+
const lines = content.split("\n");
|
|
21668
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
21669
|
+
storeIncrementalTailCache(filePath, size, mtimeMs, lines, true);
|
|
21670
|
+
return { lines, coversWholeFile: true };
|
|
21671
|
+
}
|
|
21672
|
+
let result;
|
|
21673
|
+
try {
|
|
21674
|
+
result = readReverseTailLines(filePath, needed);
|
|
21675
|
+
} catch {
|
|
21676
|
+
return { lines: [], coversWholeFile: true };
|
|
21677
|
+
}
|
|
21678
|
+
storeIncrementalTailCache(filePath, result.size, result.mtimeMs, result.lines, result.coversWholeFile);
|
|
21679
|
+
return { lines: result.lines, coversWholeFile: result.coversWholeFile };
|
|
21680
|
+
}
|
|
21681
|
+
function tryIncrementalTailGrowth(filePath, cached2, size, mtimeMs, needed) {
|
|
21682
|
+
const fd = fs5.openSync(filePath, "r");
|
|
21683
|
+
try {
|
|
21684
|
+
if (cached2.size > 0) {
|
|
21685
|
+
const boundary = Buffer.alloc(1);
|
|
21686
|
+
fs5.readSync(fd, boundary, 0, 1, cached2.size - 1);
|
|
21687
|
+
if (boundary[0] !== 10) return null;
|
|
21688
|
+
}
|
|
21689
|
+
const appendedLength = size - cached2.size;
|
|
21690
|
+
const appended = Buffer.alloc(appendedLength);
|
|
21691
|
+
fs5.readSync(fd, appended, 0, appendedLength, cached2.size);
|
|
21692
|
+
const newLines = appended.toString("utf-8").split("\n");
|
|
21693
|
+
if (newLines.length && newLines[newLines.length - 1] === "") newLines.pop();
|
|
21694
|
+
const merged = cached2.lines.concat(newLines);
|
|
21695
|
+
const trimmed = merged.length > TAIL_LINES_RETAINED ? merged.slice(merged.length - TAIL_LINES_RETAINED) : merged;
|
|
21696
|
+
const coversWholeFile = cached2.coversWholeFile && trimmed.length === merged.length;
|
|
21697
|
+
storeIncrementalTailCache(filePath, size, mtimeMs, trimmed, coversWholeFile);
|
|
21698
|
+
if (coversWholeFile || trimmed.length >= needed) {
|
|
21699
|
+
return { lines: trimmed, coversWholeFile };
|
|
21700
|
+
}
|
|
21701
|
+
return { lines: trimmed, coversWholeFile };
|
|
21702
|
+
} catch {
|
|
21703
|
+
return null;
|
|
21704
|
+
} finally {
|
|
21705
|
+
fs5.closeSync(fd);
|
|
21706
|
+
}
|
|
21707
|
+
}
|
|
21708
|
+
function storeIncrementalTailCache(filePath, size, mtimeMs, lines, coversWholeFile) {
|
|
21709
|
+
const retained = lines.length > TAIL_LINES_RETAINED ? lines.slice(lines.length - TAIL_LINES_RETAINED) : lines;
|
|
21710
|
+
const covers = coversWholeFile && retained.length === lines.length;
|
|
21711
|
+
incrementalTailCache.delete(filePath);
|
|
21712
|
+
incrementalTailCache.set(filePath, { size, mtimeMs, lines: retained, coversWholeFile: covers });
|
|
21713
|
+
evictIncrementalTailCache();
|
|
21714
|
+
}
|
|
21374
21715
|
function readBoundedTailRecords(agentType, dir, files, needed) {
|
|
21375
21716
|
const collected = [];
|
|
21376
21717
|
const seen = /* @__PURE__ */ new Set();
|
|
21377
21718
|
let readAllFiles = true;
|
|
21378
21719
|
for (let f = 0; f < files.length; f++) {
|
|
21379
21720
|
const filePath = path12.join(dir, files[f]);
|
|
21380
|
-
|
|
21381
|
-
|
|
21382
|
-
|
|
21383
|
-
} catch {
|
|
21384
|
-
continue;
|
|
21385
|
-
}
|
|
21386
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
21721
|
+
const remaining = Math.max(0, needed - collected.length);
|
|
21722
|
+
const perFileNeeded = Math.min(needed, remaining + BOUNDED_TAIL_SLACK);
|
|
21723
|
+
const { lines, coversWholeFile } = readFileTailLines(filePath, perFileNeeded);
|
|
21387
21724
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
21725
|
+
const line = lines[i];
|
|
21726
|
+
if (!line) continue;
|
|
21388
21727
|
try {
|
|
21389
|
-
const parsed = JSON.parse(
|
|
21728
|
+
const parsed = JSON.parse(line);
|
|
21390
21729
|
const sanitizedMessage = sanitizeHistoryMessage(agentType, parsed);
|
|
21391
21730
|
if (!sanitizedMessage) continue;
|
|
21392
21731
|
const hash = buildHistoryMessageHash(agentType, sanitizedMessage);
|
|
@@ -21396,6 +21735,10 @@ function readBoundedTailRecords(agentType, dir, files, needed) {
|
|
|
21396
21735
|
} catch {
|
|
21397
21736
|
}
|
|
21398
21737
|
}
|
|
21738
|
+
if (!coversWholeFile) {
|
|
21739
|
+
readAllFiles = false;
|
|
21740
|
+
break;
|
|
21741
|
+
}
|
|
21399
21742
|
if (collected.length >= needed && f < files.length - 1) {
|
|
21400
21743
|
readAllFiles = false;
|
|
21401
21744
|
break;
|
|
@@ -21999,7 +22342,7 @@ var ExtensionProviderInstance = class {
|
|
|
21999
22342
|
this.runtimeMessages = [];
|
|
22000
22343
|
}
|
|
22001
22344
|
updateSettings(newSettings) {
|
|
22002
|
-
this.settings = { ...newSettings };
|
|
22345
|
+
this.settings = { ...this.settings, ...newSettings };
|
|
22003
22346
|
this.monitor.updateConfig({
|
|
22004
22347
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
22005
22348
|
longGeneratingAlert: this.settings.longGeneratingAlert !== false,
|
|
@@ -22666,7 +23009,7 @@ var IdeProviderInstance = class {
|
|
|
22666
23009
|
this.extensions.clear();
|
|
22667
23010
|
}
|
|
22668
23011
|
updateSettings(newSettings) {
|
|
22669
|
-
this.settings = { ...newSettings };
|
|
23012
|
+
this.settings = { ...this.settings, ...newSettings };
|
|
22670
23013
|
this.monitor.updateConfig({
|
|
22671
23014
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
22672
23015
|
longGeneratingAlert: this.settings.longGeneratingAlert !== false,
|
|
@@ -24145,7 +24488,7 @@ function resolveLegacyProviderScript(fn, scriptName, params) {
|
|
|
24145
24488
|
import * as fs6 from "fs";
|
|
24146
24489
|
import * as os8 from "os";
|
|
24147
24490
|
import * as path13 from "path";
|
|
24148
|
-
import { randomUUID as
|
|
24491
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24149
24492
|
init_logger();
|
|
24150
24493
|
|
|
24151
24494
|
// src/logging/debug-trace.ts
|
|
@@ -25686,7 +26029,7 @@ function safeBundleIdSegment(value, fallback) {
|
|
|
25686
26029
|
function createChatDebugBundleId(targetSessionId) {
|
|
25687
26030
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.]/g, "").replace("T", "T").replace("Z", "Z");
|
|
25688
26031
|
const sessionSegment = safeBundleIdSegment(targetSessionId, "unknown-session");
|
|
25689
|
-
return `chat-debug-${timestamp}-${sessionSegment}-${
|
|
26032
|
+
return `chat-debug-${timestamp}-${sessionSegment}-${randomUUID11().slice(0, 8)}`;
|
|
25690
26033
|
}
|
|
25691
26034
|
function buildChatDebugBundleSummary(bundle) {
|
|
25692
26035
|
const target = bundle.target && typeof bundle.target === "object" ? bundle.target : {};
|
|
@@ -31352,12 +31695,14 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
31352
31695
|
}
|
|
31353
31696
|
this.maybeClearResolvedClaudeTuiPrompt();
|
|
31354
31697
|
this.maybeCaptureClaudeTuiPrompt();
|
|
31698
|
+
this.maybeUpgradeClaudeTuiMultiSelect();
|
|
31355
31699
|
this.statusCallback?.();
|
|
31356
31700
|
return;
|
|
31357
31701
|
case "pty_data":
|
|
31358
31702
|
this.detectInteractivePromptFromPtyChunk(ev.chunk);
|
|
31359
31703
|
this.maybeClearResolvedClaudeTuiPrompt();
|
|
31360
31704
|
this.maybeCaptureClaudeTuiPrompt();
|
|
31705
|
+
this.maybeUpgradeClaudeTuiMultiSelect();
|
|
31361
31706
|
try {
|
|
31362
31707
|
this.ptyDataCallback?.(ev.chunk);
|
|
31363
31708
|
} catch {
|
|
@@ -31506,6 +31851,35 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
31506
31851
|
this.claudeTuiPromptCaptureInFlight = false;
|
|
31507
31852
|
});
|
|
31508
31853
|
}
|
|
31854
|
+
/**
|
|
31855
|
+
* The TUI prompt is captured on the FIRST frame that renders the
|
|
31856
|
+
* "Enter to select" footer. At that instant the option rows' checkbox
|
|
31857
|
+
* column may not have drawn yet, so `detectClaudeTuiMultiSelect` returns
|
|
31858
|
+
* false and the prompt is frozen as single-select — the dashboard then
|
|
31859
|
+
* renders radio buttons even though the picker is multi-select.
|
|
31860
|
+
*
|
|
31861
|
+
* While the same TUI prompt is still on screen, re-check the live snapshot:
|
|
31862
|
+
* if checkbox glyphs have since appeared, promote any single-select
|
|
31863
|
+
* question to multi-select and re-emit status. Promotion is one-way
|
|
31864
|
+
* (false→true only) — once a question is known multi-select we never demote
|
|
31865
|
+
* it, since the glyph column can scroll out of view on later frames.
|
|
31866
|
+
*/
|
|
31867
|
+
maybeUpgradeClaudeTuiMultiSelect() {
|
|
31868
|
+
if (this.cliType !== "claude-cli" || this.interactivePromptTransport !== "tui" || !this.activeInteractivePrompt) return;
|
|
31869
|
+
const questions = this.activeInteractivePrompt.questions;
|
|
31870
|
+
if (questions.length !== 1) return;
|
|
31871
|
+
if (questions[0].multiSelect) return;
|
|
31872
|
+
let screenText = "";
|
|
31873
|
+
try {
|
|
31874
|
+
screenText = this.driver.snapshot();
|
|
31875
|
+
} catch {
|
|
31876
|
+
return;
|
|
31877
|
+
}
|
|
31878
|
+
if (!screenText.includes("Enter to select")) return;
|
|
31879
|
+
if (!detectClaudeTuiMultiSelect(screenText)) return;
|
|
31880
|
+
questions[0].multiSelect = true;
|
|
31881
|
+
this.statusCallback?.();
|
|
31882
|
+
}
|
|
31509
31883
|
readClaudeTuiHeaders(screenText) {
|
|
31510
31884
|
const navLine = screenText.split(/\r?\n/).find((line) => line.includes("\u2714 Submit") && /[☐☒]/.test(line));
|
|
31511
31885
|
if (!navLine) return [];
|
|
@@ -31655,6 +32029,7 @@ function normalizeProviderSessionId(provider, providerSessionId) {
|
|
|
31655
32029
|
}
|
|
31656
32030
|
|
|
31657
32031
|
// src/providers/cli-provider-instance.ts
|
|
32032
|
+
var STATUS_HYDRATION_TAIL_LIMIT = 200;
|
|
31658
32033
|
function isIdleStatus(value) {
|
|
31659
32034
|
const status = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
31660
32035
|
return !status || status === "idle" || status === "ready";
|
|
@@ -32214,22 +32589,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
32214
32589
|
};
|
|
32215
32590
|
}
|
|
32216
32591
|
updateSettings(newSettings) {
|
|
32217
|
-
|
|
32218
|
-
for (const key of [
|
|
32219
|
-
"meshNodeFor",
|
|
32220
|
-
"meshNodeId",
|
|
32221
|
-
"meshActiveTaskId",
|
|
32222
|
-
"meshCoordinatorFor",
|
|
32223
|
-
"meshCoordinatorDaemonId",
|
|
32224
|
-
"meshCoordinatorNodeId",
|
|
32225
|
-
"spawnedSessionVisibility",
|
|
32226
|
-
"launchedByCoordinator"
|
|
32227
|
-
]) {
|
|
32228
|
-
if (this.settings[key] !== void 0 && newSettings[key] === void 0) {
|
|
32229
|
-
runtimeMeshSettings[key] = this.settings[key];
|
|
32230
|
-
}
|
|
32231
|
-
}
|
|
32232
|
-
this.settings = { ...newSettings, ...runtimeMeshSettings };
|
|
32592
|
+
this.settings = { ...this.settings, ...newSettings };
|
|
32233
32593
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
32234
32594
|
this.monitor.updateConfig({
|
|
32235
32595
|
approvalAlert: this.settings.approvalAlert !== false,
|
|
@@ -33332,12 +33692,14 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33332
33692
|
const newestMessageAt = parsedMessages.reduce((newest, message) => Math.max(newest, getMessageTime(message)), 0);
|
|
33333
33693
|
return newestMessageAt === 0;
|
|
33334
33694
|
}
|
|
33335
|
-
syncCanonicalSavedHistoryIfNeeded() {
|
|
33695
|
+
syncCanonicalSavedHistoryIfNeeded(options = {}) {
|
|
33336
33696
|
if (!this.providerSessionId) return false;
|
|
33337
33697
|
const canonicalHistory = this.provider.nativeHistory;
|
|
33338
33698
|
if (!canonicalHistory) return false;
|
|
33699
|
+
const limit = options.full ? Number.MAX_SAFE_INTEGER : STATUS_HYDRATION_TAIL_LIMIT;
|
|
33700
|
+
const windowTag = options.full ? "full" : `tail:${STATUS_HYDRATION_TAIL_LIMIT}`;
|
|
33339
33701
|
if (isNativeSourceCanonicalHistory(canonicalHistory)) {
|
|
33340
|
-
const cacheKey = [this.type, this.providerSessionId, this.workingDir].join("\0");
|
|
33702
|
+
const cacheKey = [this.type, this.providerSessionId, this.workingDir, windowTag].join("\0");
|
|
33341
33703
|
const now = Date.now();
|
|
33342
33704
|
if (cacheKey === this.lastNativeSourceCanonicalCacheKey && now - this.lastNativeSourceCanonicalCheckAt < 2e3) {
|
|
33343
33705
|
return true;
|
|
@@ -33349,7 +33711,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33349
33711
|
historySessionId: this.providerSessionId,
|
|
33350
33712
|
workspace: this.workingDir,
|
|
33351
33713
|
offset: 0,
|
|
33352
|
-
limit
|
|
33714
|
+
limit,
|
|
33353
33715
|
historyBehavior: this.provider.historyBehavior,
|
|
33354
33716
|
scripts: this.provider.scripts
|
|
33355
33717
|
});
|
|
@@ -33365,7 +33727,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33365
33727
|
return true;
|
|
33366
33728
|
}
|
|
33367
33729
|
try {
|
|
33368
|
-
const cacheKey = [this.type, this.providerSessionId, this.workingDir, canonicalHistory.mode || "materialized-mirror"].join("\0");
|
|
33730
|
+
const cacheKey = [this.type, this.providerSessionId, this.workingDir, canonicalHistory.mode || "materialized-mirror", windowTag].join("\0");
|
|
33369
33731
|
const now = Date.now();
|
|
33370
33732
|
if (cacheKey === this.lastNativeSourceCanonicalCacheKey && now - this.lastNativeSourceCanonicalCheckAt < 2e3) {
|
|
33371
33733
|
return true;
|
|
@@ -33375,7 +33737,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33375
33737
|
if (!materializeProviderNativeHistory(this.type, canonicalHistory, this.providerSessionId, this.workingDir, this.provider.scripts)) {
|
|
33376
33738
|
return false;
|
|
33377
33739
|
}
|
|
33378
|
-
const restoredHistory = readChatHistory(this.type, 0,
|
|
33740
|
+
const restoredHistory = readChatHistory(this.type, 0, limit, this.providerSessionId, 0, this.provider.historyBehavior);
|
|
33379
33741
|
this.lastPersistedHistoryMessages = restoredHistory.messages.map((message) => ({
|
|
33380
33742
|
role: message.role,
|
|
33381
33743
|
content: message.content,
|
|
@@ -33390,7 +33752,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
33390
33752
|
}
|
|
33391
33753
|
restorePersistedHistoryFromCurrentSession() {
|
|
33392
33754
|
if (!this.providerSessionId) return;
|
|
33393
|
-
this.syncCanonicalSavedHistoryIfNeeded();
|
|
33755
|
+
this.syncCanonicalSavedHistoryIfNeeded({ full: true });
|
|
33394
33756
|
const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory) ? readProviderChatHistory(this.type, {
|
|
33395
33757
|
canonicalHistory: this.provider.nativeHistory,
|
|
33396
33758
|
historySessionId: this.providerSessionId,
|
|
@@ -46292,9 +46654,9 @@ ${hintLines.join("\n")}` : "",
|
|
|
46292
46654
|
});
|
|
46293
46655
|
let node;
|
|
46294
46656
|
if (meshRecord.inline) {
|
|
46295
|
-
const { randomUUID:
|
|
46657
|
+
const { randomUUID: randomUUID15 } = await import("crypto");
|
|
46296
46658
|
node = {
|
|
46297
|
-
id: `node_${
|
|
46659
|
+
id: `node_${randomUUID15().replace(/-/g, "")}`,
|
|
46298
46660
|
workspace: result.worktreePath,
|
|
46299
46661
|
repoRoot: result.worktreePath,
|
|
46300
46662
|
daemonId: sourceNode.daemonId,
|
|
@@ -47068,10 +47430,11 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47068
47430
|
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
47069
47431
|
const meshHost = resolveMeshHostStatus(mesh);
|
|
47070
47432
|
const refreshRequested = args?.refresh === true || args?.forceRefresh === true;
|
|
47433
|
+
const verboseMissions = args?.verbose === true || args?.compact === false;
|
|
47071
47434
|
const peekScope = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : this.deps.statusInstanceId || void 0;
|
|
47072
47435
|
const pendingCoordinatorEventCount = getPendingMeshCoordinatorEvents(meshId, peekScope).length;
|
|
47073
47436
|
const hadAggregateCache = this.aggregateMeshStatusCache.has(meshId);
|
|
47074
|
-
if (!refreshRequested && pendingCoordinatorEventCount === 0) {
|
|
47437
|
+
if (!refreshRequested && !verboseMissions && pendingCoordinatorEventCount === 0) {
|
|
47075
47438
|
const cachedStatus = this.getCachedAggregateMeshStatus(meshId, mesh, { requireDirectPeerTruth: args?.requireDirectPeerTruth === true });
|
|
47076
47439
|
if (cachedStatus) {
|
|
47077
47440
|
logRepoMeshStatusDebug("return_cached", {
|
|
@@ -47374,7 +47737,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47374
47737
|
liveSessionRecords: liveMeshSessions
|
|
47375
47738
|
});
|
|
47376
47739
|
const { getMeshStatusMissionSummaries: getMeshStatusMissionSummaries2 } = await Promise.resolve().then(() => (init_mesh_missions(), mesh_missions_exports));
|
|
47377
|
-
const missions = getMeshStatusMissionSummaries2(meshId);
|
|
47740
|
+
const missions = getMeshStatusMissionSummaries2(meshId, { verbose: verboseMissions });
|
|
47378
47741
|
const statusResult = {
|
|
47379
47742
|
success: true,
|
|
47380
47743
|
meshId: mesh.id,
|
|
@@ -47428,7 +47791,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47428
47791
|
}))
|
|
47429
47792
|
};
|
|
47430
47793
|
const { pendingCoordinatorEvents: _pendingCoordinatorEvents, unroutableDeliveries: _unroutableDeliveries, ...cacheableStatusResult } = statusResult;
|
|
47431
|
-
const rememberedStatus = this.rememberAggregateMeshStatus(meshId, cacheableStatusResult, refreshReason);
|
|
47794
|
+
const rememberedStatus = verboseMissions ? cacheableStatusResult : this.rememberAggregateMeshStatus(meshId, cacheableStatusResult, refreshReason);
|
|
47432
47795
|
const returnedStatus = {
|
|
47433
47796
|
...rememberedStatus,
|
|
47434
47797
|
...pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {},
|
|
@@ -54984,7 +55347,7 @@ var SessionHostPtyTransportFactory = class {
|
|
|
54984
55347
|
};
|
|
54985
55348
|
|
|
54986
55349
|
// src/cli-adapters/raw-terminal-io.ts
|
|
54987
|
-
import { randomUUID as
|
|
55350
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
54988
55351
|
import {
|
|
54989
55352
|
SessionHostClient as SessionHostClient2
|
|
54990
55353
|
} from "@adhdev/session-host-core";
|
|
@@ -55084,7 +55447,7 @@ var RawTerminalAttachment = class _RawTerminalAttachment {
|
|
|
55084
55447
|
const sessionId = String(options.sessionId || "").trim();
|
|
55085
55448
|
if (!sessionId) throw new Error("sessionId is required");
|
|
55086
55449
|
const mode = options.mode || "read";
|
|
55087
|
-
const clientId = options.clientId || `raw-terminal-${process.pid}-${
|
|
55450
|
+
const clientId = options.clientId || `raw-terminal-${process.pid}-${randomUUID14().slice(0, 8)}`;
|
|
55088
55451
|
const client = options.client || new SessionHostClient2({ endpoint: options.endpoint });
|
|
55089
55452
|
await client.connect();
|
|
55090
55453
|
const attachResponse = await client.request({
|