@krtclcdy/tersio 2.23.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 +56 -45
- package/cli/agent-hosts.js +42 -0
- package/cli/agents.js +266 -0
- package/cli/banner.js +2 -3
- package/cli/common.js +16 -20
- package/cli/currency.js +11 -19
- package/cli/dashboard.js +216 -40
- package/cli/doctor-fix.js +40 -26
- package/cli/doctor.js +41 -41
- package/cli/hosts.js +114 -0
- package/cli/install.js +284 -179
- package/cli/interactive.js +62 -2
- package/cli/layer.js +22 -0
- package/cli/manifest.js +33 -0
- package/cli/omp-layer.js +51 -0
- package/cli/pi-layer.js +37 -0
- package/cli/pi-wiring.js +92 -0
- package/cli/profile.js +48 -48
- package/cli/reset.js +5 -32
- package/cli/rtk-wiring.js +7 -17
- package/cli/settings.js +3 -8
- package/cli/uninstall.js +162 -52
- package/cli/update.js +19 -27
- package/cli/usage.js +15 -5
- package/dashboard/dist/index.html +18 -18
- package/extensions/ai-addons-updater/index.js +37 -29
- package/extensions/ai-addons-updater/index.ts +39 -38
- package/extensions/caveman-session/index.js +19 -34
- package/extensions/caveman-session/index.ts +20 -35
- package/extensions/combo-toggle/index.js +47 -49
- package/extensions/combo-toggle/index.ts +42 -58
- package/extensions/lib/utils.js +40 -9
- package/extensions/lib/utils.ts +40 -9
- package/extensions/rtk-session/index.js +34 -38
- package/extensions/rtk-session/index.ts +32 -41
- package/extensions/shared/carbon.js +3 -11
- package/extensions/shared/carbon.ts +4 -11
- package/extensions/shared/host.js +58 -0
- package/extensions/shared/host.ts +94 -0
- package/extensions/shared/omp-prompt.js +12 -0
- package/extensions/shared/plugin-settings.js +33 -30
- package/extensions/shared/plugin-settings.ts +35 -26
- package/extensions/shared/pricing.js +25 -16
- package/extensions/shared/pricing.ts +27 -17
- package/extensions/shared/rtk-gain.js +6 -22
- package/extensions/shared/rtk-gain.ts +6 -22
- package/extensions/shared/session-state.js +44 -36
- package/extensions/shared/session-state.ts +48 -35
- package/extensions/shared/types.ts +10 -3
- package/extensions/shared/usage-ledger.js +160 -51
- package/extensions/shared/usage-ledger.ts +170 -67
- package/extensions/shared/usage-store.js +178 -24
- package/extensions/shared/usage-store.ts +176 -25
- package/extensions/tersio-commands/index.js +3 -6
- package/extensions/tersio-commands/index.ts +3 -6
- package/package.json +6 -5
- package/tersio.js +4 -6
- package/extensions/shared/mode-reinforcement.js +0 -26
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// ponytail: `skipped: none` — semantics match one-liner: fetch + compare + run install.
|
|
4
|
-
// rtk: `skipped: signature verification` — checksums.txt ships only SHA256 of release assets; add sigchain when upstream publishes a signing key.
|
|
5
|
-
// caveman: `skipped: none` — exactly the ask: write rule.md, report old/new hash.
|
|
1
|
+
// /ai-addons manual updater. Node built-ins only. RTK ships SHA256 checksums
|
|
2
|
+
// with no signature, so checksum-only is the best available.
|
|
6
3
|
import { execFileSync } from 'node:child_process';
|
|
7
4
|
import { createHash } from 'node:crypto';
|
|
8
5
|
import fs from 'node:fs/promises';
|
|
9
6
|
import os from 'node:os';
|
|
10
7
|
import path from 'node:path';
|
|
11
|
-
import {
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { setExtensionLabel } from '../shared/host.js';
|
|
10
|
+
import { CAVEMAN_REMOTE_RULE as CAVEMAN_REMOTE, RTK_RELEASE_API, fetchJson, findFile, findHoistedPackage, httpsGet, httpsDownload, isPiProcess, sha256Hex, parseChecksum, normalizeRtkVersion, readTextIfExists, rtkPlatformSpec, resolveRtkBinary, } from '../lib/utils.js';
|
|
12
11
|
const IS_WINDOWS = process.platform === 'win32';
|
|
13
|
-
const
|
|
12
|
+
const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
14
13
|
const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
|
|
15
|
-
const PONYTAIL_LOCAL = path.join(HOME, '.omp', 'plugins', 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
16
14
|
const RTK_BINARY = resolveRtkBinary();
|
|
17
|
-
|
|
18
|
-
const
|
|
15
|
+
// Beside this file, not a host path: that is the rule.md /caveman reads.
|
|
16
|
+
const CAVEMAN_LOCAL = path.join(EXTENSION_DIR, '..', 'caveman-session', 'rule.md');
|
|
17
|
+
const HOST_NAME = isPiProcess() ? 'Pi' : 'OMP';
|
|
18
|
+
const RELOAD_MSG = `Reminder: restart ${HOST_NAME} (or reload extensions) for updates to take effect.`;
|
|
19
|
+
// Ponytail is a hoisted dependency, so the installed copy is found by walking
|
|
20
|
+
// up rather than by naming a host directory.
|
|
21
|
+
function ponytailLocal() {
|
|
22
|
+
return findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'package.json');
|
|
23
|
+
}
|
|
19
24
|
function notify(ctx, msg, level) {
|
|
20
25
|
ctx?.ui?.notify?.(String(msg), level);
|
|
21
26
|
}
|
|
@@ -29,16 +34,25 @@ function shortHash(text) {
|
|
|
29
34
|
function checkFailed(name, e) {
|
|
30
35
|
return { text: `${name} check failed: ${e.message}`, level: 'warning' };
|
|
31
36
|
}
|
|
32
|
-
// Single error-handling source for the three probes; messages unchanged.
|
|
33
37
|
function runCheck(name, probe) {
|
|
34
38
|
return probe().then((text) => ({ text, level: 'info' }), (e) => checkFailed(name, e));
|
|
35
39
|
}
|
|
36
|
-
//
|
|
40
|
+
// A missing file is "not installed"; an unreadable one is a reportable fault.
|
|
41
|
+
function parsePackageVersion(raw) {
|
|
42
|
+
if (!raw)
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(raw).version ?? null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error('ponytail package.json is not readable JSON');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
37
51
|
function checkPonytail() {
|
|
38
52
|
return runCheck('Ponytail', async () => {
|
|
39
53
|
const remoteJson = await fetchJson(PONYTAIL_REMOTE);
|
|
40
|
-
const localRaw = await readTextIfExists(
|
|
41
|
-
const localVer =
|
|
54
|
+
const localRaw = await readTextIfExists(ponytailLocal() ?? '');
|
|
55
|
+
const localVer = parsePackageVersion(localRaw);
|
|
42
56
|
const remoteVer = remoteJson.version;
|
|
43
57
|
const status = !localVer ? 'not installed'
|
|
44
58
|
: localVer === remoteVer ? 'up to date'
|
|
@@ -120,13 +134,10 @@ async function checkAddons(ctx) {
|
|
|
120
134
|
return lines.join('\n');
|
|
121
135
|
}
|
|
122
136
|
async function updatePonytail(pi, ctx, dryRun = false) {
|
|
123
|
-
// Bundled with tersio:
|
|
124
|
-
// pulls the bundled copy with the CLI.
|
|
137
|
+
// Bundled with tersio: `tersio update` pulls the copy with the CLI.
|
|
125
138
|
let localVer = null;
|
|
126
139
|
try {
|
|
127
|
-
|
|
128
|
-
if (raw)
|
|
129
|
-
localVer = JSON.parse(raw).version ?? null;
|
|
140
|
+
localVer = parsePackageVersion(await readTextIfExists(ponytailLocal() ?? ''));
|
|
130
141
|
}
|
|
131
142
|
catch {
|
|
132
143
|
localVer = null;
|
|
@@ -150,7 +161,6 @@ async function updateRtk(ctx, dryRun = false) {
|
|
|
150
161
|
const assets = Array.isArray(release.assets) ? release.assets : [];
|
|
151
162
|
if (!RTK_BINARY)
|
|
152
163
|
return report(ctx, 'RTK: executable not found in PATH', 'warning');
|
|
153
|
-
// Cross-platform asset selection (mirrors installer stepRtk)
|
|
154
164
|
const PLATFORM = process.platform;
|
|
155
165
|
const ARCH = process.arch;
|
|
156
166
|
const spec = rtkPlatformSpec(PLATFORM, ARCH);
|
|
@@ -179,7 +189,6 @@ async function updateRtk(ctx, dryRun = false) {
|
|
|
179
189
|
httpsDownload(asset.browser_download_url, archivePath),
|
|
180
190
|
httpsDownload(checksAsset.browser_download_url, checksPath),
|
|
181
191
|
]);
|
|
182
|
-
// Verify SHA256 against checksums.txt
|
|
183
192
|
const checks = await fs.readFile(checksPath, 'utf8');
|
|
184
193
|
const expected = parseChecksum(checks, asset.name);
|
|
185
194
|
if (!expected) {
|
|
@@ -193,7 +202,6 @@ async function updateRtk(ctx, dryRun = false) {
|
|
|
193
202
|
return report(ctx, m, 'warning');
|
|
194
203
|
}
|
|
195
204
|
notify(ctx, 'RTK: checksum verified.', 'info');
|
|
196
|
-
// Extract by archive format
|
|
197
205
|
const extractDir = path.join(tmp, 'extracted');
|
|
198
206
|
await fs.mkdir(extractDir, { recursive: true });
|
|
199
207
|
if (asset.name.endsWith('.zip')) {
|
|
@@ -225,7 +233,6 @@ async function updateRtk(ctx, dryRun = false) {
|
|
|
225
233
|
const backupPath = `${RTK_BINARY}.bak`;
|
|
226
234
|
const backedUp = await fs.copyFile(RTK_BINARY, backupPath).then(() => true, () => false);
|
|
227
235
|
await fs.copyFile(rtkExtracted, RTK_BINARY);
|
|
228
|
-
// Set executable bit on Unix
|
|
229
236
|
if (!IS_WINDOWS) {
|
|
230
237
|
await fs.chmod(RTK_BINARY, 0o755);
|
|
231
238
|
}
|
|
@@ -293,7 +300,7 @@ async function updateCaveman(ctx, dryRun = false) {
|
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
export default function aiAddonsUpdaterExtension(pi) {
|
|
296
|
-
pi
|
|
303
|
+
setExtensionLabel(pi, 'AI add-ons updater');
|
|
297
304
|
pi.registerCommand?.('ai-addons', {
|
|
298
305
|
description: 'Check or update AI add-ons (ponytail/rtk/caveman/all). Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]',
|
|
299
306
|
handler: async (args, ctx) => {
|
|
@@ -303,15 +310,16 @@ export default function aiAddonsUpdaterExtension(pi) {
|
|
|
303
310
|
const cleanParts = parts.filter((p) => p !== '--dry-run' && p !== 'dry-run');
|
|
304
311
|
const sub = cleanParts[0];
|
|
305
312
|
if (sub === 'check' || sub === 'status') {
|
|
306
|
-
|
|
313
|
+
await checkAddons(ctx);
|
|
307
314
|
notify(ctx, 'ai-addons check complete.', 'info');
|
|
308
|
-
return
|
|
315
|
+
return;
|
|
309
316
|
}
|
|
310
317
|
if (sub === 'update' && cleanParts[1]) {
|
|
311
|
-
|
|
318
|
+
await runAddonUpdate(pi, ctx, cleanParts.slice(1).join(' '), dryRun);
|
|
319
|
+
return;
|
|
312
320
|
}
|
|
313
|
-
|
|
314
|
-
return
|
|
321
|
+
report(ctx, 'Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]', 'warning');
|
|
322
|
+
return;
|
|
315
323
|
},
|
|
316
324
|
});
|
|
317
325
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// ponytail: `skipped: none` — semantics match one-liner: fetch + compare + run install.
|
|
4
|
-
// rtk: `skipped: signature verification` — checksums.txt ships only SHA256 of release assets; add sigchain when upstream publishes a signing key.
|
|
5
|
-
// caveman: `skipped: none` — exactly the ask: write rule.md, report old/new hash.
|
|
1
|
+
// /ai-addons manual updater. Node built-ins only. RTK ships SHA256 checksums
|
|
2
|
+
// with no signature, so checksum-only is the best available.
|
|
6
3
|
|
|
7
4
|
import { execFileSync } from 'node:child_process';
|
|
8
5
|
import { createHash } from 'node:crypto';
|
|
9
6
|
import fs from 'node:fs/promises';
|
|
10
7
|
import os from 'node:os';
|
|
11
8
|
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
12
10
|
|
|
11
|
+
import { setExtensionLabel } from '../shared/host.ts';
|
|
13
12
|
import {
|
|
14
13
|
CAVEMAN_REMOTE_RULE as CAVEMAN_REMOTE,
|
|
15
14
|
RTK_RELEASE_API,
|
|
16
15
|
RtkRelease,
|
|
17
16
|
fetchJson,
|
|
18
17
|
findFile,
|
|
18
|
+
findHoistedPackage,
|
|
19
19
|
httpsGet,
|
|
20
20
|
httpsDownload,
|
|
21
|
+
isPiProcess,
|
|
21
22
|
sha256Hex,
|
|
22
23
|
parseChecksum,
|
|
23
24
|
normalizeRtkVersion,
|
|
@@ -27,23 +28,20 @@ import {
|
|
|
27
28
|
} from '../lib/utils.ts';
|
|
28
29
|
|
|
29
30
|
const IS_WINDOWS = process.platform === 'win32';
|
|
30
|
-
const
|
|
31
|
+
const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
31
32
|
|
|
32
33
|
const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
|
|
33
|
-
const PONYTAIL_LOCAL = path.join(HOME, '.omp', 'plugins', 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
|
|
34
34
|
const RTK_BINARY = resolveRtkBinary();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
const RELOAD_MSG = 'Reminder: restart OMP (or reload extensions) for updates to take effect.';
|
|
35
|
+
// Beside this file, not a host path: that is the rule.md /caveman reads.
|
|
36
|
+
const CAVEMAN_LOCAL = path.join(EXTENSION_DIR, '..', 'caveman-session', 'rule.md');
|
|
37
|
+
const HOST_NAME = isPiProcess() ? 'Pi' : 'OMP';
|
|
38
|
+
const RELOAD_MSG = `Reminder: restart ${HOST_NAME} (or reload extensions) for updates to take effect.`;
|
|
39
|
+
|
|
40
|
+
// Ponytail is a hoisted dependency, so the installed copy is found by walking
|
|
41
|
+
// up rather than by naming a host directory.
|
|
42
|
+
function ponytailLocal(): string | null {
|
|
43
|
+
return findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'package.json');
|
|
44
|
+
}
|
|
47
45
|
|
|
48
46
|
// --- Types ---
|
|
49
47
|
|
|
@@ -56,7 +54,7 @@ interface AddonUpdaterCtx {
|
|
|
56
54
|
|
|
57
55
|
interface AddonUpdaterPi {
|
|
58
56
|
setLabel?: (label: string) => void;
|
|
59
|
-
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;
|
|
60
58
|
exec?: (cmd: string, args: string[], opts?: { cwd?: string }) => Promise<{ stdout: string; stderr: string; code: number }>;
|
|
61
59
|
cwd?: string;
|
|
62
60
|
}
|
|
@@ -85,17 +83,25 @@ function checkFailed(name: string, e: unknown): AddonStatus {
|
|
|
85
83
|
return { text: `${name} check failed: ${(e as Error).message}`, level: 'warning' };
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
// Single error-handling source for the three probes; messages unchanged.
|
|
89
86
|
function runCheck(name: string, probe: () => Promise<string>): Promise<AddonStatus> {
|
|
90
87
|
return probe().then((text) => ({ text, level: 'info' as const }), (e) => checkFailed(name, e));
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
//
|
|
90
|
+
// A missing file is "not installed"; an unreadable one is a reportable fault.
|
|
91
|
+
function parsePackageVersion(raw: string | null): string | null {
|
|
92
|
+
if (!raw) return null;
|
|
93
|
+
try {
|
|
94
|
+
return (JSON.parse(raw) as { version?: string }).version ?? null;
|
|
95
|
+
} catch {
|
|
96
|
+
throw new Error('ponytail package.json is not readable JSON');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
function checkPonytail(): Promise<AddonStatus> {
|
|
95
101
|
return runCheck('Ponytail', async () => {
|
|
96
102
|
const remoteJson = await fetchJson<{ version?: string }>(PONYTAIL_REMOTE);
|
|
97
|
-
const localRaw = await readTextIfExists(
|
|
98
|
-
const localVer =
|
|
103
|
+
const localRaw = await readTextIfExists(ponytailLocal() ?? '');
|
|
104
|
+
const localVer = parsePackageVersion(localRaw);
|
|
99
105
|
const remoteVer = remoteJson.version;
|
|
100
106
|
const status = !localVer ? 'not installed'
|
|
101
107
|
: localVer === remoteVer ? 'up to date'
|
|
@@ -175,12 +181,10 @@ async function checkAddons(ctx: AddonUpdaterCtx): Promise<string> {
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
async function updatePonytail(pi: AddonUpdaterPi, ctx: AddonUpdaterCtx, dryRun = false): Promise<string> {
|
|
178
|
-
// Bundled with tersio:
|
|
179
|
-
// pulls the bundled copy with the CLI.
|
|
184
|
+
// Bundled with tersio: `tersio update` pulls the copy with the CLI.
|
|
180
185
|
let localVer: string | null = null;
|
|
181
186
|
try {
|
|
182
|
-
|
|
183
|
-
if (raw) localVer = (JSON.parse(raw) as { version?: string }).version ?? null;
|
|
187
|
+
localVer = parsePackageVersion(await readTextIfExists(ponytailLocal() ?? ''));
|
|
184
188
|
} catch { localVer = null; }
|
|
185
189
|
void pi;
|
|
186
190
|
const m = dryRun
|
|
@@ -201,7 +205,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
|
|
|
201
205
|
const assets = Array.isArray(release.assets) ? release.assets : [];
|
|
202
206
|
if (!RTK_BINARY) return report(ctx, 'RTK: executable not found in PATH', 'warning');
|
|
203
207
|
|
|
204
|
-
// Cross-platform asset selection (mirrors installer stepRtk)
|
|
205
208
|
const PLATFORM = process.platform;
|
|
206
209
|
const ARCH = process.arch;
|
|
207
210
|
const spec = rtkPlatformSpec(PLATFORM, ARCH);
|
|
@@ -234,7 +237,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
|
|
|
234
237
|
httpsDownload(asset.browser_download_url, archivePath),
|
|
235
238
|
httpsDownload(checksAsset.browser_download_url, checksPath),
|
|
236
239
|
]);
|
|
237
|
-
// Verify SHA256 against checksums.txt
|
|
238
240
|
const checks = await fs.readFile(checksPath, 'utf8');
|
|
239
241
|
const expected = parseChecksum(checks, asset.name);
|
|
240
242
|
if (!expected) {
|
|
@@ -249,7 +251,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
|
|
|
249
251
|
}
|
|
250
252
|
notify(ctx, 'RTK: checksum verified.', 'info');
|
|
251
253
|
|
|
252
|
-
// Extract by archive format
|
|
253
254
|
const extractDir = path.join(tmp, 'extracted');
|
|
254
255
|
await fs.mkdir(extractDir, { recursive: true });
|
|
255
256
|
|
|
@@ -281,7 +282,6 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
|
|
|
281
282
|
|
|
282
283
|
await fs.copyFile(rtkExtracted, RTK_BINARY);
|
|
283
284
|
|
|
284
|
-
// Set executable bit on Unix
|
|
285
285
|
if (!IS_WINDOWS) {
|
|
286
286
|
await fs.chmod(RTK_BINARY, 0o755);
|
|
287
287
|
}
|
|
@@ -343,7 +343,7 @@ async function updateCaveman(ctx: AddonUpdaterCtx, dryRun = false): Promise<stri
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
export default function aiAddonsUpdaterExtension(pi: AddonUpdaterPi): void {
|
|
346
|
-
pi
|
|
346
|
+
setExtensionLabel(pi, 'AI add-ons updater');
|
|
347
347
|
|
|
348
348
|
pi.registerCommand?.('ai-addons', {
|
|
349
349
|
description: 'Check or update AI add-ons (ponytail/rtk/caveman/all). Usage: /ai-addons <check|status|update ponytail|rtk|caveman|all> [--dry-run]',
|
|
@@ -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,31 +1,18 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { activeModesSummary,
|
|
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
|
+
import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
|
|
5
6
|
import { readCavemanDefault } from '../shared/plugin-settings.js';
|
|
6
7
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const RULE_PATH = join(CAVERN_DIR, 'rule.md');
|
|
8
|
-
|
|
9
|
-
Respond terse like smart caveman. Keep all technical substance. Drop filler, pleasantries, and hedging.
|
|
10
|
-
|
|
11
|
-
Rules:
|
|
12
|
-
- Drop articles where meaning stays clear. Keep fragments and short sentences.
|
|
13
|
-
- Keep technical terms, code, commands, paths, API names, numbers, units, and errors exact.
|
|
14
|
-
- Never invent abbreviations or causal arrows. Do not add words to sound caveman.
|
|
15
|
-
- Use ASD-STE100 Simplified Technical English. Keep one idea per sentence, target 20 words, active voice, and consistent terms.
|
|
16
|
-
- Make no tool-call narration. Use no decorative tables or emoji.
|
|
17
|
-
- Preserve the user's reply language. Compress style, not language.
|
|
18
|
-
- Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or requests to clarify.
|
|
19
|
-
- Write code, comments, commits, docs, issues, pull requests, and third-party messages in normal prose.
|
|
20
|
-
|
|
21
|
-
Default: **full**. Switch: \`/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off\`. Stop: "stop caveman" or "normal mode".`;
|
|
22
|
-
// 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.
|
|
23
10
|
function readFullRule() {
|
|
24
11
|
try {
|
|
25
12
|
return readFileSync(RULE_PATH, 'utf8');
|
|
26
13
|
}
|
|
27
14
|
catch {
|
|
28
|
-
return
|
|
15
|
+
return 'Caveman full requested, but rule.md is missing. Run: tersio doctor --fix extensions';
|
|
29
16
|
}
|
|
30
17
|
}
|
|
31
18
|
const DEFAULT_MODE = 'off';
|
|
@@ -52,15 +39,15 @@ export default function cavemanSessionExtension(pi) {
|
|
|
52
39
|
let lastCtx = undefined;
|
|
53
40
|
function syncStatus(ctx) {
|
|
54
41
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
42
|
+
const ui = statusUi(lastCtx);
|
|
43
|
+
if (!ui)
|
|
57
44
|
return;
|
|
58
45
|
// Combo owns the bar when any preset is active; keep ours empty to avoid duplication.
|
|
59
46
|
if (isComboPresetActive() || currentMode === 'off') {
|
|
60
|
-
|
|
47
|
+
ui.setStatus('caveman', undefined);
|
|
61
48
|
return;
|
|
62
49
|
}
|
|
63
|
-
paintStatusBar(
|
|
50
|
+
paintStatusBar(ui, 'caveman', '🪨', `caveman: ${currentMode.toUpperCase()}`, isActive);
|
|
64
51
|
}
|
|
65
52
|
function setMode(mode, ctx) {
|
|
66
53
|
const normalized = normalizeMode('caveman', mode);
|
|
@@ -77,10 +64,8 @@ export default function cavemanSessionExtension(pi) {
|
|
|
77
64
|
ctx?.ui?.notify?.(msg, 'info');
|
|
78
65
|
return true;
|
|
79
66
|
}
|
|
80
|
-
pi
|
|
81
|
-
//
|
|
82
|
-
// it at once so the next turn injects the new mode with no session reload.
|
|
83
|
-
// Stable identity, so the bridge set dedupes across re-inits.
|
|
67
|
+
setExtensionLabel(pi, 'Caveman session toggle');
|
|
68
|
+
// Adopt a /tersio or /combo switch at once, so the next turn needs no reload.
|
|
84
69
|
function syncFromShared(state) {
|
|
85
70
|
const mode = normalizeMode('caveman', state.caveman);
|
|
86
71
|
if (mode) {
|
|
@@ -88,7 +73,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
88
73
|
syncStatus();
|
|
89
74
|
}
|
|
90
75
|
}
|
|
91
|
-
setSharedComboListener(syncFromShared);
|
|
76
|
+
setSharedComboListener('caveman', syncFromShared);
|
|
92
77
|
pi.registerCommand?.('caveman', {
|
|
93
78
|
description: 'Toggle terse caveman replies for this session',
|
|
94
79
|
handler: async (args, ctx) => {
|
|
@@ -106,7 +91,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
106
91
|
}
|
|
107
92
|
},
|
|
108
93
|
});
|
|
109
|
-
pi
|
|
94
|
+
onHostEvent(pi, 'input', async (event) => {
|
|
110
95
|
if (event?.source === 'extension')
|
|
111
96
|
return;
|
|
112
97
|
if (currentMode !== 'off' && isOffCommand(event?.text))
|
|
@@ -114,10 +99,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
114
99
|
});
|
|
115
100
|
function restoreMode(ctx) {
|
|
116
101
|
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.
|
|
102
|
+
// Derive the bridge here so bar suppression does not depend on load order.
|
|
121
103
|
reconcileSharedComboEntries(entries);
|
|
122
104
|
// Persisted session state wins; a fresh session falls back to the
|
|
123
105
|
// installer/user-configured default (off unless configured).
|
|
@@ -129,7 +111,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
129
111
|
restoreMode(ctx);
|
|
130
112
|
ctx?.ui?.notify?.(`Caveman loaded: ${currentMode}`, 'info');
|
|
131
113
|
});
|
|
132
|
-
pi
|
|
114
|
+
onHostEvent(pi, 'session_branch', async (_event, ctx) => {
|
|
133
115
|
restoreMode(ctx);
|
|
134
116
|
});
|
|
135
117
|
pi.on('session_tree', async (_event, ctx) => {
|
|
@@ -151,7 +133,10 @@ export default function cavemanSessionExtension(pi) {
|
|
|
151
133
|
if (!def)
|
|
152
134
|
return;
|
|
153
135
|
const instruction = typeof def === 'function' ? def() : def;
|
|
154
|
-
|
|
155
|
-
|
|
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;
|
|
140
|
+
return injectPromptText(pi, event, instruction);
|
|
156
141
|
});
|
|
157
142
|
}
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { activeModesSummary,
|
|
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
|
+
import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.ts';
|
|
5
6
|
import { readCavemanDefault } from '../shared/plugin-settings.ts';
|
|
6
7
|
import type { ExtensionApi, ExtensionCtx, InputEvent, SessionEntry, SystemPromptEvent } from '../shared/types.ts';
|
|
7
8
|
|
|
8
9
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const RULE_PATH = join(CAVERN_DIR, 'rule.md');
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
Respond terse like smart caveman. Keep all technical substance. Drop filler, pleasantries, and hedging.
|
|
13
|
-
|
|
14
|
-
Rules:
|
|
15
|
-
- Drop articles where meaning stays clear. Keep fragments and short sentences.
|
|
16
|
-
- Keep technical terms, code, commands, paths, API names, numbers, units, and errors exact.
|
|
17
|
-
- Never invent abbreviations or causal arrows. Do not add words to sound caveman.
|
|
18
|
-
- Use ASD-STE100 Simplified Technical English. Keep one idea per sentence, target 20 words, active voice, and consistent terms.
|
|
19
|
-
- Make no tool-call narration. Use no decorative tables or emoji.
|
|
20
|
-
- Preserve the user's reply language. Compress style, not language.
|
|
21
|
-
- Drop caveman for security warnings, irreversible actions, ambiguous multi-step sequences, or requests to clarify.
|
|
22
|
-
- Write code, comments, commits, docs, issues, pull requests, and third-party messages in normal prose.
|
|
23
|
-
|
|
24
|
-
Default: **full**. Switch: \`/caveman lite|full|ultra|wenyan-lite|wenyan-full|wenyan-ultra|off\`. Stop: "stop caveman" or "normal mode".`;
|
|
25
|
-
|
|
26
|
-
// 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.
|
|
27
13
|
function readFullRule(): string {
|
|
28
|
-
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
|
+
}
|
|
29
17
|
}
|
|
30
18
|
|
|
31
19
|
const DEFAULT_MODE = 'off';
|
|
@@ -56,14 +44,14 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
56
44
|
|
|
57
45
|
function syncStatus(ctx?: ExtensionCtx): void {
|
|
58
46
|
lastCtx = paintableCtx(lastCtx, ctx);
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
47
|
+
const ui = statusUi(lastCtx);
|
|
48
|
+
if (!ui) return;
|
|
61
49
|
// Combo owns the bar when any preset is active; keep ours empty to avoid duplication.
|
|
62
50
|
if (isComboPresetActive() || currentMode === 'off') {
|
|
63
|
-
|
|
51
|
+
ui.setStatus('caveman', undefined);
|
|
64
52
|
return;
|
|
65
53
|
}
|
|
66
|
-
paintStatusBar(
|
|
54
|
+
paintStatusBar(ui, 'caveman', '🪨', `caveman: ${currentMode.toUpperCase()}`, isActive);
|
|
67
55
|
}
|
|
68
56
|
|
|
69
57
|
function setMode(mode: string, ctx?: ExtensionCtx): boolean {
|
|
@@ -81,11 +69,9 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
81
69
|
return true;
|
|
82
70
|
}
|
|
83
71
|
|
|
84
|
-
pi
|
|
72
|
+
setExtensionLabel(pi, 'Caveman session toggle');
|
|
85
73
|
|
|
86
|
-
//
|
|
87
|
-
// it at once so the next turn injects the new mode with no session reload.
|
|
88
|
-
// 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.
|
|
89
75
|
function syncFromShared(state: { caveman: string }): void {
|
|
90
76
|
const mode = normalizeMode('caveman', state.caveman);
|
|
91
77
|
if (mode) {
|
|
@@ -93,7 +79,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
93
79
|
syncStatus();
|
|
94
80
|
}
|
|
95
81
|
}
|
|
96
|
-
setSharedComboListener(syncFromShared);
|
|
82
|
+
setSharedComboListener('caveman', syncFromShared);
|
|
97
83
|
pi.registerCommand?.('caveman', {
|
|
98
84
|
description: 'Toggle terse caveman replies for this session',
|
|
99
85
|
handler: async (args, ctx) => {
|
|
@@ -112,17 +98,14 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
112
98
|
},
|
|
113
99
|
});
|
|
114
100
|
|
|
115
|
-
|
|
101
|
+
onHostEvent<InputEvent>(pi, 'input', async (event) => {
|
|
116
102
|
if (event?.source === 'extension') return;
|
|
117
103
|
if (currentMode !== 'off' && isOffCommand(event?.text)) setMode('off');
|
|
118
104
|
});
|
|
119
105
|
|
|
120
106
|
function restoreMode(ctx?: ExtensionCtx): void {
|
|
121
107
|
const entries = sessionEntries(ctx);
|
|
122
|
-
//
|
|
123
|
-
// bar suppression reads the in-process bridge, which is empty in a fresh
|
|
124
|
-
// host until the combo extension reconciles — and its reconcile is
|
|
125
|
-
// 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.
|
|
126
109
|
reconcileSharedComboEntries(entries);
|
|
127
110
|
// Persisted session state wins; a fresh session falls back to the
|
|
128
111
|
// installer/user-configured default (off unless configured).
|
|
@@ -136,7 +119,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
136
119
|
ctx?.ui?.notify?.(`Caveman loaded: ${currentMode}`, 'info');
|
|
137
120
|
});
|
|
138
121
|
|
|
139
|
-
pi
|
|
122
|
+
onHostEvent(pi, 'session_branch', async (_event, ctx) => {
|
|
140
123
|
restoreMode(ctx);
|
|
141
124
|
});
|
|
142
125
|
|
|
@@ -160,7 +143,9 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
160
143
|
const def = INSTRUCTIONS[mode];
|
|
161
144
|
if (!def) return;
|
|
162
145
|
const instruction = typeof def === 'function' ? def() : def;
|
|
163
|
-
|
|
164
|
-
|
|
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;
|
|
149
|
+
return injectPromptText(pi, event, instruction);
|
|
165
150
|
});
|
|
166
151
|
}
|