@krtclcdy/tersio 2.25.5 → 2.25.6
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 +17 -36
- package/dashboard/dist/index.html +15 -15
- package/dist/cli/backups.js +7 -14
- package/dist/cli/banner.js +4 -3
- package/dist/cli/common.js +28 -12
- package/dist/cli/currency.js +1 -1
- package/dist/cli/dashboard.js +66 -35
- package/dist/cli/doctor-fix.js +204 -19
- package/dist/cli/doctor.js +226 -39
- package/dist/cli/hosts.js +25 -5
- package/dist/cli/install.js +144 -48
- package/dist/cli/interactive.js +3 -3
- package/dist/cli/manifest.js +87 -7
- package/dist/cli/profile.js +13 -8
- package/dist/cli/rtk-wiring.js +1 -1
- package/dist/cli/settings.js +20 -5
- package/dist/cli/uninstall.js +3 -3
- package/dist/cli/update.js +6 -7
- package/dist/cli/usage.js +6 -9
- package/dist/extensions/caveman/index.js +4 -2
- package/dist/extensions/caveman/rule-megacave.md +5 -3
- package/dist/extensions/caveman/rule-ultra.md +4 -4
- package/dist/extensions/caveman/rule.md +55 -45
- package/dist/extensions/combo/index.js +62 -21
- package/dist/extensions/commands/index.js +1 -1
- package/dist/extensions/pstack/index.js +119 -0
- package/dist/extensions/pstack/rule.md +65 -0
- package/dist/extensions/rtk/filter.js +0 -1
- package/dist/extensions/rtk/session.js +4 -4
- package/dist/extensions/server.js +90 -6
- package/dist/extensions/shared/agent-stats.js +214 -278
- package/dist/extensions/shared/brand-marks.js +33 -34
- package/dist/extensions/shared/carbon.js +1 -1
- package/dist/extensions/shared/plugin-settings.js +11 -6
- package/dist/extensions/shared/pricing.js +8 -16
- package/dist/extensions/shared/rtk-gain.js +4 -9
- package/dist/extensions/shared/session-state.js +15 -10
- package/dist/extensions/shared/sqlite.js +168 -0
- package/dist/extensions/shared/status.js +8 -8
- package/dist/extensions/shared/types.js +1 -1
- package/dist/extensions/shared/usage-ledger.js +53 -41
- package/dist/extensions/shared/usage-store.js +111 -52
- package/dist/extensions/shared/utils.js +5 -5
- package/dist/extensions/timing/index.js +31 -0
- package/dist/extensions/updater/index.js +3 -6
- package/dist/tersio.js +8 -3
- package/extensions/caveman/index.ts +4 -2
- package/extensions/caveman/rule-megacave.md +5 -3
- package/extensions/caveman/rule-ultra.md +4 -4
- package/extensions/caveman/rule.md +55 -45
- package/extensions/combo/index.ts +62 -19
- package/extensions/commands/index.ts +1 -1
- package/extensions/pstack/index.ts +128 -0
- package/extensions/pstack/rule.md +65 -0
- package/extensions/rtk/filter.ts +0 -1
- package/extensions/rtk/session.ts +4 -4
- package/extensions/server.ts +72 -7
- package/extensions/shared/agent-stats.ts +226 -278
- package/extensions/shared/brand-marks.ts +34 -34
- package/extensions/shared/carbon.ts +1 -1
- package/extensions/shared/plugin-settings.ts +12 -6
- package/extensions/shared/pricing.ts +10 -26
- package/extensions/shared/rtk-gain.ts +4 -9
- package/extensions/shared/session-state.ts +18 -13
- package/extensions/shared/sqlite.ts +168 -0
- package/extensions/shared/status.ts +9 -8
- package/extensions/shared/types.ts +2 -1
- package/extensions/shared/usage-ledger.ts +60 -47
- package/extensions/shared/usage-store.ts +120 -52
- package/extensions/shared/utils.ts +5 -5
- package/extensions/timing/index.ts +45 -0
- package/extensions/updater/index.ts +3 -8
- package/package.json +17 -8
package/dist/cli/rtk-wiring.js
CHANGED
|
@@ -61,7 +61,7 @@ function rtkExtensionPath() {
|
|
|
61
61
|
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
62
62
|
return path.join(home, '.omp', 'agent', 'extensions', 'rtk.ts');
|
|
63
63
|
}
|
|
64
|
-
// Append rtk.ts
|
|
64
|
+
// Append rtk.ts so the wire takes effect on the next OMP start.
|
|
65
65
|
export async function ensureRtkInConfig(options) {
|
|
66
66
|
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
67
67
|
const configPath = path.join(home, '.omp', 'agent', 'config.yml');
|
package/dist/cli/settings.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// cli/settings.ts — view/edit session-start defaults and the display currency.
|
|
2
|
-
import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, PONYTAIL_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, subagentMarkerFlags, subagentMarkersGiven, } from './common.js';
|
|
2
|
+
import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, PONYTAIL_DEFAULTS, PSTACK_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, pstackDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, subagentMarkerFlags, subagentMarkersGiven, } from './common.js';
|
|
3
3
|
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
4
4
|
import { CURRENCY_CODES } from './currency.js';
|
|
5
5
|
import { textTable } from './usage.js';
|
|
@@ -14,12 +14,15 @@ function applyFlags(base) {
|
|
|
14
14
|
next.cavemanDefault = preset.caveman;
|
|
15
15
|
next.rtkDefault = preset.rtk;
|
|
16
16
|
next.ponytailDefault = preset.ponytail;
|
|
17
|
+
next.pstackDefault = preset.pstack;
|
|
17
18
|
if (cavemanDefaultFlag !== undefined)
|
|
18
19
|
next.cavemanDefault = cavemanDefaultFlag;
|
|
19
20
|
if (rtkDefaultFlag !== undefined)
|
|
20
21
|
next.rtkDefault = rtkDefaultFlag === 'on';
|
|
21
22
|
if (ponytailDefaultFlag !== undefined)
|
|
22
23
|
next.ponytailDefault = ponytailDefaultFlag;
|
|
24
|
+
if (pstackDefaultFlag !== undefined)
|
|
25
|
+
next.pstackDefault = pstackDefaultFlag;
|
|
23
26
|
if (subagentMarkersGiven)
|
|
24
27
|
next.subagentMarkers = subagentMarkerFlags;
|
|
25
28
|
if (currencyGiven)
|
|
@@ -41,6 +44,7 @@ async function askProfile(current) {
|
|
|
41
44
|
cavemanDefault: preset.caveman,
|
|
42
45
|
rtkDefault: preset.rtk,
|
|
43
46
|
ponytailDefault: preset.ponytail,
|
|
47
|
+
pstackDefault: preset.pstack,
|
|
44
48
|
currency: current.currency,
|
|
45
49
|
backupSchedule: current.backupSchedule,
|
|
46
50
|
subagentMarkers: current.subagentMarkers,
|
|
@@ -82,11 +86,11 @@ async function askDiagSchedule(current) {
|
|
|
82
86
|
return null;
|
|
83
87
|
return picked.value;
|
|
84
88
|
}
|
|
85
|
-
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis', 'markers'];
|
|
89
|
+
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'pstack', 'currency', 'diagnosis', 'markers'];
|
|
86
90
|
function settingsUsage() {
|
|
87
|
-
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|megacave-lite|megacave-full|megacave-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--subagent-marker <text>]... [--dry-run]');
|
|
91
|
+
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|pstack|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|megacave-lite|megacave-full|megacave-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--pstack-default on|off] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--subagent-marker <text>]... [--dry-run]');
|
|
88
92
|
}
|
|
89
|
-
// Single-setting jump:
|
|
93
|
+
// Single-setting jump: prompts only that value instead of walking the whole chain; null when aborted.
|
|
90
94
|
async function runSingleSetting(name, current, nextDiag) {
|
|
91
95
|
const next = { ...current };
|
|
92
96
|
let diag = nextDiag;
|
|
@@ -105,6 +109,7 @@ async function runSingleSetting(name, current, nextDiag) {
|
|
|
105
109
|
next.cavemanDefault = preset.caveman;
|
|
106
110
|
next.rtkDefault = preset.rtk;
|
|
107
111
|
next.ponytailDefault = preset.ponytail;
|
|
112
|
+
next.pstackDefault = preset.pstack;
|
|
108
113
|
break;
|
|
109
114
|
}
|
|
110
115
|
case 'caveman': {
|
|
@@ -135,6 +140,15 @@ async function runSingleSetting(name, current, nextDiag) {
|
|
|
135
140
|
next.ponytailDefault = picked.value;
|
|
136
141
|
break;
|
|
137
142
|
}
|
|
143
|
+
case 'pstack': {
|
|
144
|
+
const picked = await askInteractiveChoice('pstack default (rigor mode)', [...PSTACK_DEFAULTS].map((v) => ({
|
|
145
|
+
value: v, label: v,
|
|
146
|
+
})), next.pstackDefault);
|
|
147
|
+
if (picked.status !== 'selected')
|
|
148
|
+
return null;
|
|
149
|
+
next.pstackDefault = picked.value;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
138
152
|
case 'currency': {
|
|
139
153
|
const picked = await askInteractiveChoice('Display currency (usage/dashboard reports)', CURRENCY_CODES.map((v) => ({
|
|
140
154
|
value: v, label: v,
|
|
@@ -173,6 +187,7 @@ function printSettingsTable(current) {
|
|
|
173
187
|
['caveman', current.cavemanDefault, [...CAVEMAN_DEFAULTS].join(' | ')],
|
|
174
188
|
['rtk', current.rtkDefault ? 'on' : 'off', 'on | off'],
|
|
175
189
|
['ponytail', current.ponytailDefault, [...PONYTAIL_DEFAULTS].join(' | ')],
|
|
190
|
+
['pstack', current.pstackDefault, [...PSTACK_DEFAULTS].join(' | ')],
|
|
176
191
|
['diagnosis', readDiagSchedule(), 'manual | daily | weekly | monthly'],
|
|
177
192
|
['markers', current.subagentMarkers.length === 0 ? 'default' : current.subagentMarkers.join(' | '), 'host subagent prompt text'],
|
|
178
193
|
];
|
|
@@ -180,7 +195,7 @@ function printSettingsTable(current) {
|
|
|
180
195
|
console.log(l);
|
|
181
196
|
}
|
|
182
197
|
console.log(` Stored: ${tersioSettingsFile()}`);
|
|
183
|
-
console.log(` Combo ${current.comboDefault} sets
|
|
198
|
+
console.log(` Combo ${current.comboDefault} sets all four modes. Tune one: tersio settings <name>`);
|
|
184
199
|
}
|
|
185
200
|
async function runSettings() {
|
|
186
201
|
const current = await storedProfile();
|
package/dist/cli/uninstall.js
CHANGED
|
@@ -11,6 +11,7 @@ const PONYTAIL_PKG = '@dietrichgebert/ponytail';
|
|
|
11
11
|
const PI_TREE_DIRS = [
|
|
12
12
|
// What an install writes now.
|
|
13
13
|
'caveman',
|
|
14
|
+
'pstack',
|
|
14
15
|
'rtk',
|
|
15
16
|
'updater',
|
|
16
17
|
'combo',
|
|
@@ -100,7 +101,7 @@ async function removePiLayer(host, shouldDryRun, shouldRemovePonytail, shouldRem
|
|
|
100
101
|
await removeCopiedPonytailSkills(shouldDryRun);
|
|
101
102
|
}
|
|
102
103
|
await Promise.all(targets.map((t) => removeUninstallTarget(t, shouldDryRun)));
|
|
103
|
-
// rtk's wiring is a loose file, not
|
|
104
|
+
// rtk's wiring is a loose file, not a tree dir, so it survived --remove-rtk on pi while OMP removed its copy.
|
|
104
105
|
if (shouldRemoveRtk) {
|
|
105
106
|
await removeUninstallTarget(rtkWiring, shouldDryRun, false);
|
|
106
107
|
await removeUninstallTarget(path.join(BUN_BIN_DIR, RTK_BINARY_NAME), shouldDryRun, false);
|
|
@@ -281,7 +282,6 @@ async function runUninstall(options = {}) {
|
|
|
281
282
|
return false;
|
|
282
283
|
// Remove extension directories (independent paths, so concurrently).
|
|
283
284
|
await Promise.all(targets.map((t) => removeUninstallTarget(t, shouldDryRun)));
|
|
284
|
-
// Remove Combo, mode-reinforcement, and rtk registrations; Ponytail on request.
|
|
285
285
|
const configRaw = await readTextIfExists(configPath);
|
|
286
286
|
if (configRaw) {
|
|
287
287
|
let lines = configRaw.split('\n');
|
|
@@ -291,7 +291,7 @@ async function runUninstall(options = {}) {
|
|
|
291
291
|
return false;
|
|
292
292
|
if (shouldRemovePonytail && l.includes('ponytail') && l.includes('pi-extension'))
|
|
293
293
|
return false;
|
|
294
|
-
// Our rtk wiring, absolute or relative. Leaving it listed would keep OMP loading a hook for
|
|
294
|
+
// Our rtk wiring, absolute or relative. Leaving it listed would keep OMP loading a hook for an uninstalled package.
|
|
295
295
|
const entry = l.trim().replace(/^-\s*/, '').replace(/^['"]|['"]$/g, '');
|
|
296
296
|
if (entry.endsWith(`extensions${path.sep}rtk.ts`) || entry.endsWith('extensions/rtk.ts'))
|
|
297
297
|
return false;
|
package/dist/cli/update.js
CHANGED
|
@@ -35,7 +35,7 @@ async function latestPublishedVersion() {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
// A newer
|
|
38
|
+
// A newer version, null when up to date, 'unknown' when the registry is unreachable. Cached for a TTL unless force.
|
|
39
39
|
async function checkForUpdate(force = false) {
|
|
40
40
|
const cachePath = tersioDataPath('update-check.json', 'tersio-update-check.json');
|
|
41
41
|
const cached = parseJsonObject(await readTextIfExists(cachePath));
|
|
@@ -48,7 +48,7 @@ async function checkForUpdate(force = false) {
|
|
|
48
48
|
await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
|
|
49
49
|
return newerThan(latest, PACKAGE_VERSION) ? latest : null;
|
|
50
50
|
}
|
|
51
|
-
// Unreachable registry: a stale cache naming a newer release is
|
|
51
|
+
// Unreachable registry: a stale cache naming a newer release is actionable, else unknown, so none claim "latest".
|
|
52
52
|
return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : 'unknown';
|
|
53
53
|
}
|
|
54
54
|
// Race a probe against a timeout; failures resolve null so nothing blocks.
|
|
@@ -74,7 +74,7 @@ async function execInherit(cmd, args) {
|
|
|
74
74
|
if (code !== 0)
|
|
75
75
|
throw new Error(`${cmd} exited with code ${code}`);
|
|
76
76
|
}
|
|
77
|
-
// Current → latest per add-on.
|
|
77
|
+
// Current → latest per add-on. Probes are capped and nullable; the plan prints `unknown` for anything unreachable.
|
|
78
78
|
async function probeUpdatePlan(cliLatest) {
|
|
79
79
|
const rtkBin = resolveRtkBinary();
|
|
80
80
|
const ponytailPkg = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
@@ -98,7 +98,7 @@ async function probeUpdatePlan(cliLatest) {
|
|
|
98
98
|
ponytail: ponytailLocalText ? parseJsonObject(ponytailLocalText)?.version ?? null : null,
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
-
// Every add-on prints a version jump when stale, "up to date" otherwise, and "unknown" when
|
|
101
|
+
// Every add-on prints a version jump when stale, "up to date" otherwise, and "unknown" when unreachable.
|
|
102
102
|
function planLines(plan) {
|
|
103
103
|
const stale = [];
|
|
104
104
|
const status = [];
|
|
@@ -147,7 +147,6 @@ async function runLatestUpdate() {
|
|
|
147
147
|
// `--prefer-online` beats the local cache, so a stale @latest cannot pin down.
|
|
148
148
|
const cliLatest = await latestPublishedVersion();
|
|
149
149
|
const target = cliLatest && /^\d+\.\d+\.\d+$/.test(cliLatest) ? `@${cliLatest}` : '@latest';
|
|
150
|
-
// Best-effort: unreachable probes print as unknown.
|
|
151
150
|
const plan = await probeUpdatePlan(cliLatest);
|
|
152
151
|
const { stale, status } = planLines(plan);
|
|
153
152
|
if (dryRun) {
|
|
@@ -181,7 +180,7 @@ async function runLatestUpdate() {
|
|
|
181
180
|
process.exitCode = 1;
|
|
182
181
|
return;
|
|
183
182
|
}
|
|
184
|
-
// The npx delegation below only refreshes the OMP-side files; the
|
|
183
|
+
// The npx delegation below only refreshes the OMP-side files; the global CLI keeps its old version until npm -g runs.
|
|
185
184
|
const globalArgs = IS_WINDOWS
|
|
186
185
|
? ['/d', '/s', '/c', 'npm', 'install', '-g', `${PACKAGE_NAME}${target}`, '--no-audit', '--no-fund', '--prefer-online']
|
|
187
186
|
: ['install', '-g', `${PACKAGE_NAME}${target}`, '--no-audit', '--no-fund', '--prefer-online'];
|
|
@@ -211,7 +210,7 @@ async function runLatestUpdate() {
|
|
|
211
210
|
...forwardedArgs,
|
|
212
211
|
];
|
|
213
212
|
const npmCommandArgs = IS_WINDOWS ? ['/d', '/s', '/c', 'npm', ...npmArgs] : npmArgs;
|
|
214
|
-
// Inherited stdio, no outer spinner; the
|
|
213
|
+
// Inherited stdio, no outer spinner; the quiet installer runs, so this parent owns the plan line and closing summary.
|
|
215
214
|
try {
|
|
216
215
|
await execInherit(npmCommand, npmCommandArgs);
|
|
217
216
|
console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION} and add-ons updated.`);
|
package/dist/cli/usage.js
CHANGED
|
@@ -6,7 +6,7 @@ import { readUsageDb, reparseGuardReport, syncUsageDb, usageDbPath } from '../ex
|
|
|
6
6
|
import { withInteractiveSpinner } from './interactive.js';
|
|
7
7
|
import { PACKAGE_VERSION, currency } from './common.js';
|
|
8
8
|
import { formatCurrency } from './currency.js';
|
|
9
|
-
// RTK
|
|
9
|
+
// RTK history is machine-wide; rows group by command. 2000 groups covers a long history while bounding /data.json.
|
|
10
10
|
const RTK_COMMAND_ROWS = 2000;
|
|
11
11
|
export function summarizeUsage(rows) {
|
|
12
12
|
refreshPricesIfStale();
|
|
@@ -17,7 +17,7 @@ export function summarizeUsage(rows) {
|
|
|
17
17
|
detailCounts[r.detail] = (detailCounts[r.detail] ?? 0) + 1;
|
|
18
18
|
}
|
|
19
19
|
const byDetail = Object.entries(detailCounts).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
20
|
-
// usage.db
|
|
20
|
+
// usage.db caches the same per-message rows: sync best-effort, read stored, fall back to the live parse when stale.
|
|
21
21
|
let synced = false;
|
|
22
22
|
try {
|
|
23
23
|
synced = syncUsageDb();
|
|
@@ -62,8 +62,7 @@ export function summarizeUsage(rows) {
|
|
|
62
62
|
const weakModels = [];
|
|
63
63
|
const measuredModels = [];
|
|
64
64
|
for (const [model, t] of Object.entries(session.byModel)) {
|
|
65
|
-
// A model
|
|
66
|
-
// model would read its unpriced messages as zero, which is the one error worse than a guess.
|
|
65
|
+
// A model counts as measured only when the host priced every message, or unpriced ones would read as zero.
|
|
67
66
|
const covered = (session.byModelCostMessages[model] ?? 0) >= (session.byModelMessages[model] ?? 0);
|
|
68
67
|
const measured = covered ? session.byModelMeasured[model] : undefined;
|
|
69
68
|
const c = usdCost(t, model, measured);
|
|
@@ -71,8 +70,7 @@ export function summarizeUsage(rows) {
|
|
|
71
70
|
priceTiers[c.tier] = (priceTiers[c.tier] ?? 0) + 1;
|
|
72
71
|
if (c.tier === 'measured')
|
|
73
72
|
measuredModels.push(model);
|
|
74
|
-
// A tier
|
|
75
|
-
// summed beside a measured cost that would make the whole figure read as a bill.
|
|
73
|
+
// A non-firm tier is kept out of the firm total, or a guessed cost would make the figure read as a bill.
|
|
76
74
|
if (!PRICE_TIERS_FIRM.includes(c.tier)) {
|
|
77
75
|
estimatedUsd += c.usd;
|
|
78
76
|
weakModels.push(model);
|
|
@@ -180,7 +178,7 @@ const ROW_CAP = 15;
|
|
|
180
178
|
function pad(s, n) {
|
|
181
179
|
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
|
182
180
|
}
|
|
183
|
-
// Box-drawing table, plain text (piped-safe, byte-stable).
|
|
181
|
+
// Box-drawing table, plain text (piped-safe, byte-stable). Numbers right-align; long cells truncate with an ellipsis.
|
|
184
182
|
function textTable(headers, rows, right = [], maxW = 32) {
|
|
185
183
|
const cells = [headers, ...rows].map((r) => r.map((c) => (c.length > maxW ? c.slice(0, maxW - 1) + '…' : c)));
|
|
186
184
|
const widths = headers.map((_, i) => Math.max(...cells.map((r) => r[i].length)));
|
|
@@ -203,8 +201,7 @@ function printReport(report) {
|
|
|
203
201
|
// A tier that is not firm is named, so a figure that is partly a guess never reads as a bill.
|
|
204
202
|
const weak = ['tail', 'default'].reduce((sum, tier) => sum + (report.priceTiers[tier] ?? 0), 0);
|
|
205
203
|
const overridden = report.priceTiers.override ?? 0;
|
|
206
|
-
// The count stays in the cell;
|
|
207
|
-
// fixed 118 columns and a list of model ids would be truncated to nothing.
|
|
204
|
+
// The count stays in the cell; names go below, since a 118-column cell would truncate a model-id list.
|
|
208
205
|
const listed = report.weakModels.map((m) => displayModelId(m));
|
|
209
206
|
const measuredCount = report.measuredModels.length;
|
|
210
207
|
const costNote = weak > 0
|
|
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { getSharedComboState, hasModeState, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, 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
|
-
import { announceStatus } from '../shared/status.js';
|
|
6
|
+
import { announceStatus, formatCavemanStatus } from '../shared/status.js';
|
|
7
7
|
import { readCavemanDefault } from '../shared/plugin-settings.js';
|
|
8
8
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
// Upstream ships one skill per register: default, ultracave, megacave.
|
|
@@ -66,6 +66,8 @@ export default function cavemanSessionExtension(pi) {
|
|
|
66
66
|
}
|
|
67
67
|
if (arg === 'status') {
|
|
68
68
|
announceStatus(ctx);
|
|
69
|
+
// The footer carries one line, so the mode is answered here instead of standing as a second status key.
|
|
70
|
+
ctx?.ui?.notify?.(formatCavemanStatus(currentMode), 'info');
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
73
|
if (!setMode(arg, ctx)) {
|
|
@@ -84,7 +86,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
84
86
|
// A subagent's branch has no Tersio entries; reconciling it would wipe shared state.
|
|
85
87
|
if (hasModeState(entries))
|
|
86
88
|
reconcileSharedComboEntries(entries);
|
|
87
|
-
// Persisted state wins, then a mode already chosen
|
|
89
|
+
// Persisted state wins, then a mode already chosen, then the default. `off` is the start value, not unset.
|
|
88
90
|
const persisted = resolveMode(entries, '');
|
|
89
91
|
const alreadyChosen = currentMode !== DEFAULT_MODE;
|
|
90
92
|
currentMode = persisted || (alreadyChosen ? currentMode : normalizeMode('caveman', readCavemanDefault())) || DEFAULT_MODE;
|
|
@@ -15,11 +15,11 @@ Megacave is caveman in Classical Chinese. 文言文: subjects omitted where reco
|
|
|
15
15
|
|
|
16
16
|
## Persistence
|
|
17
17
|
|
|
18
|
-
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value or
|
|
18
|
+
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value; with no hook, the mode you were in (or `off`) plus `(not tracked by this host)`.
|
|
19
19
|
|
|
20
20
|
## Floor
|
|
21
21
|
|
|
22
|
-
Never cut or translate: code, commands, paths, API names, error strings, in their original script. Negation (不, 非, 勿, 未, 毋). Numbers and units, Arabic numerals. One term per thing. A clause ambiguous in 文言 becomes 白話.
|
|
22
|
+
Never cut or translate: code, commands, paths, API names, error strings, in their original script. Negation (不, 非, 勿, 未, 毋). Numbers and units, Arabic numerals. One term per thing. A clause ambiguous in 文言 becomes 白話. Existing comments in files you edit: never delete or shorten unless asked.
|
|
23
23
|
|
|
24
24
|
## Rules
|
|
25
25
|
|
|
@@ -39,6 +39,8 @@ Good: "池蓄已開之連,不逐請而新開,省握手之費。"
|
|
|
39
39
|
|
|
40
40
|
### 3. Each fact once
|
|
41
41
|
|
|
42
|
+
Code change: changed lines plus context, not the whole file.
|
|
43
|
+
|
|
42
44
|
Good: "新參照則重繪。`useMemo` 包之。"
|
|
43
45
|
|
|
44
46
|
### 4. Payload in original script
|
|
@@ -56,7 +58,7 @@ No "文言模式啟", no modern answer plus classical copy. 文言 not shorter t
|
|
|
56
58
|
|
|
57
59
|
## When to break the rules
|
|
58
60
|
|
|
59
|
-
白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirm first. Any clause with two readings. User confused. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
|
|
61
|
+
白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirm first. Any clause with two readings. User confused. Question to the user, with its options. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
|
|
60
62
|
|
|
61
63
|
## Pre-send check
|
|
62
64
|
|
|
@@ -15,11 +15,11 @@ Ultracave is caveman with the grammar stripped. Payload only.
|
|
|
15
15
|
|
|
16
16
|
## Persistence
|
|
17
17
|
|
|
18
|
-
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value or
|
|
18
|
+
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value; with no hook, the mode you were in (or `off`) plus `(not tracked by this host)`.
|
|
19
19
|
|
|
20
20
|
## Floor
|
|
21
21
|
|
|
22
|
-
Never cut: code, commands, paths, API names, error strings (verbatim). not/never/no/only/except. Numbers and units. The user's language. One term per thing. No invented abbreviations, no arrows.
|
|
22
|
+
Never cut: code, commands, paths, API names, error strings (verbatim). not/never/no/only/except. Numbers and units. The user's language. One term per thing. No invented abbreviations, no arrows. Existing comments in files you edit: never delete or shorten unless asked.
|
|
23
23
|
|
|
24
24
|
## Rules
|
|
25
25
|
|
|
@@ -37,7 +37,7 @@ Good: "Yes. Null check first."
|
|
|
37
37
|
|
|
38
38
|
### 3. Each fact once
|
|
39
39
|
|
|
40
|
-
No restating, no summary after a list.
|
|
40
|
+
No restating, no summary after a list. Code change: changed lines plus context, not the whole file.
|
|
41
41
|
|
|
42
42
|
Good: "Pool reuses open DB connections. No per-request handshake."
|
|
43
43
|
|
|
@@ -56,7 +56,7 @@ No prefix, no announcement, no mangled verbs for flavor. Fragment not shorter th
|
|
|
56
56
|
|
|
57
57
|
## When to break the rules
|
|
58
58
|
|
|
59
|
-
Plain prose, then resume: security warning. Irreversible action, confirm first. Any fragment with two readings. User confused. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
|
|
59
|
+
Plain prose, then resume: security warning. Irreversible action, confirm first. Any fragment with two readings. User confused. Question to the user, with its options. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
|
|
60
60
|
|
|
61
61
|
## Pre-send check
|
|
62
62
|
|
|
@@ -1,79 +1,89 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: caveman
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Terse caveman voice: answer first, fluff gone, every technical fact kept.
|
|
5
|
+
Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less
|
|
6
|
+
tokens". Stays on until "stop caveman" or "normal mode".
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
# caveman
|
|
10
|
+
|
|
9
11
|
Respond terse like smart caveman. All technical substance stay. Only fluff die.
|
|
10
12
|
|
|
13
|
+
Caveman is a voice, not broken grammar. Reader pays per token and reads in a terminal. Every word earns its place. Every fact survives.
|
|
14
|
+
|
|
11
15
|
## Persistence
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Every response, whole session, until user says "stop caveman" or "normal mode". Unsure if still on? It is. Confirm the switch-off in one line.
|
|
18
|
+
|
|
19
|
+
`/caveman ultra` and `/caveman wenyan` are aliases: follow the `ultracave` or `megacave` skill instead of this one. `/caveman status` reports the mode and changes nothing. Relay the hook's `Caveman mode: <mode>` value when present. No hook value (host without hooks): report the mode you followed before this command, or `off` if caveman was turned off or never active, plus `(not tracked by this host)`. Example: `Caveman mode: caveman (not tracked by this host)`. Loading this skill to answer status is not activation. Never infer a mode from the configured default.
|
|
20
|
+
|
|
21
|
+
## Why
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
1. Every output token is billed and read. Filler costs twice.
|
|
24
|
+
2. Code, commands, paths, numbers, errors are the payload. One changed character breaks them.
|
|
25
|
+
3. Ceremony is expensive, grammar is cheap. "Sure, I'd be happy to help" is ten tokens. "the" is one.
|
|
26
|
+
4. A dropped negation costs more than every token saved. Clarity beats compression.
|
|
16
27
|
|
|
17
28
|
## Rules
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
### 1. Answer first
|
|
31
|
+
|
|
32
|
+
Answer, then reason, then next step. Pattern: `[thing] [action] [reason]. [next step].`
|
|
33
|
+
|
|
34
|
+
Bad: "Sure! I'd be happy to help. The issue you're experiencing is likely caused by..."
|
|
35
|
+
Good: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
|
|
36
|
+
|
|
37
|
+
### 2. Kill ceremony
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
No greeting, hedging, pleasantries, recap, or closer. No "Sure!", "Let me", "I'll now", "Hope this helps". No just/really/basically/actually/simply.
|
|
22
40
|
|
|
23
|
-
|
|
41
|
+
### 3. Short word
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
"fix" not "implement a solution for". Standard acronyms fine (DB, API, HTTP). Invented abbreviations not (cfg, impl, fn): same tokens, harder read. No arrows.
|
|
26
44
|
|
|
27
|
-
|
|
45
|
+
### 4. Articles optional, meaning never
|
|
28
46
|
|
|
29
|
-
|
|
47
|
+
Drop a/an/the when the sentence still reads in one pass. Fragments fine. Never drop not/never/no/only/except. Numbers and units exact.
|
|
30
48
|
|
|
31
|
-
|
|
49
|
+
Bad: "Migration drop column backup first."
|
|
50
|
+
Good: "Back up first. Then run migration: it drops the column."
|
|
32
51
|
|
|
33
|
-
|
|
52
|
+
### 5. One idea per sentence
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
ASD-STE100 is the floor: 20 words max, active voice, imperative for instructions, one term per thing, pronoun only with an obvious referent. Compression and clarity conflict? Clarity wins.
|
|
36
55
|
|
|
37
|
-
|
|
56
|
+
### 6. Payload verbatim
|
|
38
57
|
|
|
39
|
-
|
|
40
|
-
Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
|
|
58
|
+
Code blocks unchanged. Commands, paths, API names exact. Errors quoted exact, shortest decisive line only. Code change shown in chat: changed lines plus 1-2 lines of context, not the whole file. Whole file only if the user asks, the file is new, or most of it changes. Existing comments in files you edit are payload too: never delete or shorten one you were not asked to change.
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
### 7. Tool runs: bounded status
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|-------|------------|
|
|
46
|
-
| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
|
|
47
|
-
| **full** | Drop articles, fragments OK, short synonyms. Classic caveman. No tool-call narration, no decorative tables/emoji, no long raw error-log dumps unless asked. Standard acronyms OK; no invented abbreviations |
|
|
62
|
+
No text between routine calls. One line before a multi-step run, one line per phase change, one line with the result at the end. Otherwise text before a call only to clarify, warn, or disambiguate.
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
|
|
51
|
-
- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
|
|
64
|
+
### 8. User's language
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
|
|
55
|
-
- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
|
|
66
|
+
Compress the style, not the language. An explicit reply-language instruction wins. Never switch because of quoted text. Technical terms and errors stay verbatim. Particles and case markers are grammar, not filler.
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
### 9. Never perform caveman
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
- Security warnings
|
|
61
|
-
- Irreversible action confirmations
|
|
62
|
-
- Multi-step sequences where fragment order or omitted conjunctions risk misread
|
|
63
|
-
- Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` order unclear without articles/conjunctions)
|
|
64
|
-
- User asks to clarify or repeats question
|
|
70
|
+
No "caveman mode on", no "me think", no "Caveman:" prefix, no normal answer plus caveman copy. No decorative tables or emoji. Never add a word to sound caveman. Caveman phrasing not shorter than plain? Use plain.
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
## When to break the rules
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
Plain prose, then resume:
|
|
69
75
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
1. Security warning.
|
|
77
|
+
2. Irreversible action. Confirm in full sentences first.
|
|
78
|
+
3. Step order a fragment could scramble.
|
|
79
|
+
4. User confused or repeats the question.
|
|
80
|
+
5. Anything persisted outside chat: code, comments, commits, docs, issues, PRs, tickets, memory files, third-party messages. `/caveman-compress` exempt.
|
|
81
|
+
6. Harness asks for a status line or confirmation. Give it. Harness decides *when* you speak, caveman decides *how*.
|
|
82
|
+
7. You ask the user a question or offer options. Full sentences, so the answer comes back right first time.
|
|
76
83
|
|
|
77
|
-
##
|
|
84
|
+
## Pre-send check
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
1. First sentence announces what you will do? Delete.
|
|
87
|
+
2. Last sentence recaps or offers help? Delete.
|
|
88
|
+
3. Every not/never/no/only present? Every code span, path, number, error verbatim?
|
|
89
|
+
4. Any sentence with two readings? Make it a full sentence.
|