@krtclcdy/tersio 2.24.0 → 2.24.1
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/README.md +1 -3
- package/cli/banner.js +2 -3
- package/cli/currency.js +3 -7
- package/cli/dashboard.js +167 -3
- package/cli/doctor-fix.js +15 -12
- package/cli/doctor.js +1 -12
- package/cli/hosts.js +7 -11
- package/cli/install.js +113 -71
- package/cli/interactive.js +36 -2
- package/cli/layer.js +1 -3
- package/cli/manifest.js +1 -4
- package/cli/profile.js +9 -7
- package/cli/reset.js +3 -7
- package/cli/rtk-wiring.js +1 -6
- package/cli/settings.js +3 -8
- package/cli/uninstall.js +43 -24
- package/cli/update.js +7 -8
- package/cli/usage.js +15 -5
- package/dashboard/dist/index.html +18 -18
- package/extensions/ai-addons-updater/index.js +6 -5
- package/extensions/ai-addons-updater/index.ts +7 -6
- package/extensions/caveman-session/index.js +14 -29
- package/extensions/caveman-session/index.ts +15 -30
- package/extensions/combo-toggle/index.js +10 -14
- package/extensions/combo-toggle/index.ts +10 -15
- package/extensions/lib/utils.js +4 -0
- package/extensions/lib/utils.ts +3 -0
- package/extensions/rtk-session/index.js +27 -31
- package/extensions/rtk-session/index.ts +25 -29
- package/extensions/shared/carbon.js +3 -8
- package/extensions/shared/carbon.ts +4 -8
- package/extensions/shared/host.js +1 -11
- package/extensions/shared/host.ts +1 -11
- package/extensions/shared/plugin-settings.js +4 -14
- package/extensions/shared/plugin-settings.ts +4 -14
- package/extensions/shared/pricing.js +4 -6
- package/extensions/shared/pricing.ts +4 -6
- package/extensions/shared/rtk-gain.js +2 -7
- package/extensions/shared/rtk-gain.ts +2 -7
- package/extensions/shared/session-state.js +30 -18
- package/extensions/shared/session-state.ts +33 -16
- package/extensions/shared/usage-ledger.js +138 -17
- package/extensions/shared/usage-ledger.ts +143 -19
- package/extensions/shared/usage-store.js +175 -23
- package/extensions/shared/usage-store.ts +171 -22
- package/extensions/tersio-commands/index.js +1 -5
- package/extensions/tersio-commands/index.ts +1 -5
- package/package.json +6 -7
- package/tersio.js +1 -3
|
@@ -310,15 +310,16 @@ export default function aiAddonsUpdaterExtension(pi) {
|
|
|
310
310
|
const cleanParts = parts.filter((p) => p !== '--dry-run' && p !== 'dry-run');
|
|
311
311
|
const sub = cleanParts[0];
|
|
312
312
|
if (sub === 'check' || sub === 'status') {
|
|
313
|
-
|
|
313
|
+
await checkAddons(ctx);
|
|
314
314
|
notify(ctx, 'ai-addons check complete.', 'info');
|
|
315
|
-
return
|
|
315
|
+
return;
|
|
316
316
|
}
|
|
317
317
|
if (sub === 'update' && cleanParts[1]) {
|
|
318
|
-
|
|
318
|
+
await runAddonUpdate(pi, ctx, cleanParts.slice(1).join(' '), dryRun);
|
|
319
|
+
return;
|
|
319
320
|
}
|
|
320
|
-
|
|
321
|
-
return
|
|
321
|
+
report(ctx, 'Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]', 'warning');
|
|
322
|
+
return;
|
|
322
323
|
},
|
|
323
324
|
});
|
|
324
325
|
}
|
|
@@ -54,7 +54,7 @@ interface AddonUpdaterCtx {
|
|
|
54
54
|
|
|
55
55
|
interface AddonUpdaterPi {
|
|
56
56
|
setLabel?: (label: string) => void;
|
|
57
|
-
registerCommand?: (name: string, config: { description: string; handler: (args: string, ctx: AddonUpdaterCtx) => Promise<
|
|
57
|
+
registerCommand?: (name: string, config: { description: string; handler: (args: string, ctx: AddonUpdaterCtx) => Promise<void> }) => void;
|
|
58
58
|
exec?: (cmd: string, args: string[], opts?: { cwd?: string }) => Promise<{ stdout: string; stderr: string; code: number }>;
|
|
59
59
|
cwd?: string;
|
|
60
60
|
}
|
|
@@ -355,15 +355,16 @@ export default function aiAddonsUpdaterExtension(pi: AddonUpdaterPi): void {
|
|
|
355
355
|
const sub = cleanParts[0];
|
|
356
356
|
|
|
357
357
|
if (sub === 'check' || sub === 'status') {
|
|
358
|
-
|
|
358
|
+
await checkAddons(ctx);
|
|
359
359
|
notify(ctx, 'ai-addons check complete.', 'info');
|
|
360
|
-
return
|
|
360
|
+
return;
|
|
361
361
|
}
|
|
362
362
|
if (sub === 'update' && cleanParts[1]) {
|
|
363
|
-
|
|
363
|
+
await runAddonUpdate(pi, ctx, cleanParts.slice(1).join(' '), dryRun);
|
|
364
|
+
return;
|
|
364
365
|
}
|
|
365
|
-
|
|
366
|
-
return
|
|
366
|
+
report(ctx, 'Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]', 'warning');
|
|
367
|
+
return;
|
|
367
368
|
},
|
|
368
369
|
});
|
|
369
370
|
}
|
|
@@ -1,32 +1,18 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.js';
|
|
3
|
+
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, statusUi, systemPromptIncludes } from '../shared/session-state.js';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
|
|
6
6
|
import { readCavemanDefault } from '../shared/plugin-settings.js';
|
|
7
7
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const RULE_PATH = join(CAVERN_DIR, 'rule.md');
|
|
9
|
-
|
|
10
|
-
Respond terse like smart caveman. Keep all technical substance. Drop filler, pleasantries, and hedging.
|
|
11
|
-
|
|
12
|
-
Rules:
|
|
13
|
-
- Drop articles where meaning stays clear. Keep fragments and short sentences.
|
|
14
|
-
- Keep technical terms, code, commands, paths, API names, numbers, units, and errors exact.
|
|
15
|
-
- Never invent abbreviations or causal arrows. Do not add words to sound caveman.
|
|
16
|
-
- Use ASD-STE100 Simplified Technical English. Keep one idea per sentence, target 20 words, active voice, and consistent terms.
|
|
17
|
-
- Make no tool-call narration. Use no decorative tables or emoji.
|
|
18
|
-
- Preserve the user's reply language. Compress style, not language.
|
|
19
|
-
- Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or requests to clarify.
|
|
20
|
-
- Write code, comments, commits, docs, issues, pull requests, and third-party messages in normal prose.
|
|
21
|
-
|
|
22
|
-
Default: **full**. Switch: \`/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off\`. Stop: "stop caveman" or "normal mode".`;
|
|
23
|
-
// ponytail: synchronous read each full-mode injection; ceiling = small file, cold session start. Upgrade path: cache file contents + mtime, invalidate on change.
|
|
9
|
+
// Fail loud: a wrong rule reads to the model as the real one.
|
|
24
10
|
function readFullRule() {
|
|
25
11
|
try {
|
|
26
12
|
return readFileSync(RULE_PATH, 'utf8');
|
|
27
13
|
}
|
|
28
14
|
catch {
|
|
29
|
-
return
|
|
15
|
+
return 'Caveman full requested, but rule.md is missing. Run: tersio doctor --fix extensions';
|
|
30
16
|
}
|
|
31
17
|
}
|
|
32
18
|
const DEFAULT_MODE = 'off';
|
|
@@ -53,15 +39,15 @@ export default function cavemanSessionExtension(pi) {
|
|
|
53
39
|
let lastCtx = undefined;
|
|
54
40
|
function syncStatus(ctx) {
|
|
55
41
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
42
|
+
const ui = statusUi(lastCtx);
|
|
43
|
+
if (!ui)
|
|
58
44
|
return;
|
|
59
45
|
// Combo owns the bar when any preset is active; keep ours empty to avoid duplication.
|
|
60
46
|
if (isComboPresetActive() || currentMode === 'off') {
|
|
61
|
-
|
|
47
|
+
ui.setStatus('caveman', undefined);
|
|
62
48
|
return;
|
|
63
49
|
}
|
|
64
|
-
paintStatusBar(
|
|
50
|
+
paintStatusBar(ui, 'caveman', '🪨', `caveman: ${currentMode.toUpperCase()}`, isActive);
|
|
65
51
|
}
|
|
66
52
|
function setMode(mode, ctx) {
|
|
67
53
|
const normalized = normalizeMode('caveman', mode);
|
|
@@ -79,9 +65,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
79
65
|
return true;
|
|
80
66
|
}
|
|
81
67
|
setExtensionLabel(pi, 'Caveman session toggle');
|
|
82
|
-
//
|
|
83
|
-
// it at once so the next turn injects the new mode with no session reload.
|
|
84
|
-
// Stable identity, so the bridge set dedupes across re-inits.
|
|
68
|
+
// Adopt a /tersio or /combo switch at once, so the next turn needs no reload.
|
|
85
69
|
function syncFromShared(state) {
|
|
86
70
|
const mode = normalizeMode('caveman', state.caveman);
|
|
87
71
|
if (mode) {
|
|
@@ -89,7 +73,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
89
73
|
syncStatus();
|
|
90
74
|
}
|
|
91
75
|
}
|
|
92
|
-
setSharedComboListener(syncFromShared);
|
|
76
|
+
setSharedComboListener('caveman', syncFromShared);
|
|
93
77
|
pi.registerCommand?.('caveman', {
|
|
94
78
|
description: 'Toggle terse caveman replies for this session',
|
|
95
79
|
handler: async (args, ctx) => {
|
|
@@ -115,10 +99,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
115
99
|
});
|
|
116
100
|
function restoreMode(ctx) {
|
|
117
101
|
const entries = sessionEntries(ctx);
|
|
118
|
-
//
|
|
119
|
-
// bar suppression reads the in-process bridge, which is empty in a fresh
|
|
120
|
-
// host until the combo extension reconciles — and its reconcile is
|
|
121
|
-
// UI-gated. Deriving it here makes suppression independent of load order.
|
|
102
|
+
// Derive the bridge here so bar suppression does not depend on load order.
|
|
122
103
|
reconcileSharedComboEntries(entries);
|
|
123
104
|
// Persisted session state wins; a fresh session falls back to the
|
|
124
105
|
// installer/user-configured default (off unless configured).
|
|
@@ -152,6 +133,10 @@ export default function cavemanSessionExtension(pi) {
|
|
|
152
133
|
if (!def)
|
|
153
134
|
return;
|
|
154
135
|
const instruction = typeof def === 'function' ? def() : def;
|
|
136
|
+
// Skip when already present, so a host that re-presents the mutated prompt
|
|
137
|
+
// cannot stack it; matching the whole instruction keeps upstream rule.md intact.
|
|
138
|
+
if (systemPromptIncludes(event.systemPrompt, instruction))
|
|
139
|
+
return;
|
|
155
140
|
return injectPromptText(pi, event, instruction);
|
|
156
141
|
});
|
|
157
142
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode } from '../shared/session-state.ts';
|
|
3
|
+
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, statusUi, systemPromptIncludes } from '../shared/session-state.ts';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.ts';
|
|
6
6
|
import { readCavemanDefault } from '../shared/plugin-settings.ts';
|
|
@@ -9,24 +9,11 @@ import type { ExtensionApi, ExtensionCtx, InputEvent, SessionEntry, SystemPrompt
|
|
|
9
9
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const RULE_PATH = join(CAVERN_DIR, 'rule.md');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
Respond terse like smart caveman. Keep all technical substance. Drop filler, pleasantries, and hedging.
|
|
14
|
-
|
|
15
|
-
Rules:
|
|
16
|
-
- Drop articles where meaning stays clear. Keep fragments and short sentences.
|
|
17
|
-
- Keep technical terms, code, commands, paths, API names, numbers, units, and errors exact.
|
|
18
|
-
- Never invent abbreviations or causal arrows. Do not add words to sound caveman.
|
|
19
|
-
- Use ASD-STE100 Simplified Technical English. Keep one idea per sentence, target 20 words, active voice, and consistent terms.
|
|
20
|
-
- Make no tool-call narration. Use no decorative tables or emoji.
|
|
21
|
-
- Preserve the user's reply language. Compress style, not language.
|
|
22
|
-
- Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or requests to clarify.
|
|
23
|
-
- Write code, comments, commits, docs, issues, pull requests, and third-party messages in normal prose.
|
|
24
|
-
|
|
25
|
-
Default: **full**. Switch: \`/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off\`. Stop: "stop caveman" or "normal mode".`;
|
|
26
|
-
|
|
27
|
-
// ponytail: synchronous read each full-mode injection; ceiling = small file, cold session start. Upgrade path: cache file contents + mtime, invalidate on change.
|
|
12
|
+
// Fail loud: a wrong rule reads to the model as the real one.
|
|
28
13
|
function readFullRule(): string {
|
|
29
|
-
try { return readFileSync(RULE_PATH, 'utf8'); } catch {
|
|
14
|
+
try { return readFileSync(RULE_PATH, 'utf8'); } catch {
|
|
15
|
+
return 'Caveman full requested, but rule.md is missing. Run: tersio doctor --fix extensions';
|
|
16
|
+
}
|
|
30
17
|
}
|
|
31
18
|
|
|
32
19
|
const DEFAULT_MODE = 'off';
|
|
@@ -57,14 +44,14 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
57
44
|
|
|
58
45
|
function syncStatus(ctx?: ExtensionCtx): void {
|
|
59
46
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
47
|
+
const ui = statusUi(lastCtx);
|
|
48
|
+
if (!ui) return;
|
|
62
49
|
// Combo owns the bar when any preset is active; keep ours empty to avoid duplication.
|
|
63
50
|
if (isComboPresetActive() || currentMode === 'off') {
|
|
64
|
-
|
|
51
|
+
ui.setStatus('caveman', undefined);
|
|
65
52
|
return;
|
|
66
53
|
}
|
|
67
|
-
paintStatusBar(
|
|
54
|
+
paintStatusBar(ui, 'caveman', '🪨', `caveman: ${currentMode.toUpperCase()}`, isActive);
|
|
68
55
|
}
|
|
69
56
|
|
|
70
57
|
function setMode(mode: string, ctx?: ExtensionCtx): boolean {
|
|
@@ -84,9 +71,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
84
71
|
|
|
85
72
|
setExtensionLabel(pi, 'Caveman session toggle');
|
|
86
73
|
|
|
87
|
-
//
|
|
88
|
-
// it at once so the next turn injects the new mode with no session reload.
|
|
89
|
-
// Stable identity, so the bridge set dedupes across re-inits.
|
|
74
|
+
// Adopt a /tersio or /combo switch at once, so the next turn needs no reload.
|
|
90
75
|
function syncFromShared(state: { caveman: string }): void {
|
|
91
76
|
const mode = normalizeMode('caveman', state.caveman);
|
|
92
77
|
if (mode) {
|
|
@@ -94,7 +79,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
94
79
|
syncStatus();
|
|
95
80
|
}
|
|
96
81
|
}
|
|
97
|
-
setSharedComboListener(syncFromShared);
|
|
82
|
+
setSharedComboListener('caveman', syncFromShared);
|
|
98
83
|
pi.registerCommand?.('caveman', {
|
|
99
84
|
description: 'Toggle terse caveman replies for this session',
|
|
100
85
|
handler: async (args, ctx) => {
|
|
@@ -120,10 +105,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
120
105
|
|
|
121
106
|
function restoreMode(ctx?: ExtensionCtx): void {
|
|
122
107
|
const entries = sessionEntries(ctx);
|
|
123
|
-
//
|
|
124
|
-
// bar suppression reads the in-process bridge, which is empty in a fresh
|
|
125
|
-
// host until the combo extension reconciles — and its reconcile is
|
|
126
|
-
// UI-gated. Deriving it here makes suppression independent of load order.
|
|
108
|
+
// Derive the bridge here so bar suppression does not depend on load order.
|
|
127
109
|
reconcileSharedComboEntries(entries);
|
|
128
110
|
// Persisted session state wins; a fresh session falls back to the
|
|
129
111
|
// installer/user-configured default (off unless configured).
|
|
@@ -161,6 +143,9 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
161
143
|
const def = INSTRUCTIONS[mode];
|
|
162
144
|
if (!def) return;
|
|
163
145
|
const instruction = typeof def === 'function' ? def() : def;
|
|
146
|
+
// Skip when already present, so a host that re-presents the mutated prompt
|
|
147
|
+
// cannot stack it; matching the whole instruction keeps upstream rule.md intact.
|
|
148
|
+
if (systemPromptIncludes(event.systemPrompt, instruction)) return;
|
|
164
149
|
return injectPromptText(pi, event, instruction);
|
|
165
150
|
});
|
|
166
151
|
}
|
|
@@ -9,7 +9,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
};
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
|
-
import { activeModesSummary, COMBO_LEVELS, getSharedComboState, isComboPresetActive,
|
|
12
|
+
import { activeModesSummary, COMBO_LEVELS, getSharedComboState, isComboPresetActive, normalizeComboLevel, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboLevel, statusUi, setSharedComboListener, setSharedComboMode, systemPromptIncludes, themeStatus, } from '../shared/session-state.js';
|
|
13
13
|
import { hostSelect, injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
|
|
14
14
|
import { isComboSetupComplete, readComboDefault, readPonytailDefault, saveComboSetup, } from '../shared/plugin-settings.js';
|
|
15
15
|
import { findHoistedPackage } from '../lib/utils.js';
|
|
@@ -46,12 +46,12 @@ export default function comboToggleExtension(pi) {
|
|
|
46
46
|
let setupPrompted = false;
|
|
47
47
|
function syncStatus(ctx) {
|
|
48
48
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
49
|
+
const ui = statusUi(lastCtx);
|
|
50
|
+
if (!ui)
|
|
51
51
|
return;
|
|
52
52
|
// One unified bar replaces the per-extension bars while a preset is active.
|
|
53
53
|
if (!isComboPresetActive()) {
|
|
54
|
-
|
|
54
|
+
ui.setStatus('combo', undefined);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
// Read the live bridge, not a cache: siblings reconcile it first.
|
|
@@ -60,11 +60,11 @@ export default function comboToggleExtension(pi) {
|
|
|
60
60
|
const r = state.rtk.toUpperCase();
|
|
61
61
|
const p = state.ponytail.toUpperCase();
|
|
62
62
|
const lvl = state.level.toUpperCase();
|
|
63
|
-
|
|
63
|
+
ui.setStatus('combo', themeStatus(ui, '🧩', `combo ${lvl}: 🪨caveman=${c0} ⚡rtk=${r} 🦥ponytail=${p}`, true));
|
|
64
64
|
// Our bar is canonical for the preset, so clear any sibling bar.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
ui.setStatus('caveman', undefined);
|
|
66
|
+
ui.setStatus('rtk', undefined);
|
|
67
|
+
ui.setStatus('ponytail', undefined);
|
|
68
68
|
}
|
|
69
69
|
// Siblings restore from these entries, so the fallback must write them too.
|
|
70
70
|
function persistPreset(level) {
|
|
@@ -84,9 +84,8 @@ export default function comboToggleExtension(pi) {
|
|
|
84
84
|
return useState(reconcileSharedComboEntries(sessionEntries(ctx)), ctx);
|
|
85
85
|
}
|
|
86
86
|
function listen(ctx) {
|
|
87
|
-
// Stable identity: the bridge set dedupes, so repeat calls register once.
|
|
88
87
|
if (ctx?.hasUI)
|
|
89
|
-
setSharedComboListener(useState);
|
|
88
|
+
setSharedComboListener('combo', useState);
|
|
90
89
|
}
|
|
91
90
|
function track(ctx) {
|
|
92
91
|
listen(ctx);
|
|
@@ -175,13 +174,10 @@ export default function comboToggleExtension(pi) {
|
|
|
175
174
|
pi.on('before_agent_start', async (event, ctx) => {
|
|
176
175
|
if (ctx?.hasUI)
|
|
177
176
|
reconcile(ctx);
|
|
178
|
-
if (!isOmpSubagentPrompt(event.systemPrompt))
|
|
179
|
-
return;
|
|
180
177
|
const mode = getSharedComboState().ponytail;
|
|
181
178
|
if (mode === 'off' || systemPromptIncludes(event.systemPrompt, 'PONYTAIL MODE ACTIVE'))
|
|
182
179
|
return;
|
|
183
|
-
|
|
184
|
-
return injectPromptText(pi, event, instructions);
|
|
180
|
+
return injectPromptText(pi, event, await loadPonytailInstructions(mode));
|
|
185
181
|
});
|
|
186
182
|
// Slash commands only; natural-language input caused accidental toggles and has no reload context.
|
|
187
183
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// /combo session toggle: off | medium | balanced | max.
|
|
2
2
|
|
|
3
|
-
import os from 'node:os';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
5
|
import {
|
|
@@ -8,12 +7,12 @@ import {
|
|
|
8
7
|
COMBO_LEVELS,
|
|
9
8
|
getSharedComboState,
|
|
10
9
|
isComboPresetActive,
|
|
11
|
-
isOmpSubagentPrompt,
|
|
12
10
|
normalizeComboLevel,
|
|
13
11
|
paintableCtx,
|
|
14
12
|
reconcileSharedComboEntries,
|
|
15
13
|
sessionEntries,
|
|
16
14
|
setSharedComboLevel,
|
|
15
|
+
statusUi,
|
|
17
16
|
setSharedComboListener,
|
|
18
17
|
setSharedComboMode,
|
|
19
18
|
systemPromptIncludes,
|
|
@@ -70,11 +69,11 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
70
69
|
|
|
71
70
|
function syncStatus(ctx?: ExtensionCtx): void {
|
|
72
71
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
72
|
+
const ui = statusUi(lastCtx);
|
|
73
|
+
if (!ui) return;
|
|
75
74
|
// One unified bar replaces the per-extension bars while a preset is active.
|
|
76
75
|
if (!isComboPresetActive()) {
|
|
77
|
-
|
|
76
|
+
ui.setStatus('combo', undefined);
|
|
78
77
|
return;
|
|
79
78
|
}
|
|
80
79
|
// Read the live bridge, not a cache: siblings reconcile it first.
|
|
@@ -83,11 +82,11 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
83
82
|
const r = state.rtk.toUpperCase();
|
|
84
83
|
const p = state.ponytail.toUpperCase();
|
|
85
84
|
const lvl = state.level.toUpperCase();
|
|
86
|
-
|
|
85
|
+
ui.setStatus('combo', themeStatus(ui, '🧩', `combo ${lvl}: 🪨caveman=${c0} ⚡rtk=${r} 🦥ponytail=${p}`, true));
|
|
87
86
|
// Our bar is canonical for the preset, so clear any sibling bar.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
ui.setStatus('caveman', undefined);
|
|
88
|
+
ui.setStatus('rtk', undefined);
|
|
89
|
+
ui.setStatus('ponytail', undefined);
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
// Siblings restore from these entries, so the fallback must write them too.
|
|
@@ -109,8 +108,7 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
109
108
|
return useState(reconcileSharedComboEntries(sessionEntries(ctx)), ctx);
|
|
110
109
|
}
|
|
111
110
|
function listen(ctx?: ExtensionCtx): void {
|
|
112
|
-
|
|
113
|
-
if (ctx?.hasUI) setSharedComboListener(useState);
|
|
111
|
+
if (ctx?.hasUI) setSharedComboListener('combo', useState);
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
function track(ctx?: ExtensionCtx): void {
|
|
@@ -218,12 +216,9 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
218
216
|
|
|
219
217
|
pi.on<SystemPromptEvent>('before_agent_start', async (event, ctx) => {
|
|
220
218
|
if (ctx?.hasUI) reconcile(ctx);
|
|
221
|
-
if (!isOmpSubagentPrompt(event.systemPrompt)) return;
|
|
222
|
-
|
|
223
219
|
const mode = getSharedComboState().ponytail;
|
|
224
220
|
if (mode === 'off' || systemPromptIncludes(event.systemPrompt, 'PONYTAIL MODE ACTIVE')) return;
|
|
225
|
-
|
|
226
|
-
return injectPromptText(pi, event, instructions);
|
|
221
|
+
return injectPromptText(pi, event, await loadPonytailInstructions(mode));
|
|
227
222
|
});
|
|
228
223
|
|
|
229
224
|
// Slash commands only; natural-language input caused accidental toggles and has no reload context.
|
package/extensions/lib/utils.js
CHANGED
|
@@ -198,6 +198,10 @@ export async function readTextIfExists(p) {
|
|
|
198
198
|
// ~/.omp/plugins/tersio-* files over so existing installs keep their history.
|
|
199
199
|
const migratedTersioFiles = new Set();
|
|
200
200
|
export function tersioHome() {
|
|
201
|
+
// Matches the other TERSIO_* overrides so a test never reads the real home.
|
|
202
|
+
const override = process.env.TERSIO_HOME;
|
|
203
|
+
if (override && override.trim() !== '')
|
|
204
|
+
return override;
|
|
201
205
|
return path.join(os.homedir(), '.tersio');
|
|
202
206
|
}
|
|
203
207
|
export function tersioDataPath(name, legacy) {
|
package/extensions/lib/utils.ts
CHANGED
|
@@ -205,6 +205,9 @@ export async function readTextIfExists(p: string): Promise<string | null> {
|
|
|
205
205
|
const migratedTersioFiles = new Set<string>();
|
|
206
206
|
|
|
207
207
|
export function tersioHome(): string {
|
|
208
|
+
// Matches the other TERSIO_* overrides so a test never reads the real home.
|
|
209
|
+
const override = process.env.TERSIO_HOME;
|
|
210
|
+
if (override && override.trim() !== '') return override;
|
|
208
211
|
return path.join(os.homedir(), '.tersio');
|
|
209
212
|
}
|
|
210
213
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue,
|
|
1
|
+
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, statusUi, systemPromptIncludes } from '../shared/session-state.js';
|
|
2
2
|
import { injectPromptText, onHostEvent, setExtensionLabel, stringArrayToolParams } from '../shared/host.js';
|
|
3
3
|
import { readRtkDefault } from '../shared/plugin-settings.js';
|
|
4
4
|
const DEFAULT_ENABLED = false;
|
|
@@ -25,17 +25,20 @@ export default function rtkSessionExtension(pi) {
|
|
|
25
25
|
let enabled = DEFAULT_ENABLED;
|
|
26
26
|
let isActive = false;
|
|
27
27
|
let lastCtx = undefined;
|
|
28
|
+
function fail(text) {
|
|
29
|
+
return { isError: true, content: [{ type: 'text', text }], details: { enabled } };
|
|
30
|
+
}
|
|
28
31
|
function syncStatus(ctx) {
|
|
29
32
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
30
|
-
const
|
|
31
|
-
if (!
|
|
33
|
+
const ui = statusUi(lastCtx);
|
|
34
|
+
if (!ui)
|
|
32
35
|
return;
|
|
33
36
|
// Combo owns the bar when any preset is active; keep ours empty to avoid duplication.
|
|
34
37
|
if (isComboPresetActive() || !enabled) {
|
|
35
|
-
|
|
38
|
+
ui.setStatus('rtk', undefined);
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
38
|
-
paintStatusBar(
|
|
41
|
+
paintStatusBar(ui, 'rtk', '⚡', 'rtk: ON', isActive);
|
|
39
42
|
}
|
|
40
43
|
function setEnabled(next, ctx) {
|
|
41
44
|
enabled = Boolean(next);
|
|
@@ -53,7 +56,7 @@ export default function rtkSessionExtension(pi) {
|
|
|
53
56
|
setRtkProcessEnabled(enabled);
|
|
54
57
|
syncStatus();
|
|
55
58
|
}
|
|
56
|
-
setSharedComboListener(syncFromShared);
|
|
59
|
+
setSharedComboListener('rtk', syncFromShared);
|
|
57
60
|
pi.registerCommand?.('rtk', {
|
|
58
61
|
description: 'Toggle RTK compact shell-output guidance for this session',
|
|
59
62
|
handler: async (args, ctx) => {
|
|
@@ -82,38 +85,28 @@ export default function rtkSessionExtension(pi) {
|
|
|
82
85
|
description: "Arguments passed to rtk, e.g. ['git','status'] or ['read','src/index.ts']",
|
|
83
86
|
}),
|
|
84
87
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
85
|
-
if (!enabled)
|
|
88
|
+
if (!enabled)
|
|
89
|
+
return fail('RTK mode is off. Run /rtk on for this session, or use bash explicitly.');
|
|
90
|
+
if (typeof pi.exec !== 'function')
|
|
91
|
+
return fail('This host cannot run rtk.');
|
|
92
|
+
onUpdate?.({ content: [{ type: 'text', text: `rtk ${params.args.join(' ')}` }], details: { phase: 'start' } });
|
|
93
|
+
try {
|
|
94
|
+
const result = await pi.exec('rtk', params.args, { signal, cwd: ctx?.cwd || pi.cwd });
|
|
95
|
+
const text = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
86
96
|
return {
|
|
87
|
-
isError:
|
|
88
|
-
content: [{ type: 'text', text:
|
|
89
|
-
details: { enabled },
|
|
97
|
+
isError: result.code !== 0,
|
|
98
|
+
content: [{ type: 'text', text: text || `rtk exited ${result.code}` }],
|
|
99
|
+
details: { code: result.code, killed: result.killed, enabled },
|
|
90
100
|
};
|
|
91
101
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
isError: result.code !== 0,
|
|
97
|
-
content: [{ type: 'text', text: text || `rtk exited ${result.code}` }],
|
|
98
|
-
details: { code: result.code, killed: result.killed, enabled },
|
|
99
|
-
};
|
|
102
|
+
catch (e) {
|
|
103
|
+
return fail(`rtk could not run: ${e instanceof Error ? e.message : String(e)}`);
|
|
104
|
+
}
|
|
100
105
|
},
|
|
101
106
|
});
|
|
102
|
-
onHostEvent(pi, 'input', async (event) => {
|
|
103
|
-
if (event?.source === 'extension')
|
|
104
|
-
return;
|
|
105
|
-
const t = normalizeInputCommand(event?.text);
|
|
106
|
-
if (t === 'rtk on' || t === 'use rtk')
|
|
107
|
-
setEnabled(true);
|
|
108
|
-
if (t === 'rtk off' || t === 'stop rtk')
|
|
109
|
-
setEnabled(false);
|
|
110
|
-
});
|
|
111
107
|
function restoreEnabled(ctx) {
|
|
112
108
|
const entries = sessionEntries(ctx);
|
|
113
|
-
//
|
|
114
|
-
// bar suppression reads the in-process bridge, which is empty in a fresh
|
|
115
|
-
// host until the combo extension reconciles — and its reconcile is
|
|
116
|
-
// UI-gated. Deriving it here makes suppression independent of load order.
|
|
109
|
+
// Derive the bridge here so bar suppression does not depend on load order.
|
|
117
110
|
reconcileSharedComboEntries(entries);
|
|
118
111
|
// Persisted session state wins; a fresh session falls back to the
|
|
119
112
|
// installer/user-configured default (off unless configured).
|
|
@@ -144,6 +137,9 @@ export default function rtkSessionExtension(pi) {
|
|
|
144
137
|
const active = isOmpSubagentPrompt(event.systemPrompt) ? getSharedComboState().rtk === 'on' : enabled;
|
|
145
138
|
if (!active)
|
|
146
139
|
return;
|
|
140
|
+
// Skip when already present, so a host that re-presents the prompt cannot stack it.
|
|
141
|
+
if (systemPromptIncludes(event.systemPrompt, RTK_PROMPT))
|
|
142
|
+
return;
|
|
147
143
|
return injectPromptText(pi, event, RTK_PROMPT);
|
|
148
144
|
});
|
|
149
145
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue,
|
|
1
|
+
import { activeModesSummary, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, lastCustomValue, paintStatusBar, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, statusUi, systemPromptIncludes } from '../shared/session-state.ts';
|
|
2
2
|
import { injectPromptText, onHostEvent, setExtensionLabel, stringArrayToolParams } from '../shared/host.ts';
|
|
3
3
|
import { readRtkDefault } from '../shared/plugin-settings.ts';
|
|
4
|
-
import type { ExtensionApi, ExtensionCtx,
|
|
4
|
+
import type { ExtensionApi, ExtensionCtx, SessionEntry, SystemPromptEvent } from '../shared/types.ts';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_ENABLED = false;
|
|
7
7
|
|
|
@@ -28,16 +28,20 @@ export default function rtkSessionExtension(pi: ExtensionApi): void {
|
|
|
28
28
|
let isActive = false;
|
|
29
29
|
let lastCtx: ExtensionCtx | undefined = undefined;
|
|
30
30
|
|
|
31
|
+
function fail(text: string) {
|
|
32
|
+
return { isError: true as const, content: [{ type: 'text' as const, text }], details: { enabled } };
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
function syncStatus(ctx?: ExtensionCtx): void {
|
|
32
36
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
33
|
-
const
|
|
34
|
-
if (!
|
|
37
|
+
const ui = statusUi(lastCtx);
|
|
38
|
+
if (!ui) return;
|
|
35
39
|
// Combo owns the bar when any preset is active; keep ours empty to avoid duplication.
|
|
36
40
|
if (isComboPresetActive() || !enabled) {
|
|
37
|
-
|
|
41
|
+
ui.setStatus('rtk', undefined);
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
|
-
paintStatusBar(
|
|
44
|
+
paintStatusBar(ui, 'rtk', '⚡', 'rtk: ON', isActive);
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
function setEnabled(next: unknown, ctx?: ExtensionCtx): void {
|
|
@@ -58,7 +62,7 @@ export default function rtkSessionExtension(pi: ExtensionApi): void {
|
|
|
58
62
|
setRtkProcessEnabled(enabled);
|
|
59
63
|
syncStatus();
|
|
60
64
|
}
|
|
61
|
-
setSharedComboListener(syncFromShared);
|
|
65
|
+
setSharedComboListener('rtk', syncFromShared);
|
|
62
66
|
pi.registerCommand?.('rtk', {
|
|
63
67
|
description: 'Toggle RTK compact shell-output guidance for this session',
|
|
64
68
|
handler: async (args, ctx) => {
|
|
@@ -88,36 +92,26 @@ export default function rtkSessionExtension(pi: ExtensionApi): void {
|
|
|
88
92
|
description: "Arguments passed to rtk, e.g. ['git','status'] or ['read','src/index.ts']",
|
|
89
93
|
}),
|
|
90
94
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
91
|
-
if (!enabled)
|
|
95
|
+
if (!enabled) return fail('RTK mode is off. Run /rtk on for this session, or use bash explicitly.');
|
|
96
|
+
if (typeof pi.exec !== 'function') return fail('This host cannot run rtk.');
|
|
97
|
+
onUpdate?.({ content: [{ type: 'text', text: `rtk ${params.args.join(' ')}` }], details: { phase: 'start' } });
|
|
98
|
+
try {
|
|
99
|
+
const result = await pi.exec('rtk', params.args, { signal, cwd: ctx?.cwd || pi.cwd });
|
|
100
|
+
const text = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
92
101
|
return {
|
|
93
|
-
isError:
|
|
94
|
-
content: [{ type: 'text', text:
|
|
95
|
-
details: { enabled },
|
|
102
|
+
isError: result.code !== 0,
|
|
103
|
+
content: [{ type: 'text', text: text || `rtk exited ${result.code}` }],
|
|
104
|
+
details: { code: result.code, killed: result.killed, enabled },
|
|
96
105
|
};
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return fail(`rtk could not run: ${e instanceof Error ? e.message : String(e)}`);
|
|
97
108
|
}
|
|
98
|
-
onUpdate?.({ content: [{ type: 'text', text: `rtk ${params.args.join(' ')}` }], details: { phase: 'start' } });
|
|
99
|
-
const result = await pi.exec!('rtk', params.args, { signal, cwd: ctx?.cwd || pi.cwd });
|
|
100
|
-
const text = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
101
|
-
return {
|
|
102
|
-
isError: result.code !== 0,
|
|
103
|
-
content: [{ type: 'text', text: text || `rtk exited ${result.code}` }],
|
|
104
|
-
details: { code: result.code, killed: result.killed, enabled },
|
|
105
|
-
};
|
|
106
109
|
},
|
|
107
110
|
});
|
|
108
111
|
|
|
109
|
-
onHostEvent<InputEvent>(pi, 'input', async (event) => {
|
|
110
|
-
if (event?.source === 'extension') return;
|
|
111
|
-
const t = normalizeInputCommand(event?.text);
|
|
112
|
-
if (t === 'rtk on' || t === 'use rtk') setEnabled(true);
|
|
113
|
-
if (t === 'rtk off' || t === 'stop rtk') setEnabled(false);
|
|
114
|
-
});
|
|
115
112
|
function restoreEnabled(ctx?: ExtensionCtx): void {
|
|
116
113
|
const entries = sessionEntries(ctx);
|
|
117
|
-
//
|
|
118
|
-
// bar suppression reads the in-process bridge, which is empty in a fresh
|
|
119
|
-
// host until the combo extension reconciles — and its reconcile is
|
|
120
|
-
// UI-gated. Deriving it here makes suppression independent of load order.
|
|
114
|
+
// Derive the bridge here so bar suppression does not depend on load order.
|
|
121
115
|
reconcileSharedComboEntries(entries);
|
|
122
116
|
// Persisted session state wins; a fresh session falls back to the
|
|
123
117
|
// installer/user-configured default (off unless configured).
|
|
@@ -153,6 +147,8 @@ export default function rtkSessionExtension(pi: ExtensionApi): void {
|
|
|
153
147
|
pi.on<SystemPromptEvent>('before_agent_start', async (event) => {
|
|
154
148
|
const active = isOmpSubagentPrompt(event.systemPrompt) ? getSharedComboState().rtk === 'on' : enabled;
|
|
155
149
|
if (!active) return;
|
|
150
|
+
// Skip when already present, so a host that re-presents the prompt cannot stack it.
|
|
151
|
+
if (systemPromptIncludes(event.systemPrompt, RTK_PROMPT)) return;
|
|
156
152
|
return injectPromptText(pi, event, RTK_PROMPT);
|
|
157
153
|
});
|
|
158
154
|
}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// Port of the EcoLogits v0.8.2 LLM impact model (genai-impact/ecologits,
|
|
4
|
-
// MPL-2.0 — see ECO_NOTICE.md): the single-stream (batch-1) ceiling divided by
|
|
5
|
-
// serving_concurrency for continuous-batching amortization. Model params come
|
|
6
|
-
// from EcoLogits' registry, else a documented class borrow; unknown ids fall
|
|
7
|
-
// to the gpt-4o default. Render everything from here as ~est., never merged
|
|
8
|
-
// with measured figures.
|
|
1
|
+
// EcoLogits v0.8.2 port; always render as ~est, never merged with measured.
|
|
9
2
|
// EcoLogits 0.8.2 constants — kWh, kgCO2eq, seconds, GB.
|
|
10
3
|
const Q_BITS = 4;
|
|
11
4
|
const E_ALPHA = 8.91e-8;
|
|
@@ -28,6 +21,8 @@ const GRIDS = {
|
|
|
28
21
|
};
|
|
29
22
|
// Serving amortization: single-stream ceiling ÷ concurrency ≈ served impact.
|
|
30
23
|
export const SERVING_CONCURRENCY = 32;
|
|
24
|
+
/** EcoLogits release this port tracks, so a UI can name it instead of guessing. */
|
|
25
|
+
export const ECOLOGITS_VERSION = '0.8.2';
|
|
31
26
|
// Registry values verbatim from EcoLogits' model_repository; borrows cite
|
|
32
27
|
// the public param disclosure they stand in for (MoE active/total).
|
|
33
28
|
const TABLE = [
|