jeopi-cli 16.2.27 → 16.2.29
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/CHANGELOG.md +12 -0
- package/dist/cli.js +2754 -2753
- package/dist/types/advisor/runtime.d.ts +3 -0
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/package.json +13 -12
- package/src/advisor/__tests__/advisor.test.ts +43 -0
- package/src/advisor/runtime.ts +8 -2
- package/src/config/model-registry.ts +39 -6
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/session/session-history-format.ts +18 -1
|
@@ -3,6 +3,18 @@ export interface HistoryFormatOptions {
|
|
|
3
3
|
title?: string;
|
|
4
4
|
/** Render assistant thinking blocks (default: elided). */
|
|
5
5
|
includeThinking?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* When rendering thinking (requires `includeThinking`), disguise it through
|
|
8
|
+
* {@link renderDemotedThinking} for this target model id instead of the literal
|
|
9
|
+
* `_thinking:_ ` label. Anthropic's `reasoning_extraction` classifier can flag a
|
|
10
|
+
* transcript that replays a *different* model's prior thinking back at a
|
|
11
|
+
* Fable/Mythos model labeled as "thinking" — this is the same trigger
|
|
12
|
+
* `renderDemotedThinking` already avoids for cross-model/cross-turn replay in the
|
|
13
|
+
* wire layer. Omitted for the human-facing `history://` dump, where the explicit
|
|
14
|
+
* label is more useful than the disguise and there is no live model on the other
|
|
15
|
+
* end to refuse it.
|
|
16
|
+
*/
|
|
17
|
+
demoteThinkingForModelId?: string;
|
|
6
18
|
/** Render tool intent comment before tool call lines. */
|
|
7
19
|
includeToolIntent?: boolean;
|
|
8
20
|
/** Render watched-session roles as inline `**agent**:` / `**user**:` labels (collapsing consecutive same-role messages) instead of `## ` headings, so a primary transcript embedded inside an advisor turn stays visually distinct. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-cli",
|
|
4
|
-
"version": "16.2.
|
|
4
|
+
"version": "16.2.29",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"gen:docs": "bun scripts/generate-docs-index.ts --generate",
|
|
44
44
|
"gen:docs:reset": "bun scripts/generate-docs-index.ts --reset",
|
|
45
45
|
"gen:tool-views": "bun --cwd=../collab-web run gen:tool-views",
|
|
46
|
+
"gen:share-viewer": "bun scripts/generate-share-viewer.ts",
|
|
46
47
|
"gen:bundle": "bun scripts/bundle-dist.ts",
|
|
47
48
|
"gen:mupdf": "bun scripts/embed-mupdf-wasm.ts --generate",
|
|
48
49
|
"gen:mupdf:reset": "bun scripts/embed-mupdf-wasm.ts --reset",
|
|
@@ -56,17 +57,17 @@
|
|
|
56
57
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
57
58
|
"@babel/parser": "^7.29.7",
|
|
58
59
|
"@mozilla/readability": "^0.6.0",
|
|
59
|
-
"jeopi-hashline": "16.2.
|
|
60
|
-
"jeopi-stats": "16.2.
|
|
61
|
-
"jeopi-agent-core": "16.2.
|
|
62
|
-
"jeopi-ai": "16.2.
|
|
63
|
-
"jeopi-catalog": "16.2.
|
|
64
|
-
"jeopi-mnemopi": "16.2.
|
|
65
|
-
"jeopi-natives": "16.2.
|
|
66
|
-
"jeopi-tui": "16.2.
|
|
67
|
-
"jeopi-utils": "16.2.
|
|
68
|
-
"jeopi-wire": "16.2.
|
|
69
|
-
"jeopi-snapcompact": "16.2.
|
|
60
|
+
"jeopi-hashline": "16.2.29",
|
|
61
|
+
"jeopi-stats": "16.2.29",
|
|
62
|
+
"jeopi-agent-core": "16.2.29",
|
|
63
|
+
"jeopi-ai": "16.2.29",
|
|
64
|
+
"jeopi-catalog": "16.2.29",
|
|
65
|
+
"jeopi-mnemopi": "16.2.29",
|
|
66
|
+
"jeopi-natives": "16.2.29",
|
|
67
|
+
"jeopi-tui": "16.2.29",
|
|
68
|
+
"jeopi-utils": "16.2.29",
|
|
69
|
+
"jeopi-wire": "16.2.29",
|
|
70
|
+
"jeopi-snapcompact": "16.2.29",
|
|
70
71
|
"@opentelemetry/api": "^1.9.1",
|
|
71
72
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
72
73
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -1381,6 +1381,49 @@ describe("advisor", () => {
|
|
|
1381
1381
|
expect(strippedNotices).toHaveLength(1);
|
|
1382
1382
|
});
|
|
1383
1383
|
|
|
1384
|
+
it("never sends the literal reasoning_extraction-triggering label when the advisor's own model is Fable/Mythos (no refusal, no degrade)", async () => {
|
|
1385
|
+
const promptInputs: string[] = [];
|
|
1386
|
+
const strippedNotices: number[] = [];
|
|
1387
|
+
const agent: AdvisorAgent = {
|
|
1388
|
+
prompt: async input => {
|
|
1389
|
+
promptInputs.push(input);
|
|
1390
|
+
},
|
|
1391
|
+
abort: () => {},
|
|
1392
|
+
reset: () => {},
|
|
1393
|
+
state: { messages: [], model: { id: "claude-fable-5" } },
|
|
1394
|
+
};
|
|
1395
|
+
const thinking = "secret chain of thought the classifier flags";
|
|
1396
|
+
const messages: AgentMessage[] = [
|
|
1397
|
+
{ role: "user", content: "do the thing", timestamp: 1 } as AgentMessage,
|
|
1398
|
+
{
|
|
1399
|
+
role: "assistant",
|
|
1400
|
+
content: [
|
|
1401
|
+
{ type: "thinking", thinking },
|
|
1402
|
+
{ type: "text", text: "done" },
|
|
1403
|
+
],
|
|
1404
|
+
timestamp: 2,
|
|
1405
|
+
} as AgentMessage,
|
|
1406
|
+
];
|
|
1407
|
+
const host: AdvisorRuntimeHost = {
|
|
1408
|
+
snapshotMessages: () => messages,
|
|
1409
|
+
enqueueAdvice: () => {},
|
|
1410
|
+
notifyThinkingStripped: () => strippedNotices.push(1),
|
|
1411
|
+
};
|
|
1412
|
+
const runtime = new AdvisorRuntime(agent, host, 0);
|
|
1413
|
+
|
|
1414
|
+
runtime.onTurnEnd(messages);
|
|
1415
|
+
await runtime.waitForCatchup(5000, 1);
|
|
1416
|
+
|
|
1417
|
+
// Sent once, on the first attempt — the disguised form carries the
|
|
1418
|
+
// reasoning content but never the literal "_thinking:_" label, so
|
|
1419
|
+
// there is nothing for the classifier to catch and no degrade fires.
|
|
1420
|
+
expect(promptInputs).toHaveLength(1);
|
|
1421
|
+
expect(promptInputs[0]).not.toContain("_thinking:_");
|
|
1422
|
+
expect(promptInputs[0]).toContain(thinking);
|
|
1423
|
+
expect(strippedNotices).toHaveLength(0);
|
|
1424
|
+
expect(runtime.backlog).toBe(0);
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1384
1427
|
it("skips the degrade path when thinking is disabled at construction and falls through to the watchdog", async () => {
|
|
1385
1428
|
const promptInputs: string[] = [];
|
|
1386
1429
|
const strippedNotices: number[] = [];
|
package/src/advisor/runtime.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface AdvisorAgent {
|
|
|
21
21
|
* turn `Agent.#runLoop` records on its internal state.
|
|
22
22
|
*/
|
|
23
23
|
rollbackTo?(count: number): void;
|
|
24
|
-
readonly state: { messages: AgentMessage[]; error?: string };
|
|
24
|
+
readonly state: { messages: AgentMessage[]; error?: string; model?: { id: string } };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface AdvisorRuntimeHost {
|
|
@@ -116,7 +116,12 @@ export class AdvisorRuntime {
|
|
|
116
116
|
* `false` exactly once, the first time a classifier refusal is caught — see
|
|
117
117
|
* the `#drain` catch block. That single flip is sufficient to both change
|
|
118
118
|
* future renders AND guard against re-triggering the degrade path, so no
|
|
119
|
-
* separate one-shot flag is needed alongside it.
|
|
119
|
+
* separate one-shot flag is needed alongside it. When the render target is a
|
|
120
|
+
* Fable/Mythos model, {@link formatSessionHistoryMarkdown}'s
|
|
121
|
+
* `demoteThinkingForModelId` (wired from `agent.state.model.id` in
|
|
122
|
+
* {@link AdvisorRuntime.#renderDelta}) already disguises the rendered thinking
|
|
123
|
+
* before the first send, so this latch mainly guards other/unforeseen
|
|
124
|
+
* classifier triggers on the first turn.
|
|
120
125
|
*/
|
|
121
126
|
#includeThinking: boolean;
|
|
122
127
|
|
|
@@ -243,6 +248,7 @@ export class AdvisorRuntime {
|
|
|
243
248
|
const formattedDelta = obfuscator?.hasSecrets() ? obfuscateAdvisorDelta(obfuscator, delta) : delta;
|
|
244
249
|
const md = formatSessionHistoryMarkdown(formattedDelta, {
|
|
245
250
|
includeThinking: this.#includeThinking,
|
|
251
|
+
demoteThinkingForModelId: this.agent.state.model?.id,
|
|
246
252
|
includeToolIntent: true,
|
|
247
253
|
watchedRoles: true,
|
|
248
254
|
expandPrimaryContext: true,
|
|
@@ -156,6 +156,17 @@ const AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS = new Set<string>(
|
|
|
156
156
|
),
|
|
157
157
|
);
|
|
158
158
|
|
|
159
|
+
function isAuthoritativeLocalDiscovery(providerConfig: DiscoveryProviderConfig): boolean {
|
|
160
|
+
switch (providerConfig.discovery.type) {
|
|
161
|
+
case "ollama":
|
|
162
|
+
case "llama.cpp":
|
|
163
|
+
case "lm-studio":
|
|
164
|
+
return true;
|
|
165
|
+
default:
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
159
170
|
function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
|
|
160
171
|
return (
|
|
161
172
|
model.provider === "google-vertex" &&
|
|
@@ -1334,7 +1345,21 @@ export class ModelRegistry {
|
|
|
1334
1345
|
this.#discoverBuiltInProviderModels(strategy, providerFilter),
|
|
1335
1346
|
]);
|
|
1336
1347
|
const discovered = [...configuredDiscovered, ...builtInDiscovery.models];
|
|
1337
|
-
|
|
1348
|
+
const successfulAuthoritativeDiscoverableProviders = new Set<string>();
|
|
1349
|
+
for (const providerConfig of selectedDiscoverableProviders) {
|
|
1350
|
+
if (!isAuthoritativeLocalDiscovery(providerConfig)) {
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
const state = this.#providerDiscoveryStates.get(providerConfig.provider);
|
|
1354
|
+
if (state && (state.status === "ok" || state.status === "empty") && !state.stale) {
|
|
1355
|
+
successfulAuthoritativeDiscoverableProviders.add(providerConfig.provider);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (
|
|
1359
|
+
discovered.length === 0 &&
|
|
1360
|
+
builtInDiscovery.authoritativeProviders.size === 0 &&
|
|
1361
|
+
successfulAuthoritativeDiscoverableProviders.size === 0
|
|
1362
|
+
) {
|
|
1338
1363
|
return;
|
|
1339
1364
|
}
|
|
1340
1365
|
const discoveredModels = this.#applyHardcodedModelPolicies(
|
|
@@ -1350,6 +1375,9 @@ export class ModelRegistry {
|
|
|
1350
1375
|
for (const provider of builtInDiscovery.authoritativeProviders) {
|
|
1351
1376
|
authoritativeProviders.add(provider);
|
|
1352
1377
|
}
|
|
1378
|
+
for (const provider of successfulAuthoritativeDiscoverableProviders) {
|
|
1379
|
+
authoritativeProviders.add(provider);
|
|
1380
|
+
}
|
|
1353
1381
|
const baseModels =
|
|
1354
1382
|
authoritativeProviders.size > 0 ? dropProviderModels(this.#models, authoritativeProviders) : this.#models;
|
|
1355
1383
|
const resolved = this.#mergeResolvedModels(baseModels, discoveredModels);
|
|
@@ -1385,9 +1413,9 @@ export class ModelRegistry {
|
|
|
1385
1413
|
const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
|
|
1386
1414
|
const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1387
1415
|
const cacheOlderThanConfig = cached !== null && this.#isDiscoveryCacheOlderThanModelsConfig(cached.updatedAt);
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
|
|
1416
|
+
const shouldForceOnlineDiscovery =
|
|
1417
|
+
strategy === "online-if-uncached" && (cacheOlderThanConfig || isAuthoritativeLocalDiscovery(providerConfig));
|
|
1418
|
+
const effectiveStrategy = shouldForceOnlineDiscovery ? "online" : strategy;
|
|
1391
1419
|
const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
|
|
1392
1420
|
if (requiresAuth) {
|
|
1393
1421
|
const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
|
|
@@ -1450,7 +1478,10 @@ export class ModelRegistry {
|
|
|
1450
1478
|
provider: providerId,
|
|
1451
1479
|
status,
|
|
1452
1480
|
optional: providerConfig.optional ?? false,
|
|
1453
|
-
stale:
|
|
1481
|
+
stale:
|
|
1482
|
+
result.stale ||
|
|
1483
|
+
status === "cached" ||
|
|
1484
|
+
(shouldForceOnlineDiscovery && status !== "ok" && status !== "empty"),
|
|
1454
1485
|
fetchedAt: discoveryError ? cached?.updatedAt : Date.now(),
|
|
1455
1486
|
models: result.models.map(model => model.id),
|
|
1456
1487
|
error: discoveryError,
|
|
@@ -1627,7 +1658,9 @@ export class ModelRegistry {
|
|
|
1627
1658
|
): Promise<BuiltInDiscoveryResult> {
|
|
1628
1659
|
try {
|
|
1629
1660
|
const manager = createModelManager({ ...options, cacheDbPath: this.#cacheDbPath });
|
|
1630
|
-
const
|
|
1661
|
+
const effectiveStrategy =
|
|
1662
|
+
strategy === "online-if-uncached" && options.providerId === "lm-studio" ? "online" : strategy;
|
|
1663
|
+
const result = await manager.refresh(effectiveStrategy);
|
|
1631
1664
|
const models = result.models.map(model =>
|
|
1632
1665
|
model.provider === options.providerId ? model : { ...model, provider: options.providerId },
|
|
1633
1666
|
);
|