@adhdev/daemon-core 0.9.82-rc.211 → 0.9.82-rc.213

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.mjs CHANGED
@@ -40,6 +40,7 @@ var init_repo_mesh_types = __esm({
40
40
  maxParallelTasks: 2,
41
41
  spawnedSessionVisibility: "visible",
42
42
  sessionCleanupOnNodeRemove: "preserve",
43
+ autoFastForward: { enabled: true },
43
44
  maxTaskRetries: 1
44
45
  };
45
46
  }
@@ -1357,7 +1358,17 @@ function normalizeRepoIdentity(remoteUrl) {
1357
1358
  return identity;
1358
1359
  }
1359
1360
  function mergeMeshPolicy(base, patch) {
1360
- const policy = { ...DEFAULT_MESH_POLICY, ...base || {}, ...patch || {} };
1361
+ const autoFastForward = normalizeAutoFastForwardPolicy({
1362
+ ...DEFAULT_MESH_POLICY.autoFastForward,
1363
+ ...base?.autoFastForward && typeof base.autoFastForward === "object" ? base.autoFastForward : {},
1364
+ ...patch?.autoFastForward && typeof patch.autoFastForward === "object" ? patch.autoFastForward : {}
1365
+ });
1366
+ const policy = {
1367
+ ...DEFAULT_MESH_POLICY,
1368
+ ...base || {},
1369
+ ...patch || {},
1370
+ autoFastForward
1371
+ };
1361
1372
  if (!["block", "warn", "checkpoint_then_continue"].includes(policy.dirtyWorkspaceBehavior)) {
1362
1373
  policy.dirtyWorkspaceBehavior = "warn";
1363
1374
  }
@@ -1372,6 +1383,15 @@ function mergeMeshPolicy(base, patch) {
1372
1383
  }
1373
1384
  return policy;
1374
1385
  }
1386
+ function normalizeAutoFastForwardPolicy(value) {
1387
+ const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
1388
+ const maxBehind = Number(record.maxBehind);
1389
+ return {
1390
+ enabled: record.enabled !== false,
1391
+ ...Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {},
1392
+ requireCleanSubmodules: record.requireCleanSubmodules !== false
1393
+ };
1394
+ }
1375
1395
  function listMeshes() {
1376
1396
  return loadMeshConfig().meshes;
1377
1397
  }
@@ -5554,12 +5574,14 @@ function formatCompletionMetadata(event) {
5554
5574
  const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === "object" ? event.completionDiagnostic : null;
5555
5575
  const diagnosticReason = completionDiagnostic ? readNonEmptyString2(completionDiagnostic.blockReason) || "present" : "";
5556
5576
  const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === "boolean" ? String(completionDiagnostic.finalAssistantPresent) : "";
5577
+ const evidenceLevel = readNonEmptyString2(event.evidenceLevel);
5557
5578
  const parts = [
5558
5579
  readNonEmptyString2(event.targetSessionId) ? `session_id=${readNonEmptyString2(event.targetSessionId)}` : "",
5559
5580
  readNonEmptyString2(event.providerType) ? `provider=${readNonEmptyString2(event.providerType)}` : "",
5560
5581
  readNonEmptyString2(event.providerSessionId) ? `provider_session_id=${readNonEmptyString2(event.providerSessionId)}` : "",
5561
5582
  diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : "",
5562
- finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : ""
5583
+ finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : "",
5584
+ evidenceLevel && evidenceLevel !== "sufficient" ? `evidence_level=${evidenceLevel}` : ""
5563
5585
  ].filter(Boolean);
5564
5586
  return parts.length > 0 ? ` (${parts.join("; ")})` : "";
5565
5587
  }
@@ -5569,7 +5591,8 @@ function buildMeshSystemMessage(args) {
5569
5591
  if (args.metadataEvent.source === "long_generating_reconciliation") {
5570
5592
  return `[System] ${args.nodeLabel} already has completion evidence${metadata}. The long-generating monitor reconciled the terminal handoff and marked the session complete; wait for the queued completion event/status refresh before doing any manual transcript check.`;
5571
5593
  }
5572
- return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path; use mesh_read_chat once to review its final progress, but do not poll repeatedly.`;
5594
+ const reviewNote = args.metadataEvent.reviewRecommended === true ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done. Use mesh_read_chat once if needed, but do not poll repeatedly." : " Use mesh_read_chat once to review its final progress, but do not poll repeatedly.";
5595
+ return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
5573
5596
  }
5574
5597
  if (args.event === "agent:waiting_approval") {
5575
5598
  return `[System] ${args.nodeLabel} is waiting for approval to proceed${metadata}. You may use mesh_read_chat and mesh_approve to handle it.`;
@@ -6716,6 +6739,34 @@ function isIdleSessionState(state) {
6716
6739
  function isDirtyNode(node) {
6717
6740
  return node?.health === "dirty" || node?.git?.dirty === true;
6718
6741
  }
6742
+ function resolveAutoFastForwardPolicy(mesh) {
6743
+ const record = mesh?.policy?.autoFastForward && typeof mesh.policy.autoFastForward === "object" && !Array.isArray(mesh.policy.autoFastForward) ? mesh.policy.autoFastForward : {};
6744
+ const maxBehind = Number(record.maxBehind);
6745
+ return {
6746
+ enabled: record.enabled !== false,
6747
+ ...Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {},
6748
+ requireCleanSubmodules: record.requireCleanSubmodules !== false
6749
+ };
6750
+ }
6751
+ function sessionStateLooksActive(state) {
6752
+ const status = readNonEmptyString2(state?.status).toLowerCase();
6753
+ const chatStatus = readNonEmptyString2(state?.activeChat?.status).toLowerCase();
6754
+ const active = /* @__PURE__ */ new Set(["generating", "streaming", "long_generating", "working", "starting", "waiting_approval"]);
6755
+ return active.has(status) || active.has(chatStatus);
6756
+ }
6757
+ function nodeHasActiveMeshWork(components, meshId, nodeId, currentSessionId) {
6758
+ if (nodeHasActiveAssignment(meshId, nodeId)) return true;
6759
+ return components.instanceManager.getByCategory("cli").some((inst) => {
6760
+ const state = inst.getState();
6761
+ const settings = state.settings || {};
6762
+ if (readNonEmptyString2(settings.meshNodeFor) !== meshId) return false;
6763
+ const instNodeId = readNonEmptyString2(settings.meshNodeId) || readNonEmptyString2(settings.nodeId);
6764
+ if (instNodeId !== nodeId) return false;
6765
+ const sessionId = readNonEmptyString2(state.instanceId);
6766
+ if (currentSessionId && sessionId === currentSessionId && isIdleSessionState(state)) return false;
6767
+ return sessionStateLooksActive(state);
6768
+ });
6769
+ }
6719
6770
  function isLaunchableNode(node) {
6720
6771
  if (!node || node.status === "disabled" || node.status === "removed") return false;
6721
6772
  const health = readNonEmptyString2(node.health).toLowerCase();
@@ -7044,6 +7095,9 @@ async function maybeAutoFastForwardIdleNode(components, args) {
7044
7095
  const workspace = readNonEmptyString2(node?.workspace);
7045
7096
  if (!workspace) return;
7046
7097
  if (!existsSync14(workspace)) return;
7098
+ const policy = resolveAutoFastForwardPolicy(mesh);
7099
+ if (!policy.enabled) return;
7100
+ if (nodeHasActiveMeshWork(components, args.meshId, args.nodeId, args.sessionId)) return;
7047
7101
  const throttleKey = `${args.meshId}:${args.nodeId}`;
7048
7102
  const now = Date.now();
7049
7103
  const lastAttempt = idleAutoFastForwardLastAttempt.get(throttleKey) || 0;
@@ -7062,6 +7116,12 @@ async function maybeAutoFastForwardIdleNode(components, args) {
7062
7116
  trigger: "idle_auto"
7063
7117
  });
7064
7118
  if (!dryRun || dryRun.code !== "fast_forward_available" || dryRun.allowed !== true) return;
7119
+ const behind = Number(dryRun.current?.behind);
7120
+ if (policy.maxBehind !== void 0 && Number.isFinite(behind) && behind > policy.maxBehind) return;
7121
+ if (policy.requireCleanSubmodules) {
7122
+ const submodules = Array.isArray(dryRun.current?.submodules) ? dryRun.current.submodules : [];
7123
+ if (submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return;
7124
+ }
7065
7125
  await fastForwardMeshNode({
7066
7126
  meshId: args.meshId,
7067
7127
  nodeId: args.nodeId,
@@ -28185,6 +28245,7 @@ var SCHEMA_V3 = {
28185
28245
  "busy_hold_ms": { "type": "integer", "minimum": 0 },
28186
28246
  "idle_hold_ms": { "type": "integer", "minimum": 0 },
28187
28247
  "startup_grace_ms": { "type": "integer", "minimum": 0 },
28248
+ "screen_active_hold_ms": { "type": "integer", "minimum": 0 },
28188
28249
  "completion_marker": {
28189
28250
  "type": "object",
28190
28251
  "additionalProperties": false,
@@ -28527,6 +28588,7 @@ function attachDebounceAlias(spec) {
28527
28588
  ...t.busy_hold_ms !== void 0 ? { busy_hold_ms: t.busy_hold_ms } : {},
28528
28589
  ...t.idle_hold_ms !== void 0 ? { idle_hold_ms: t.idle_hold_ms } : {},
28529
28590
  ...t.startup_grace_ms !== void 0 ? { startup_grace_ms: t.startup_grace_ms } : {},
28591
+ ...t.screen_active_hold_ms !== void 0 ? { screen_active_hold_ms: t.screen_active_hold_ms } : {},
28530
28592
  ...cm ? {
28531
28593
  completion_idle_after: {
28532
28594
  ...cm.section ? { section: cm.section } : {},
@@ -28830,6 +28892,10 @@ var SpecDriver = class {
28830
28892
  completionIdleKey = "";
28831
28893
  /** Previous screen lines — passed to evaluate() for `changed` condition detection. */
28832
28894
  prevScreenLines = [];
28895
+ /** Timestamp of the last PTY frame that changed the screen content.
28896
+ * Used by screen_active_hold_ms to suppress idle downshifts while
28897
+ * the terminal is still actively updating. */
28898
+ lastScreenChangedAt = 0;
28833
28899
  /** Timer that re-runs evaluate() once the hold window expires. Needed
28834
28900
  * because the PTY stops emitting once the agent finishes; without an
28835
28901
  * explicit wake-up there's nothing to trigger the busy → idle
@@ -29031,8 +29097,12 @@ var SpecDriver = class {
29031
29097
  reevaluate(forceEmit = false) {
29032
29098
  const screen = this.adapter.snapshot();
29033
29099
  const cursor = this.adapter.getCursorPosition();
29100
+ const currentLines = screen.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
29034
29101
  const ev = evaluate(this.spec, screen, cursor, this.prevScreenLines.length > 0 ? this.prevScreenLines : void 0);
29035
- this.prevScreenLines = screen.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
29102
+ if (this.prevScreenLines.length > 0 && currentLines.join("\n") !== this.prevScreenLines.join("\n")) {
29103
+ this.lastScreenChangedAt = Date.now();
29104
+ }
29105
+ this.prevScreenLines = currentLines;
29036
29106
  let evState = ev.state;
29037
29107
  const busyHoldMs = this.spec.debounce?.busy_hold_ms ?? BUSY_HOLD_MS;
29038
29108
  if (this.currentStateId === "busy" && evState.id === "idle") {
@@ -29050,12 +29120,19 @@ var SpecDriver = class {
29050
29120
  }
29051
29121
  }
29052
29122
  const completionIdleRule = this.spec.debounce?.completion_idle_after;
29123
+ const screenActiveHoldMs = this.spec.debounce?.screen_active_hold_ms;
29053
29124
  let busyWakeMs = busyHoldMs;
29054
29125
  const postModalGraceMs = busyHoldMs;
29055
29126
  const now = Date.now();
29056
29127
  const recentlyInModal = isModalState(this.currentStateId) || this.lastModalAt > 0 && now - this.lastModalAt < postModalGraceMs;
29057
29128
  const recentlyLeftModal = !recentlyInModal && this.lastModalExitAt > 0 && now - this.lastModalExitAt < postModalGraceMs;
29058
- if (evState.id === "busy" && completionIdleRule && !recentlyInModal && !recentlyLeftModal) {
29129
+ const screenActiveMs = screenActiveHoldMs ?? 0;
29130
+ const screenStableMs = this.lastScreenChangedAt > 0 ? now - this.lastScreenChangedAt : Infinity;
29131
+ const screenIsActive = screenActiveMs > 0 && screenStableMs < screenActiveMs;
29132
+ if (screenIsActive) {
29133
+ busyWakeMs = Math.min(busyWakeMs, screenActiveMs - screenStableMs + 50);
29134
+ }
29135
+ if (evState.id === "busy" && completionIdleRule && !recentlyInModal && !recentlyLeftModal && !screenIsActive) {
29059
29136
  const completionKey = matchesCompletionIdleRule(this.spec, ev, screen);
29060
29137
  if (completionKey) {
29061
29138
  const now2 = Date.now();
@@ -29085,8 +29162,13 @@ var SpecDriver = class {
29085
29162
  this.completionIdleFirstSeenAt = 0;
29086
29163
  }
29087
29164
  } else if (evState.id !== "busy") {
29088
- this.completionIdleKey = "";
29089
- this.completionIdleFirstSeenAt = 0;
29165
+ if (!screenIsActive) {
29166
+ this.completionIdleKey = "";
29167
+ this.completionIdleFirstSeenAt = 0;
29168
+ }
29169
+ }
29170
+ if (screenIsActive && this.currentStateId === "busy" && evState.id === (this.spec.default_state ?? "idle")) {
29171
+ evState = this.lastBusyState ?? evState;
29090
29172
  }
29091
29173
  if (evState.id === "busy") {
29092
29174
  this.lastBusyAt = Date.now();
@@ -31793,7 +31875,7 @@ var CliProviderInstance = class {
31793
31875
  message: typeof modal?.message === "string" ? modal.message.trim() : "",
31794
31876
  buttons: Array.isArray(modal?.buttons) ? modal.buttons.map((button) => String(button).trim()) : []
31795
31877
  });
31796
- if (this.lastStatus !== "waiting_approval" && approvalFingerprint !== this.lastApprovalEventFingerprint) {
31878
+ if (approvalFingerprint !== this.lastApprovalEventFingerprint) {
31797
31879
  this.lastApprovalEventFingerprint = approvalFingerprint;
31798
31880
  this.appendRuntimeSystemMessage(
31799
31881
  this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
@@ -39868,6 +39950,23 @@ function getGitSubmoduleDriftState(git) {
39868
39950
  }
39869
39951
  return { dirty, outOfSync };
39870
39952
  }
39953
+ function isInlineMeshAutoFastForwardEligible(git) {
39954
+ if (!git) return false;
39955
+ if (readBooleanValue(git.isGitRepo) !== true) return false;
39956
+ if (!readStringValue(git.branch)) return false;
39957
+ if (!readStringValue(git.upstream)) return false;
39958
+ const upstreamStatus = readStringValue(git.upstreamStatus, git.upstream_status);
39959
+ if (upstreamStatus !== "fresh") return false;
39960
+ if ((readNumberValue(git.ahead) ?? 0) !== 0) return false;
39961
+ if ((readNumberValue(git.behind) ?? 0) <= 0) return false;
39962
+ const hasConflicts = readBooleanValue(git.hasConflicts) ?? (Array.isArray(git.conflictFiles) && git.conflictFiles.length > 0);
39963
+ if (hasConflicts) return false;
39964
+ if ((readNumberValue(git.stashCount, git.stash_count) ?? 0) > 0) return false;
39965
+ const submoduleDrift = getGitSubmoduleDriftState(git);
39966
+ if (submoduleDrift.dirty || submoduleDrift.outOfSync) return false;
39967
+ const dirty = readBooleanValue(git.dirty) ?? countGitWorktreeChanges(git) > 0;
39968
+ return dirty !== true && countGitWorktreeChanges(git) === 0;
39969
+ }
39871
39970
  function deriveMeshNodeHealthFromGit(git) {
39872
39971
  if (!git || readBooleanValue(git.isGitRepo) === false) return "degraded";
39873
39972
  const branch = readStringValue(git.branch);
@@ -39997,6 +40096,12 @@ function applyInlineMeshBranchConvergence(mesh, node, status) {
39997
40096
  status.isDirty = uncommittedChanges > 0;
39998
40097
  status.uncommittedChanges = uncommittedChanges;
39999
40098
  status.branchConvergence = buildInlineMeshBranchConvergence({ mesh, node, status });
40099
+ status.autoFastForwardEligible = isInlineMeshAutoFastForwardEligible(git);
40100
+ if (status.autoFastForwardEligible) {
40101
+ status.suggestedAction = "auto_fast_forward";
40102
+ } else {
40103
+ delete status.suggestedAction;
40104
+ }
40000
40105
  }
40001
40106
  function summarizeInlineMeshBranchConvergence(nodes) {
40002
40107
  const followUps = nodes.filter((node) => readObjectRecord(node.branchConvergence).needsConvergence === true).map((node) => {