@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.js CHANGED
@@ -45,6 +45,7 @@ var init_repo_mesh_types = __esm({
45
45
  maxParallelTasks: 2,
46
46
  spawnedSessionVisibility: "visible",
47
47
  sessionCleanupOnNodeRemove: "preserve",
48
+ autoFastForward: { enabled: true },
48
49
  maxTaskRetries: 1
49
50
  };
50
51
  }
@@ -1359,7 +1360,17 @@ function normalizeRepoIdentity(remoteUrl) {
1359
1360
  return identity;
1360
1361
  }
1361
1362
  function mergeMeshPolicy(base, patch) {
1362
- const policy = { ...DEFAULT_MESH_POLICY, ...base || {}, ...patch || {} };
1363
+ const autoFastForward = normalizeAutoFastForwardPolicy({
1364
+ ...DEFAULT_MESH_POLICY.autoFastForward,
1365
+ ...base?.autoFastForward && typeof base.autoFastForward === "object" ? base.autoFastForward : {},
1366
+ ...patch?.autoFastForward && typeof patch.autoFastForward === "object" ? patch.autoFastForward : {}
1367
+ });
1368
+ const policy = {
1369
+ ...DEFAULT_MESH_POLICY,
1370
+ ...base || {},
1371
+ ...patch || {},
1372
+ autoFastForward
1373
+ };
1363
1374
  if (!["block", "warn", "checkpoint_then_continue"].includes(policy.dirtyWorkspaceBehavior)) {
1364
1375
  policy.dirtyWorkspaceBehavior = "warn";
1365
1376
  }
@@ -1374,6 +1385,15 @@ function mergeMeshPolicy(base, patch) {
1374
1385
  }
1375
1386
  return policy;
1376
1387
  }
1388
+ function normalizeAutoFastForwardPolicy(value) {
1389
+ const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
1390
+ const maxBehind = Number(record.maxBehind);
1391
+ return {
1392
+ enabled: record.enabled !== false,
1393
+ ...Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {},
1394
+ requireCleanSubmodules: record.requireCleanSubmodules !== false
1395
+ };
1396
+ }
1377
1397
  function listMeshes() {
1378
1398
  return loadMeshConfig().meshes;
1379
1399
  }
@@ -5560,12 +5580,14 @@ function formatCompletionMetadata(event) {
5560
5580
  const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === "object" ? event.completionDiagnostic : null;
5561
5581
  const diagnosticReason = completionDiagnostic ? readNonEmptyString2(completionDiagnostic.blockReason) || "present" : "";
5562
5582
  const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === "boolean" ? String(completionDiagnostic.finalAssistantPresent) : "";
5583
+ const evidenceLevel = readNonEmptyString2(event.evidenceLevel);
5563
5584
  const parts = [
5564
5585
  readNonEmptyString2(event.targetSessionId) ? `session_id=${readNonEmptyString2(event.targetSessionId)}` : "",
5565
5586
  readNonEmptyString2(event.providerType) ? `provider=${readNonEmptyString2(event.providerType)}` : "",
5566
5587
  readNonEmptyString2(event.providerSessionId) ? `provider_session_id=${readNonEmptyString2(event.providerSessionId)}` : "",
5567
5588
  diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : "",
5568
- finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : ""
5589
+ finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : "",
5590
+ evidenceLevel && evidenceLevel !== "sufficient" ? `evidence_level=${evidenceLevel}` : ""
5569
5591
  ].filter(Boolean);
5570
5592
  return parts.length > 0 ? ` (${parts.join("; ")})` : "";
5571
5593
  }
@@ -5575,7 +5597,8 @@ function buildMeshSystemMessage(args) {
5575
5597
  if (args.metadataEvent.source === "long_generating_reconciliation") {
5576
5598
  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.`;
5577
5599
  }
5578
- 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.`;
5600
+ 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.";
5601
+ return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
5579
5602
  }
5580
5603
  if (args.event === "agent:waiting_approval") {
5581
5604
  return `[System] ${args.nodeLabel} is waiting for approval to proceed${metadata}. You may use mesh_read_chat and mesh_approve to handle it.`;
@@ -6722,6 +6745,34 @@ function isIdleSessionState(state) {
6722
6745
  function isDirtyNode(node) {
6723
6746
  return node?.health === "dirty" || node?.git?.dirty === true;
6724
6747
  }
6748
+ function resolveAutoFastForwardPolicy(mesh) {
6749
+ const record = mesh?.policy?.autoFastForward && typeof mesh.policy.autoFastForward === "object" && !Array.isArray(mesh.policy.autoFastForward) ? mesh.policy.autoFastForward : {};
6750
+ const maxBehind = Number(record.maxBehind);
6751
+ return {
6752
+ enabled: record.enabled !== false,
6753
+ ...Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {},
6754
+ requireCleanSubmodules: record.requireCleanSubmodules !== false
6755
+ };
6756
+ }
6757
+ function sessionStateLooksActive(state) {
6758
+ const status = readNonEmptyString2(state?.status).toLowerCase();
6759
+ const chatStatus = readNonEmptyString2(state?.activeChat?.status).toLowerCase();
6760
+ const active = /* @__PURE__ */ new Set(["generating", "streaming", "long_generating", "working", "starting", "waiting_approval"]);
6761
+ return active.has(status) || active.has(chatStatus);
6762
+ }
6763
+ function nodeHasActiveMeshWork(components, meshId, nodeId, currentSessionId) {
6764
+ if (nodeHasActiveAssignment(meshId, nodeId)) return true;
6765
+ return components.instanceManager.getByCategory("cli").some((inst) => {
6766
+ const state = inst.getState();
6767
+ const settings = state.settings || {};
6768
+ if (readNonEmptyString2(settings.meshNodeFor) !== meshId) return false;
6769
+ const instNodeId = readNonEmptyString2(settings.meshNodeId) || readNonEmptyString2(settings.nodeId);
6770
+ if (instNodeId !== nodeId) return false;
6771
+ const sessionId = readNonEmptyString2(state.instanceId);
6772
+ if (currentSessionId && sessionId === currentSessionId && isIdleSessionState(state)) return false;
6773
+ return sessionStateLooksActive(state);
6774
+ });
6775
+ }
6725
6776
  function isLaunchableNode(node) {
6726
6777
  if (!node || node.status === "disabled" || node.status === "removed") return false;
6727
6778
  const health = readNonEmptyString2(node.health).toLowerCase();
@@ -7050,6 +7101,9 @@ async function maybeAutoFastForwardIdleNode(components, args) {
7050
7101
  const workspace = readNonEmptyString2(node?.workspace);
7051
7102
  if (!workspace) return;
7052
7103
  if (!(0, import_fs10.existsSync)(workspace)) return;
7104
+ const policy = resolveAutoFastForwardPolicy(mesh);
7105
+ if (!policy.enabled) return;
7106
+ if (nodeHasActiveMeshWork(components, args.meshId, args.nodeId, args.sessionId)) return;
7053
7107
  const throttleKey = `${args.meshId}:${args.nodeId}`;
7054
7108
  const now = Date.now();
7055
7109
  const lastAttempt = idleAutoFastForwardLastAttempt.get(throttleKey) || 0;
@@ -7068,6 +7122,12 @@ async function maybeAutoFastForwardIdleNode(components, args) {
7068
7122
  trigger: "idle_auto"
7069
7123
  });
7070
7124
  if (!dryRun || dryRun.code !== "fast_forward_available" || dryRun.allowed !== true) return;
7125
+ const behind = Number(dryRun.current?.behind);
7126
+ if (policy.maxBehind !== void 0 && Number.isFinite(behind) && behind > policy.maxBehind) return;
7127
+ if (policy.requireCleanSubmodules) {
7128
+ const submodules = Array.isArray(dryRun.current?.submodules) ? dryRun.current.submodules : [];
7129
+ if (submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return;
7130
+ }
7071
7131
  await fastForwardMeshNode({
7072
7132
  meshId: args.meshId,
7073
7133
  nodeId: args.nodeId,
@@ -28518,6 +28578,7 @@ var SCHEMA_V3 = {
28518
28578
  "busy_hold_ms": { "type": "integer", "minimum": 0 },
28519
28579
  "idle_hold_ms": { "type": "integer", "minimum": 0 },
28520
28580
  "startup_grace_ms": { "type": "integer", "minimum": 0 },
28581
+ "screen_active_hold_ms": { "type": "integer", "minimum": 0 },
28521
28582
  "completion_marker": {
28522
28583
  "type": "object",
28523
28584
  "additionalProperties": false,
@@ -28860,6 +28921,7 @@ function attachDebounceAlias(spec) {
28860
28921
  ...t.busy_hold_ms !== void 0 ? { busy_hold_ms: t.busy_hold_ms } : {},
28861
28922
  ...t.idle_hold_ms !== void 0 ? { idle_hold_ms: t.idle_hold_ms } : {},
28862
28923
  ...t.startup_grace_ms !== void 0 ? { startup_grace_ms: t.startup_grace_ms } : {},
28924
+ ...t.screen_active_hold_ms !== void 0 ? { screen_active_hold_ms: t.screen_active_hold_ms } : {},
28863
28925
  ...cm ? {
28864
28926
  completion_idle_after: {
28865
28927
  ...cm.section ? { section: cm.section } : {},
@@ -29163,6 +29225,10 @@ var SpecDriver = class {
29163
29225
  completionIdleKey = "";
29164
29226
  /** Previous screen lines — passed to evaluate() for `changed` condition detection. */
29165
29227
  prevScreenLines = [];
29228
+ /** Timestamp of the last PTY frame that changed the screen content.
29229
+ * Used by screen_active_hold_ms to suppress idle downshifts while
29230
+ * the terminal is still actively updating. */
29231
+ lastScreenChangedAt = 0;
29166
29232
  /** Timer that re-runs evaluate() once the hold window expires. Needed
29167
29233
  * because the PTY stops emitting once the agent finishes; without an
29168
29234
  * explicit wake-up there's nothing to trigger the busy → idle
@@ -29364,8 +29430,12 @@ var SpecDriver = class {
29364
29430
  reevaluate(forceEmit = false) {
29365
29431
  const screen = this.adapter.snapshot();
29366
29432
  const cursor = this.adapter.getCursorPosition();
29433
+ const currentLines = screen.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
29367
29434
  const ev = evaluate(this.spec, screen, cursor, this.prevScreenLines.length > 0 ? this.prevScreenLines : void 0);
29368
- this.prevScreenLines = screen.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
29435
+ if (this.prevScreenLines.length > 0 && currentLines.join("\n") !== this.prevScreenLines.join("\n")) {
29436
+ this.lastScreenChangedAt = Date.now();
29437
+ }
29438
+ this.prevScreenLines = currentLines;
29369
29439
  let evState = ev.state;
29370
29440
  const busyHoldMs = this.spec.debounce?.busy_hold_ms ?? BUSY_HOLD_MS;
29371
29441
  if (this.currentStateId === "busy" && evState.id === "idle") {
@@ -29383,12 +29453,19 @@ var SpecDriver = class {
29383
29453
  }
29384
29454
  }
29385
29455
  const completionIdleRule = this.spec.debounce?.completion_idle_after;
29456
+ const screenActiveHoldMs = this.spec.debounce?.screen_active_hold_ms;
29386
29457
  let busyWakeMs = busyHoldMs;
29387
29458
  const postModalGraceMs = busyHoldMs;
29388
29459
  const now = Date.now();
29389
29460
  const recentlyInModal = isModalState(this.currentStateId) || this.lastModalAt > 0 && now - this.lastModalAt < postModalGraceMs;
29390
29461
  const recentlyLeftModal = !recentlyInModal && this.lastModalExitAt > 0 && now - this.lastModalExitAt < postModalGraceMs;
29391
- if (evState.id === "busy" && completionIdleRule && !recentlyInModal && !recentlyLeftModal) {
29462
+ const screenActiveMs = screenActiveHoldMs ?? 0;
29463
+ const screenStableMs = this.lastScreenChangedAt > 0 ? now - this.lastScreenChangedAt : Infinity;
29464
+ const screenIsActive = screenActiveMs > 0 && screenStableMs < screenActiveMs;
29465
+ if (screenIsActive) {
29466
+ busyWakeMs = Math.min(busyWakeMs, screenActiveMs - screenStableMs + 50);
29467
+ }
29468
+ if (evState.id === "busy" && completionIdleRule && !recentlyInModal && !recentlyLeftModal && !screenIsActive) {
29392
29469
  const completionKey = matchesCompletionIdleRule(this.spec, ev, screen);
29393
29470
  if (completionKey) {
29394
29471
  const now2 = Date.now();
@@ -29418,8 +29495,13 @@ var SpecDriver = class {
29418
29495
  this.completionIdleFirstSeenAt = 0;
29419
29496
  }
29420
29497
  } else if (evState.id !== "busy") {
29421
- this.completionIdleKey = "";
29422
- this.completionIdleFirstSeenAt = 0;
29498
+ if (!screenIsActive) {
29499
+ this.completionIdleKey = "";
29500
+ this.completionIdleFirstSeenAt = 0;
29501
+ }
29502
+ }
29503
+ if (screenIsActive && this.currentStateId === "busy" && evState.id === (this.spec.default_state ?? "idle")) {
29504
+ evState = this.lastBusyState ?? evState;
29423
29505
  }
29424
29506
  if (evState.id === "busy") {
29425
29507
  this.lastBusyAt = Date.now();
@@ -32126,7 +32208,7 @@ var CliProviderInstance = class {
32126
32208
  message: typeof modal?.message === "string" ? modal.message.trim() : "",
32127
32209
  buttons: Array.isArray(modal?.buttons) ? modal.buttons.map((button) => String(button).trim()) : []
32128
32210
  });
32129
- if (this.lastStatus !== "waiting_approval" && approvalFingerprint !== this.lastApprovalEventFingerprint) {
32211
+ if (approvalFingerprint !== this.lastApprovalEventFingerprint) {
32130
32212
  this.lastApprovalEventFingerprint = approvalFingerprint;
32131
32213
  this.appendRuntimeSystemMessage(
32132
32214
  this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
@@ -40196,6 +40278,23 @@ function getGitSubmoduleDriftState(git) {
40196
40278
  }
40197
40279
  return { dirty, outOfSync };
40198
40280
  }
40281
+ function isInlineMeshAutoFastForwardEligible(git) {
40282
+ if (!git) return false;
40283
+ if (readBooleanValue(git.isGitRepo) !== true) return false;
40284
+ if (!readStringValue(git.branch)) return false;
40285
+ if (!readStringValue(git.upstream)) return false;
40286
+ const upstreamStatus = readStringValue(git.upstreamStatus, git.upstream_status);
40287
+ if (upstreamStatus !== "fresh") return false;
40288
+ if ((readNumberValue(git.ahead) ?? 0) !== 0) return false;
40289
+ if ((readNumberValue(git.behind) ?? 0) <= 0) return false;
40290
+ const hasConflicts = readBooleanValue(git.hasConflicts) ?? (Array.isArray(git.conflictFiles) && git.conflictFiles.length > 0);
40291
+ if (hasConflicts) return false;
40292
+ if ((readNumberValue(git.stashCount, git.stash_count) ?? 0) > 0) return false;
40293
+ const submoduleDrift = getGitSubmoduleDriftState(git);
40294
+ if (submoduleDrift.dirty || submoduleDrift.outOfSync) return false;
40295
+ const dirty = readBooleanValue(git.dirty) ?? countGitWorktreeChanges(git) > 0;
40296
+ return dirty !== true && countGitWorktreeChanges(git) === 0;
40297
+ }
40199
40298
  function deriveMeshNodeHealthFromGit(git) {
40200
40299
  if (!git || readBooleanValue(git.isGitRepo) === false) return "degraded";
40201
40300
  const branch = readStringValue(git.branch);
@@ -40325,6 +40424,12 @@ function applyInlineMeshBranchConvergence(mesh, node, status) {
40325
40424
  status.isDirty = uncommittedChanges > 0;
40326
40425
  status.uncommittedChanges = uncommittedChanges;
40327
40426
  status.branchConvergence = buildInlineMeshBranchConvergence({ mesh, node, status });
40427
+ status.autoFastForwardEligible = isInlineMeshAutoFastForwardEligible(git);
40428
+ if (status.autoFastForwardEligible) {
40429
+ status.suggestedAction = "auto_fast_forward";
40430
+ } else {
40431
+ delete status.suggestedAction;
40432
+ }
40328
40433
  }
40329
40434
  function summarizeInlineMeshBranchConvergence(nodes) {
40330
40435
  const followUps = nodes.filter((node) => readObjectRecord(node.branchConvergence).needsConvergence === true).map((node) => {