@adhdev/daemon-core 0.9.82-rc.215 → 0.9.82-rc.217
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 +48 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -9
- package/dist/index.mjs.map +1 -1
- package/dist/providers/spec/driver.d.ts +3 -0
- package/dist/providers/spec/schema.gen.d.ts +8 -2
- package/dist/providers/spec/types.d.ts +5 -2
- package/package.json +1 -1
- package/src/providers/spec/driver.ts +47 -5
- package/src/providers/spec/evaluator.ts +5 -2
- package/src/providers/spec/schema.gen.ts +2 -1
- package/src/providers/spec/types.ts +5 -2
|
@@ -156,6 +156,9 @@ export declare class SpecDriver {
|
|
|
156
156
|
* Used by screen_active_hold_ms to suppress idle downshifts while
|
|
157
157
|
* the terminal is still actively updating. */
|
|
158
158
|
private lastScreenChangedAt;
|
|
159
|
+
/** Per-cursor_above region last-changed timestamps for stable_ms tracking.
|
|
160
|
+
* Key: cursor_above value. Value: last time that region changed. */
|
|
161
|
+
private regionLastChangedAt;
|
|
159
162
|
/** Timer that re-runs evaluate() once the hold window expires. Needed
|
|
160
163
|
* because the PTY stops emitting once the agent finishes; without an
|
|
161
164
|
* explicit wake-up there's nothing to trigger the busy → idle
|
|
@@ -908,7 +908,10 @@ export declare const SCHEMA_V3: {
|
|
|
908
908
|
};
|
|
909
909
|
readonly changed: {
|
|
910
910
|
readonly type: "boolean";
|
|
911
|
-
|
|
911
|
+
};
|
|
912
|
+
readonly stable_ms: {
|
|
913
|
+
readonly type: "integer";
|
|
914
|
+
readonly minimum: 0;
|
|
912
915
|
};
|
|
913
916
|
};
|
|
914
917
|
};
|
|
@@ -1608,7 +1611,10 @@ export declare const SCHEMA: {
|
|
|
1608
1611
|
};
|
|
1609
1612
|
readonly changed: {
|
|
1610
1613
|
readonly type: "boolean";
|
|
1611
|
-
|
|
1614
|
+
};
|
|
1615
|
+
readonly stable_ms: {
|
|
1616
|
+
readonly type: "integer";
|
|
1617
|
+
readonly minimum: 0;
|
|
1612
1618
|
};
|
|
1613
1619
|
};
|
|
1614
1620
|
};
|
|
@@ -155,10 +155,13 @@ export interface RegexCondition {
|
|
|
155
155
|
cursor_col_min?: number;
|
|
156
156
|
cursor_col_max?: number;
|
|
157
157
|
}
|
|
158
|
-
/** v3 delta condition: N lines above cursor changed vs prevLines.
|
|
158
|
+
/** v3 delta condition: N lines above cursor changed vs prevLines.
|
|
159
|
+
* changed:false + stable_ms: region must be stable for at least N ms. */
|
|
159
160
|
export interface ChangedCondition {
|
|
160
161
|
cursor_above: number;
|
|
161
|
-
changed:
|
|
162
|
+
changed: boolean;
|
|
163
|
+
/** Only for changed:false — region must have been stable for this many ms. */
|
|
164
|
+
stable_ms?: number;
|
|
162
165
|
}
|
|
163
166
|
/** v3 AND composite. */
|
|
164
167
|
export interface AllCondition {
|
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
|
|
|
41
41
|
import { DEFAULT_SESSION_HOST_COLS, DEFAULT_SESSION_HOST_ROWS } from '@adhdev/session-host-core';
|
|
42
42
|
import { evaluate, type SpecEvaluation, type TraceEntry } from './evaluator.js';
|
|
43
43
|
import { loadSpec } from './loader.js';
|
|
44
|
-
import type { CliSpec, Control, DelegateTrigger, SectionDef } from './types.js';
|
|
44
|
+
import type { CliSpec, Condition, ChangedCondition, Control, DelegateTrigger, SectionDef } from './types.js';
|
|
45
45
|
import { LOG } from '../../logging/logger.js';
|
|
46
46
|
|
|
47
47
|
export type DashboardEvent =
|
|
@@ -112,6 +112,14 @@ const BUSY_HOLD_MS = 6000;
|
|
|
112
112
|
* codex's explicit setting and is barely perceptible to a human caller. */
|
|
113
113
|
const SUBMIT_DELAY_FLOOR_MS = 200;
|
|
114
114
|
|
|
115
|
+
function collectChangedConditions(when: Condition | undefined): ChangedCondition[] {
|
|
116
|
+
if (!when) return [];
|
|
117
|
+
if ('cursor_above' in when && 'changed' in when) return [when as ChangedCondition];
|
|
118
|
+
if ('all' in when) return when.all.flatMap(c => collectChangedConditions(c));
|
|
119
|
+
if ('any' in when) return when.any.flatMap(c => collectChangedConditions(c));
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
115
123
|
function countNewlines(s: string): number {
|
|
116
124
|
let n = 0;
|
|
117
125
|
for (let i = 0; i < s.length; i += 1) if (s.charCodeAt(i) === 10) n += 1;
|
|
@@ -240,6 +248,9 @@ export class SpecDriver {
|
|
|
240
248
|
* Used by screen_active_hold_ms to suppress idle downshifts while
|
|
241
249
|
* the terminal is still actively updating. */
|
|
242
250
|
private lastScreenChangedAt = 0;
|
|
251
|
+
/** Per-cursor_above region last-changed timestamps for stable_ms tracking.
|
|
252
|
+
* Key: cursor_above value. Value: last time that region changed. */
|
|
253
|
+
private regionLastChangedAt = new Map<number, number>();
|
|
243
254
|
/** Timer that re-runs evaluate() once the hold window expires. Needed
|
|
244
255
|
* because the PTY stops emitting once the agent finishes; without an
|
|
245
256
|
* explicit wake-up there's nothing to trigger the busy → idle
|
|
@@ -482,9 +493,24 @@ export class SpecDriver {
|
|
|
482
493
|
const cursor = this.adapter.getCursorPosition();
|
|
483
494
|
const currentLines = screen.split('\n').map(l => l.endsWith('\r') ? l.slice(0, -1) : l);
|
|
484
495
|
const ev = evaluate(this.spec, screen, cursor, this.prevScreenLines.length > 0 ? this.prevScreenLines : undefined);
|
|
496
|
+
const now = Date.now();
|
|
485
497
|
// Track when the screen last changed for screen_active_hold_ms.
|
|
486
498
|
if (this.prevScreenLines.length > 0 && currentLines.join('\n') !== this.prevScreenLines.join('\n')) {
|
|
487
|
-
this.lastScreenChangedAt =
|
|
499
|
+
this.lastScreenChangedAt = now;
|
|
500
|
+
}
|
|
501
|
+
// Track per-region last-changed timestamps for stable_ms conditions.
|
|
502
|
+
if (cursor && this.prevScreenLines.length > 0) {
|
|
503
|
+
for (const state of this.spec.states) {
|
|
504
|
+
const conditions = collectChangedConditions(state.when);
|
|
505
|
+
for (const cond of conditions) {
|
|
506
|
+
if (cond.stable_ms == null) continue;
|
|
507
|
+
const startRow = Math.max(0, cursor.row - cond.cursor_above);
|
|
508
|
+
const endRow = cursor.row;
|
|
509
|
+
const cur = currentLines.slice(startRow, endRow).join('\n');
|
|
510
|
+
const prev = this.prevScreenLines.slice(startRow, endRow).join('\n');
|
|
511
|
+
if (cur !== prev) this.regionLastChangedAt.set(cond.cursor_above, now);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
488
514
|
}
|
|
489
515
|
// Update prevScreenLines for next evaluation's `changed` condition detection.
|
|
490
516
|
this.prevScreenLines = currentLines;
|
|
@@ -500,7 +526,7 @@ export class SpecDriver {
|
|
|
500
526
|
let evState = ev.state;
|
|
501
527
|
const busyHoldMs = this.spec.debounce?.busy_hold_ms ?? BUSY_HOLD_MS;
|
|
502
528
|
if (this.currentStateId === 'busy' && evState.id === 'idle') {
|
|
503
|
-
const ageMs =
|
|
529
|
+
const ageMs = now - this.lastBusyAt;
|
|
504
530
|
if (ageMs < busyHoldMs) {
|
|
505
531
|
// Pin to the last seen busy state directly — currentEval can
|
|
506
532
|
// already be idle at this point (it tracks the previous tick,
|
|
@@ -509,6 +535,24 @@ export class SpecDriver {
|
|
|
509
535
|
evState = this.lastBusyState ?? evState;
|
|
510
536
|
}
|
|
511
537
|
}
|
|
538
|
+
// stable_ms gate: if the matched idle state has a changed:false/stable_ms
|
|
539
|
+
// condition, verify the region has been stable long enough. If not, pin
|
|
540
|
+
// to busy and schedule a re-evaluation when the stable window expires.
|
|
541
|
+
if (evState.id === (this.spec.default_state ?? 'idle') && cursor) {
|
|
542
|
+
const stableConditions = collectChangedConditions(
|
|
543
|
+
this.spec.states.find(s => s.id === evState.id)?.when
|
|
544
|
+
).filter(c => c.changed === false && c.stable_ms != null);
|
|
545
|
+
for (const cond of stableConditions) {
|
|
546
|
+
const lastChanged = this.regionLastChangedAt.get(cond.cursor_above) ?? 0;
|
|
547
|
+
const stableMs = cond.stable_ms!;
|
|
548
|
+
const stableAge = lastChanged > 0 ? now - lastChanged : Infinity;
|
|
549
|
+
if (stableAge < stableMs) {
|
|
550
|
+
evState = this.lastBusyState ?? { id: 'busy', label: 'Generating', title: null };
|
|
551
|
+
this.scheduleBusyExpiry(stableMs - stableAge + 50);
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
512
556
|
// Modal hold: when in a modal state (approval, picker, etc.) a brief
|
|
513
557
|
// busy reading should not interrupt the modal. Claude Code streams
|
|
514
558
|
// body content while the approval modal is visible, causing a spinner
|
|
@@ -538,7 +582,6 @@ export class SpecDriver {
|
|
|
538
582
|
// approval appeared and its hold expires immediately after dismissal,
|
|
539
583
|
// causing a false-idle even though the agent is still generating.
|
|
540
584
|
const postModalGraceMs = busyHoldMs;
|
|
541
|
-
const now = Date.now();
|
|
542
585
|
const recentlyInModal = isModalState(this.currentStateId) || (this.lastModalAt > 0 && now - this.lastModalAt < postModalGraceMs);
|
|
543
586
|
const recentlyLeftModal = !recentlyInModal && this.lastModalExitAt > 0 && now - this.lastModalExitAt < postModalGraceMs;
|
|
544
587
|
// screen_active_hold_ms: suppress idle downshift while the screen is
|
|
@@ -555,7 +598,6 @@ export class SpecDriver {
|
|
|
555
598
|
if (evState.id === 'busy' && completionIdleRule && !recentlyInModal && !recentlyLeftModal && !screenIsActive) {
|
|
556
599
|
const completionKey = matchesCompletionIdleRule(this.spec, ev, screen);
|
|
557
600
|
if (completionKey) {
|
|
558
|
-
const now = Date.now();
|
|
559
601
|
if (completionKey !== this.completionIdleKey) {
|
|
560
602
|
this.completionIdleKey = completionKey;
|
|
561
603
|
this.completionIdleFirstSeenAt = now;
|
|
@@ -227,8 +227,11 @@ function evaluateCondition(
|
|
|
227
227
|
const endRow = cursor.row; // exclusive
|
|
228
228
|
const currentSlice = curLines.slice(startRow, endRow).join('\n');
|
|
229
229
|
const prevSlice = prevLines.slice(startRow, endRow).join('\n');
|
|
230
|
-
const
|
|
231
|
-
|
|
230
|
+
const didChange = currentSlice !== prevSlice;
|
|
231
|
+
// changed:false means "region is currently stable" — stable_ms duration
|
|
232
|
+
// is enforced by the driver, not here.
|
|
233
|
+
const result = cond.changed ? didChange : !didChange;
|
|
234
|
+
trace.push({ kind: 'section', text: `state[${stateId}] changed cond cursor_above=${cond.cursor_above} rows[${startRow},${endRow}) changed=${didChange} expected=${cond.changed} result=${result}` });
|
|
232
235
|
return result;
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -460,7 +460,8 @@ export const SCHEMA_V3 = {
|
|
|
460
460
|
"required": ["cursor_above", "changed"],
|
|
461
461
|
"properties": {
|
|
462
462
|
"cursor_above": { "type": "integer", "minimum": 1 },
|
|
463
|
-
"changed": { "type": "boolean"
|
|
463
|
+
"changed": { "type": "boolean" },
|
|
464
|
+
"stable_ms": { "type": "integer", "minimum": 0 }
|
|
464
465
|
}
|
|
465
466
|
},
|
|
466
467
|
"allCondition": {
|
|
@@ -192,10 +192,13 @@ export interface RegexCondition {
|
|
|
192
192
|
cursor_col_max?: number;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
/** v3 delta condition: N lines above cursor changed vs prevLines.
|
|
195
|
+
/** v3 delta condition: N lines above cursor changed vs prevLines.
|
|
196
|
+
* changed:false + stable_ms: region must be stable for at least N ms. */
|
|
196
197
|
export interface ChangedCondition {
|
|
197
198
|
cursor_above: number;
|
|
198
|
-
changed:
|
|
199
|
+
changed: boolean;
|
|
200
|
+
/** Only for changed:false — region must have been stable for this many ms. */
|
|
201
|
+
stable_ms?: number;
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
/** v3 AND composite. */
|