@krtclcdy/tersio 2.23.0 → 2.24.0
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 +58 -45
- package/cli/agent-hosts.js +42 -0
- package/cli/agents.js +266 -0
- package/cli/common.js +16 -20
- package/cli/currency.js +8 -12
- package/cli/dashboard.js +49 -37
- package/cli/doctor-fix.js +28 -17
- package/cli/doctor.js +52 -41
- package/cli/hosts.js +118 -0
- package/cli/install.js +180 -117
- package/cli/interactive.js +27 -1
- package/cli/layer.js +24 -0
- package/cli/manifest.js +36 -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 +44 -46
- package/cli/reset.js +3 -26
- package/cli/rtk-wiring.js +10 -15
- package/cli/uninstall.js +135 -44
- package/cli/update.js +12 -19
- package/dashboard/dist/index.html +16 -16
- package/extensions/ai-addons-updater/index.js +31 -24
- package/extensions/ai-addons-updater/index.ts +32 -32
- package/extensions/caveman-session/index.js +6 -6
- package/extensions/caveman-session/index.ts +6 -6
- package/extensions/combo-toggle/index.js +42 -40
- package/extensions/combo-toggle/index.ts +36 -47
- package/extensions/lib/utils.js +36 -9
- package/extensions/lib/utils.ts +37 -9
- package/extensions/rtk-session/index.js +9 -9
- package/extensions/rtk-session/index.ts +9 -14
- package/extensions/shared/carbon.js +5 -8
- package/extensions/shared/carbon.ts +5 -8
- package/extensions/shared/host.js +68 -0
- package/extensions/shared/host.ts +104 -0
- package/extensions/shared/omp-prompt.js +12 -0
- package/extensions/shared/plugin-settings.js +43 -30
- package/extensions/shared/plugin-settings.ts +45 -26
- package/extensions/shared/pricing.js +26 -15
- package/extensions/shared/pricing.ts +28 -16
- package/extensions/shared/rtk-gain.js +11 -22
- package/extensions/shared/rtk-gain.ts +11 -22
- package/extensions/shared/session-state.js +21 -25
- package/extensions/shared/session-state.ts +22 -26
- package/extensions/shared/types.ts +10 -3
- package/extensions/shared/usage-ledger.js +24 -36
- package/extensions/shared/usage-ledger.ts +29 -50
- package/extensions/shared/usage-store.js +14 -12
- package/extensions/shared/usage-store.ts +16 -14
- package/extensions/tersio-commands/index.js +2 -1
- package/extensions/tersio-commands/index.ts +2 -1
- package/package.json +4 -2
- package/tersio.js +4 -4
- 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) => {
|
|
@@ -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
|
|
|
@@ -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]',
|
|
@@ -1,7 +1,8 @@
|
|
|
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 } 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');
|
|
@@ -77,7 +78,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
77
78
|
ctx?.ui?.notify?.(msg, 'info');
|
|
78
79
|
return true;
|
|
79
80
|
}
|
|
80
|
-
pi
|
|
81
|
+
setExtensionLabel(pi, 'Caveman session toggle');
|
|
81
82
|
// Live mirror: a /tersio or /combo switch publishes shared state — adopt
|
|
82
83
|
// it at once so the next turn injects the new mode with no session reload.
|
|
83
84
|
// Stable identity, so the bridge set dedupes across re-inits.
|
|
@@ -106,7 +107,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
106
107
|
}
|
|
107
108
|
},
|
|
108
109
|
});
|
|
109
|
-
pi
|
|
110
|
+
onHostEvent(pi, 'input', async (event) => {
|
|
110
111
|
if (event?.source === 'extension')
|
|
111
112
|
return;
|
|
112
113
|
if (currentMode !== 'off' && isOffCommand(event?.text))
|
|
@@ -129,7 +130,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
129
130
|
restoreMode(ctx);
|
|
130
131
|
ctx?.ui?.notify?.(`Caveman loaded: ${currentMode}`, 'info');
|
|
131
132
|
});
|
|
132
|
-
pi
|
|
133
|
+
onHostEvent(pi, 'session_branch', async (_event, ctx) => {
|
|
133
134
|
restoreMode(ctx);
|
|
134
135
|
});
|
|
135
136
|
pi.on('session_tree', async (_event, ctx) => {
|
|
@@ -151,7 +152,6 @@ export default function cavemanSessionExtension(pi) {
|
|
|
151
152
|
if (!def)
|
|
152
153
|
return;
|
|
153
154
|
const instruction = typeof def === 'function' ? def() : def;
|
|
154
|
-
|
|
155
|
-
return { systemPrompt: [...base, instruction] };
|
|
155
|
+
return injectPromptText(pi, event, instruction);
|
|
156
156
|
});
|
|
157
157
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
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 } 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
|
|
|
@@ -81,7 +82,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
81
82
|
return true;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
pi
|
|
85
|
+
setExtensionLabel(pi, 'Caveman session toggle');
|
|
85
86
|
|
|
86
87
|
// Live mirror: a /tersio or /combo switch publishes shared state — adopt
|
|
87
88
|
// it at once so the next turn injects the new mode with no session reload.
|
|
@@ -112,7 +113,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
112
113
|
},
|
|
113
114
|
});
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
onHostEvent<InputEvent>(pi, 'input', async (event) => {
|
|
116
117
|
if (event?.source === 'extension') return;
|
|
117
118
|
if (currentMode !== 'off' && isOffCommand(event?.text)) setMode('off');
|
|
118
119
|
});
|
|
@@ -136,7 +137,7 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
136
137
|
ctx?.ui?.notify?.(`Caveman loaded: ${currentMode}`, 'info');
|
|
137
138
|
});
|
|
138
139
|
|
|
139
|
-
pi
|
|
140
|
+
onHostEvent(pi, 'session_branch', async (_event, ctx) => {
|
|
140
141
|
restoreMode(ctx);
|
|
141
142
|
});
|
|
142
143
|
|
|
@@ -160,7 +161,6 @@ export default function cavemanSessionExtension(pi: ExtensionApi): void {
|
|
|
160
161
|
const def = INSTRUCTIONS[mode];
|
|
161
162
|
if (!def) return;
|
|
162
163
|
const instruction = typeof def === 'function' ? def() : def;
|
|
163
|
-
|
|
164
|
-
return { systemPrompt: [...base, instruction] };
|
|
164
|
+
return injectPromptText(pi, event, instruction);
|
|
165
165
|
});
|
|
166
166
|
}
|
|
@@ -1,33 +1,47 @@
|
|
|
1
|
-
// /combo session toggle
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// /combo session toggle: off | medium | balanced | max.
|
|
2
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
3
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
4
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
5
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
return path;
|
|
9
|
+
};
|
|
4
10
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import { activeModesSummary,
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
|
+
import { activeModesSummary, COMBO_LEVELS, getSharedComboState, isComboPresetActive, isOmpSubagentPrompt, normalizeComboLevel, paintableCtx, reconcileSharedComboEntries, sessionEntries, setSharedComboLevel, setSharedComboListener, setSharedComboMode, systemPromptIncludes, themeStatus, } from '../shared/session-state.js';
|
|
13
|
+
import { hostSelect, injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
|
|
7
14
|
import { isComboSetupComplete, readComboDefault, readPonytailDefault, saveComboSetup, } from '../shared/plugin-settings.js';
|
|
8
|
-
|
|
15
|
+
import { findHoistedPackage } from '../lib/utils.js';
|
|
16
|
+
const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
17
|
const PONYTAIL_FALLBACK_INTENSITY = {
|
|
10
18
|
lite: 'Prefer the simplest correct solution.',
|
|
11
19
|
};
|
|
12
|
-
|
|
20
|
+
// Fallback text when Ponytail cannot be found; exported for its own test.
|
|
21
|
+
export function ponytailFallback(mode) {
|
|
13
22
|
const intensity = PONYTAIL_FALLBACK_INTENSITY[mode] ?? 'Use the minimum correct solution. Delete or reuse before adding.';
|
|
14
23
|
return `🦥 PONYTAIL MODE ACTIVE — level: ${mode}\n${intensity} Understand the path first and fix root causes, not symptoms. Prefer the standard library and YAGNI. Avoid speculative abstractions and dependencies. Preserve correctness. Verify changed behavior.`;
|
|
15
24
|
}
|
|
16
25
|
function levelSummary(state) {
|
|
17
26
|
return `caveman=${state.caveman} rtk=${state.rtk} ponytail=${state.ponytail}`;
|
|
18
27
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
// Ponytail is hoisted, so one upward walk covers every install layout. The hook
|
|
29
|
+
// is CommonJS, so both namespace shapes are tried.
|
|
30
|
+
async function loadPonytailInstructions(mode) {
|
|
31
|
+
const installed = findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'hooks', 'ponytail-instructions.js');
|
|
32
|
+
if (installed) {
|
|
33
|
+
try {
|
|
34
|
+
const loaded = await import(__rewriteRelativeImportExtension(pathToFileURL(installed).href));
|
|
35
|
+
const build = loaded.getPonytailInstructions ?? loaded.default?.getPonytailInstructions;
|
|
36
|
+
if (build)
|
|
37
|
+
return build(mode);
|
|
38
|
+
}
|
|
39
|
+
catch { /* fall through to the built-in text */ }
|
|
25
40
|
}
|
|
26
|
-
catch { }
|
|
27
41
|
return ponytailFallback(mode);
|
|
28
42
|
}
|
|
29
43
|
export default function comboToggleExtension(pi) {
|
|
30
|
-
pi
|
|
44
|
+
setExtensionLabel(pi, 'Combo session toggle (all 3 add-ons)');
|
|
31
45
|
let lastCtx = undefined;
|
|
32
46
|
let setupPrompted = false;
|
|
33
47
|
function syncStatus(ctx) {
|
|
@@ -35,31 +49,24 @@ export default function comboToggleExtension(pi) {
|
|
|
35
49
|
const c = lastCtx;
|
|
36
50
|
if (!c?.ui?.setStatus)
|
|
37
51
|
return;
|
|
38
|
-
//
|
|
39
|
-
// active. In custom/off, the individual bars come back and combo stays clear.
|
|
52
|
+
// One unified bar replaces the per-extension bars while a preset is active.
|
|
40
53
|
if (!isComboPresetActive()) {
|
|
41
54
|
c.ui.setStatus('combo', undefined);
|
|
42
55
|
return;
|
|
43
56
|
}
|
|
44
|
-
// Read the live bridge, not
|
|
45
|
-
// reconcile it from persisted entries before this one runs.
|
|
57
|
+
// Read the live bridge, not a cache: siblings reconcile it first.
|
|
46
58
|
const state = getSharedComboState();
|
|
47
|
-
const theme = c.ui.theme;
|
|
48
59
|
const c0 = state.caveman.toUpperCase();
|
|
49
60
|
const r = state.rtk.toUpperCase();
|
|
50
61
|
const p = state.ponytail.toUpperCase();
|
|
51
62
|
const lvl = state.level.toUpperCase();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Clobber any sibling bars another extension may have painted in a race
|
|
55
|
-
// during session_start — our bar is canonical for the duration of the preset.
|
|
63
|
+
c.ui.setStatus('combo', themeStatus(c.ui, '🧩', `combo ${lvl}: 🪨caveman=${c0} ⚡rtk=${r} 🦥ponytail=${p}`, true));
|
|
64
|
+
// Our bar is canonical for the preset, so clear any sibling bar.
|
|
56
65
|
c.ui.setStatus('caveman', undefined);
|
|
57
66
|
c.ui.setStatus('rtk', undefined);
|
|
58
67
|
c.ui.setStatus('ponytail', undefined);
|
|
59
68
|
}
|
|
60
|
-
//
|
|
61
|
-
// restore from these entries, so the fallback must write them too or the
|
|
62
|
-
// preset evaporates on resume and ponytail never activates.
|
|
69
|
+
// Siblings restore from these entries, so the fallback must write them too.
|
|
63
70
|
function persistPreset(level) {
|
|
64
71
|
const modes = COMBO_LEVELS[level];
|
|
65
72
|
pi.appendEntry?.('caveman-mode', { mode: modes.caveman });
|
|
@@ -77,8 +84,7 @@ export default function comboToggleExtension(pi) {
|
|
|
77
84
|
return useState(reconcileSharedComboEntries(sessionEntries(ctx)), ctx);
|
|
78
85
|
}
|
|
79
86
|
function listen(ctx) {
|
|
80
|
-
// Stable identity: the bridge set dedupes, so
|
|
81
|
-
// calls register once instead of stacking duplicate listeners.
|
|
87
|
+
// Stable identity: the bridge set dedupes, so repeat calls register once.
|
|
82
88
|
if (ctx?.hasUI)
|
|
83
89
|
setSharedComboListener(useState);
|
|
84
90
|
}
|
|
@@ -119,7 +125,7 @@ export default function comboToggleExtension(pi) {
|
|
|
119
125
|
if (setupPrompted || !ctx?.hasUI || !ctx.ui?.select || isComboSetupComplete())
|
|
120
126
|
return;
|
|
121
127
|
setupPrompted = true;
|
|
122
|
-
const choice = await ctx.ui
|
|
128
|
+
const choice = await hostSelect(pi, ctx.ui, 'Session-start defaults — Combo preset', [
|
|
123
129
|
{ label: 'off', description: 'Keep every Tersio mode inactive' },
|
|
124
130
|
{ label: 'medium', description: 'caveman=lite, rtk=on, ponytail=lite' },
|
|
125
131
|
{ label: 'balanced', description: 'caveman=full, rtk=on, ponytail=full' },
|
|
@@ -137,9 +143,7 @@ export default function comboToggleExtension(pi) {
|
|
|
137
143
|
if (!ctx?.hasUI)
|
|
138
144
|
syncStatus(ctx);
|
|
139
145
|
await runFirstRunSetup(ctx);
|
|
140
|
-
//
|
|
141
|
-
// state exists — unrelated session entries must not block it, or the
|
|
142
|
-
// combo bar never paints on sessions that already carry other entries.
|
|
146
|
+
// The configured default applies only when no persisted mode state exists.
|
|
143
147
|
const entries = sessionEntries(ctx);
|
|
144
148
|
const hasModeState = entries.some((e) => e?.type === 'custom' && (e.customType === 'combo-level' || e.customType === 'caveman-mode' ||
|
|
145
149
|
e.customType === 'rtk-mode' || e.customType === 'ponytail-mode'));
|
|
@@ -151,10 +155,8 @@ export default function comboToggleExtension(pi) {
|
|
|
151
155
|
ctx?.ui?.notify?.(`Combo default applied: ${fallback} — ${activeModesSummary(getSharedComboState())} active for this session.`, 'info');
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// ponytail-mode entry. Skipped when it matches the active preset — the
|
|
157
|
-
// preset entry already covers that, no redundant write.
|
|
158
|
+
// No sibling restores a standalone ponytail default, so apply it here; skip
|
|
159
|
+
// it when the active preset already writes the same mode.
|
|
158
160
|
if (!entries.some((e) => e?.type === 'custom' && e.customType === 'ponytail-mode')) {
|
|
159
161
|
const ponytailFallback = readPonytailDefault();
|
|
160
162
|
if (ponytailFallback !== 'off' && ponytailFallback !== getSharedComboState().ponytail) {
|
|
@@ -164,7 +166,7 @@ export default function comboToggleExtension(pi) {
|
|
|
164
166
|
}
|
|
165
167
|
});
|
|
166
168
|
for (const event of ['session_branch', 'session_tree', 'agent_start']) {
|
|
167
|
-
pi
|
|
169
|
+
onHostEvent(pi, event, async (_event, ctx) => {
|
|
168
170
|
track(ctx);
|
|
169
171
|
if (event === 'agent_start')
|
|
170
172
|
await runFirstRunSetup(ctx);
|
|
@@ -178,8 +180,8 @@ export default function comboToggleExtension(pi) {
|
|
|
178
180
|
const mode = getSharedComboState().ponytail;
|
|
179
181
|
if (mode === 'off' || systemPromptIncludes(event.systemPrompt, 'PONYTAIL MODE ACTIVE'))
|
|
180
182
|
return;
|
|
181
|
-
const
|
|
182
|
-
return
|
|
183
|
+
const instructions = await loadPonytailInstructions(mode);
|
|
184
|
+
return injectPromptText(pi, event, instructions);
|
|
183
185
|
});
|
|
184
186
|
// Slash commands only; natural-language input caused accidental toggles and has no reload context.
|
|
185
187
|
}
|