@oh-my-pi/pi-coding-agent 3.1.1337 → 3.4.1337
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/CHANGELOG.md +19 -0
- package/docs/extension-loading.md +2 -2
- package/docs/sdk.md +2 -2
- package/package.json +4 -4
- package/src/capability/rule.ts +4 -0
- package/src/core/agent-session.ts +92 -1
- package/src/core/sdk.ts +14 -0
- package/src/core/session-manager.ts +60 -4
- package/src/core/settings-manager.ts +68 -0
- package/src/core/ttsr.ts +211 -0
- package/src/discovery/builtin.ts +1 -0
- package/src/discovery/cline.ts +2 -0
- package/src/discovery/cursor.ts +2 -0
- package/src/discovery/windsurf.ts +3 -0
- package/src/modes/interactive/components/footer.ts +15 -1
- package/src/modes/interactive/components/settings-defs.ts +29 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/interactive-mode.ts +161 -130
- package/src/modes/print-mode.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [3.4.1337] - 2026-01-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added Time Traveling Stream Rules (TTSR) feature that monitors agent output for pattern matches and injects rule reminders mid-stream
|
|
10
|
+
- Added `ttsr_trigger` frontmatter field for rules to define regex patterns that trigger mid-stream injection
|
|
11
|
+
- Added TTSR settings for enabled state, context mode (keep/discard partial output), and repeat mode (once/after-gap)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fixed excessive subprocess spawns by caching git status for 1 second in the footer component
|
|
16
|
+
|
|
17
|
+
## [3.3.1337] - 2026-01-03
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Improved `/status` command output formatting to use consistent column alignment across all sections
|
|
22
|
+
- Updated version update notification to suggest `omp update` instead of manual npm install command
|
|
23
|
+
|
|
5
24
|
## [3.1.1337] - 2026-01-03
|
|
6
25
|
### Added
|
|
7
26
|
|
|
@@ -775,7 +775,7 @@ Update argument parsing:
|
|
|
775
775
|
result.noHooks = true;
|
|
776
776
|
```
|
|
777
777
|
|
|
778
|
-
Add subcommand handling for `
|
|
778
|
+
Add subcommand handling for `omp install`, `omp remove`, `omp update`.
|
|
779
779
|
|
|
780
780
|
### `src/core/hooks/loader.ts`
|
|
781
781
|
|
|
@@ -949,7 +949,7 @@ if (settings.skills?.customDirectories) {
|
|
|
949
949
|
5. **Phase 5: CLI updates**
|
|
950
950
|
- Add new flags to args.ts
|
|
951
951
|
- Update help text
|
|
952
|
-
- Add `
|
|
952
|
+
- Add `omp install`, `omp remove`, `omp update` subcommands
|
|
953
953
|
|
|
954
954
|
6. **Phase 6: Integration**
|
|
955
955
|
- Update sdk.ts
|
package/docs/sdk.md
CHANGED
|
@@ -54,7 +54,7 @@ The main factory function. Creates an `AgentSession` with configurable options.
|
|
|
54
54
|
|
|
55
55
|
**Philosophy:** "Omit to discover, provide to override."
|
|
56
56
|
|
|
57
|
-
- Omit an option →
|
|
57
|
+
- Omit an option → omp discovers/loads from standard locations
|
|
58
58
|
- Provide an option → your value is used, discovery skipped for that option
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
@@ -405,7 +405,7 @@ const { session } = await createAgentSession({
|
|
|
405
405
|
|
|
406
406
|
**When you don't need factories:**
|
|
407
407
|
|
|
408
|
-
- If you omit `tools`,
|
|
408
|
+
- If you omit `tools`, omp automatically creates them with the correct `cwd`
|
|
409
409
|
- If you use `process.cwd()` as your `cwd`, the pre-built instances work fine
|
|
410
410
|
|
|
411
411
|
**When you must use factories:**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1337",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-agent-core": "3.
|
|
43
|
-
"@oh-my-pi/pi-ai": "3.
|
|
44
|
-
"@oh-my-pi/pi-tui": "3.
|
|
42
|
+
"@oh-my-pi/pi-agent-core": "3.4.1337",
|
|
43
|
+
"@oh-my-pi/pi-ai": "3.4.1337",
|
|
44
|
+
"@oh-my-pi/pi-tui": "3.4.1337",
|
|
45
45
|
"@sinclair/typebox": "^0.34.46",
|
|
46
46
|
"ajv": "^8.17.1",
|
|
47
47
|
"chalk": "^5.5.0",
|
package/src/capability/rule.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface RuleFrontmatter {
|
|
|
15
15
|
description?: string;
|
|
16
16
|
globs?: string[];
|
|
17
17
|
alwaysApply?: boolean;
|
|
18
|
+
/** Regex pattern that triggers time-traveling rule injection */
|
|
19
|
+
ttsr_trigger?: string;
|
|
18
20
|
[key: string]: unknown;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -34,6 +36,8 @@ export interface Rule {
|
|
|
34
36
|
alwaysApply?: boolean;
|
|
35
37
|
/** Description (for agent-requested rules) */
|
|
36
38
|
description?: string;
|
|
39
|
+
/** Regex pattern that triggers time-traveling rule injection */
|
|
40
|
+
ttsrTrigger?: string;
|
|
37
41
|
/** Source metadata */
|
|
38
42
|
_source: SourceMeta;
|
|
39
43
|
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import type { Agent, AgentEvent, AgentMessage, AgentState, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
17
17
|
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@oh-my-pi/pi-ai";
|
|
18
18
|
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
19
|
+
import type { Rule } from "../capability/rule";
|
|
19
20
|
import { getAuthPath } from "../config";
|
|
20
21
|
import { type BashResult, executeBash as executeBashCommand } from "./bash-executor";
|
|
21
22
|
import {
|
|
@@ -47,6 +48,7 @@ import type { ModelRegistry } from "./model-registry";
|
|
|
47
48
|
import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionManager } from "./session-manager";
|
|
48
49
|
import type { SettingsManager, SkillsSettings } from "./settings-manager";
|
|
49
50
|
import { expandSlashCommand, type FileSlashCommand, parseCommandArgs } from "./slash-commands";
|
|
51
|
+
import type { TtsrManager } from "./ttsr";
|
|
50
52
|
|
|
51
53
|
/** Session-specific events that extend the core AgentEvent */
|
|
52
54
|
export type AgentSessionEvent =
|
|
@@ -54,7 +56,8 @@ export type AgentSessionEvent =
|
|
|
54
56
|
| { type: "auto_compaction_start"; reason: "threshold" | "overflow" }
|
|
55
57
|
| { type: "auto_compaction_end"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean }
|
|
56
58
|
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
|
|
57
|
-
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
|
|
59
|
+
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
|
|
60
|
+
| { type: "ttsr_triggered"; rules: Rule[] };
|
|
58
61
|
|
|
59
62
|
/** Listener function for agent session events */
|
|
60
63
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
@@ -80,6 +83,8 @@ export interface AgentSessionConfig {
|
|
|
80
83
|
skillsSettings?: Required<SkillsSettings>;
|
|
81
84
|
/** Model registry for API key resolution and model discovery */
|
|
82
85
|
modelRegistry: ModelRegistry;
|
|
86
|
+
/** TTSR manager for time-traveling stream rules */
|
|
87
|
+
ttsrManager?: TtsrManager;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
/** Options for AgentSession.prompt() */
|
|
@@ -179,6 +184,11 @@ export class AgentSession {
|
|
|
179
184
|
// Model registry for API key resolution
|
|
180
185
|
private _modelRegistry: ModelRegistry;
|
|
181
186
|
|
|
187
|
+
// TTSR manager for time-traveling stream rules
|
|
188
|
+
private _ttsrManager: TtsrManager | undefined = undefined;
|
|
189
|
+
private _pendingTtsrInjections: Rule[] = [];
|
|
190
|
+
private _ttsrAbortPending = false;
|
|
191
|
+
|
|
182
192
|
constructor(config: AgentSessionConfig) {
|
|
183
193
|
this.agent = config.agent;
|
|
184
194
|
this.sessionManager = config.sessionManager;
|
|
@@ -190,6 +200,7 @@ export class AgentSession {
|
|
|
190
200
|
this._customCommands = config.customCommands ?? [];
|
|
191
201
|
this._skillsSettings = config.skillsSettings;
|
|
192
202
|
this._modelRegistry = config.modelRegistry;
|
|
203
|
+
this._ttsrManager = config.ttsrManager;
|
|
193
204
|
|
|
194
205
|
// Always subscribe to agent events for internal handling
|
|
195
206
|
// (session persistence, hooks, auto-compaction, retry logic)
|
|
@@ -201,6 +212,16 @@ export class AgentSession {
|
|
|
201
212
|
return this._modelRegistry;
|
|
202
213
|
}
|
|
203
214
|
|
|
215
|
+
/** TTSR manager for time-traveling stream rules */
|
|
216
|
+
get ttsrManager(): TtsrManager | undefined {
|
|
217
|
+
return this._ttsrManager;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Whether a TTSR abort is pending (stream was aborted to inject rules) */
|
|
221
|
+
get isTtsrAbortPending(): boolean {
|
|
222
|
+
return this._ttsrAbortPending;
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
// =========================================================================
|
|
205
226
|
// Event Subscription
|
|
206
227
|
// =========================================================================
|
|
@@ -239,6 +260,60 @@ export class AgentSession {
|
|
|
239
260
|
// Notify all listeners
|
|
240
261
|
this._emit(event);
|
|
241
262
|
|
|
263
|
+
// TTSR: Reset buffer on turn start
|
|
264
|
+
if (event.type === "turn_start" && this._ttsrManager) {
|
|
265
|
+
this._ttsrManager.resetBuffer();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// TTSR: Increment message count on turn end (for repeat-after-gap tracking)
|
|
269
|
+
if (event.type === "turn_end" && this._ttsrManager) {
|
|
270
|
+
this._ttsrManager.incrementMessageCount();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// TTSR: Check for pattern matches on text deltas and tool call argument deltas
|
|
274
|
+
if (event.type === "message_update" && this._ttsrManager?.hasRules()) {
|
|
275
|
+
const assistantEvent = event.assistantMessageEvent;
|
|
276
|
+
// Monitor both assistant prose (text_delta) and tool call arguments (toolcall_delta)
|
|
277
|
+
if (assistantEvent.type === "text_delta" || assistantEvent.type === "toolcall_delta") {
|
|
278
|
+
this._ttsrManager.appendToBuffer(assistantEvent.delta);
|
|
279
|
+
const matches = this._ttsrManager.check(this._ttsrManager.getBuffer());
|
|
280
|
+
if (matches.length > 0) {
|
|
281
|
+
// Mark rules as injected so they don't trigger again
|
|
282
|
+
this._ttsrManager.markInjected(matches);
|
|
283
|
+
// Store for injection on retry
|
|
284
|
+
this._pendingTtsrInjections.push(...matches);
|
|
285
|
+
// Emit TTSR event before aborting (so UI can handle it)
|
|
286
|
+
this._ttsrAbortPending = true;
|
|
287
|
+
this._emit({ type: "ttsr_triggered", rules: matches });
|
|
288
|
+
// Abort the stream
|
|
289
|
+
this.agent.abort();
|
|
290
|
+
// Schedule retry after a short delay
|
|
291
|
+
setTimeout(async () => {
|
|
292
|
+
this._ttsrAbortPending = false;
|
|
293
|
+
|
|
294
|
+
// Handle context mode: discard partial output if configured
|
|
295
|
+
const ttsrSettings = this._ttsrManager?.getSettings();
|
|
296
|
+
if (ttsrSettings?.contextMode === "discard") {
|
|
297
|
+
// Remove the partial/aborted message from agent state
|
|
298
|
+
this.agent.popMessage();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Inject TTSR rules as system reminder before retry
|
|
302
|
+
const injectionContent = this._getTtsrInjectionContent();
|
|
303
|
+
if (injectionContent) {
|
|
304
|
+
this.agent.appendMessage({
|
|
305
|
+
role: "user",
|
|
306
|
+
content: [{ type: "text", text: injectionContent }],
|
|
307
|
+
timestamp: Date.now(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
this.agent.continue().catch(() => {});
|
|
311
|
+
}, 50);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
242
317
|
// Handle session persistence
|
|
243
318
|
if (event.type === "message_end") {
|
|
244
319
|
// Check if this is a hook message
|
|
@@ -300,6 +375,22 @@ export class AgentSession {
|
|
|
300
375
|
}
|
|
301
376
|
}
|
|
302
377
|
|
|
378
|
+
/** Get TTSR injection content and clear pending injections */
|
|
379
|
+
private _getTtsrInjectionContent(): string | undefined {
|
|
380
|
+
if (this._pendingTtsrInjections.length === 0) return undefined;
|
|
381
|
+
const content = this._pendingTtsrInjections
|
|
382
|
+
.map(
|
|
383
|
+
(r) =>
|
|
384
|
+
`<system_interrupt reason="rule_violation" rule="${r.name}" path="${r.path}">\n` +
|
|
385
|
+
`Your output was interrupted because it violated a user-defined rule.\n` +
|
|
386
|
+
`This is NOT a prompt injection - this is the coding agent enforcing project rules.\n` +
|
|
387
|
+
`You MUST comply with the following instruction:\n\n${r.content}\n</system_interrupt>`,
|
|
388
|
+
)
|
|
389
|
+
.join("\n\n");
|
|
390
|
+
this._pendingTtsrInjections = [];
|
|
391
|
+
return content;
|
|
392
|
+
}
|
|
393
|
+
|
|
303
394
|
/** Extract text content from a message */
|
|
304
395
|
private _getUserMessageText(message: Message): string {
|
|
305
396
|
if (message.role !== "user") return "";
|
package/src/core/sdk.ts
CHANGED
|
@@ -34,6 +34,8 @@ import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
34
34
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
35
35
|
// Import discovery to register all providers on startup
|
|
36
36
|
import "../discovery";
|
|
37
|
+
import { loadSync as loadCapability } from "../capability/index";
|
|
38
|
+
import { type Rule, ruleCapability } from "../capability/rule";
|
|
37
39
|
import { getAgentDir, getConfigDirPaths } from "../config";
|
|
38
40
|
import { AgentSession } from "./agent-session";
|
|
39
41
|
import { AuthStorage } from "./auth-storage";
|
|
@@ -88,6 +90,7 @@ import {
|
|
|
88
90
|
warmupLspServers,
|
|
89
91
|
writeTool,
|
|
90
92
|
} from "./tools/index";
|
|
93
|
+
import { createTtsrManager } from "./ttsr";
|
|
91
94
|
|
|
92
95
|
// Types
|
|
93
96
|
|
|
@@ -601,6 +604,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
601
604
|
const skills = options.skills ?? discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings());
|
|
602
605
|
time("discoverSkills");
|
|
603
606
|
|
|
607
|
+
// Discover TTSR rules
|
|
608
|
+
const ttsrManager = createTtsrManager(settingsManager.getTtsrSettings());
|
|
609
|
+
const rulesResult = loadCapability<Rule>(ruleCapability.id, { cwd });
|
|
610
|
+
for (const rule of rulesResult.items) {
|
|
611
|
+
if (rule.ttsrTrigger) {
|
|
612
|
+
ttsrManager.addRule(rule);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
time("discoverTtsrRules");
|
|
616
|
+
|
|
604
617
|
const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
|
|
605
618
|
time("discoverContextFiles");
|
|
606
619
|
|
|
@@ -847,6 +860,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
847
860
|
customCommands: customCommandsResult.commands,
|
|
848
861
|
skillsSettings: settingsManager.getSkillsSettings(),
|
|
849
862
|
modelRegistry,
|
|
863
|
+
ttsrManager,
|
|
850
864
|
});
|
|
851
865
|
time("createAgentSession");
|
|
852
866
|
|
|
@@ -107,6 +107,13 @@ export interface LabelEntry extends SessionEntryBase {
|
|
|
107
107
|
label: string | undefined;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/** TTSR injection entry - tracks which time-traveling rules have been injected this session. */
|
|
111
|
+
export interface TtsrInjectionEntry extends SessionEntryBase {
|
|
112
|
+
type: "ttsr_injection";
|
|
113
|
+
/** Names of rules that were injected */
|
|
114
|
+
injectedRules: string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
110
117
|
/**
|
|
111
118
|
* Custom message entry for hooks to inject messages into LLM context.
|
|
112
119
|
* Use customType to identify your hook's entries.
|
|
@@ -136,7 +143,8 @@ export type SessionEntry =
|
|
|
136
143
|
| BranchSummaryEntry
|
|
137
144
|
| CustomEntry
|
|
138
145
|
| CustomMessageEntry
|
|
139
|
-
| LabelEntry
|
|
146
|
+
| LabelEntry
|
|
147
|
+
| TtsrInjectionEntry;
|
|
140
148
|
|
|
141
149
|
/** Raw file entry (includes header) */
|
|
142
150
|
export type FileEntry = SessionHeader | SessionEntry;
|
|
@@ -154,6 +162,8 @@ export interface SessionContext {
|
|
|
154
162
|
thinkingLevel: string;
|
|
155
163
|
/** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */
|
|
156
164
|
models: Record<string, string>;
|
|
165
|
+
/** Names of TTSR rules that have been injected this session */
|
|
166
|
+
injectedTtsrRules: string[];
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
export interface SessionInfo {
|
|
@@ -295,7 +305,7 @@ export function buildSessionContext(
|
|
|
295
305
|
let leaf: SessionEntry | undefined;
|
|
296
306
|
if (leafId === null) {
|
|
297
307
|
// Explicitly null - return no messages (navigated to before first entry)
|
|
298
|
-
return { messages: [], thinkingLevel: "off", models: {} };
|
|
308
|
+
return { messages: [], thinkingLevel: "off", models: {}, injectedTtsrRules: [] };
|
|
299
309
|
}
|
|
300
310
|
if (leafId) {
|
|
301
311
|
leaf = byId.get(leafId);
|
|
@@ -306,7 +316,7 @@ export function buildSessionContext(
|
|
|
306
316
|
}
|
|
307
317
|
|
|
308
318
|
if (!leaf) {
|
|
309
|
-
return { messages: [], thinkingLevel: "off", models: {} };
|
|
319
|
+
return { messages: [], thinkingLevel: "off", models: {}, injectedTtsrRules: [] };
|
|
310
320
|
}
|
|
311
321
|
|
|
312
322
|
// Walk from leaf to root, collecting path
|
|
@@ -321,6 +331,7 @@ export function buildSessionContext(
|
|
|
321
331
|
let thinkingLevel = "off";
|
|
322
332
|
const models: Record<string, string> = {};
|
|
323
333
|
let compaction: CompactionEntry | null = null;
|
|
334
|
+
const injectedTtsrRulesSet = new Set<string>();
|
|
324
335
|
|
|
325
336
|
for (const entry of path) {
|
|
326
337
|
if (entry.type === "thinking_level_change") {
|
|
@@ -336,9 +347,16 @@ export function buildSessionContext(
|
|
|
336
347
|
models.default = `${entry.message.provider}/${entry.message.model}`;
|
|
337
348
|
} else if (entry.type === "compaction") {
|
|
338
349
|
compaction = entry;
|
|
350
|
+
} else if (entry.type === "ttsr_injection") {
|
|
351
|
+
// Collect injected TTSR rule names
|
|
352
|
+
for (const ruleName of entry.injectedRules) {
|
|
353
|
+
injectedTtsrRulesSet.add(ruleName);
|
|
354
|
+
}
|
|
339
355
|
}
|
|
340
356
|
}
|
|
341
357
|
|
|
358
|
+
const injectedTtsrRules = Array.from(injectedTtsrRulesSet);
|
|
359
|
+
|
|
342
360
|
// Build messages and collect corresponding entries
|
|
343
361
|
// When there's a compaction, we need to:
|
|
344
362
|
// 1. Emit summary first (entry = compaction)
|
|
@@ -389,7 +407,7 @@ export function buildSessionContext(
|
|
|
389
407
|
}
|
|
390
408
|
}
|
|
391
409
|
|
|
392
|
-
return { messages, thinkingLevel, models };
|
|
410
|
+
return { messages, thinkingLevel, models, injectedTtsrRules };
|
|
393
411
|
}
|
|
394
412
|
|
|
395
413
|
/**
|
|
@@ -814,6 +832,44 @@ export class SessionManager {
|
|
|
814
832
|
return entry.id;
|
|
815
833
|
}
|
|
816
834
|
|
|
835
|
+
// =========================================================================
|
|
836
|
+
// TTSR (Time Traveling Stream Rules)
|
|
837
|
+
// =========================================================================
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Append a TTSR injection entry recording which rules were injected.
|
|
841
|
+
* @param ruleNames Names of rules that were injected
|
|
842
|
+
* @returns Entry id
|
|
843
|
+
*/
|
|
844
|
+
appendTtsrInjection(ruleNames: string[]): string {
|
|
845
|
+
const entry: TtsrInjectionEntry = {
|
|
846
|
+
type: "ttsr_injection",
|
|
847
|
+
id: generateId(this.byId),
|
|
848
|
+
parentId: this.leafId,
|
|
849
|
+
timestamp: new Date().toISOString(),
|
|
850
|
+
injectedRules: ruleNames,
|
|
851
|
+
};
|
|
852
|
+
this._appendEntry(entry);
|
|
853
|
+
return entry.id;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Get all unique TTSR rule names that have been injected in the current branch.
|
|
858
|
+
* Scans from root to current leaf for ttsr_injection entries.
|
|
859
|
+
*/
|
|
860
|
+
getInjectedTtsrRules(): string[] {
|
|
861
|
+
const path = this.getBranch();
|
|
862
|
+
const ruleNames = new Set<string>();
|
|
863
|
+
for (const entry of path) {
|
|
864
|
+
if (entry.type === "ttsr_injection") {
|
|
865
|
+
for (const name of entry.injectedRules) {
|
|
866
|
+
ruleNames.add(name);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return Array.from(ruleNames);
|
|
871
|
+
}
|
|
872
|
+
|
|
817
873
|
// =========================================================================
|
|
818
874
|
// Tree Traversal
|
|
819
875
|
// =========================================================================
|
|
@@ -68,6 +68,16 @@ export interface EditSettings {
|
|
|
68
68
|
fuzzyMatch?: boolean; // default: true (accept high-confidence fuzzy matches for whitespace/indentation)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export interface TtsrSettings {
|
|
72
|
+
enabled?: boolean; // default: true
|
|
73
|
+
/** What to do with partial output when TTSR triggers: "keep" shows interrupted attempt, "discard" removes it */
|
|
74
|
+
contextMode?: "keep" | "discard"; // default: "discard"
|
|
75
|
+
/** How TTSR rules repeat: "once" = only trigger once per session, "after-gap" = can repeat after N messages */
|
|
76
|
+
repeatMode?: "once" | "after-gap"; // default: "once"
|
|
77
|
+
/** Number of messages before a rule can trigger again (only used when repeatMode is "after-gap") */
|
|
78
|
+
repeatGap?: number; // default: 10
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
export interface Settings {
|
|
72
82
|
lastChangelogVersion?: string;
|
|
73
83
|
/** Model roles map: { default: "provider/modelId", small: "provider/modelId", ... } */
|
|
@@ -93,6 +103,7 @@ export interface Settings {
|
|
|
93
103
|
mcp?: MCPSettings;
|
|
94
104
|
lsp?: LspSettings;
|
|
95
105
|
edit?: EditSettings;
|
|
106
|
+
ttsr?: TtsrSettings;
|
|
96
107
|
disabledProviders?: string[]; // Discovery provider IDs that are disabled
|
|
97
108
|
}
|
|
98
109
|
|
|
@@ -582,4 +593,61 @@ export class SettingsManager {
|
|
|
582
593
|
this.globalSettings.disabledProviders = providerIds;
|
|
583
594
|
this.save();
|
|
584
595
|
}
|
|
596
|
+
|
|
597
|
+
getTtsrSettings(): TtsrSettings {
|
|
598
|
+
return this.settings.ttsr ?? {};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
setTtsrSettings(settings: TtsrSettings): void {
|
|
602
|
+
this.globalSettings.ttsr = { ...this.globalSettings.ttsr, ...settings };
|
|
603
|
+
this.save();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
getTtsrEnabled(): boolean {
|
|
607
|
+
return this.settings.ttsr?.enabled ?? true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
setTtsrEnabled(enabled: boolean): void {
|
|
611
|
+
if (!this.globalSettings.ttsr) {
|
|
612
|
+
this.globalSettings.ttsr = {};
|
|
613
|
+
}
|
|
614
|
+
this.globalSettings.ttsr.enabled = enabled;
|
|
615
|
+
this.save();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
getTtsrContextMode(): "keep" | "discard" {
|
|
619
|
+
return this.settings.ttsr?.contextMode ?? "discard";
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
setTtsrContextMode(mode: "keep" | "discard"): void {
|
|
623
|
+
if (!this.globalSettings.ttsr) {
|
|
624
|
+
this.globalSettings.ttsr = {};
|
|
625
|
+
}
|
|
626
|
+
this.globalSettings.ttsr.contextMode = mode;
|
|
627
|
+
this.save();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
getTtsrRepeatMode(): "once" | "after-gap" {
|
|
631
|
+
return this.settings.ttsr?.repeatMode ?? "once";
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
setTtsrRepeatMode(mode: "once" | "after-gap"): void {
|
|
635
|
+
if (!this.globalSettings.ttsr) {
|
|
636
|
+
this.globalSettings.ttsr = {};
|
|
637
|
+
}
|
|
638
|
+
this.globalSettings.ttsr.repeatMode = mode;
|
|
639
|
+
this.save();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
getTtsrRepeatGap(): number {
|
|
643
|
+
return this.settings.ttsr?.repeatGap ?? 10;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
setTtsrRepeatGap(gap: number): void {
|
|
647
|
+
if (!this.globalSettings.ttsr) {
|
|
648
|
+
this.globalSettings.ttsr = {};
|
|
649
|
+
}
|
|
650
|
+
this.globalSettings.ttsr.repeatGap = gap;
|
|
651
|
+
this.save();
|
|
652
|
+
}
|
|
585
653
|
}
|