wotann 0.5.39 → 0.5.41

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.
Files changed (149) hide show
  1. package/dist/autopilot/completion-oracle.d.ts +71 -0
  2. package/dist/autopilot/completion-oracle.d.ts.map +1 -1
  3. package/dist/autopilot/completion-oracle.js +69 -2
  4. package/dist/autopilot/completion-oracle.js.map +1 -1
  5. package/dist/context/compaction-floor.d.ts +100 -0
  6. package/dist/context/compaction-floor.d.ts.map +1 -0
  7. package/dist/context/compaction-floor.js +94 -0
  8. package/dist/context/compaction-floor.js.map +1 -0
  9. package/dist/context/compaction.d.ts +27 -1
  10. package/dist/context/compaction.d.ts.map +1 -1
  11. package/dist/context/compaction.js +31 -0
  12. package/dist/context/compaction.js.map +1 -1
  13. package/dist/daemon/kairos-rpc.d.ts +1 -0
  14. package/dist/daemon/kairos-rpc.d.ts.map +1 -1
  15. package/dist/daemon/kairos-rpc.js +24 -0
  16. package/dist/daemon/kairos-rpc.js.map +1 -1
  17. package/dist/daemon/rpc-handlers/ports-rpc.d.ts +100 -0
  18. package/dist/daemon/rpc-handlers/ports-rpc.d.ts.map +1 -0
  19. package/dist/daemon/rpc-handlers/ports-rpc.js +278 -0
  20. package/dist/daemon/rpc-handlers/ports-rpc.js.map +1 -0
  21. package/dist/hooks/agentmemory-event-taxonomy.d.ts +144 -0
  22. package/dist/hooks/agentmemory-event-taxonomy.d.ts.map +1 -0
  23. package/dist/hooks/agentmemory-event-taxonomy.js +85 -0
  24. package/dist/hooks/agentmemory-event-taxonomy.js.map +1 -0
  25. package/dist/hooks/built-in.d.ts.map +1 -1
  26. package/dist/hooks/built-in.js +7 -3
  27. package/dist/hooks/built-in.js.map +1 -1
  28. package/dist/hooks/engine.d.ts +14 -0
  29. package/dist/hooks/engine.d.ts.map +1 -1
  30. package/dist/hooks/engine.js +62 -0
  31. package/dist/hooks/engine.js.map +1 -1
  32. package/dist/index.js +91 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/lib.d.ts +1 -1
  35. package/dist/lib.d.ts.map +1 -1
  36. package/dist/lib.js +1 -1
  37. package/dist/lib.js.map +1 -1
  38. package/dist/memory/injection-scanner.d.ts +78 -0
  39. package/dist/memory/injection-scanner.d.ts.map +1 -0
  40. package/dist/memory/injection-scanner.js +204 -0
  41. package/dist/memory/injection-scanner.js.map +1 -0
  42. package/dist/middleware/output-truncation.d.ts +18 -5
  43. package/dist/middleware/output-truncation.d.ts.map +1 -1
  44. package/dist/middleware/output-truncation.js +73 -1
  45. package/dist/middleware/output-truncation.js.map +1 -1
  46. package/dist/middleware/ttsr.d.ts +79 -3
  47. package/dist/middleware/ttsr.d.ts.map +1 -1
  48. package/dist/middleware/ttsr.js +136 -16
  49. package/dist/middleware/ttsr.js.map +1 -1
  50. package/dist/orchestration/autonomous.d.ts +182 -0
  51. package/dist/orchestration/autonomous.d.ts.map +1 -1
  52. package/dist/orchestration/autonomous.js +288 -1
  53. package/dist/orchestration/autonomous.js.map +1 -1
  54. package/dist/orchestration/ports-bridge.d.ts +325 -0
  55. package/dist/orchestration/ports-bridge.d.ts.map +1 -0
  56. package/dist/orchestration/ports-bridge.js +712 -0
  57. package/dist/orchestration/ports-bridge.js.map +1 -0
  58. package/dist/orchestration/textgrad-refinement.d.ts +123 -0
  59. package/dist/orchestration/textgrad-refinement.d.ts.map +1 -0
  60. package/dist/orchestration/textgrad-refinement.js +111 -0
  61. package/dist/orchestration/textgrad-refinement.js.map +1 -0
  62. package/dist/prompt/engine.d.ts +15 -0
  63. package/dist/prompt/engine.d.ts.map +1 -1
  64. package/dist/prompt/engine.js +20 -7
  65. package/dist/prompt/engine.js.map +1 -1
  66. package/dist/prompt/modules/capabilities.d.ts.map +1 -1
  67. package/dist/prompt/modules/capabilities.js +1 -0
  68. package/dist/prompt/modules/capabilities.js.map +1 -1
  69. package/dist/prompt/modules/caveman.d.ts.map +1 -1
  70. package/dist/prompt/modules/caveman.js +1 -0
  71. package/dist/prompt/modules/caveman.js.map +1 -1
  72. package/dist/prompt/modules/conventions.d.ts.map +1 -1
  73. package/dist/prompt/modules/conventions.js +1 -0
  74. package/dist/prompt/modules/conventions.js.map +1 -1
  75. package/dist/prompt/modules/identity.d.ts.map +1 -1
  76. package/dist/prompt/modules/identity.js +1 -0
  77. package/dist/prompt/modules/identity.js.map +1 -1
  78. package/dist/prompt/modules/index.d.ts +29 -0
  79. package/dist/prompt/modules/index.d.ts.map +1 -1
  80. package/dist/prompt/modules/index.js +51 -9
  81. package/dist/prompt/modules/index.js.map +1 -1
  82. package/dist/prompt/modules/llms-txt.d.ts.map +1 -1
  83. package/dist/prompt/modules/llms-txt.js +2 -4
  84. package/dist/prompt/modules/llms-txt.js.map +1 -1
  85. package/dist/prompt/modules/safety.d.ts.map +1 -1
  86. package/dist/prompt/modules/safety.js +1 -0
  87. package/dist/prompt/modules/safety.js.map +1 -1
  88. package/dist/prompt/modules/security.d.ts.map +1 -1
  89. package/dist/prompt/modules/security.js +1 -0
  90. package/dist/prompt/modules/security.js.map +1 -1
  91. package/dist/prompt/modules/skills.d.ts.map +1 -1
  92. package/dist/prompt/modules/skills.js +1 -0
  93. package/dist/prompt/modules/skills.js.map +1 -1
  94. package/dist/prompt/modules/tools.d.ts.map +1 -1
  95. package/dist/prompt/modules/tools.js +1 -0
  96. package/dist/prompt/modules/tools.js.map +1 -1
  97. package/dist/prompt/modules/user.d.ts.map +1 -1
  98. package/dist/prompt/modules/user.js +1 -0
  99. package/dist/prompt/modules/user.js.map +1 -1
  100. package/dist/providers/credential-pool.d.ts +45 -1
  101. package/dist/providers/credential-pool.d.ts.map +1 -1
  102. package/dist/providers/credential-pool.js +94 -1
  103. package/dist/providers/credential-pool.js.map +1 -1
  104. package/dist/providers/sticky-rotation-wire.d.ts +133 -0
  105. package/dist/providers/sticky-rotation-wire.d.ts.map +1 -0
  106. package/dist/providers/sticky-rotation-wire.js +185 -0
  107. package/dist/providers/sticky-rotation-wire.js.map +1 -0
  108. package/dist/runtime-hooks/ttsr-rule.d.ts +82 -0
  109. package/dist/runtime-hooks/ttsr-rule.d.ts.map +1 -0
  110. package/dist/runtime-hooks/ttsr-rule.js +207 -0
  111. package/dist/runtime-hooks/ttsr-rule.js.map +1 -0
  112. package/dist/runtime-hooks/ttsr-runner.d.ts +129 -0
  113. package/dist/runtime-hooks/ttsr-runner.d.ts.map +1 -0
  114. package/dist/runtime-hooks/ttsr-runner.js +193 -0
  115. package/dist/runtime-hooks/ttsr-runner.js.map +1 -0
  116. package/dist/runtime-hooks/ttsr-scope.d.ts +115 -0
  117. package/dist/runtime-hooks/ttsr-scope.d.ts.map +1 -0
  118. package/dist/runtime-hooks/ttsr-scope.js +378 -0
  119. package/dist/runtime-hooks/ttsr-scope.js.map +1 -0
  120. package/dist/sandbox/unified-exec.d.ts.map +1 -1
  121. package/dist/sandbox/unified-exec.js +6 -1
  122. package/dist/sandbox/unified-exec.js.map +1 -1
  123. package/dist/skills/cli-anything.d.ts.map +1 -1
  124. package/dist/skills/cli-anything.js +4 -1
  125. package/dist/skills/cli-anything.js.map +1 -1
  126. package/dist/skills/limits.d.ts +86 -0
  127. package/dist/skills/limits.d.ts.map +1 -0
  128. package/dist/skills/limits.js +140 -0
  129. package/dist/skills/limits.js.map +1 -0
  130. package/dist/skills/loader.d.ts +9 -0
  131. package/dist/skills/loader.d.ts.map +1 -1
  132. package/dist/skills/loader.js +29 -1
  133. package/dist/skills/loader.js.map +1 -1
  134. package/dist/storage/session-artifacts.d.ts +105 -0
  135. package/dist/storage/session-artifacts.d.ts.map +1 -0
  136. package/dist/storage/session-artifacts.js +198 -0
  137. package/dist/storage/session-artifacts.js.map +1 -0
  138. package/dist/tools/workspace-pack.d.ts +97 -0
  139. package/dist/tools/workspace-pack.d.ts.map +1 -0
  140. package/dist/tools/workspace-pack.js +228 -0
  141. package/dist/tools/workspace-pack.js.map +1 -0
  142. package/dist/tui/composer-history.d.ts +99 -0
  143. package/dist/tui/composer-history.d.ts.map +1 -0
  144. package/dist/tui/composer-history.js +169 -0
  145. package/dist/tui/composer-history.js.map +1 -0
  146. package/dist/ui/components/PromptInput.d.ts.map +1 -1
  147. package/dist/ui/components/PromptInput.js +80 -9
  148. package/dist/ui/components/PromptInput.js.map +1 -1
  149. package/package.json +1 -1
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Sticky-Rotation Wire — integration layer between {@link StickyRotation}
3
+ * and {@link CredentialPool}.
4
+ *
5
+ * WHY A WIRE INSTEAD OF DIRECT EMBEDDING?
6
+ * The integration option we picked is Option B (wrap, not replace). The
7
+ * existing `getCredential()` switch on `rotationStrategy` keeps every
8
+ * pre-wire behaviour intact — sticky-rotation is consulted ONLY when:
9
+ * 1. The feature gate `WOTANN_STICKY_ROTATION=1` is set, OR
10
+ * 2. The caller supplied a sticky config on the pool.
11
+ *
12
+ * When the sticky layer "votes to stay" (returns a current credential),
13
+ * the pool returns that credential without consulting its strategy. When
14
+ * sticky-rotation votes to advance (returns `null` from {@link consult}),
15
+ * the pool falls through to the original strategy switch. This means:
16
+ *
17
+ * - Existing tests, existing behaviours: unchanged.
18
+ * - With the gate ON: hot conversations reuse the same credential for
19
+ * `stickyLimit` calls so Anthropic's prompt cache keeps warming up
20
+ * the same prefix — up to 10x cache savings on the cached portion.
21
+ * - Auth/rate-limit errors call {@link forceRotate} so we don't burn
22
+ * the sticky budget on a credential we already know is unusable.
23
+ *
24
+ * NO TOKEN VALUES ever flow through this wire. We rotate credential IDs
25
+ * (opaque handles); the pool dereferences id -> AccountCredential when
26
+ * the caller is ready. This preserves the Quality Bar #6 security rule.
27
+ *
28
+ * NOT a singleton. One instance per `CredentialPool` per provider. The
29
+ * caller (CredentialPool) owns the lifecycle so test isolation is
30
+ * straightforward — every fresh `CredentialPool` builds its own wire.
31
+ */
32
+ import { StickyRotation } from "./sticky-rotation.js";
33
+ import type { ProviderName } from "../core/types.js";
34
+ /**
35
+ * Resolve the sticky-rotation feature gate from the environment.
36
+ *
37
+ * Truthy values: "1", "true" (case-insensitive). Anything else is OFF.
38
+ * The lookup happens at call-time, NOT at module load, so tests can
39
+ * mutate `process.env.WOTANN_STICKY_ROTATION` between cases without
40
+ * needing module reload tricks.
41
+ */
42
+ export declare function isStickyRotationEnabled(): boolean;
43
+ /**
44
+ * Resolve the sticky-rotation debug gate. When ON, the wire emits debug
45
+ * lines to stderr via `process.stderr.write` (the existing provider
46
+ * diagnostic channel — see codex-adapter.ts:339).
47
+ */
48
+ export declare function isStickyRotationDebugEnabled(): boolean;
49
+ export interface StickyWireConfig {
50
+ /** Override the default sticky limit (10). Same semantics as StickyRotation. */
51
+ readonly stickyLimit?: number;
52
+ /**
53
+ * Force the wire ON even if the env gate is OFF. Used by callers that
54
+ * want sticky behaviour unconditionally (e.g. integration tests).
55
+ */
56
+ readonly forceEnabled?: boolean;
57
+ }
58
+ /**
59
+ * Result of {@link StickyRotationWire.consult}.
60
+ *
61
+ * - `kind: "stay"` — sticky-rotation votes to reuse the current id;
62
+ * the pool should return that id's credential.
63
+ * - `kind: "advance"` — sticky-rotation has exhausted its budget OR no
64
+ * active rotation exists yet for the provider;
65
+ * the pool should defer to its strategy switch.
66
+ * - `kind: "no-id"` — the resolved id is no longer in the pool (it was
67
+ * removed / blacklisted); the pool should advance.
68
+ */
69
+ export type StickyConsultation = {
70
+ readonly kind: "stay";
71
+ readonly credentialId: string;
72
+ readonly usesRemaining: number;
73
+ } | {
74
+ readonly kind: "advance";
75
+ } | {
76
+ readonly kind: "no-id";
77
+ };
78
+ /**
79
+ * Per-provider sticky-rotation state. Built lazily — we only create a
80
+ * StickyRotation for a provider when the wire is actively consulted for
81
+ * it. Keeps the memory footprint zero for providers that never opt-in.
82
+ */
83
+ export declare class StickyRotationWire {
84
+ private readonly rotators;
85
+ private readonly config;
86
+ constructor(config?: StickyWireConfig);
87
+ /**
88
+ * Is the sticky layer active for THIS call? Combines the env gate and
89
+ * the `forceEnabled` config override. Evaluated per-call so tests can
90
+ * flip the gate between cases.
91
+ */
92
+ isActive(): boolean;
93
+ /**
94
+ * Consult the sticky rotator for a provider.
95
+ *
96
+ * The caller passes the currently-available credential IDs (already
97
+ * filtered for blacklist / rate-limit / billing) in their preferred
98
+ * order. The wire makes one of three decisions:
99
+ *
100
+ * 1. Sticky is inactive -> returns `advance` (caller's strategy wins)
101
+ * 2. No active rotator yet -> builds one from `candidateIds` and returns `advance`
102
+ * 3. Active rotator exists -> {@link StickyRotation.next} returns an id;
103
+ * if id is still in `candidateIds`, vote `stay`;
104
+ * otherwise vote `no-id` (caller should advance)
105
+ *
106
+ * IMPORTANT: This method calls `next()` on the rotator (i.e. it
107
+ * consumes a use). Callers that want to inspect without consuming
108
+ * should use {@link peek} instead.
109
+ */
110
+ consult(provider: ProviderName, candidateIds: readonly string[]): StickyConsultation;
111
+ /**
112
+ * Manually force the rotator for a provider to advance. Intended for
113
+ * auth-error / 429 / 503 hits where the current credential is known
114
+ * bad — we don't want to keep burning the sticky budget on it.
115
+ *
116
+ * No-op if there is no rotator for this provider yet.
117
+ */
118
+ forceRotate(provider: ProviderName): void;
119
+ /**
120
+ * Snapshot diagnostic info for a provider — used by tests / status UI.
121
+ * Returns `null` when no rotator has been built for the provider.
122
+ */
123
+ stats(provider: ProviderName): ReturnType<StickyRotation["stats"]> | null;
124
+ /**
125
+ * Drop the rotator for a provider (e.g. when the credentials list
126
+ * changes underneath the wire and we want to rebuild fresh on next
127
+ * consult). Quality Bar #19 — explicit reset rather than a dead
128
+ * zombie instance.
129
+ */
130
+ reset(provider?: ProviderName): void;
131
+ private debug;
132
+ }
133
+ //# sourceMappingURL=sticky-rotation-wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sticky-rotation-wire.d.ts","sourceRoot":"","sources":["../../src/providers/sticky-rotation-wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAKjD;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAKtD;AAED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACxF;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/B;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgD;IACzE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;gBAE9B,MAAM,CAAC,EAAE,gBAAgB;IAIrC;;;;OAIG;IACH,QAAQ,IAAI,OAAO;IAInB;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB;IA8CpF;;;;;;OAMG;IACH,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAOzC;;;OAGG;IACH,KAAK,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI;IAKzE;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI;IAQpC,OAAO,CAAC,KAAK;CAQd"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Sticky-Rotation Wire — integration layer between {@link StickyRotation}
3
+ * and {@link CredentialPool}.
4
+ *
5
+ * WHY A WIRE INSTEAD OF DIRECT EMBEDDING?
6
+ * The integration option we picked is Option B (wrap, not replace). The
7
+ * existing `getCredential()` switch on `rotationStrategy` keeps every
8
+ * pre-wire behaviour intact — sticky-rotation is consulted ONLY when:
9
+ * 1. The feature gate `WOTANN_STICKY_ROTATION=1` is set, OR
10
+ * 2. The caller supplied a sticky config on the pool.
11
+ *
12
+ * When the sticky layer "votes to stay" (returns a current credential),
13
+ * the pool returns that credential without consulting its strategy. When
14
+ * sticky-rotation votes to advance (returns `null` from {@link consult}),
15
+ * the pool falls through to the original strategy switch. This means:
16
+ *
17
+ * - Existing tests, existing behaviours: unchanged.
18
+ * - With the gate ON: hot conversations reuse the same credential for
19
+ * `stickyLimit` calls so Anthropic's prompt cache keeps warming up
20
+ * the same prefix — up to 10x cache savings on the cached portion.
21
+ * - Auth/rate-limit errors call {@link forceRotate} so we don't burn
22
+ * the sticky budget on a credential we already know is unusable.
23
+ *
24
+ * NO TOKEN VALUES ever flow through this wire. We rotate credential IDs
25
+ * (opaque handles); the pool dereferences id -> AccountCredential when
26
+ * the caller is ready. This preserves the Quality Bar #6 security rule.
27
+ *
28
+ * NOT a singleton. One instance per `CredentialPool` per provider. The
29
+ * caller (CredentialPool) owns the lifecycle so test isolation is
30
+ * straightforward — every fresh `CredentialPool` builds its own wire.
31
+ */
32
+ import { StickyRotation } from "./sticky-rotation.js";
33
+ /**
34
+ * Resolve the sticky-rotation feature gate from the environment.
35
+ *
36
+ * Truthy values: "1", "true" (case-insensitive). Anything else is OFF.
37
+ * The lookup happens at call-time, NOT at module load, so tests can
38
+ * mutate `process.env.WOTANN_STICKY_ROTATION` between cases without
39
+ * needing module reload tricks.
40
+ */
41
+ export function isStickyRotationEnabled() {
42
+ const raw = process.env["WOTANN_STICKY_ROTATION"];
43
+ if (raw === undefined)
44
+ return false;
45
+ const normalised = raw.toLowerCase();
46
+ return normalised === "1" || normalised === "true";
47
+ }
48
+ /**
49
+ * Resolve the sticky-rotation debug gate. When ON, the wire emits debug
50
+ * lines to stderr via `process.stderr.write` (the existing provider
51
+ * diagnostic channel — see codex-adapter.ts:339).
52
+ */
53
+ export function isStickyRotationDebugEnabled() {
54
+ const raw = process.env["WOTANN_STICKY_ROTATION_DEBUG"];
55
+ if (raw === undefined)
56
+ return false;
57
+ const normalised = raw.toLowerCase();
58
+ return normalised === "1" || normalised === "true";
59
+ }
60
+ /**
61
+ * Per-provider sticky-rotation state. Built lazily — we only create a
62
+ * StickyRotation for a provider when the wire is actively consulted for
63
+ * it. Keeps the memory footprint zero for providers that never opt-in.
64
+ */
65
+ export class StickyRotationWire {
66
+ rotators = new Map();
67
+ config;
68
+ constructor(config) {
69
+ this.config = config ?? {};
70
+ }
71
+ /**
72
+ * Is the sticky layer active for THIS call? Combines the env gate and
73
+ * the `forceEnabled` config override. Evaluated per-call so tests can
74
+ * flip the gate between cases.
75
+ */
76
+ isActive() {
77
+ return this.config.forceEnabled === true || isStickyRotationEnabled();
78
+ }
79
+ /**
80
+ * Consult the sticky rotator for a provider.
81
+ *
82
+ * The caller passes the currently-available credential IDs (already
83
+ * filtered for blacklist / rate-limit / billing) in their preferred
84
+ * order. The wire makes one of three decisions:
85
+ *
86
+ * 1. Sticky is inactive -> returns `advance` (caller's strategy wins)
87
+ * 2. No active rotator yet -> builds one from `candidateIds` and returns `advance`
88
+ * 3. Active rotator exists -> {@link StickyRotation.next} returns an id;
89
+ * if id is still in `candidateIds`, vote `stay`;
90
+ * otherwise vote `no-id` (caller should advance)
91
+ *
92
+ * IMPORTANT: This method calls `next()` on the rotator (i.e. it
93
+ * consumes a use). Callers that want to inspect without consuming
94
+ * should use {@link peek} instead.
95
+ */
96
+ consult(provider, candidateIds) {
97
+ if (!this.isActive())
98
+ return { kind: "advance" };
99
+ if (candidateIds.length === 0)
100
+ return { kind: "advance" };
101
+ let rotator = this.rotators.get(provider);
102
+ if (!rotator) {
103
+ // First consult for this provider — build a fresh rotator from
104
+ // the current candidates. We don't consume a use on the build
105
+ // call (return `advance` so the caller's strategy chooses the
106
+ // first credential normally) because the rotator's natural
107
+ // contract is "call next() to get a credential". Mixing build +
108
+ // consume would cause the first credential to skip rotation
109
+ // accounting entirely.
110
+ rotator = new StickyRotation({
111
+ items: candidateIds,
112
+ stickyLimit: this.config.stickyLimit,
113
+ rotationKey: `cred-pool:${provider}`,
114
+ });
115
+ this.rotators.set(provider, rotator);
116
+ const id = rotator.next();
117
+ // Trace the build path so operators can confirm sticky kicked in.
118
+ this.debug(`built rotator for provider=${provider} items=${candidateIds.length} initial=${id}`);
119
+ // The initial id IS valid (it came from candidateIds) — vote stay.
120
+ // If it isn't in candidateIds (shouldn't happen because we built
121
+ // from candidateIds), fall through to no-id.
122
+ if (candidateIds.includes(id)) {
123
+ const stats = rotator.stats();
124
+ return { kind: "stay", credentialId: id, usesRemaining: stats.usesRemaining };
125
+ }
126
+ return { kind: "no-id" };
127
+ }
128
+ const id = rotator.next();
129
+ if (!candidateIds.includes(id)) {
130
+ // The id we'd reuse is no longer healthy (blacklisted / rate-limited
131
+ // / removed). Tell the caller to advance via their strategy.
132
+ this.debug(`id ${id} not in candidates (provider=${provider}); advancing`);
133
+ return { kind: "no-id" };
134
+ }
135
+ const stats = rotator.stats();
136
+ this.debug(`reused ${id} for provider=${provider} (uses remaining: ${stats.usesRemaining})`);
137
+ return { kind: "stay", credentialId: id, usesRemaining: stats.usesRemaining };
138
+ }
139
+ /**
140
+ * Manually force the rotator for a provider to advance. Intended for
141
+ * auth-error / 429 / 503 hits where the current credential is known
142
+ * bad — we don't want to keep burning the sticky budget on it.
143
+ *
144
+ * No-op if there is no rotator for this provider yet.
145
+ */
146
+ forceRotate(provider) {
147
+ const rotator = this.rotators.get(provider);
148
+ if (!rotator)
149
+ return;
150
+ rotator.forceRotate();
151
+ this.debug(`forceRotate fired for provider=${provider}`);
152
+ }
153
+ /**
154
+ * Snapshot diagnostic info for a provider — used by tests / status UI.
155
+ * Returns `null` when no rotator has been built for the provider.
156
+ */
157
+ stats(provider) {
158
+ const rotator = this.rotators.get(provider);
159
+ return rotator ? rotator.stats() : null;
160
+ }
161
+ /**
162
+ * Drop the rotator for a provider (e.g. when the credentials list
163
+ * changes underneath the wire and we want to rebuild fresh on next
164
+ * consult). Quality Bar #19 — explicit reset rather than a dead
165
+ * zombie instance.
166
+ */
167
+ reset(provider) {
168
+ if (provider) {
169
+ this.rotators.delete(provider);
170
+ }
171
+ else {
172
+ this.rotators.clear();
173
+ }
174
+ }
175
+ debug(line) {
176
+ if (!isStickyRotationDebugEnabled())
177
+ return;
178
+ // Match the existing provider debug pattern (codex-adapter.ts:339):
179
+ // write to stderr so it never pollutes stdout / chat. Prefix is
180
+ // grep-friendly: `WOTANN_STICKY_ROTATION_DEBUG=1 wotann 2>diag.log`
181
+ // then `grep '\[sticky\]' diag.log`.
182
+ process.stderr.write(`[sticky] ${line}\n`);
183
+ }
184
+ }
185
+ //# sourceMappingURL=sticky-rotation-wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sticky-rotation-wire.js","sourceRoot":"","sources":["../../src/providers/sticky-rotation-wire.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * TTSR Rule - canonical shape + factory + markdown front-matter parser.
3
+ *
4
+ * Mirrors the oh-my-pi `Rule` capability (rule.ts) and the canonical
5
+ * `ttsr-condition` / `ttsr-scope` front-matter keys, but lives in
6
+ * WOTANN as a tiny, dependency-free runtime primitive.
7
+ *
8
+ * A `TTSRRule` is the structured form of an interrupt: when a
9
+ * streaming model produces a token that matches `condition` AND the
10
+ * current stream context matches `scope`, the orchestrator aborts
11
+ * the turn and injects `body` as a system reminder.
12
+ *
13
+ * This file is PURE. It does not touch streams; see ttsr-runner.ts
14
+ * for the per-token evaluation logic and ttsr.ts (middleware) for
15
+ * the orchestrator-facing wire.
16
+ */
17
+ import { type ScopeFilter } from "./ttsr-scope.js";
18
+ /**
19
+ * A single TTSR rule.
20
+ *
21
+ * - `id` is the stable identity used for one-shot bookkeeping. It
22
+ * should be globally unique within a session (use file path,
23
+ * slug, or hash).
24
+ * - `condition` is the RegExp tested against the streaming buffer
25
+ * for the matching scope. Always non-global so `RegExp.test`
26
+ * stays stateless.
27
+ * - `scope` restricts which stream kinds + tool calls the rule
28
+ * listens to (see `ttsr-scope.ts`). Default permissive value is
29
+ * `"any"` if the caller does not specify.
30
+ * - `body` is the injection text. The orchestrator wraps this in
31
+ * a `<system-reminder>` block at retry time.
32
+ * - `oneShot` (optional, default true). When true, the rule fires
33
+ * AT MOST once per session - the runner enforces this via
34
+ * `acknowledgeFired`. When false, the rule may fire repeatedly.
35
+ */
36
+ export interface TTSRRule {
37
+ readonly id: string;
38
+ readonly condition: RegExp;
39
+ readonly scope: ScopeFilter;
40
+ readonly body: string;
41
+ readonly oneShot?: boolean;
42
+ }
43
+ /**
44
+ * Options to `createTTSRRule`. Mirrors `TTSRRule` but accepts string
45
+ * conditions and an optional default scope.
46
+ */
47
+ export interface CreateTTSRRuleOptions {
48
+ readonly id: string;
49
+ readonly condition: RegExp | string;
50
+ readonly scope?: ScopeFilter;
51
+ readonly body: string;
52
+ readonly oneShot?: boolean;
53
+ /** Regex flags when `condition` is a string. Default: no flags. */
54
+ readonly flags?: string;
55
+ }
56
+ /**
57
+ * Build a `TTSRRule` from a partial input, validating every field.
58
+ *
59
+ * Throws on invalid input - TTSR rules are not the place to fail
60
+ * silently. A malformed regex, an empty body, an unreachable scope,
61
+ * or an empty id all indicate a configuration bug that the caller
62
+ * needs to fix.
63
+ */
64
+ export declare function createTTSRRule(opts: CreateTTSRRuleOptions): TTSRRule;
65
+ /**
66
+ * Parse a markdown rule file into a TTSRRule, or `null` if the text
67
+ * does not declare TTSR front-matter (no `---` block, or no
68
+ * `ttsr-condition` key).
69
+ *
70
+ * `fallbackId` is used when the front-matter omits an `id`. Typical
71
+ * callers pass the source filename.
72
+ *
73
+ * Validation errors throw - same contract as `createTTSRRule`.
74
+ */
75
+ export declare function parseRuleFromMarkdown(text: string, fallbackId?: string): TTSRRule | null;
76
+ /**
77
+ * Round-trip helper: serialise a TTSRRule back to markdown
78
+ * front-matter. Used by the runner for `mem_save` snapshots and
79
+ * by tests for parse/serialise symmetry.
80
+ */
81
+ export declare function serializeRuleToMarkdown(rule: TTSRRule): string;
82
+ //# sourceMappingURL=ttsr-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttsr-rule.d.ts","sourceRoot":"","sources":["../../src/runtime-hooks/ttsr-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,QAAQ,CA4CpE;AA6GD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,QAAQ,GAAG,IAAI,CA+BpF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAe9D"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * TTSR Rule - canonical shape + factory + markdown front-matter parser.
3
+ *
4
+ * Mirrors the oh-my-pi `Rule` capability (rule.ts) and the canonical
5
+ * `ttsr-condition` / `ttsr-scope` front-matter keys, but lives in
6
+ * WOTANN as a tiny, dependency-free runtime primitive.
7
+ *
8
+ * A `TTSRRule` is the structured form of an interrupt: when a
9
+ * streaming model produces a token that matches `condition` AND the
10
+ * current stream context matches `scope`, the orchestrator aborts
11
+ * the turn and injects `body` as a system reminder.
12
+ *
13
+ * This file is PURE. It does not touch streams; see ttsr-runner.ts
14
+ * for the per-token evaluation logic and ttsr.ts (middleware) for
15
+ * the orchestrator-facing wire.
16
+ */
17
+ import { isReachableScope } from "./ttsr-scope.js";
18
+ /**
19
+ * Build a `TTSRRule` from a partial input, validating every field.
20
+ *
21
+ * Throws on invalid input - TTSR rules are not the place to fail
22
+ * silently. A malformed regex, an empty body, an unreachable scope,
23
+ * or an empty id all indicate a configuration bug that the caller
24
+ * needs to fix.
25
+ */
26
+ export function createTTSRRule(opts) {
27
+ const id = (opts.id ?? "").trim();
28
+ if (id.length === 0) {
29
+ throw new Error("TTSR rule id must be a non-empty string");
30
+ }
31
+ const body = opts.body ?? "";
32
+ if (typeof body !== "string" || body.trim().length === 0) {
33
+ throw new Error(`TTSR rule '${id}' must have a non-empty body`);
34
+ }
35
+ // Normalize condition: regex passthrough; string compiles to RegExp.
36
+ // We strip the `g` flag because TTSR uses `RegExp.test`, and a sticky
37
+ // global state would make matches non-idempotent across calls.
38
+ let condition;
39
+ if (opts.condition instanceof RegExp) {
40
+ const flags = opts.condition.flags.replace(/g/g, "");
41
+ condition = new RegExp(opts.condition.source, flags);
42
+ }
43
+ else if (typeof opts.condition === "string") {
44
+ const flags = (opts.flags ?? "").replace(/g/g, "");
45
+ try {
46
+ condition = new RegExp(opts.condition, flags);
47
+ }
48
+ catch (err) {
49
+ const msg = err instanceof Error ? err.message : String(err);
50
+ throw new Error(`TTSR rule '${id}' has invalid regex: ${msg}`);
51
+ }
52
+ }
53
+ else {
54
+ throw new Error(`TTSR rule '${id}' condition must be a RegExp or non-empty string`);
55
+ }
56
+ const scope = opts.scope ?? "any";
57
+ if (!isReachableScope(scope)) {
58
+ throw new Error(`TTSR rule '${id}' scope is unreachable (no tokens matched): '${scope}'`);
59
+ }
60
+ const oneShot = opts.oneShot ?? true;
61
+ return {
62
+ id,
63
+ condition,
64
+ scope,
65
+ body,
66
+ oneShot,
67
+ };
68
+ }
69
+ /**
70
+ * Slug a markdown filename or first-line heading into a rule id.
71
+ *
72
+ * Lowercases, replaces non-word runs with `-`, trims leading/trailing
73
+ * separators. Returns empty string for empty input.
74
+ */
75
+ function slugify(s) {
76
+ return s
77
+ .toLowerCase()
78
+ .replace(/\.(md|mdc|markdown)$/i, "")
79
+ .replace(/[^\w]+/g, "-")
80
+ .replace(/^-+|-+$/g, "");
81
+ }
82
+ /**
83
+ * Strip `/.../flags` regex literal wrappers if present, returning
84
+ * { source, flags }. If the value does not look like a regex literal
85
+ * we pass it through as the source with empty flags.
86
+ */
87
+ function parseRegexLiteral(value) {
88
+ const trimmed = value.trim();
89
+ if (trimmed.length === 0)
90
+ return undefined;
91
+ const m = /^\/(.+)\/([a-z]*)$/i.exec(trimmed);
92
+ if (m && typeof m[1] === "string") {
93
+ return { source: m[1], flags: m[2] ?? "" };
94
+ }
95
+ return { source: trimmed, flags: "" };
96
+ }
97
+ /**
98
+ * Tiny YAML-ish front-matter reader. We do NOT depend on a YAML lib.
99
+ * The keys we accept are simple scalars (string / boolean) and we
100
+ * trim/unquote them by hand. Anything that looks like a list value
101
+ * (joined with `, `) is preserved as a single string and forwarded
102
+ * to the scope tokenizer which already understands commas.
103
+ */
104
+ function readFrontMatter(text) {
105
+ // Markdown front-matter delimited by `---` on its own line.
106
+ const m = /^---\s*\n([\s\S]*?)\n---\s*\n?/.exec(text);
107
+ if (!m) {
108
+ return undefined;
109
+ }
110
+ const head = m[1] ?? "";
111
+ const body = text.slice(m[0].length).trim();
112
+ const map = {};
113
+ for (const rawLine of head.split(/\r?\n/)) {
114
+ const line = rawLine.trim();
115
+ if (line.length === 0 || line.startsWith("#"))
116
+ continue;
117
+ const idx = line.indexOf(":");
118
+ if (idx === -1)
119
+ continue;
120
+ const key = line.slice(0, idx).trim().toLowerCase();
121
+ let val = line.slice(idx + 1).trim();
122
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
123
+ val = val.slice(1, -1);
124
+ }
125
+ map[key] = val;
126
+ }
127
+ const conditionVal = map["ttsr-condition"] ??
128
+ map["ttsr_condition"] ??
129
+ map["condition"] ??
130
+ map["ttsr_trigger"] ??
131
+ undefined;
132
+ const scopeVal = map["ttsr-scope"] ?? map["ttsr_scope"] ?? map["scope"] ?? undefined;
133
+ const idVal = map["id"] ?? map["name"] ?? undefined;
134
+ const oneShotRaw = map["one-shot"] ?? map["oneshot"] ?? map["one_shot"];
135
+ const oneShot = typeof oneShotRaw === "string"
136
+ ? !["false", "no", "0", "off"].includes(oneShotRaw.toLowerCase())
137
+ : undefined;
138
+ return {
139
+ ...(idVal !== undefined ? { id: idVal } : {}),
140
+ ...(oneShot !== undefined ? { oneShot } : {}),
141
+ ...(conditionVal !== undefined ? { condition: conditionVal } : {}),
142
+ ...(scopeVal !== undefined ? { scope: scopeVal } : {}),
143
+ body,
144
+ };
145
+ }
146
+ /**
147
+ * Parse a markdown rule file into a TTSRRule, or `null` if the text
148
+ * does not declare TTSR front-matter (no `---` block, or no
149
+ * `ttsr-condition` key).
150
+ *
151
+ * `fallbackId` is used when the front-matter omits an `id`. Typical
152
+ * callers pass the source filename.
153
+ *
154
+ * Validation errors throw - same contract as `createTTSRRule`.
155
+ */
156
+ export function parseRuleFromMarkdown(text, fallbackId = "") {
157
+ if (typeof text !== "string" || text.trim().length === 0) {
158
+ return null;
159
+ }
160
+ const fm = readFrontMatter(text);
161
+ if (!fm)
162
+ return null;
163
+ if (typeof fm.condition !== "string" || fm.condition.trim().length === 0) {
164
+ return null;
165
+ }
166
+ if (typeof fm.body !== "string" || fm.body.trim().length === 0) {
167
+ return null;
168
+ }
169
+ const literal = parseRegexLiteral(fm.condition);
170
+ if (!literal)
171
+ return null;
172
+ const idSource = fm.id ?? fallbackId;
173
+ const id = slugify(idSource);
174
+ if (id.length === 0) {
175
+ throw new Error("TTSR markdown rule needs an `id:` in front-matter or a non-empty fallbackId");
176
+ }
177
+ return createTTSRRule({
178
+ id,
179
+ condition: literal.source,
180
+ flags: literal.flags,
181
+ scope: fm.scope ?? "any",
182
+ body: fm.body,
183
+ ...(fm.oneShot !== undefined ? { oneShot: fm.oneShot } : {}),
184
+ });
185
+ }
186
+ /**
187
+ * Round-trip helper: serialise a TTSRRule back to markdown
188
+ * front-matter. Used by the runner for `mem_save` snapshots and
189
+ * by tests for parse/serialise symmetry.
190
+ */
191
+ export function serializeRuleToMarkdown(rule) {
192
+ const flags = rule.condition.flags;
193
+ const source = rule.condition.source;
194
+ const conditionLiteral = flags ? `/${source}/${flags}` : `/${source}/`;
195
+ const lines = [
196
+ "---",
197
+ `id: ${rule.id}`,
198
+ `ttsr-condition: ${conditionLiteral}`,
199
+ `ttsr-scope: ${rule.scope}`,
200
+ ];
201
+ if (rule.oneShot === false) {
202
+ lines.push("one-shot: false");
203
+ }
204
+ lines.push("---", "", rule.body);
205
+ return lines.join("\n");
206
+ }
207
+ //# sourceMappingURL=ttsr-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttsr-rule.js","sourceRoot":"","sources":["../../src/runtime-hooks/ttsr-rule.ts"],"names":[],"mappings":"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"}