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.
- package/dist/autopilot/completion-oracle.d.ts +71 -0
- package/dist/autopilot/completion-oracle.d.ts.map +1 -1
- package/dist/autopilot/completion-oracle.js +69 -2
- package/dist/autopilot/completion-oracle.js.map +1 -1
- package/dist/context/compaction-floor.d.ts +100 -0
- package/dist/context/compaction-floor.d.ts.map +1 -0
- package/dist/context/compaction-floor.js +94 -0
- package/dist/context/compaction-floor.js.map +1 -0
- package/dist/context/compaction.d.ts +27 -1
- package/dist/context/compaction.d.ts.map +1 -1
- package/dist/context/compaction.js +31 -0
- package/dist/context/compaction.js.map +1 -1
- package/dist/daemon/kairos-rpc.d.ts +1 -0
- package/dist/daemon/kairos-rpc.d.ts.map +1 -1
- package/dist/daemon/kairos-rpc.js +24 -0
- package/dist/daemon/kairos-rpc.js.map +1 -1
- package/dist/daemon/rpc-handlers/ports-rpc.d.ts +100 -0
- package/dist/daemon/rpc-handlers/ports-rpc.d.ts.map +1 -0
- package/dist/daemon/rpc-handlers/ports-rpc.js +278 -0
- package/dist/daemon/rpc-handlers/ports-rpc.js.map +1 -0
- package/dist/hooks/agentmemory-event-taxonomy.d.ts +144 -0
- package/dist/hooks/agentmemory-event-taxonomy.d.ts.map +1 -0
- package/dist/hooks/agentmemory-event-taxonomy.js +85 -0
- package/dist/hooks/agentmemory-event-taxonomy.js.map +1 -0
- package/dist/hooks/built-in.d.ts.map +1 -1
- package/dist/hooks/built-in.js +7 -3
- package/dist/hooks/built-in.js.map +1 -1
- package/dist/hooks/engine.d.ts +14 -0
- package/dist/hooks/engine.d.ts.map +1 -1
- package/dist/hooks/engine.js +62 -0
- package/dist/hooks/engine.js.map +1 -1
- package/dist/index.js +91 -2
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/memory/injection-scanner.d.ts +78 -0
- package/dist/memory/injection-scanner.d.ts.map +1 -0
- package/dist/memory/injection-scanner.js +204 -0
- package/dist/memory/injection-scanner.js.map +1 -0
- package/dist/middleware/output-truncation.d.ts +18 -5
- package/dist/middleware/output-truncation.d.ts.map +1 -1
- package/dist/middleware/output-truncation.js +73 -1
- package/dist/middleware/output-truncation.js.map +1 -1
- package/dist/middleware/ttsr.d.ts +79 -3
- package/dist/middleware/ttsr.d.ts.map +1 -1
- package/dist/middleware/ttsr.js +136 -16
- package/dist/middleware/ttsr.js.map +1 -1
- package/dist/orchestration/autonomous.d.ts +182 -0
- package/dist/orchestration/autonomous.d.ts.map +1 -1
- package/dist/orchestration/autonomous.js +288 -1
- package/dist/orchestration/autonomous.js.map +1 -1
- package/dist/orchestration/ports-bridge.d.ts +325 -0
- package/dist/orchestration/ports-bridge.d.ts.map +1 -0
- package/dist/orchestration/ports-bridge.js +712 -0
- package/dist/orchestration/ports-bridge.js.map +1 -0
- package/dist/orchestration/textgrad-refinement.d.ts +123 -0
- package/dist/orchestration/textgrad-refinement.d.ts.map +1 -0
- package/dist/orchestration/textgrad-refinement.js +111 -0
- package/dist/orchestration/textgrad-refinement.js.map +1 -0
- package/dist/prompt/engine.d.ts +15 -0
- package/dist/prompt/engine.d.ts.map +1 -1
- package/dist/prompt/engine.js +20 -7
- package/dist/prompt/engine.js.map +1 -1
- package/dist/prompt/modules/capabilities.d.ts.map +1 -1
- package/dist/prompt/modules/capabilities.js +1 -0
- package/dist/prompt/modules/capabilities.js.map +1 -1
- package/dist/prompt/modules/caveman.d.ts.map +1 -1
- package/dist/prompt/modules/caveman.js +1 -0
- package/dist/prompt/modules/caveman.js.map +1 -1
- package/dist/prompt/modules/conventions.d.ts.map +1 -1
- package/dist/prompt/modules/conventions.js +1 -0
- package/dist/prompt/modules/conventions.js.map +1 -1
- package/dist/prompt/modules/identity.d.ts.map +1 -1
- package/dist/prompt/modules/identity.js +1 -0
- package/dist/prompt/modules/identity.js.map +1 -1
- package/dist/prompt/modules/index.d.ts +29 -0
- package/dist/prompt/modules/index.d.ts.map +1 -1
- package/dist/prompt/modules/index.js +51 -9
- package/dist/prompt/modules/index.js.map +1 -1
- package/dist/prompt/modules/llms-txt.d.ts.map +1 -1
- package/dist/prompt/modules/llms-txt.js +2 -4
- package/dist/prompt/modules/llms-txt.js.map +1 -1
- package/dist/prompt/modules/safety.d.ts.map +1 -1
- package/dist/prompt/modules/safety.js +1 -0
- package/dist/prompt/modules/safety.js.map +1 -1
- package/dist/prompt/modules/security.d.ts.map +1 -1
- package/dist/prompt/modules/security.js +1 -0
- package/dist/prompt/modules/security.js.map +1 -1
- package/dist/prompt/modules/skills.d.ts.map +1 -1
- package/dist/prompt/modules/skills.js +1 -0
- package/dist/prompt/modules/skills.js.map +1 -1
- package/dist/prompt/modules/tools.d.ts.map +1 -1
- package/dist/prompt/modules/tools.js +1 -0
- package/dist/prompt/modules/tools.js.map +1 -1
- package/dist/prompt/modules/user.d.ts.map +1 -1
- package/dist/prompt/modules/user.js +1 -0
- package/dist/prompt/modules/user.js.map +1 -1
- package/dist/providers/credential-pool.d.ts +45 -1
- package/dist/providers/credential-pool.d.ts.map +1 -1
- package/dist/providers/credential-pool.js +94 -1
- package/dist/providers/credential-pool.js.map +1 -1
- package/dist/providers/sticky-rotation-wire.d.ts +133 -0
- package/dist/providers/sticky-rotation-wire.d.ts.map +1 -0
- package/dist/providers/sticky-rotation-wire.js +185 -0
- package/dist/providers/sticky-rotation-wire.js.map +1 -0
- package/dist/runtime-hooks/ttsr-rule.d.ts +82 -0
- package/dist/runtime-hooks/ttsr-rule.d.ts.map +1 -0
- package/dist/runtime-hooks/ttsr-rule.js +207 -0
- package/dist/runtime-hooks/ttsr-rule.js.map +1 -0
- package/dist/runtime-hooks/ttsr-runner.d.ts +129 -0
- package/dist/runtime-hooks/ttsr-runner.d.ts.map +1 -0
- package/dist/runtime-hooks/ttsr-runner.js +193 -0
- package/dist/runtime-hooks/ttsr-runner.js.map +1 -0
- package/dist/runtime-hooks/ttsr-scope.d.ts +115 -0
- package/dist/runtime-hooks/ttsr-scope.d.ts.map +1 -0
- package/dist/runtime-hooks/ttsr-scope.js +378 -0
- package/dist/runtime-hooks/ttsr-scope.js.map +1 -0
- package/dist/sandbox/unified-exec.d.ts.map +1 -1
- package/dist/sandbox/unified-exec.js +6 -1
- package/dist/sandbox/unified-exec.js.map +1 -1
- package/dist/skills/cli-anything.d.ts.map +1 -1
- package/dist/skills/cli-anything.js +4 -1
- package/dist/skills/cli-anything.js.map +1 -1
- package/dist/skills/limits.d.ts +86 -0
- package/dist/skills/limits.d.ts.map +1 -0
- package/dist/skills/limits.js +140 -0
- package/dist/skills/limits.js.map +1 -0
- package/dist/skills/loader.d.ts +9 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +29 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/storage/session-artifacts.d.ts +105 -0
- package/dist/storage/session-artifacts.d.ts.map +1 -0
- package/dist/storage/session-artifacts.js +198 -0
- package/dist/storage/session-artifacts.js.map +1 -0
- package/dist/tools/workspace-pack.d.ts +97 -0
- package/dist/tools/workspace-pack.d.ts.map +1 -0
- package/dist/tools/workspace-pack.js +228 -0
- package/dist/tools/workspace-pack.js.map +1 -0
- package/dist/tui/composer-history.d.ts +99 -0
- package/dist/tui/composer-history.d.ts.map +1 -0
- package/dist/tui/composer-history.js +169 -0
- package/dist/tui/composer-history.js.map +1 -0
- package/dist/ui/components/PromptInput.d.ts.map +1 -1
- package/dist/ui/components/PromptInput.js +80 -9
- package/dist/ui/components/PromptInput.js.map +1 -1
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAClD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,MAAM,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IACxD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,MAAM,CAAC;AACrD,CAAC;AA4BD;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IACZ,QAAQ,GAAsC,IAAI,GAAG,EAAE,CAAC;IACxD,MAAM,CAAmB;IAE1C,YAAY,MAAyB;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,KAAK,IAAI,IAAI,uBAAuB,EAAE,CAAC;IACxE,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,QAAsB,EAAE,YAA+B;QAC7D,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QACjD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAE1D,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,+DAA+D;YAC/D,8DAA8D;YAC9D,8DAA8D;YAC9D,2DAA2D;YAC3D,gEAAgE;YAChE,4DAA4D;YAC5D,uBAAuB;YACvB,OAAO,GAAG,IAAI,cAAc,CAAC;gBAC3B,KAAK,EAAE,YAAY;gBACnB,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;gBACpC,WAAW,EAAE,aAAa,QAAQ,EAAE;aACrC,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACrC,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAC1B,kEAAkE;YAClE,IAAI,CAAC,KAAK,CACR,8BAA8B,QAAQ,UAAU,YAAY,CAAC,MAAM,YAAY,EAAE,EAAE,CACpF,CAAC;YACF,mEAAmE;YACnE,iEAAiE;YACjE,6CAA6C;YAC7C,IAAI,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;gBAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;YAChF,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC3B,CAAC;QAED,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/B,qEAAqE;YACrE,6DAA6D;YAC7D,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,gCAAgC,QAAQ,cAAc,CAAC,CAAC;YAC3E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,iBAAiB,QAAQ,qBAAqB,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;QAC7F,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC;IAChF,CAAC;IAED;;;;;;OAMG;IACH,WAAW,CAAC,QAAsB;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,OAAO,CAAC,WAAW,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,CAAC,kCAAkC,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,QAAsB;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAuB;QAC3B,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,IAAY;QACxB,IAAI,CAAC,4BAA4B,EAAE;YAAE,OAAO;QAC5C,oEAAoE;QACpE,gEAAgE;QAChE,oEAAoE;QACpE,qCAAqC;QACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;IAC7C,CAAC;CACF"}
|
|
@@ -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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"}
|