@lexwdex-org/opencode-dcp 3.4.14 → 3.5.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"auto-prune.d.ts","sourceRoot":"","sources":["../../lib/auto-prune.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE/C,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,QAAQ,GAAG,UAAU,CAAA;AAE/D,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,WAAW,EAAE,CAAA;CACzB;AAUD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAiBlD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO9D;AAwBD,qBAAa,UAAU;IAKf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJ3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAGb,MAAM,EAAE,eAAe,EACxC,GAAG,CAAC,EAAE,MAAM,MAAM;IAKtB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,SAAa,GAAG,aAAa;IAoBvF,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,SAAa,GAAG,WAAW,EAAE,GAAG,IAAI;IAUxE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,SAAa,GAAG,IAAI;IASpD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIpC,OAAO,CAAC,QAAQ;IA6BhB,OAAO,CAAC,KAAK;CAYhB"}
1
+ {"version":3,"file":"auto-prune.d.ts","sourceRoot":"","sources":["../../lib/auto-prune.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE/C,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,QAAQ,GAAG,UAAU,CAAA;AAE/D,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,WAAW,EAAE,CAAA;CACzB;AAUD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAiBlD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAO9D;AAwBD,qBAAa,UAAU;IAKf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJ3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAGb,MAAM,EAAE,eAAe,EACxC,GAAG,CAAC,EAAE,MAAM,MAAM;IAKtB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,SAAa,GAAG,aAAa;IAoBvF,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,SAAa,GAAG,WAAW,EAAE,GAAG,IAAI;IAUxE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,SAAa,GAAG,IAAI;IASpD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIpC,OAAO,CAAC,QAAQ;IA8BhB,OAAO,CAAC,KAAK;CAYhB"}
@@ -8,8 +8,15 @@ export interface ExperimentalConfig {
8
8
  export interface SummarizeConfig {
9
9
  failureCooldownMs: number;
10
10
  }
11
+ export interface AutoPruneSignals {
12
+ topicDrift: boolean;
13
+ volume: boolean;
14
+ idleGap: boolean;
15
+ }
11
16
  export interface AutoPruneConfig {
12
17
  enabled: boolean;
18
+ /** Per-signal switches; only topic changes are enabled by default (LD1). */
19
+ signals: AutoPruneSignals;
13
20
  minMessages: number;
14
21
  volumeThreshold: number;
15
22
  driftThreshold: number;
@@ -42,6 +49,13 @@ interface ValidationError {
42
49
  actual: string;
43
50
  }
44
51
  export declare function validateConfigTypes(config: Record<string, any>): ValidationError[];
52
+ /**
53
+ * SR1 migration hint condition: auto prune is on but the user config predates
54
+ * the per-signal switches. The hint fires on every config load where this
55
+ * holds (same always-fires, stateless mechanism as the compress.* hints — no
56
+ * persistent "one-time" flag).
57
+ */
58
+ export declare function needsSignalsMigrationHint(configData: Record<string, any>): boolean;
45
59
  export declare function getConfig(ctx: PluginInput): PluginConfig;
46
60
  export {};
47
61
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../lib/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,WAAW,QAAQ;IACrB,OAAO,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IAC/B,aAAa,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,eAAe;IAC5B,iBAAiB,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAA;AAErC,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,WAAW,CAAA;IACrB,QAAQ,EAAE,QAAQ,CAAA;IAClB,YAAY,EAAE,kBAAkB,CAAA;IAChC,SAAS,EAAE,eAAe,CAAA;IAC1B,SAAS,EAAE,eAAe,CAAA;IAC1B,IAAI,EAAE,UAAU,CAAA;CACnB;AAID,eAAO,MAAM,kBAAkB,EAAE,eAOhC,CAAA;AAED,eAAO,MAAM,iBAAiB,aAqB5B,CAAA;AAKF,eAAO,MAAM,sBAAsB,aA0CjC,CAAA;AAeF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAEjF;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAI9E;AAED,UAAU,eAAe;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,eAAe,EAAE,CAuJlF;AA2SD,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,YAAY,CAsCxD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../lib/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,WAAW,QAAQ;IACrB,OAAO,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IAC/B,aAAa,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,eAAe;IAC5B,iBAAiB,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC7B,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,4EAA4E;IAC5E,OAAO,EAAE,gBAAgB,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAA;AAErC,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,WAAW,CAAA;IACrB,QAAQ,EAAE,QAAQ,CAAA;IAClB,YAAY,EAAE,kBAAkB,CAAA;IAChC,SAAS,EAAE,eAAe,CAAA;IAC1B,SAAS,EAAE,eAAe,CAAA;IAC1B,IAAI,EAAE,UAAU,CAAA;CACnB;AAID,eAAO,MAAM,kBAAkB,EAAE,eAQhC,CAAA;AAED,eAAO,MAAM,iBAAiB,aAyB5B,CAAA;AAKF,eAAO,MAAM,sBAAsB,aA0CjC,CAAA;AAeF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAEjF;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAI9E;AAED,UAAU,eAAe;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,eAAe,EAAE,CA4KlF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CASlF;AA2UD,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,YAAY,CAsCxD"}
@@ -4,6 +4,7 @@ import type { OpenCodeClient } from "./opencode-client";
4
4
  import type { PromptStore } from "./prompts/store";
5
5
  import type { AutoPruner } from "./auto-prune";
6
6
  import type { PruneService } from "./prune-service";
7
+ import type { RestFireReason } from "./session-boundary";
7
8
  interface CompactionOutput {
8
9
  context: string[];
9
10
  prompt?: string;
@@ -15,18 +16,36 @@ export declare function createChatMessageHandler(autoPruner: AutoPruner): (input
15
16
  sessionID: string;
16
17
  }, _output?: unknown) => Promise<void>;
17
18
  export interface EventHandlerDeps {
18
- client: OpenCodeClient;
19
19
  prune: PruneService;
20
20
  autoPruner: AutoPruner;
21
- config: AutoPruneConfig;
22
21
  logger: Logger;
23
22
  }
23
+ /**
24
+ * Every host event feeds the PruneService first: `session.status` / legacy
25
+ * `session.idle` observations go to the boundary tracker, which classifies
26
+ * at-rest boundaries (quiet window + live probe) and drives both the
27
+ * deferred-prune drain and the at-rest auto-prune listener. This handler only
28
+ * keeps the heuristic pruner's own lifecycle state in sync.
29
+ */
24
30
  export declare function createEventHandler(deps: EventHandlerDeps): (input: {
25
31
  event: {
26
32
  type: string;
27
33
  properties?: Record<string, any>;
28
34
  };
29
35
  }) => Promise<void>;
36
+ export interface AtRestAutoPruneDeps {
37
+ client: OpenCodeClient;
38
+ prune: PruneService;
39
+ autoPruner: AutoPruner;
40
+ config: AutoPruneConfig;
41
+ logger: Logger;
42
+ }
43
+ /**
44
+ * At-rest listener #2: the heuristic auto prune. The `enabled` gate stays at
45
+ * this mount point — a tool-only configuration (auto prune disabled) never
46
+ * registers the listener, so it gains no auto-prune behavior.
47
+ */
48
+ export declare function createAtRestAutoPruneListener(deps: AtRestAutoPruneDeps): (sessionID: string, _reason: RestFireReason) => Promise<void>;
30
49
  export declare function createCommandExecuteHandler(client: OpenCodeClient, prune: PruneService, logger: Logger): (input: {
31
50
  command: string;
32
51
  sessionID: string;
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../lib/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAGnE,UAAU,gBAAgB;IACtB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IACjE,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAAC,CAiBvF;AAeD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,IAC7C,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,UAAU,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAIhF;AAQD,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,KAAK,EAAE,YAAY,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,eAAe,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAiDvC,OAAO;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,CAAA;CAAE,mBAiCrF;AAED,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,IAGV,OAAO;IACH,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CACpB,EACD,UAAU;IAAE,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,KAC/B,OAAO,CAAC,IAAI,CAAC,CAuDnB"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../lib/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGxD,UAAU,gBAAgB;IACtB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IACjE,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,gBAAgB,KAAG,OAAO,CAAC,IAAI,CAAC,CAiBvF;AAeD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,IAC7C,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,UAAU,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAIhF;AAQD,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,YAAY,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IACvC,OAAO;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,CAAA;CAAE,mBAoBrF;AAoDD,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,KAAK,EAAE,YAAY,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,eAAe,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,mBAAmB,IACrD,WAAW,MAAM,EAAE,SAAS,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CAK3E;AAED,wBAAgB,2BAA2B,CACvC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,IAGV,OAAO;IACH,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CACpB,EACD,UAAU;IAAE,KAAK,EAAE,OAAO,EAAE,CAAA;CAAE,KAC/B,OAAO,CAAC,IAAI,CAAC,CAuDnB"}
@@ -1,15 +1,16 @@
1
- import type { SessionActivityTracker } from "./activity";
2
1
  import type { Logger } from "./logger";
3
2
  import type { OpenCodeClient } from "./opencode-client";
3
+ import { SessionBoundaryTracker } from "./session-boundary";
4
4
  import type { SummarizeCoordinator } from "./summarize";
5
+ export { eventSessionID, retrySeconds } from "./session-boundary";
5
6
  /**
6
7
  * How a trigger surface wants the service to react when the session is (or may
7
8
  * be) mid-turn:
8
9
  * - `"defer"`: compaction must never interrupt a running turn. Queue the prune
9
- * and execute it at the next observed turn boundary. Used by the
10
+ * and execute it at the next confirmed at-rest boundary. Used by the
10
11
  * model-invokable tool, which by definition executes mid-turn.
11
12
  * - `"proceed"`: fire at turn boundaries anyway; only stand down on positive
12
- * busy evidence. Used by idle-driven auto prune and the manual command.
13
+ * busy evidence. Used by at-rest-driven auto prune and the manual command.
13
14
  */
14
15
  export type PruneBusyPolicy = "defer" | "proceed";
15
16
  export type PruneOutcome = {
@@ -34,25 +35,22 @@ export interface PruneRequest {
34
35
  export interface PruneServiceDeps {
35
36
  client: OpenCodeClient;
36
37
  summarize: SummarizeCoordinator;
37
- activity: SessionActivityTracker;
38
38
  logger: Logger;
39
+ /** Injectable clock/timers (tests). BOUNDARY_QUIET_MS stays a code constant. */
40
+ now?: () => number;
41
+ setTimer?: (fn: () => void, ms: number) => unknown;
42
+ clearTimer?: (t: unknown) => void;
43
+ /** Injectable probe deadline (tests only; defaults to PROBE_TIMEOUT_MS). */
44
+ probeTimeoutMs?: number;
39
45
  }
40
46
  /** Host event names this service derives safety decisions from. */
41
47
  export type PruneEventName = "session.idle" | "session.status" | "session.compacted" | "session.deleted" | (string & {});
42
- /** Ceil a cooldown to whole seconds for user-facing copy. */
43
- export declare function retrySeconds(retryAfterMs: number): number;
44
- /**
45
- * Resolves the session ID from a host event's properties. Most events carry
46
- * `sessionID` directly, but some (e.g. `session.deleted`) only carry
47
- * `info: Session`. Returns `undefined` when neither is a non-empty string.
48
- */
49
- export declare function eventSessionID(properties?: Record<string, any>): string | undefined;
50
48
  /**
51
49
  * The single compression entry point. Every trigger surface (model tool,
52
50
  * heuristic auto prune, manual command) goes through `request`; the service
53
51
  * owns everything a caller must not have to know: session busy state, the
54
- * never-interrupt-a-running-turn invariant, deferral to the next turn
55
- * boundary, session model resolution, and delegation to the native
52
+ * never-interrupt-a-running-turn invariant, deferral to the next confirmed
53
+ * at-rest boundary, session model resolution, and delegation to the native
56
54
  * summarize coordinator (single-flight + failure cooldown).
57
55
  *
58
56
  * `request` never rejects: every failure mode is a `PruneOutcome`.
@@ -61,26 +59,31 @@ export declare class PruneService {
61
59
  private readonly deferred;
62
60
  private readonly client;
63
61
  private readonly summarize;
64
- private readonly activity;
65
62
  private readonly logger;
63
+ private readonly probeTimeoutMs;
64
+ /** THE single busy/idle event state machine (absorbed the former
65
+ * SessionActivityTracker — no second busy cache may exist). */
66
+ readonly boundary: SessionBoundaryTracker;
66
67
  constructor(deps: PruneServiceDeps);
67
68
  /**
68
- * Feed every host event through here. Drains the deferral queue on
69
- * `session.idle` and forgets queued prunes once a compaction (or the
70
- * session itself) is gone.
69
+ * Feed every host event through here. `session.status` and legacy
70
+ * `session.idle` feed the boundary tracker (quiet window + expiry probe);
71
+ * the deferral queue drains at the confirmed at-rest classification, and
72
+ * queued prunes are forgotten once a compaction (or the session itself)
73
+ * is gone.
71
74
  */
72
75
  observeEvent(type: PruneEventName, properties?: Record<string, any>): void;
73
76
  request(request: PruneRequest): Promise<PruneOutcome>;
74
77
  /** One admission check; `null` means the request may proceed. */
75
78
  private gate;
76
79
  /**
77
- * Executes a queued prune at the idle boundary. The tool already promised
78
- * its caller this prune would run; losing the busy race must not silently
79
- * break that promise, so a busy outcome re-queues for the next idle
80
- * boundary. Every other outcome is terminal: it is logged and never
81
- * retried — new prune demand arrives through new triggers only.
82
- * Execution is re-guarded on every attempt, so re-queueing never violates
83
- * the never-interrupt invariant.
80
+ * Executes a queued prune at the confirmed at-rest boundary. The tool
81
+ * already promised its caller this prune would run; losing the busy race
82
+ * must not silently break that promise, so a busy outcome re-queues for
83
+ * the next at-rest boundary. Every other outcome is terminal: it is
84
+ * logged and never retried — new prune demand arrives through new
85
+ * triggers only. Execution is re-guarded on every attempt, so re-queueing
86
+ * never violates the never-interrupt invariant.
84
87
  */
85
88
  private drainQueued;
86
89
  /**
@@ -90,10 +93,12 @@ export declare class PruneService {
90
93
  private outcomeFor;
91
94
  private admit;
92
95
  /**
93
- * Queries the host's live session status. Returns `null` when the answer
94
- * is unknown (endpoint or SDK method missing, request failed) — callers
95
- * must fail open in that case.
96
+ * THE single live busy probe, reused verbatim by the boundary tracker's
97
+ * expiry check. Bounded by a finite deadline: a never-returning probe
98
+ * resolves `null` within the timeout (fail-open). Returns `null` when the
99
+ * answer is unknown (endpoint or SDK method missing, request failed,
100
+ * timeout) — callers must fail open in that case.
96
101
  */
97
- private isBusyOnServer;
102
+ probeBusy(sessionID: string): Promise<boolean | null>;
98
103
  }
99
104
  //# sourceMappingURL=prune-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prune-service.d.ts","sourceRoot":"","sources":["../../lib/prune-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAA;AAEjD,MAAM,MAAM,YAAY,GAClB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzC,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,eAAe,CAAA;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,SAAS,EAAE,oBAAoB,CAAA;IAC/B,QAAQ,EAAE,sBAAsB,CAAA;IAChC,MAAM,EAAE,MAAM,CAAA;CACjB;AAID,mEAAmE;AACnE,MAAM,MAAM,cAAc,GACpB,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEnB,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS,CAMnF;AAED;;;;;;;;;GASG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,IAAI,EAAE,gBAAgB;IAOlC;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAoBpE,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAmC3D,iEAAiE;IACjE,OAAO,CAAC,IAAI;IAIZ;;;;;;;;OAQG;YACW,WAAW;IAqBzB;;;OAGG;YACW,UAAU;IAaxB,OAAO,CAAC,KAAK;IAUb;;;;OAIG;YACW,cAAc;CAe/B"}
1
+ {"version":3,"file":"prune-service.d.ts","sourceRoot":"","sources":["../../lib/prune-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAEH,sBAAsB,EAGzB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjE;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAA;AAEjD,MAAM,MAAM,YAAY,GAClB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzC,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,eAAe,CAAA;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,SAAS,EAAE,oBAAoB,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAA;IAClD,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACjC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAID,mEAAmE;AACnE,MAAM,MAAM,cAAc,GACpB,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEnB;;;;;;;;;GASG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC;mEAC+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAA;gBAE7B,IAAI,EAAE,gBAAgB;IAqBlC;;;;;;OAMG;IACH,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAwBpE,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAmC3D,iEAAiE;IACjE,OAAO,CAAC,IAAI;IAIZ;;;;;;;;OAQG;YACW,WAAW;IAqBzB;;;OAGG;YACW,UAAU;IAaxB,OAAO,CAAC,KAAK;IAUb;;;;;;OAMG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAqB9D"}
@@ -0,0 +1,90 @@
1
+ import type { Logger } from "./logger";
2
+ /**
3
+ * Per-session boundary phase. The compression boundary is NOT the raw
4
+ * `session.idle` instant: an idle observation opens a quiet window, and only
5
+ * a window that survives its full duration plus a live status probe is
6
+ * classified as AT-REST. Busy/retry evidence at any point cancels the window.
7
+ */
8
+ export type BoundaryPhase = "unknown" | "busy" | "pending-rest" | "at-rest";
9
+ /** Why an at-rest classification fired. `probe-failopen` covers a missing
10
+ * endpoint, an HTTP error, and a timed-out probe — all fail open. */
11
+ export type RestFireReason = "window-expired" | "probe-failopen";
12
+ export type AtRestListener = (sessionID: string, reason: RestFireReason) => void | Promise<void>;
13
+ /** Internal constants — deliberately NOT config: safety semantics, not user
14
+ * preference (design ADR-0004). Clock and timers are injectable for tests. */
15
+ export declare const BOUNDARY_QUIET_MS = 2000;
16
+ export declare const PROBE_TIMEOUT_MS = 2000;
17
+ export interface SessionBoundaryDeps {
18
+ /** THE single busy probe (PruneService.probeBusy), injected — never
19
+ * imported, so this module never depends on prune-service. */
20
+ probeBusy: (sessionID: string) => Promise<boolean | null>;
21
+ logger: Logger;
22
+ now?: () => number;
23
+ setTimer?: (fn: () => void, ms: number) => unknown;
24
+ clearTimer?: (t: unknown) => void;
25
+ }
26
+ /**
27
+ * Classifies turn boundaries as AT-REST or relay-idle. Owns the ONLY
28
+ * per-session busy/idle event state machine (it absorbed the former
29
+ * SessionActivityTracker): exactly one entry per session, LRU-bounded.
30
+ *
31
+ * State model per session:
32
+ * - unknown no evidence (fresh, TTL-decayed, or post-lifecycle); fail-open
33
+ * - busy busy/retry evidence cached; TTL-decays to unknown
34
+ * - pending-rest quiet window armed (BOUNDARY_QUIET_MS); busy/retry cancels
35
+ * - at-rest rest confirmed; onAtRest fired once
36
+ *
37
+ * Idle handling: `pending-rest` + idle -> ignore (dual-publish second leg).
38
+ * `at-rest` + idle -> ignore on status-capable hosts (T1/T2: a real next turn
39
+ * is preceded by a busy/retry status that exits at-rest first). In degrade
40
+ * mode (T3: no status observation ever received) a legacy idle from at-rest
41
+ * RE-ARMS the window — every turn-end idle is a new boundary candidate.
42
+ */
43
+ export declare class SessionBoundaryTracker {
44
+ private readonly sessions;
45
+ private readonly listeners;
46
+ private readonly probeBusy;
47
+ private readonly logger;
48
+ private readonly now;
49
+ private readonly setTimer;
50
+ private readonly clearTimer;
51
+ constructor(deps: SessionBoundaryDeps);
52
+ /** Register an at-rest listener; listeners fire in registration order
53
+ * (1. deferred drain, 2. heuristic auto prune) and are individually
54
+ * error-isolated: a throw or rejection is warn-logged and never blocks
55
+ * later listeners nor escapes into the timer callback. */
56
+ onAtRest(listener: AtRestListener): void;
57
+ /** Primary input: `session.status` event payload status type. */
58
+ observeStatus(sessionID: string | undefined, statusType: unknown): void;
59
+ /** First-class input: legacy `session.idle`. Absorbed by dedup on hosts
60
+ * that dual-publish; the only boundary signal on status-less hosts (T3). */
61
+ observeLegacyIdle(sessionID: string | undefined): void;
62
+ /** Lifecycle: cancel any pending window and forget the session. */
63
+ observeDeleted(sessionID: string | undefined): void;
64
+ /** Lifecycle: compaction ended; reset to unknown so the next idle opens a
65
+ * fresh window. */
66
+ observeCompacted(sessionID: string | undefined): void;
67
+ /** Absorbed busy-evidence cache: `busy` decays to `unknown` after the TTL;
68
+ * the table is LRU-bounded (pending-rest/at-rest entries have no TTL exit
69
+ * and rely on the cap). */
70
+ state(sessionID: string): BoundaryPhase;
71
+ dispose(sessionID: string): void;
72
+ private observeIdle;
73
+ private armWindow;
74
+ private onWindowExpiry;
75
+ private transition;
76
+ private cancelWindow;
77
+ private fireAtRest;
78
+ private entry;
79
+ private refresh;
80
+ private evictIfNeeded;
81
+ }
82
+ /** Ceil a cooldown to whole seconds for user-facing copy. */
83
+ export declare function retrySeconds(retryAfterMs: number): number;
84
+ /**
85
+ * Resolves the session ID from a host event's properties. Most events carry
86
+ * `sessionID` directly, but some (e.g. `session.deleted`) only carry
87
+ * `info: Session`. Returns `undefined` when neither is a non-empty string.
88
+ */
89
+ export declare function eventSessionID(properties?: Record<string, any>): string | undefined;
90
+ //# sourceMappingURL=session-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-boundary.d.ts","sourceRoot":"","sources":["../../lib/session-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEtC;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,SAAS,CAAA;AAE3E;qEACqE;AACrE,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;AAEhE,MAAM,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAEhG;8EAC8E;AAC9E,eAAO,MAAM,iBAAiB,OAAQ,CAAA;AACtC,eAAO,MAAM,gBAAgB,OAAQ,CAAA;AAMrC,MAAM,WAAW,mBAAmB;IAChC;kEAC8D;IAC9D,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IACzD,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAA;IAClD,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAeD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,sBAAsB;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgD;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAClE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;gBAErC,IAAI,EAAE,mBAAmB;IASrC;;;8DAG0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAIxC,iEAAiE;IACjE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI;IAkBvE;gFAC4E;IAC5E,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAKtD,mEAAmE;IACnE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IASnD;uBACmB;IACnB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAgBrD;;+BAE2B;IAC3B,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa;IAavC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIhC,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,SAAS;YAeH,cAAc;IAyC5B,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,KAAK;IAYb,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,aAAa;CAQxB;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS,CAMnF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@lexwdex-org/opencode-dcp",
4
- "version": "3.4.14",
4
+ "version": "3.5.0",
5
5
  "type": "module",
6
6
  "description": "Semantic pruning for OpenCode native context compaction",
7
7
  "main": "./dist/index.js",
@@ -1,22 +0,0 @@
1
- export type ActivityState = "busy" | "idle" | "unknown";
2
- /**
3
- * Tracks per-session busy/idle state from host events so compression can be
4
- * gated on real session activity instead of optimistic assumptions.
5
- *
6
- * State sources (in order of preference):
7
- * - `session.status` events carrying `{ status: { type: "busy" | "idle" } }`
8
- * - `session.idle` events (authoritative turn-boundary signal)
9
- *
10
- * Sessions with no observed events report "unknown"; callers decide what
11
- * fail-open means for their own trigger surface.
12
- */
13
- export declare class SessionActivityTracker {
14
- private readonly sessions;
15
- private readonly now;
16
- constructor(now?: () => number);
17
- observe(type: string, properties?: Record<string, any>): void;
18
- state(sessionID: string): ActivityState;
19
- dropSession(sessionID: string): void;
20
- private set;
21
- }
22
- //# sourceMappingURL=activity.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../lib/activity.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;AAYvD;;;;;;;;;;GAUG;AACH,qBAAa,sBAAsB;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,GAAG,CAAC,EAAE,MAAM,MAAM;IAI9B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAe7D,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa;IAOvC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIpC,OAAO,CAAC,GAAG;CAUd"}