@adhdev/daemon-core 0.9.82-rc.470 → 0.9.82-rc.471

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.
@@ -69,6 +69,17 @@ export declare class CliProviderInstance implements ProviderInstance {
69
69
  * bound below still caps the episode, so a worker whose approval truly never
70
70
  * returns is surfaced to the coordinator within AUTO_APPROVE_MASK_STALL_MS
71
71
  * rather than held forever.
72
+ *
73
+ * INVARIANT (do not regress the ordering): this window must fully BRIDGE a
74
+ * single busy phase, and the mask-stall bound below must in turn exceed it —
75
+ * AUTO_APPROVE_MASK_STALL_MS > AUTO_APPROVE_FLAP_CONTINUITY_MS + max_busy_phase
76
+ * + AUTO_APPROVE_SETTLE_MS. Observed flap geometry (delegated-worker Bash
77
+ * approval): approval frames last ~1.5s, busy phases (modal=none) last
78
+ * ~4.3–4.5s. With the old 4000ms this window was SHORTER than a busy phase, so
79
+ * the settle clock was torn down every cycle and never accrued 600ms while the
80
+ * 4500ms mask-stall tripped INSIDE the first busy phase → a stalled-approval
81
+ * nudge leaked to the coordinator. 6000ms bridges the ~4.5s busy phase with
82
+ * margin so the returning approval frame survives to resume its settle clock.
72
83
  */
73
84
  private static readonly AUTO_APPROVE_FLAP_CONTINUITY_MS;
74
85
  /**
@@ -87,6 +98,15 @@ export declare class CliProviderInstance implements ProviderInstance {
87
98
  * genuine never-resolving stall surfaces within this window. The settle gate keeps
88
99
  * running underneath, so a prompt that finally stabilises still auto-approves, and
89
100
  * mesh_approve (raw FSM, unmasked) works throughout.
101
+ *
102
+ * INVARIANT (do not regress): must be STRICTLY GREATER than
103
+ * AUTO_APPROVE_FLAP_CONTINUITY_MS + max_busy_phase + AUTO_APPROVE_SETTLE_MS so
104
+ * that during a flap the settle clock (which FLAP_CONTINUITY keeps alive across
105
+ * each ~4.3–4.5s busy phase) gets to accrue its 600ms on the RETURNING approval
106
+ * frame before this stall bound can trip. Observed geometry: approval ~1.5s,
107
+ * busy ~4.3–4.5s. 9000ms ≥ CONTINUITY(6000) + busy(~4.5s) + SETTLE(600) headroom;
108
+ * the old 4500ms tripped inside the very first busy phase (while modal=none, so
109
+ * the nudge was NOT deferred) and leaked to the coordinator.
90
110
  */
91
111
  private static readonly AUTO_APPROVE_MASK_STALL_MS;
92
112
  private adapter;
@@ -340,6 +360,21 @@ export declare class CliProviderInstance implements ProviderInstance {
340
360
  private recordCompletionGateTrace;
341
361
  private recordFsmTransitionTrace;
342
362
  private flushCompletedDebounceIfFinalized;
363
+ /**
364
+ * A-3 (CLI 완료판정 통합): single authoritative emit for a CLI turn's
365
+ * `agent:generating_completed` event. The completion JUDGMENT — WHETHER and
366
+ * WHEN a turn is done (settle windows, continuity guards, the finalization
367
+ * gate, the short-gen / startup-grace / no-progress-monitor discriminators) —
368
+ * stays at each call site, where it is legitimately path-specific. What used
369
+ * to be duplicated across all five completion paths was the EVENT-SHAPE
370
+ * assembly: the `event` name, the conditional `taskId` spread, and the
371
+ * optional `finalSummary` / `evidenceLevel` / `completionDiagnostic` fields.
372
+ * Folding that assembly here removes the divergence (e.g. one site spreading
373
+ * taskId, others omitting it) without touching any judgment. Callers pass the
374
+ * values they have already computed; omitted optionals are simply absent from
375
+ * the emitted event, exactly as each inline builder produced before.
376
+ */
377
+ private emitGeneratingCompleted;
343
378
  private maybeAutoApproveStatus;
344
379
  /**
345
380
  * Re-drive the auto-approve check after the settle quiet window elapses.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.470",
3
+ "version": "0.9.82-rc.471",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,8 +47,8 @@
47
47
  "author": "vilmire",
48
48
  "license": "AGPL-3.0-or-later",
49
49
  "dependencies": {
50
- "@adhdev/mesh-shared": "0.9.82-rc.470",
51
- "@adhdev/session-host-core": "0.9.82-rc.470",
50
+ "@adhdev/mesh-shared": "0.9.82-rc.471",
51
+ "@adhdev/session-host-core": "0.9.82-rc.471",
52
52
  "@agentclientprotocol/sdk": "^0.16.1",
53
53
  "ajv": "^8.20.0",
54
54
  "ajv-formats": "^3.0.1",
@@ -145,8 +145,19 @@ export class CliProviderInstance implements ProviderInstance {
145
145
  * bound below still caps the episode, so a worker whose approval truly never
146
146
  * returns is surfaced to the coordinator within AUTO_APPROVE_MASK_STALL_MS
147
147
  * rather than held forever.
148
+ *
149
+ * INVARIANT (do not regress the ordering): this window must fully BRIDGE a
150
+ * single busy phase, and the mask-stall bound below must in turn exceed it —
151
+ * AUTO_APPROVE_MASK_STALL_MS > AUTO_APPROVE_FLAP_CONTINUITY_MS + max_busy_phase
152
+ * + AUTO_APPROVE_SETTLE_MS. Observed flap geometry (delegated-worker Bash
153
+ * approval): approval frames last ~1.5s, busy phases (modal=none) last
154
+ * ~4.3–4.5s. With the old 4000ms this window was SHORTER than a busy phase, so
155
+ * the settle clock was torn down every cycle and never accrued 600ms while the
156
+ * 4500ms mask-stall tripped INSIDE the first busy phase → a stalled-approval
157
+ * nudge leaked to the coordinator. 6000ms bridges the ~4.5s busy phase with
158
+ * margin so the returning approval frame survives to resume its settle clock.
148
159
  */
149
- private static readonly AUTO_APPROVE_FLAP_CONTINUITY_MS = 4000;
160
+ private static readonly AUTO_APPROVE_FLAP_CONTINUITY_MS = 6000;
150
161
 
151
162
  /**
152
163
  * STATUS-MISMATCH: upper bound on how long the auto-approve→`generating` SURFACE
@@ -164,8 +175,17 @@ export class CliProviderInstance implements ProviderInstance {
164
175
  * genuine never-resolving stall surfaces within this window. The settle gate keeps
165
176
  * running underneath, so a prompt that finally stabilises still auto-approves, and
166
177
  * mesh_approve (raw FSM, unmasked) works throughout.
178
+ *
179
+ * INVARIANT (do not regress): must be STRICTLY GREATER than
180
+ * AUTO_APPROVE_FLAP_CONTINUITY_MS + max_busy_phase + AUTO_APPROVE_SETTLE_MS so
181
+ * that during a flap the settle clock (which FLAP_CONTINUITY keeps alive across
182
+ * each ~4.3–4.5s busy phase) gets to accrue its 600ms on the RETURNING approval
183
+ * frame before this stall bound can trip. Observed geometry: approval ~1.5s,
184
+ * busy ~4.3–4.5s. 9000ms ≥ CONTINUITY(6000) + busy(~4.5s) + SETTLE(600) headroom;
185
+ * the old 4500ms tripped inside the very first busy phase (while modal=none, so
186
+ * the nudge was NOT deferred) and leaked to the coordinator.
167
187
  */
168
- private static readonly AUTO_APPROVE_MASK_STALL_MS = 4500;
188
+ private static readonly AUTO_APPROVE_MASK_STALL_MS = 9000;
169
189
 
170
190
  private adapter: ProviderCliAdapter;
171
191
  private context: InstanceContext | null = null;
@@ -1836,13 +1856,11 @@ export class CliProviderInstance implements ProviderInstance {
1836
1856
  waitedMs,
1837
1857
  busyEpoch: this.busyEpoch,
1838
1858
  });
1839
- this.pushEvent({
1840
- event: 'agent:generating_completed',
1859
+ this.emitGeneratingCompleted({
1841
1860
  chatTitle: pending.chatTitle,
1842
1861
  duration: pending.duration,
1843
1862
  timestamp: pending.timestamp,
1844
- // ARCH-REFACTOR R1: attribute to the turn captured at idle-transition.
1845
- ...(pending.taskId ? { taskId: pending.taskId } : {}),
1863
+ taskId: pending.taskId,
1846
1864
  // When finalization is forced past the timeout on a `parsed_status:` block
1847
1865
  // (the parser never confirmed a final assistant turn) we previously rode an
1848
1866
  // empty `finalSummary` unconditionally. That empty value propagates to the
@@ -1875,13 +1893,11 @@ export class CliProviderInstance implements ProviderInstance {
1875
1893
  duration: pending.duration,
1876
1894
  busyEpoch: this.busyEpoch,
1877
1895
  });
1878
- this.pushEvent({
1879
- event: 'agent:generating_completed',
1896
+ this.emitGeneratingCompleted({
1880
1897
  chatTitle: pending.chatTitle,
1881
1898
  duration: pending.duration,
1882
1899
  timestamp: pending.timestamp,
1883
- // ARCH-REFACTOR R1: attribute to the turn captured at idle-transition.
1884
- ...(pending.taskId ? { taskId: pending.taskId } : {}),
1900
+ taskId: pending.taskId,
1885
1901
  finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages, pending.turnStartedAt),
1886
1902
  });
1887
1903
  this.completedDebouncePending = null;
@@ -1890,7 +1906,46 @@ export class CliProviderInstance implements ProviderInstance {
1890
1906
  this.lastApprovalEventFingerprint = '';
1891
1907
  }
1892
1908
 
1909
+ /**
1910
+ * A-3 (CLI 완료판정 통합): single authoritative emit for a CLI turn's
1911
+ * `agent:generating_completed` event. The completion JUDGMENT — WHETHER and
1912
+ * WHEN a turn is done (settle windows, continuity guards, the finalization
1913
+ * gate, the short-gen / startup-grace / no-progress-monitor discriminators) —
1914
+ * stays at each call site, where it is legitimately path-specific. What used
1915
+ * to be duplicated across all five completion paths was the EVENT-SHAPE
1916
+ * assembly: the `event` name, the conditional `taskId` spread, and the
1917
+ * optional `finalSummary` / `evidenceLevel` / `completionDiagnostic` fields.
1918
+ * Folding that assembly here removes the divergence (e.g. one site spreading
1919
+ * taskId, others omitting it) without touching any judgment. Callers pass the
1920
+ * values they have already computed; omitted optionals are simply absent from
1921
+ * the emitted event, exactly as each inline builder produced before.
1922
+ */
1923
+ private emitGeneratingCompleted(opts: {
1924
+ chatTitle: string;
1925
+ duration: number | undefined;
1926
+ timestamp: number;
1927
+ taskId?: string;
1928
+ finalSummary?: string;
1929
+ evidenceLevel?: string;
1930
+ completionDiagnostic?: Record<string, unknown>;
1931
+ }): void {
1932
+ this.pushEvent({
1933
+ event: 'agent:generating_completed',
1934
+ chatTitle: opts.chatTitle,
1935
+ duration: opts.duration,
1936
+ timestamp: opts.timestamp,
1937
+ // ARCH-REFACTOR R1: attribute to the turn captured at idle-transition.
1938
+ ...(opts.taskId ? { taskId: opts.taskId } : {}),
1939
+ // finalSummary is always carried (value may be undefined) — every prior
1940
+ // inline builder included the key, so downstream consumers see the same shape.
1941
+ finalSummary: opts.finalSummary,
1942
+ ...(opts.evidenceLevel !== undefined ? { evidenceLevel: opts.evidenceLevel } : {}),
1943
+ ...(opts.completionDiagnostic !== undefined ? { completionDiagnostic: opts.completionDiagnostic } : {}),
1944
+ });
1945
+ }
1946
+
1893
1947
  private maybeAutoApproveStatus(adapterStatus: any, now = Date.now()): boolean {
1948
+ // Manual-attendance suppression (provider-common): when a human is
1894
1949
  // Manual-attendance suppression (provider-common): when a human is
1895
1950
  // actively driving this session from the dashboard, hold auto-approve so
1896
1951
  // the modal stays visible and they can pick a button / use the controlbar
@@ -2281,8 +2336,7 @@ export class CliProviderInstance implements ProviderInstance {
2281
2336
  missingEvidence,
2282
2337
  evidenceLevel: 'weak',
2283
2338
  });
2284
- this.pushEvent({
2285
- event: 'agent:generating_completed',
2339
+ this.emitGeneratingCompleted({
2286
2340
  chatTitle,
2287
2341
  duration: 0,
2288
2342
  timestamp: now,
@@ -2618,8 +2672,7 @@ export class CliProviderInstance implements ProviderInstance {
2618
2672
  // surface its completion promptly without a 4s settle) still holds, and a
2619
2673
  // non-mesh session has no coordinator to be falsely notified — the false-idle
2620
2674
  // bug being fixed is specifically the mesh worker/coordinator misfire above.
2621
- this.pushEvent({
2622
- event: 'agent:generating_completed',
2675
+ this.emitGeneratingCompleted({
2623
2676
  chatTitle,
2624
2677
  duration: 0,
2625
2678
  timestamp: now,
@@ -2857,8 +2910,7 @@ export class CliProviderInstance implements ProviderInstance {
2857
2910
  if (this.isMeshWorkerSession()) {
2858
2911
  traceMeshEventStage('fired', this.meshTraceCtx(), 'no_progress_monitor_final_summary');
2859
2912
  }
2860
- this.pushEvent({
2861
- event: 'agent:generating_completed',
2913
+ this.emitGeneratingCompleted({
2862
2914
  chatTitle,
2863
2915
  duration: this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : undefined,
2864
2916
  timestamp: me.timestamp,