@krtclcdy/tersio 2.25.5 → 2.25.7
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 +42 -38
- package/dashboard/dist/index.html +15 -15
- package/dist/bench/code-out/pfull-0/debounce.js +13 -0
- package/dist/bench/code-out/pfull-0/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-0/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-1/debounce.js +13 -0
- package/dist/bench/code-out/pfull-1/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-1/groupBy.js +14 -0
- package/dist/bench/code-out/pfull-2/debounce.js +13 -0
- package/dist/bench/code-out/pfull-2/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-2/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-3/debounce.js +12 -0
- package/dist/bench/code-out/pfull-3/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-3/groupBy.js +7 -0
- package/dist/bench/code-out/pfull-4/debounce.js +10 -0
- package/dist/bench/code-out/pfull-4/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-4/groupBy.js +14 -0
- package/dist/bench/code-out/plite-0/debounce.js +13 -0
- package/dist/bench/code-out/plite-0/deepClone.js +18 -0
- package/dist/bench/code-out/plite-0/groupBy.js +15 -0
- package/dist/bench/code-out/plite-1/debounce.js +13 -0
- package/dist/bench/code-out/plite-1/deepClone.js +17 -0
- package/dist/bench/code-out/plite-1/groupBy.js +11 -0
- package/dist/bench/code-out/plite-2/debounce.js +12 -0
- package/dist/bench/code-out/plite-2/deepClone.js +17 -0
- package/dist/bench/code-out/plite-2/groupBy.js +14 -0
- package/dist/bench/code-out/plite-3/debounce.js +12 -0
- package/dist/bench/code-out/plite-3/deepClone.js +17 -0
- package/dist/bench/code-out/plite-3/groupBy.js +14 -0
- package/dist/bench/code-out/plite-4/debounce.js +12 -0
- package/dist/bench/code-out/plite-4/deepClone.js +17 -0
- package/dist/bench/code-out/plite-4/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-0/debounce.js +12 -0
- package/dist/bench/code-out/pultra-0/deepClone.js +20 -0
- package/dist/bench/code-out/pultra-0/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-1/debounce.js +10 -0
- package/dist/bench/code-out/pultra-1/deepClone.js +17 -0
- package/dist/bench/code-out/pultra-1/groupBy.js +7 -0
- package/dist/bench/code-out/pultra-2/debounce.js +13 -0
- package/dist/bench/code-out/pultra-2/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-2/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-3/debounce.js +13 -0
- package/dist/bench/code-out/pultra-3/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-3/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-4/debounce.js +13 -0
- package/dist/bench/code-out/pultra-4/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-4/groupBy.js +15 -0
- package/dist/bench/code-out/run-0/debounce.js +12 -0
- package/dist/bench/code-out/run-0/deepClone.js +17 -0
- package/dist/bench/code-out/run-0/groupBy.js +14 -0
- package/dist/bench/code-out/run-1/debounce.js +13 -0
- package/dist/bench/code-out/run-1/deepClone.js +15 -0
- package/dist/bench/code-out/run-1/groupBy.js +13 -0
- package/dist/bench/code-out/run-2/debounce.js +12 -0
- package/dist/bench/code-out/run-2/deepClone.js +18 -0
- package/dist/bench/code-out/run-2/groupBy.js +13 -0
- package/dist/bench/code-out/run-3/debounce.js +12 -0
- package/dist/bench/code-out/run-3/deepClone.js +18 -0
- package/dist/bench/code-out/run-3/groupBy.js +14 -0
- package/dist/bench/code-out/run-4/debounce.js +12 -0
- package/dist/bench/code-out/run-4/deepClone.js +16 -0
- package/dist/bench/code-out/run-4/groupBy.js +13 -0
- package/dist/cli/backups.js +7 -14
- package/dist/cli/banner.js +3 -3
- package/dist/cli/common.js +29 -13
- 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 +263 -40
- package/dist/cli/hosts.js +25 -6
- package/dist/cli/install.js +150 -49
- package/dist/cli/interactive.js +3 -3
- package/dist/cli/manifest.js +109 -9
- 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 +4 -4
- package/dist/cli/update.js +6 -7
- package/dist/cli/usage.js +6 -9
- package/dist/extensions/caveman/index.js +10 -4
- package/dist/extensions/caveman/rule-lite.md +77 -0
- package/dist/extensions/caveman/rule-megacave-lite.md +59 -0
- package/dist/extensions/caveman/rule-megacave-ultra.md +59 -0
- 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 +85 -25
- package/dist/extensions/commands/index.js +1 -1
- package/dist/extensions/pstack/index.js +121 -0
- package/dist/extensions/pstack/rule.md +141 -0
- package/dist/extensions/rtk/filter.js +0 -1
- package/dist/extensions/rtk/session.js +20 -12
- 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-eligible.js +84 -0
- package/dist/extensions/shared/rtk-gain.js +5 -10
- package/dist/extensions/shared/session-state.js +15 -10
- package/dist/extensions/shared/sqlite.js +162 -0
- package/dist/extensions/shared/status.js +8 -9
- package/dist/extensions/shared/types.js +1 -1
- package/dist/extensions/shared/usage-ledger.js +155 -61
- 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 +10 -4
- package/extensions/caveman/rule-lite.md +77 -0
- package/extensions/caveman/rule-megacave-lite.md +59 -0
- package/extensions/caveman/rule-megacave-ultra.md +59 -0
- 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 +86 -23
- package/extensions/commands/index.ts +1 -1
- package/extensions/pstack/index.ts +130 -0
- package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
- package/extensions/pstack/playbooks/autonomous-run.md +13 -0
- package/extensions/pstack/playbooks/autopilot-full.md +13 -0
- package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
- package/extensions/pstack/playbooks/babysit.md +23 -0
- package/extensions/pstack/playbooks/bug-fix.md +15 -0
- package/extensions/pstack/playbooks/eval.md +25 -0
- package/extensions/pstack/playbooks/feature.md +21 -0
- package/extensions/pstack/playbooks/hillclimb.md +21 -0
- package/extensions/pstack/playbooks/investigation.md +14 -0
- package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
- package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
- package/extensions/pstack/playbooks/orchestrate.md +111 -0
- package/extensions/pstack/playbooks/pause-safely.md +10 -0
- package/extensions/pstack/playbooks/perf-issue.md +25 -0
- package/extensions/pstack/playbooks/prototype.md +14 -0
- package/extensions/pstack/playbooks/refactoring.md +16 -0
- package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
- package/extensions/pstack/playbooks/session-pickup.md +11 -0
- package/extensions/pstack/playbooks/shipping.md +17 -0
- package/extensions/pstack/playbooks/trace-forensics.md +14 -0
- package/extensions/pstack/playbooks/visual-parity.md +11 -0
- package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
- package/extensions/pstack/principles/attack-the-premise.md +22 -0
- package/extensions/pstack/principles/boundary-discipline.md +33 -0
- package/extensions/pstack/principles/build-the-lever.md +22 -0
- package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
- package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
- package/extensions/pstack/principles/experience-first.md +18 -0
- package/extensions/pstack/principles/explain-the-number.md +16 -0
- package/extensions/pstack/principles/fix-root-causes.md +23 -0
- package/extensions/pstack/principles/foundational-thinking.md +20 -0
- package/extensions/pstack/principles/guard-the-context-window.md +11 -0
- package/extensions/pstack/principles/laziness-protocol.md +9 -0
- package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
- package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
- package/extensions/pstack/principles/minimize-reader-load.md +17 -0
- package/extensions/pstack/principles/model-the-domain.md +23 -0
- package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
- package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
- package/extensions/pstack/principles/prove-it-works.md +21 -0
- package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
- package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
- package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
- package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
- package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
- package/extensions/pstack/principles/type-system-discipline.md +17 -0
- package/extensions/pstack/references/bugbot-triage.md +142 -0
- package/extensions/pstack/rule.md +141 -0
- package/extensions/pstack/skills/architect/SKILL.md +78 -0
- package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
- package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
- package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
- package/extensions/pstack/skills/arena/SKILL.md +47 -0
- package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
- package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
- package/extensions/pstack/skills/bro/SKILL.md +6 -0
- package/extensions/pstack/skills/correct/SKILL.md +31 -0
- package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
- package/extensions/pstack/skills/how/SKILL.md +35 -0
- package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
- package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
- package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
- package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
- package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
- package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
- package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
- package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
- package/extensions/pstack/skills/reflect/SKILL.md +66 -0
- package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
- package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
- package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
- package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
- package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
- package/extensions/pstack/skills/swarm/SKILL.md +45 -0
- package/extensions/pstack/skills/tdd/SKILL.md +41 -0
- package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
- package/extensions/pstack/skills/unslop/SKILL.md +61 -0
- package/extensions/pstack/skills/why/SKILL.md +97 -0
- package/extensions/pstack/skills/why/references/epistemics.md +71 -0
- package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
- package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
- package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
- package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
- package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
- package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
- package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
- package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
- package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
- package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
- package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
- package/extensions/rtk/filter.ts +0 -1
- package/extensions/rtk/session.ts +19 -11
- 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-eligible.ts +83 -0
- package/extensions/shared/rtk-gain.ts +5 -10
- package/extensions/shared/session-state.ts +18 -13
- package/extensions/shared/sqlite.ts +162 -0
- package/extensions/shared/status.ts +9 -9
- package/extensions/shared/types.ts +2 -1
- package/extensions/shared/usage-ledger.ts +157 -66
- package/extensions/shared/usage-store.ts +120 -52
- package/extensions/shared/utils.ts +5 -5
- package/extensions/timing/index.ts +44 -0
- package/extensions/updater/index.ts +3 -8
- package/package.json +21 -9
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
import { execFileSync } from 'node:child_process';
|
|
1
|
+
// usage-store.ts — tersio-owned usage.db. A cache of the live parse, not a fork; no SQLite → sync false / read null.
|
|
3
2
|
import fs from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
4
|
+
import { sqliteExec, sqliteRead } from './sqlite.js';
|
|
5
|
+
import { RECENT_LIMIT, canonicalModelId, classifySessionLine, classifyOpencodeMessage, costOf, durOf, agentOfSessionText, hostOfSessionFile, ingestSessionRow, messageRowId, OPENCODE_DB_OVERLAP_MS, opencodeDbPath, opencodeSessionsDir, newSessionAccum, opencodeToolNames, readOpencodeDbRows, sessionsDirs, walkJsonl, } from './usage-ledger.js';
|
|
6
6
|
import { tersioDataPath } from './utils.js';
|
|
7
7
|
import { MODEL_ALIASES } from './pricing.js';
|
|
8
8
|
import { tersioSettingsFile } from './plugin-settings.js';
|
|
@@ -24,25 +24,21 @@ function nullNum(v) {
|
|
|
24
24
|
function nullStr(v) {
|
|
25
25
|
return v === undefined ? 'NULL' : esc(v);
|
|
26
26
|
}
|
|
27
|
+
// A failed INSERT stops before COMMIT, so it leaves no DELETEs behind.
|
|
27
28
|
function run(db, sql) {
|
|
28
|
-
|
|
29
|
-
execFileSync('sqlite3', ['-bail', db, sql], { stdio: ['ignore', 'ignore', 'ignore'], timeout: 30000 });
|
|
29
|
+
sqliteExec(db, sql);
|
|
30
30
|
}
|
|
31
31
|
function query(db, sql) {
|
|
32
|
-
|
|
33
|
-
encoding: 'utf8',
|
|
34
|
-
timeout: 30000,
|
|
35
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
36
|
-
maxBuffer: SQLITE_READ_BUFFER,
|
|
37
|
-
});
|
|
38
|
-
return out
|
|
32
|
+
return sqliteRead(db, sql)
|
|
39
33
|
.split('\n')
|
|
40
34
|
.filter((line) => line.trim())
|
|
41
35
|
.map((line) => line.split('\t'));
|
|
42
36
|
}
|
|
43
37
|
// Bump on a parse change: unchanged transcripts are never re-read.
|
|
44
|
-
const PARSER_VERSION = '
|
|
45
|
-
|
|
38
|
+
const PARSER_VERSION = '13';
|
|
39
|
+
/** OpenCode rows gained tool names; re-read by id, since a PARSER_VERSION bump drops rotated history. */
|
|
40
|
+
const OPENCODE_TOOLS_VERSION = '1';
|
|
41
|
+
// A re-parse keeping under half the rows means transcripts vanished mid-migration; the backup is restored instead.
|
|
46
42
|
const REPARSE_GUARD_RATIO = 0.5;
|
|
47
43
|
let guardReport = null;
|
|
48
44
|
/** Why the last sync refused to publish a re-parse, if it did. */
|
|
@@ -52,18 +48,14 @@ export function reparseGuardReport() {
|
|
|
52
48
|
const SCHEMA_SQL = `CREATE TABLE IF NOT EXISTS meta (k TEXT PRIMARY KEY, v TEXT NOT NULL);` +
|
|
53
49
|
`CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT UNIQUE NOT NULL, mtime REAL NOT NULL, size INTEGER NOT NULL);` +
|
|
54
50
|
`CREATE TABLE IF NOT EXISTS messages (file_id INTEGER NOT NULL REFERENCES files(id), t REAL, model TEXT NOT NULL,` +
|
|
55
|
-
` i INTEGER NOT NULL, o INTEGER NOT NULL, d REAL, cr INTEGER NOT NULL, cw INTEGER NOT NULL,` +
|
|
51
|
+
` i INTEGER NOT NULL, o INTEGER NOT NULL, d REAL, tf REAL, cr INTEGER NOT NULL, cw INTEGER NOT NULL,` +
|
|
56
52
|
` usd REAL, st TEXT NOT NULL, code REAL, note TEXT, tools TEXT NOT NULL DEFAULT '[]', h TEXT, a TEXT, id TEXT);` +
|
|
57
|
-
// Every read filters or orders by one of these three
|
|
58
|
-
// walks the whole table: the watermark scan, the day buckets, and the recent list all scan.
|
|
53
|
+
// Every read filters or orders by one of these three; without them each sync and read scans the table.
|
|
59
54
|
`CREATE INDEX IF NOT EXISTS idx_messages_file ON messages(file_id);` +
|
|
60
55
|
`CREATE INDEX IF NOT EXISTS idx_messages_t ON messages(t);` +
|
|
61
56
|
`CREATE INDEX IF NOT EXISTS idx_messages_h_t ON messages(h, t);` +
|
|
62
57
|
`CREATE INDEX IF NOT EXISTS idx_messages_model ON messages(model);`;
|
|
63
|
-
/**
|
|
64
|
-
* WAL plus a checkpoint at the end of a sync: the write path stops paying an fsync per commit,
|
|
65
|
-
* and the main file still holds every row, so a backup that copies only usage.db loses nothing.
|
|
66
|
-
*/
|
|
58
|
+
/** WAL + a checkpoint: the write path avoids an fsync per commit, and usage.db alone holds every row. */
|
|
67
59
|
function pragmas(db) {
|
|
68
60
|
run(db, 'PRAGMA journal_mode=WAL;' +
|
|
69
61
|
'PRAGMA synchronous=NORMAL;' +
|
|
@@ -81,7 +73,7 @@ function storedParserVersion(db) {
|
|
|
81
73
|
return null;
|
|
82
74
|
}
|
|
83
75
|
}
|
|
84
|
-
// A store written by a newer tersio:
|
|
76
|
+
// A store written by a newer tersio: no forward migration, so a re-parse would swap rotated history for what is left.
|
|
85
77
|
function isDowngrade(stored) {
|
|
86
78
|
if (stored === null)
|
|
87
79
|
return false;
|
|
@@ -109,6 +101,16 @@ function newestOpencodeMs(db) {
|
|
|
109
101
|
return null;
|
|
110
102
|
}
|
|
111
103
|
}
|
|
104
|
+
/** Whether the OpenCode tools backfill ran; unfilled rows stay empty, so this widens the scan once. */
|
|
105
|
+
function opencodeToolsBackfilled(db) {
|
|
106
|
+
try {
|
|
107
|
+
const rows = query(db, `SELECT v FROM meta WHERE k='opencode_tools';`);
|
|
108
|
+
return rows.length > 0 && rows[0][0] === OPENCODE_TOOLS_VERSION;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
112
114
|
// Same pre-migration state as the newest backup: another copy churns the 3-slot rotation for nothing.
|
|
113
115
|
function newestBackupMatches(stored) {
|
|
114
116
|
if (stored === null)
|
|
@@ -124,7 +126,7 @@ function newestBackupMatches(stored) {
|
|
|
124
126
|
return false;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
// v9 shape (path-keyed rows) migrates in place with zero re-parse, so history survives even
|
|
129
|
+
// v9 shape (path-keyed rows) migrates in place with zero re-parse, so history survives even without transcripts.
|
|
128
130
|
function canMigrateInPlace(db) {
|
|
129
131
|
try {
|
|
130
132
|
const names = query(db, `PRAGMA table_info(messages);`).map((r) => r[1]);
|
|
@@ -141,7 +143,7 @@ function migrateInPlace(db, preCount) {
|
|
|
141
143
|
`CREATE TABLE files_new (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT UNIQUE NOT NULL, mtime REAL NOT NULL, size INTEGER NOT NULL);` +
|
|
142
144
|
`INSERT INTO files_new (path, mtime, size) SELECT path, mtime, size FROM files;` +
|
|
143
145
|
`CREATE TABLE messages_new (file_id INTEGER NOT NULL REFERENCES files_new(id), t REAL, model TEXT NOT NULL,` +
|
|
144
|
-
` i INTEGER NOT NULL, o INTEGER NOT NULL, d REAL, cr INTEGER NOT NULL, cw INTEGER NOT NULL,` +
|
|
146
|
+
` i INTEGER NOT NULL, o INTEGER NOT NULL, d REAL, tf REAL, cr INTEGER NOT NULL, cw INTEGER NOT NULL,` +
|
|
145
147
|
` usd REAL, st TEXT NOT NULL, code REAL, note TEXT, tools TEXT NOT NULL DEFAULT '[]', h TEXT, a TEXT, id TEXT);` +
|
|
146
148
|
`INSERT INTO messages_new (file_id, ${cols}) SELECT (SELECT id FROM files_new WHERE files_new.path = messages.file), ${cols} FROM messages;` +
|
|
147
149
|
`DROP TABLE messages;DROP TABLE files;` +
|
|
@@ -160,9 +162,7 @@ function migrateInPlace(db, preCount) {
|
|
|
160
162
|
return false;
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
|
-
// v12
|
|
164
|
-
// store already holds. No rows move and no re-parse is forced, so the existing rows read
|
|
165
|
-
// `NULL` for it: a report then shows them under the main agent only, and new rows carry it.
|
|
165
|
+
// v12 adds the agent kind with a bare ALTER; existing rows read NULL and report under the main agent.
|
|
166
166
|
function canAddAgent(db) {
|
|
167
167
|
try {
|
|
168
168
|
const names = query(db, `PRAGMA table_info(messages);`).map((r) => r[1]);
|
|
@@ -172,8 +172,6 @@ function canAddAgent(db) {
|
|
|
172
172
|
return false;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
-
// v12 shape gains the agent kind (`main`, `subagent`) with a bare ALTER: no rows move, so no
|
|
176
|
-
// re-parse is forced. Existing rows read NULL, which a report folds under the main agent.
|
|
177
175
|
function addAgentColumn(db) {
|
|
178
176
|
try {
|
|
179
177
|
run(db, `ALTER TABLE messages ADD COLUMN a TEXT;`);
|
|
@@ -183,6 +181,44 @@ function addAgentColumn(db) {
|
|
|
183
181
|
}
|
|
184
182
|
return true;
|
|
185
183
|
}
|
|
184
|
+
// v13 shape gains the first-token latency with a bare ALTER, so no row moves and no wipe runs.
|
|
185
|
+
function canAddTtft(db) {
|
|
186
|
+
try {
|
|
187
|
+
const names = query(db, `PRAGMA table_info(messages);`).map((r) => r[1]);
|
|
188
|
+
return names.includes('file_id') && !names.includes('tf');
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// The column is additive, and a rotated transcript cannot be re-parsed, so the ALTER precedes the backfill.
|
|
195
|
+
function addTtftColumn(db) {
|
|
196
|
+
try {
|
|
197
|
+
run(db, `ALTER TABLE messages ADD COLUMN tf REAL;`);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return !canAddTtft(db);
|
|
203
|
+
}
|
|
204
|
+
/** First-token-latency marker; unchanged transcripts are never revisited, so old stores stay null. */
|
|
205
|
+
const TTFT_VERSION = '1';
|
|
206
|
+
function ttftBackfilled(db) {
|
|
207
|
+
try {
|
|
208
|
+
const rows = query(db, `SELECT v FROM meta WHERE k='ttft_backfill';`);
|
|
209
|
+
return rows.length > 0 && rows[0][0] === TTFT_VERSION;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Clears ledger mtimes so every transcript re-reads; synthetic `db#row-id` OpenCode keys stay untouched. */
|
|
216
|
+
function markTranscriptsDirty(db) {
|
|
217
|
+
try {
|
|
218
|
+
run(db, `UPDATE files SET mtime = 0 WHERE path NOT LIKE '%#%';`);
|
|
219
|
+
}
|
|
220
|
+
catch { /* a store with no files table yet, or a read-only file: nothing to backfill */ }
|
|
221
|
+
}
|
|
186
222
|
// v10 shape gains the message id with a bare ALTER: no rows move, so no re-parse and no guard trip.
|
|
187
223
|
function canAlterInPlace(db) {
|
|
188
224
|
try {
|
|
@@ -233,10 +269,14 @@ function ensureSchema(db, forceWipe) {
|
|
|
233
269
|
run(db, `INSERT OR REPLACE INTO meta (k,v) VALUES ('parser_version','${PARSER_VERSION}');`);
|
|
234
270
|
return;
|
|
235
271
|
}
|
|
236
|
-
// The agent column is additive
|
|
237
|
-
// unchanged transcripts that already hold their rows simply read NULL until a re-parse.
|
|
272
|
+
// The agent column is additive: every row survives and unchanged transcripts read NULL until a re-parse.
|
|
238
273
|
if (stored !== null && canAddAgent(db) && addAgentColumn(db)) {
|
|
239
|
-
run(db, `INSERT OR REPLACE INTO meta (k,v) VALUES ('parser_version','${PARSER_VERSION}');`);
|
|
274
|
+
run(db, `INSERT OR REPLACE INTO meta (k, v) VALUES ('parser_version','${PARSER_VERSION}');`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// The ttft column is additive too; its values come from the backfill in `syncUsageDb`.
|
|
278
|
+
if (stored !== null && canAddTtft(db) && addTtftColumn(db)) {
|
|
279
|
+
run(db, `INSERT OR REPLACE INTO meta (k, v) VALUES ('parser_version','${PARSER_VERSION}');`);
|
|
240
280
|
return;
|
|
241
281
|
}
|
|
242
282
|
run(db, `DROP TABLE IF EXISTS messages;DROP TABLE IF EXISTS files;` + SCHEMA_SQL +
|
|
@@ -347,22 +387,22 @@ function readFiles(db) {
|
|
|
347
387
|
catch { /* fresh db */ }
|
|
348
388
|
return known;
|
|
349
389
|
}
|
|
350
|
-
// OpenCode rows carry
|
|
351
|
-
|
|
352
|
-
function opencodeStoredRow(oc, host, id) {
|
|
390
|
+
// OpenCode rows carry only tokens, msg id, and tools; they record as the main agent, so no NULL kind.
|
|
391
|
+
function opencodeStoredRow(oc, host, id, tools) {
|
|
353
392
|
return {
|
|
354
393
|
t: oc.ms ?? null,
|
|
355
394
|
model: canonicalModelId(oc.model),
|
|
356
395
|
i: intOf(oc.usage.input),
|
|
357
396
|
o: intOf(oc.usage.output),
|
|
358
397
|
d: durOf(oc.durMs),
|
|
398
|
+
tf: durOf(oc.ttftMs),
|
|
359
399
|
cr: intOf(oc.usage.cacheRead),
|
|
360
400
|
cw: intOf(oc.usage.cacheWrite),
|
|
361
401
|
usd: costOf(oc.usage),
|
|
362
402
|
st: 'completed',
|
|
363
403
|
code: undefined,
|
|
364
404
|
note: undefined,
|
|
365
|
-
tools
|
|
405
|
+
tools,
|
|
366
406
|
host,
|
|
367
407
|
agent: 'main',
|
|
368
408
|
id,
|
|
@@ -373,13 +413,14 @@ function parseFile(text, host, file, agent) {
|
|
|
373
413
|
// Guarded: a JSONL transcript must fall through, not abort the sync.
|
|
374
414
|
let oc = null;
|
|
375
415
|
try {
|
|
376
|
-
|
|
416
|
+
const message = JSON.parse(text);
|
|
417
|
+
oc = classifyOpencodeMessage(message);
|
|
418
|
+
if (oc) {
|
|
419
|
+
rows.push(opencodeStoredRow(oc, host, file ? path.basename(file).replace(/\.[^.]+$/, '') : undefined, opencodeToolNames(message)));
|
|
420
|
+
return rows;
|
|
421
|
+
}
|
|
377
422
|
}
|
|
378
423
|
catch { /* JSONL */ }
|
|
379
|
-
if (oc) {
|
|
380
|
-
rows.push(opencodeStoredRow(oc, host, file ? path.basename(file).replace(/\.[^.]+$/, '') : undefined));
|
|
381
|
-
return rows;
|
|
382
|
-
}
|
|
383
424
|
let codexProvider = null;
|
|
384
425
|
let codexModel = null;
|
|
385
426
|
for (const line of text.split('\n')) {
|
|
@@ -411,6 +452,7 @@ function parseFile(text, host, file, agent) {
|
|
|
411
452
|
i: intOf(usage.input),
|
|
412
453
|
o: intOf(usage.output),
|
|
413
454
|
d: undefined,
|
|
455
|
+
tf: undefined,
|
|
414
456
|
cr: intOf(usage.cacheRead),
|
|
415
457
|
cw: intOf(usage.cacheWrite),
|
|
416
458
|
usd: costOf(usage),
|
|
@@ -436,6 +478,7 @@ function parseFile(text, host, file, agent) {
|
|
|
436
478
|
i: intOf(usage.input),
|
|
437
479
|
o: intOf(usage.output),
|
|
438
480
|
d: dur,
|
|
481
|
+
tf: durOf(parsed.ttftMs),
|
|
439
482
|
cr: intOf(usage.cacheRead),
|
|
440
483
|
cw: intOf(usage.cacheWrite),
|
|
441
484
|
usd: costOf(usage),
|
|
@@ -453,12 +496,12 @@ function parseFile(text, host, file, agent) {
|
|
|
453
496
|
return rows;
|
|
454
497
|
}
|
|
455
498
|
function insertSql(file, r) {
|
|
456
|
-
return `INSERT INTO messages (file_id, t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, a, id) VALUES (` +
|
|
499
|
+
return `INSERT INTO messages (file_id, t, model, i, o, d, tf, cr, cw, usd, st, code, note, tools, h, a, id) VALUES (` +
|
|
457
500
|
`(SELECT id FROM files WHERE path=${esc(file)}),${r.t === null ? 'NULL' : String(r.t)},${esc(r.model)},${r.i},${r.o},` +
|
|
458
|
-
`${nullNum(r.d)},${r.cr},${r.cw},${nullNum(r.usd)},${esc(r.st)},` +
|
|
501
|
+
`${nullNum(r.d)},${nullNum(r.tf)},${r.cr},${r.cw},${nullNum(r.usd)},${esc(r.st)},` +
|
|
459
502
|
`${nullNum(r.code)},${nullStr(r.note)},${esc(JSON.stringify(r.tools))},${nullStr(r.host)},${nullStr(r.agent ?? undefined)},${nullStr(r.id)});`;
|
|
460
503
|
}
|
|
461
|
-
// Unchanged transcripts are skipped via mtime+size; rows for deleted ones are kept so rotation never erases history.
|
|
504
|
+
// Unchanged transcripts are skipped via mtime+size; rows for deleted ones are kept so rotation never erases history.
|
|
462
505
|
export function syncUsageDb() {
|
|
463
506
|
let db;
|
|
464
507
|
guardReport = null;
|
|
@@ -477,6 +520,9 @@ export function syncUsageDb() {
|
|
|
477
520
|
if (migrated && !forced)
|
|
478
521
|
preWipe = countMessages(db);
|
|
479
522
|
ensureSchema(db, forced);
|
|
523
|
+
// One rewrite so the additive column's NULL rows gain latency; runs while the marker is absent.
|
|
524
|
+
if (!downgrade && !ttftBackfilled(db))
|
|
525
|
+
markTranscriptsDirty(db);
|
|
480
526
|
maybeScheduledBackup(db);
|
|
481
527
|
}
|
|
482
528
|
catch {
|
|
@@ -501,8 +547,10 @@ export function syncUsageDb() {
|
|
|
501
547
|
const ocDbPath = opencodeDbPath();
|
|
502
548
|
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DB !== undefined) {
|
|
503
549
|
const newest = newestOpencodeMs(db);
|
|
550
|
+
// A store predating the tool names re-reads the whole table once; the marker closes the window.
|
|
551
|
+
const since = newest === null || !opencodeToolsBackfilled(db) ? 0 : newest - OPENCODE_DB_OVERLAP_MS;
|
|
504
552
|
if (newest !== null || fs.existsSync(ocDbPath)) {
|
|
505
|
-
ocDbRows.push(...readOpencodeDbRows(ocDbPath,
|
|
553
|
+
ocDbRows.push(...readOpencodeDbRows(ocDbPath, since));
|
|
506
554
|
}
|
|
507
555
|
}
|
|
508
556
|
const known = readFiles(db);
|
|
@@ -522,6 +570,7 @@ export function syncUsageDb() {
|
|
|
522
570
|
if (!prev || prev.mtime !== entry.mtime || prev.size !== entry.size)
|
|
523
571
|
changed.push(file);
|
|
524
572
|
}
|
|
573
|
+
// `ocDbRows` is non-empty whenever the backfill window is open, so this never skips it.
|
|
525
574
|
if (!changed.length && !ocDbRows.length)
|
|
526
575
|
return true;
|
|
527
576
|
const chunks = [];
|
|
@@ -571,7 +620,7 @@ export function syncUsageDb() {
|
|
|
571
620
|
if (!push(`INSERT INTO files (path, mtime, size) VALUES (${esc(key)},${row.created},0)` +
|
|
572
621
|
` ON CONFLICT(path) DO UPDATE SET mtime=excluded.mtime;` +
|
|
573
622
|
`DELETE FROM messages WHERE file_id=(SELECT id FROM files WHERE path=${esc(key)});` +
|
|
574
|
-
insertSql(key, opencodeStoredRow(parsed, 'opencode', row.id))))
|
|
623
|
+
insertSql(key, opencodeStoredRow(parsed, 'opencode', row.id, opencodeToolNames(row.row, row.tools)))))
|
|
575
624
|
return false;
|
|
576
625
|
}
|
|
577
626
|
}
|
|
@@ -580,6 +629,16 @@ export function syncUsageDb() {
|
|
|
580
629
|
}
|
|
581
630
|
if (!push(`INSERT OR REPLACE INTO meta (k, v) VALUES ('last_sync',${esc(String(Date.now()))});`))
|
|
582
631
|
return false;
|
|
632
|
+
// Only once the rows are written, so a sync that fails midway widens the scan again.
|
|
633
|
+
if (ocDbRows.length > 0) {
|
|
634
|
+
if (!push(`INSERT OR REPLACE INTO meta (k, v) VALUES ('opencode_tools','${OPENCODE_TOOLS_VERSION}');`))
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
// Set only after the rewrite commits: a crash mid-backfill leaves mtimes cleared and the marker absent.
|
|
638
|
+
if (changed.length > 0) {
|
|
639
|
+
if (!push(`INSERT OR REPLACE INTO meta (k, v) VALUES ('ttft_backfill','${TTFT_VERSION}');`))
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
583
642
|
if (!flush())
|
|
584
643
|
return false;
|
|
585
644
|
if (preWipe > 0) {
|
|
@@ -617,18 +676,18 @@ export function readUsageDb() {
|
|
|
617
676
|
}
|
|
618
677
|
let rows;
|
|
619
678
|
try {
|
|
620
|
-
// The `tools` column is
|
|
621
|
-
// read as one column and skipped in JS rather than parsed per row. A store written before
|
|
622
|
-
// the agent column does not hold it, so the row is read without that column.
|
|
679
|
+
// The wide `tools` column is read as one string and skipped in JS; a pre-agent store is read without it.
|
|
623
680
|
const names = query(db, `PRAGMA table_info(messages);`).map((r) => r[1]);
|
|
624
681
|
const agentCol = names.includes('a') ? 'a' : "''";
|
|
625
|
-
|
|
682
|
+
// A store predating first-token latency has no `tf` column; read NULL so the row still loads.
|
|
683
|
+
const tfCol = names.includes('tf') ? 'tf' : 'NULL';
|
|
684
|
+
rows = query(db, `SELECT t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, ${agentCol}, id, ${tfCol} FROM messages;`);
|
|
626
685
|
}
|
|
627
686
|
catch {
|
|
628
687
|
return null;
|
|
629
688
|
}
|
|
630
689
|
const accum = newSessionAccum();
|
|
631
|
-
for (const [t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, a, id] of rows) {
|
|
690
|
+
for (const [t, model, i, o, d, cr, cw, usd, st, code, note, tools, h, a, id, tf] of rows) {
|
|
632
691
|
const ts = t === '' ? undefined : Number(t);
|
|
633
692
|
let toolNames = [];
|
|
634
693
|
if (tools && tools !== '[]' && tools !== '') {
|
|
@@ -654,7 +713,7 @@ export function readUsageDb() {
|
|
|
654
713
|
st: st === 'error' || st === 'aborted' ? st : 'completed',
|
|
655
714
|
code: code === '' ? undefined : Number(code),
|
|
656
715
|
note: note === '' ? undefined : note,
|
|
657
|
-
}, toolNames, h === '' ? undefined : h, id === '' || id === undefined ? undefined : id, a === '' ? undefined : a);
|
|
716
|
+
}, toolNames, h === '' ? undefined : h, id === '' || id === undefined ? undefined : id, a === '' ? undefined : a, tf === undefined || tf === '' ? undefined : Number(tf));
|
|
658
717
|
}
|
|
659
718
|
accum.recent.sort((a, b) => b.t - a.t);
|
|
660
719
|
let syncedAt = 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Shared helpers for the installer and
|
|
1
|
+
// Shared helpers for the installer and AI add-ons updater. Node built-ins only — must stay dependency-free.
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { accessSync, constants, createWriteStream, existsSync, mkdirSync, renameSync, statSync } from 'node:fs';
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
@@ -87,7 +87,7 @@ function redirectNext(res, url) {
|
|
|
87
87
|
}
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
|
-
// pi sets PI_CODING_AGENT; OMP does not.
|
|
90
|
+
// pi sets PI_CODING_AGENT; OMP does not. It reads PI_CODING_AGENT_DIR to relocate, so the override only counts there.
|
|
91
91
|
export function isPiProcess() {
|
|
92
92
|
return process.env.PI_CODING_AGENT === 'true';
|
|
93
93
|
}
|
|
@@ -95,7 +95,7 @@ export function piAgentDir() {
|
|
|
95
95
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
96
96
|
return isPiProcess() && override ? override : path.join(homeDir(), '.pi', 'agent');
|
|
97
97
|
}
|
|
98
|
-
// Ponytail is
|
|
98
|
+
// Ponytail is hoisted, so it sits in Tersio's node_modules, a host's plugin dir, or beside it; walking up covers all.
|
|
99
99
|
export function findHoistedPackage(pkg, fromDir, ...relInside) {
|
|
100
100
|
let dir = fromDir;
|
|
101
101
|
for (;;) {
|
|
@@ -235,7 +235,7 @@ export async function readTextIfExists(p) {
|
|
|
235
235
|
return null;
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
// Chunked
|
|
238
|
+
// Chunked: a 200 MiB binary never fits memory. `tail` keeps needle.length-1 bytes in case a match spans chunks.
|
|
239
239
|
export async function fileContains(filePath, needleText) {
|
|
240
240
|
const needle = Buffer.from(needleText, 'utf8');
|
|
241
241
|
if (needle.length === 0)
|
|
@@ -266,7 +266,7 @@ export async function fileContains(filePath, needleText) {
|
|
|
266
266
|
await handle?.close().catch(() => { });
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
|
-
// Tersio
|
|
269
|
+
// Tersio data home: ~/.tersio. First use moves legacy ~/.omp/plugins/tersio-* files over so installs keep history.
|
|
270
270
|
const migratedTersioFiles = new Set();
|
|
271
271
|
export function tersioHome() {
|
|
272
272
|
// Matches the other TERSIO_* overrides so a test never reads the real home.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default function timingExtension(pi) {
|
|
2
|
+
// First token per request, keyed by the request's own start stamp, which survives every stream event.
|
|
3
|
+
const firstTokenAt = new Map();
|
|
4
|
+
pi.on('message_update', (event) => {
|
|
5
|
+
const message = event.message;
|
|
6
|
+
if (!message || message.role !== 'assistant')
|
|
7
|
+
return;
|
|
8
|
+
const anchor = typeof message.timestamp === 'number' ? message.timestamp : 0;
|
|
9
|
+
if (anchor <= 0 || firstTokenAt.has(anchor))
|
|
10
|
+
return;
|
|
11
|
+
// `start` opens the stream with no token yet; the first delta is the first token.
|
|
12
|
+
if (!String(event.assistantMessageEvent?.type ?? '').endsWith('_delta'))
|
|
13
|
+
return;
|
|
14
|
+
firstTokenAt.set(anchor, Date.now());
|
|
15
|
+
});
|
|
16
|
+
pi.on('message_end', (event) => {
|
|
17
|
+
const message = event.message;
|
|
18
|
+
if (!message || message.role !== 'assistant')
|
|
19
|
+
return;
|
|
20
|
+
const anchor = typeof message.timestamp === 'number' ? message.timestamp : 0;
|
|
21
|
+
const at = anchor > 0 ? firstTokenAt.get(anchor) : undefined;
|
|
22
|
+
if (anchor > 0)
|
|
23
|
+
firstTokenAt.delete(anchor);
|
|
24
|
+
// A host that recorded its own latency wins; this only fills the gap.
|
|
25
|
+
if (at === undefined || message.ttft !== undefined)
|
|
26
|
+
return;
|
|
27
|
+
const ttft = at - anchor;
|
|
28
|
+
if (ttft > 0)
|
|
29
|
+
message.ttft = ttft;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// /ai-addons manual updater. Node built-ins only. RTK ships SHA256 checksums
|
|
1
|
+
// /ai-addons manual updater. Node built-ins only. RTK ships unsigned SHA256 checksums, so checksum-only it is.
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import fsSync from 'node:fs';
|
|
@@ -12,9 +12,7 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
12
12
|
const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
|
|
14
14
|
const RTK_BINARY = resolveRtkBinary();
|
|
15
|
-
// Beside this file
|
|
16
|
-
// agent-tree copy has no `caveman` sibling, so the OMP plugin install is the
|
|
17
|
-
// fallback: it is the one the plugin manifest loads from.
|
|
15
|
+
// Beside this file: a retired agent-tree copy has no `caveman` sibling, so fall back to the OMP plugin.
|
|
18
16
|
const CAVEMAN_SIBLING = (name) => path.join(EXTENSION_DIR, '..', 'caveman', name);
|
|
19
17
|
function cavemanRule(name) {
|
|
20
18
|
const beside = CAVEMAN_SIBLING(name);
|
|
@@ -30,7 +28,7 @@ const CAVEMAN_TRACKED = [
|
|
|
30
28
|
];
|
|
31
29
|
// Host-neutral: this updater runs inside pi, OMP, and OpenCode alike.
|
|
32
30
|
const RELOAD_MSG = 'Reminder: restart your agent (or reload extensions) for updates to take effect.';
|
|
33
|
-
// Ponytail is
|
|
31
|
+
// Ponytail is hoisted, so the installed copy is found by walking up, not by naming a host directory.
|
|
34
32
|
function ponytailLocal() {
|
|
35
33
|
return findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'package.json');
|
|
36
34
|
}
|
|
@@ -149,7 +147,6 @@ async function checkAddons(ctx) {
|
|
|
149
147
|
}
|
|
150
148
|
return lines.join('\n');
|
|
151
149
|
}
|
|
152
|
-
// Ponytail is a hoisted dependency; replace its package dir from the npm tarball.
|
|
153
150
|
async function updatePonytail(pi, ctx, dryRun = false) {
|
|
154
151
|
void pi;
|
|
155
152
|
const localPkg = ponytailLocal();
|
package/dist/tersio.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Install Tersio add-ons on any OMP device; see help for commands.
|
|
3
|
-
import { PACKAGE_BIN, PACKAGE_VERSION, applyUpdate, commandArg, dashboard, dashboardExport, dashboardOpen, dashboardPort, doctor, reset, settings, showHelp, showVersion, uninstall, unknownCommand, update, usage, currency, } from './cli/common.js';
|
|
3
|
+
import { PACKAGE_BIN, PACKAGE_VERSION, applyUpdate, commandArg, dashboard, dashboardExport, dashboardOpen, dashboardPort, doctor, reinstall, reset, settings, showHelp, showVersion, uninstall, unknownCommand, update, usage, currency, } from './cli/common.js';
|
|
4
4
|
import { closeRL } from './cli/interactive.js';
|
|
5
|
-
import { runInstall } from './cli/install.js';
|
|
5
|
+
import { runInstall, runReinstall } from './cli/install.js';
|
|
6
6
|
import { runDoctor } from './cli/doctor.js';
|
|
7
7
|
import { runLatestUpdate } from './cli/update.js';
|
|
8
8
|
import { runUninstall } from './cli/uninstall.js';
|
|
@@ -15,6 +15,7 @@ function printHelp() {
|
|
|
15
15
|
|
|
16
16
|
Commands:
|
|
17
17
|
install Install the add-ons for one agent (--host omp|pi|opencode, or ask at a terminal)
|
|
18
|
+
reinstall Refresh one agent's install: pick it, reinstall, then verify (--host picks without asking)
|
|
18
19
|
update Refresh the CLI and add-ons (RTK binary, Caveman rule, Ponytail; --host limits to one agent)
|
|
19
20
|
doctor Check the current installation (--fix repairs, --dry-run previews)
|
|
20
21
|
usage Ledger-backed usage + savings report
|
|
@@ -26,7 +27,7 @@ Commands:
|
|
|
26
27
|
help Show this help
|
|
27
28
|
|
|
28
29
|
Options:
|
|
29
|
-
--fix (doctor: repairs all; --fix=<scope> repairs one of extensions, registrations, rtk, ponytail, cli)
|
|
30
|
+
--fix (doctor: repairs all; --fix=<scope> repairs one of extensions, registrations, rtk, ponytail, cli, service)
|
|
30
31
|
--scope user (legacy; accepted and ignored — user scope is the only scope)
|
|
31
32
|
--host omp|pi|opencode (install|uninstall|update: target agent; a terminal asks instead)
|
|
32
33
|
--keep-ponytail (uninstall: keep the bundled Ponytail copy — removed by default)
|
|
@@ -97,6 +98,10 @@ async function main() {
|
|
|
97
98
|
closeRL();
|
|
98
99
|
return;
|
|
99
100
|
}
|
|
101
|
+
if (reinstall) {
|
|
102
|
+
await runReinstall();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
100
105
|
await runInstall();
|
|
101
106
|
}
|
|
102
107
|
main().catch((e) => { closeRL(); console.error(e); process.exitCode = 1; });
|
|
@@ -3,13 +3,17 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { getSharedComboState, hasModeState, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, 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
|
-
import { announceStatus } from '../shared/status.ts';
|
|
6
|
+
import { announceStatus, formatCavemanStatus } from '../shared/status.ts';
|
|
7
7
|
import { readCavemanDefault } from '../shared/plugin-settings.ts';
|
|
8
8
|
import type { ExtensionApi, ExtensionCtx, InputEvent, SessionEntry, SystemPromptEvent } from '../shared/types.ts';
|
|
9
9
|
|
|
10
10
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// `rule.md`, `rule-ultra.md`, and `rule-megacave.md` track upstream's three registers: `rule.md` is bundled with
|
|
12
|
+
// one host-adapted line, and the other two are fetched from upstream on install and updated in-session.
|
|
13
|
+
// `lite`, `megacave-lite`, and `megacave-ultra` are our forks of upstream's rows at 84cc3c1, so they are
|
|
14
|
+
// bundled rather than fetched. rule-ultra.md and rule-megacave.md are never edited here: the in-session
|
|
15
|
+
// updater rewrites them from upstream and restores the backup when the write does not match.
|
|
16
|
+
const RULE_FILES = { lite: 'rule-lite.md', full: 'rule.md', ultra: 'rule-ultra.md', 'megacave-lite': 'rule-megacave-lite.md', 'megacave-full': 'rule-megacave.md', 'megacave-ultra': 'rule-megacave-ultra.md' };
|
|
13
17
|
|
|
14
18
|
const DEFAULT_MODE = 'off';
|
|
15
19
|
|
|
@@ -71,6 +75,8 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
71
75
|
}
|
|
72
76
|
if (arg === 'status') {
|
|
73
77
|
announceStatus(ctx);
|
|
78
|
+
// The footer carries one line, so the mode is answered here instead of standing as a second status key.
|
|
79
|
+
ctx?.ui?.notify?.(formatCavemanStatus(currentMode), 'info');
|
|
74
80
|
return;
|
|
75
81
|
}
|
|
76
82
|
if (!setMode(arg, ctx)) {
|
|
@@ -88,7 +94,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
88
94
|
const entries = sessionEntries(ctx);
|
|
89
95
|
// A subagent's branch has no Tersio entries; reconciling it would wipe shared state.
|
|
90
96
|
if (hasModeState(entries)) reconcileSharedComboEntries(entries);
|
|
91
|
-
// Persisted state wins, then a mode already chosen
|
|
97
|
+
// Persisted state wins, then a mode already chosen, then the default. `off` is the start value, not unset.
|
|
92
98
|
const persisted = resolveMode(entries, '');
|
|
93
99
|
const alreadyChosen = currentMode !== DEFAULT_MODE;
|
|
94
100
|
currentMode = persisted || (alreadyChosen ? currentMode : normalizeMode('caveman', readCavemanDefault())) || DEFAULT_MODE;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lightcave
|
|
3
|
+
description: >
|
|
4
|
+
Caveman at its lightest register: no filler and no hedging, with articles and
|
|
5
|
+
full sentences kept. Professional but tight. Use for /caveman lite, or the lite
|
|
6
|
+
part of a Combo preset. Stays on until "stop caveman" or "normal mode".
|
|
7
|
+
source: >
|
|
8
|
+
Upstream JuliusBrussee/caveman, skills/caveman/SKILL.md at 84cc3c1, the "Intensity" table, row `lite`.
|
|
9
|
+
The row reads: "No filler/hedging. Keep articles + full sentences. Professional but tight"
|
|
10
|
+
The worked example under it is this register's model answer. The floor, the break-the-rules list,
|
|
11
|
+
and the pre-send check come from the same body. Adapted for Tersio's hosts and hook; no rule here
|
|
12
|
+
is Tersio's own.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# lightcave
|
|
16
|
+
|
|
17
|
+
Respond terse like smart caveman. All technical substance stay. Only fluff die. This is the light register: sentences stay sentences and articles stay, and the words that carry nothing go.
|
|
18
|
+
|
|
19
|
+
## Persistence
|
|
20
|
+
|
|
21
|
+
Every response, whole session, until "stop caveman" or "normal mode". Unsure if still on? It is. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value when present. With no hook, report the mode you followed, or `off` if caveman was turned off or never active, plus `(not tracked by this host)`.
|
|
22
|
+
|
|
23
|
+
## What changes at lite
|
|
24
|
+
|
|
25
|
+
Drop filler, hedging, pleasantries, recap, and closers. Keep articles, keep full sentences, keep punctuation. Professional but tight.
|
|
26
|
+
|
|
27
|
+
Bad: "Sure! I'd be happy to help. The issue you're experiencing is likely caused by an inline object prop."
|
|
28
|
+
Good: "Your component re-renders because you create a new object reference on each render. Wrap it in `useMemo`."
|
|
29
|
+
|
|
30
|
+
## Floor
|
|
31
|
+
|
|
32
|
+
Never cut: code blocks, 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 one you were not asked to change.
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
### 1. Answer first
|
|
37
|
+
|
|
38
|
+
Answer, then reason, then next step. Pattern: `[thing] [action] [reason]. [next step].`
|
|
39
|
+
|
|
40
|
+
### 2. Kill ceremony
|
|
41
|
+
|
|
42
|
+
No greeting, hedging, pleasantries, recap, or closer. No "Sure!", "Let me", "I'll now", "Hope this helps". No just/really/basically/actually/simply.
|
|
43
|
+
|
|
44
|
+
### 3. Short word
|
|
45
|
+
|
|
46
|
+
"fix" not "implement a solution for". Standard acronyms fine (DB, API, HTTP). Invented abbreviations not (cfg, impl, fn): same tokens, harder read. No arrows.
|
|
47
|
+
|
|
48
|
+
### 4. One idea per sentence
|
|
49
|
+
|
|
50
|
+
20 words max, active voice, imperative for instructions, pronoun only with an obvious referent. Compression and clarity conflict? Clarity wins.
|
|
51
|
+
|
|
52
|
+
### 5. Payload verbatim
|
|
53
|
+
|
|
54
|
+
Code blocks unchanged. Commands, paths, API names exact. Errors quoted exact, shortest decisive line only. A code change in chat shows the changed lines plus 1-2 lines of context, not the whole file, unless the user asks for the whole file or most of it changes.
|
|
55
|
+
|
|
56
|
+
### 6. Tool runs: bounded status
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
60
|
+
### 7. User's language
|
|
61
|
+
|
|
62
|
+
Compress the style, not the language. An explicit reply-language instruction wins. Never switch because of quoted text. Technical terms and errors stay verbatim.
|
|
63
|
+
|
|
64
|
+
### 8. Never perform caveman
|
|
65
|
+
|
|
66
|
+
No "caveman mode on", no "me think", no "Caveman:" prefix, no normal answer plus caveman copy. No decorative tables or emoji.
|
|
67
|
+
|
|
68
|
+
## When to break the rules
|
|
69
|
+
|
|
70
|
+
Plain prose, then resume: security warning. Irreversible action, confirmed first. Step order a sentence could scramble. User confused or repeats the question. Anything persisted outside chat — code, comments, commits, docs, issues, PRs, tickets, memory files, third-party messages — with `/caveman-compress`, where the host offers it, exempt. A status line or confirmation the harness asks for. A question you ask the user, with its options.
|
|
71
|
+
|
|
72
|
+
## Pre-send check
|
|
73
|
+
|
|
74
|
+
1. First sentence announces what you will do? Delete.
|
|
75
|
+
2. Last sentence recaps or offers help? Delete.
|
|
76
|
+
3. Any filler or hedging left? Cut it.
|
|
77
|
+
4. Every code span, path, number, and error verbatim? Any sentence with two readings is a full sentence.
|