@oh-my-pi/pi-coding-agent 15.11.4 → 15.11.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/CHANGELOG.md +82 -1
- package/dist/cli.js +520 -451
- package/dist/types/cli/bench-cli.d.ts +78 -0
- package/dist/types/cli/usage-cli.d.ts +10 -1
- package/dist/types/commands/bench.d.ts +29 -0
- package/dist/types/commands/usage.d.ts +9 -0
- package/dist/types/config/model-resolver.d.ts +3 -2
- package/dist/types/config/settings-schema.d.ts +125 -3
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -1
- package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-selector.d.ts +8 -1
- package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -0
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +3 -0
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +10 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +3 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -1
- package/dist/types/session/agent-session.d.ts +14 -1
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/codex-auto-reset.d.ts +107 -0
- package/dist/types/session/snapcompact-inline.d.ts +107 -4
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/task/render.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/renderers.d.ts +13 -0
- package/dist/types/tools/ssh.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +0 -11
- package/package.json +11 -11
- package/src/cli/bench-cli.ts +437 -0
- package/src/cli/usage-cli.ts +187 -16
- package/src/cli-commands.ts +1 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/usage.ts +8 -0
- package/src/config/model-registry.ts +52 -5
- package/src/config/model-resolver.ts +36 -5
- package/src/config/settings-schema.ts +148 -3
- package/src/config/settings.ts +9 -0
- package/src/edit/renderer.ts +5 -0
- package/src/hindsight/client.ts +26 -1
- package/src/hindsight/state.ts +6 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/mcp/transports/stdio.ts +81 -7
- package/src/modes/components/oauth-selector.ts +67 -7
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/session-selector.ts +8 -2
- package/src/modes/components/settings-selector.ts +89 -47
- package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/tool-execution.ts +26 -0
- package/src/modes/components/transcript-container.ts +23 -1
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/input-controller.ts +8 -6
- package/src/modes/controllers/selector-controller.ts +72 -2
- package/src/modes/interactive-mode.ts +83 -0
- package/src/modes/session-observer-registry.ts +61 -3
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +24 -6
- package/src/modes/setup-wizard/scenes/providers.ts +36 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +10 -1
- package/src/modes/setup-wizard/scenes/theme.ts +28 -1
- package/src/modes/setup-wizard/scenes/types.ts +10 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +22 -6
- package/src/modes/setup-wizard/wizard-overlay.ts +38 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/context-usage.ts +75 -1
- package/src/prompts/bench.md +7 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
- package/src/prompts/tools/browser.md +33 -43
- package/src/prompts/tools/eval.md +27 -50
- package/src/prompts/tools/irc.md +29 -31
- package/src/prompts/tools/read.md +31 -37
- package/src/prompts/tools/todo.md +1 -2
- package/src/sdk.ts +4 -2
- package/src/session/agent-session.ts +136 -6
- package/src/session/auth-storage.ts +3 -0
- package/src/session/codex-auto-reset.ts +190 -0
- package/src/session/snapcompact-inline.ts +404 -75
- package/src/slash-commands/builtin-registry.ts +145 -8
- package/src/slash-commands/helpers/context-report.ts +28 -1
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/usage-report.ts +12 -0
- package/src/task/index.ts +30 -7
- package/src/task/render.ts +34 -19
- package/src/tools/bash.ts +3 -0
- package/src/tools/eval-render.ts +4 -0
- package/src/tools/renderers.ts +13 -0
- package/src/tools/ssh.ts +3 -0
- package/src/tools/todo.ts +8 -128
package/src/cli-commands.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const commands: CommandEntry[] = [
|
|
|
16
16
|
{ name: "auth-broker", load: () => import("./commands/auth-broker").then(m => m.default) },
|
|
17
17
|
{ name: "auth-gateway", load: () => import("./commands/auth-gateway").then(m => m.default) },
|
|
18
18
|
{ name: "agents", load: () => import("./commands/agents").then(m => m.default) },
|
|
19
|
+
{ name: "bench", load: () => import("./commands/bench").then(m => m.default) },
|
|
19
20
|
{ name: "commit", load: () => import("./commands/commit").then(m => m.default) },
|
|
20
21
|
{ name: "completions", load: () => import("./commands/completions").then(m => m.default) },
|
|
21
22
|
{ name: "__complete", load: () => import("./commands/complete").then(m => m.default) },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
2
|
+
import { runBenchCommand } from "../cli/bench-cli";
|
|
3
|
+
|
|
4
|
+
export default class Bench extends Command {
|
|
5
|
+
static description =
|
|
6
|
+
"Benchmark models with the same prompt: time-to-first-token and generation throughput (tokens/s)";
|
|
7
|
+
|
|
8
|
+
static args = {
|
|
9
|
+
models: Args.string({
|
|
10
|
+
description: "Model selectors (provider/model or fuzzy id, e.g. opus)",
|
|
11
|
+
required: true,
|
|
12
|
+
multiple: true,
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
static flags = {
|
|
17
|
+
runs: Flags.integer({ description: "Requests per model (results are averaged)", default: 1 }),
|
|
18
|
+
"max-tokens": Flags.integer({ description: "Max output tokens per request", default: 512 }),
|
|
19
|
+
prompt: Flags.string({ description: "Custom prompt text (default: bundled bench prompt)" }),
|
|
20
|
+
json: Flags.boolean({ description: "Output JSON" }),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
static examples = [
|
|
24
|
+
"# Compare two models\n omp bench anthropic/claude-opus-4-5 openai/gpt-5.2",
|
|
25
|
+
"# Fuzzy selectors work\n omp bench opus sonnet",
|
|
26
|
+
"# Average over 3 runs each\n omp bench opus gpt-5.2 --runs 3",
|
|
27
|
+
"# Machine-readable output\n omp bench opus --json",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
async run(): Promise<void> {
|
|
31
|
+
const { args, flags } = await this.parse(Bench);
|
|
32
|
+
await runBenchCommand({
|
|
33
|
+
models: args.models ?? [],
|
|
34
|
+
flags: {
|
|
35
|
+
runs: flags.runs,
|
|
36
|
+
maxTokens: flags["max-tokens"],
|
|
37
|
+
prompt: flags.prompt,
|
|
38
|
+
json: flags.json,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/commands/usage.ts
CHANGED
|
@@ -15,6 +15,11 @@ export default class Usage extends Command {
|
|
|
15
15
|
description: "Redact account emails/ids (shortest unique prefix) for sharing screenshots",
|
|
16
16
|
default: false,
|
|
17
17
|
}),
|
|
18
|
+
history: Flags.boolean({
|
|
19
|
+
description: "Show recorded usage-limit history (hourly snapshots) instead of a live snapshot",
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
days: Flags.integer({ char: "d", description: "History window in days (with --history)", default: 7 }),
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
static examples = [
|
|
@@ -22,6 +27,7 @@ export default class Usage extends Command {
|
|
|
22
27
|
"# Only Anthropic accounts\n omp usage --provider anthropic",
|
|
23
28
|
"# Redact account identifiers for screenshots\n omp usage --redact",
|
|
24
29
|
"# Machine-readable output\n omp usage --json",
|
|
30
|
+
"# Usage-limit trend over the last 30 days\n omp usage --history --days 30",
|
|
25
31
|
];
|
|
26
32
|
|
|
27
33
|
async run(): Promise<void> {
|
|
@@ -30,6 +36,8 @@ export default class Usage extends Command {
|
|
|
30
36
|
json: flags.json,
|
|
31
37
|
provider: flags.provider,
|
|
32
38
|
redact: flags.redact,
|
|
39
|
+
history: flags.history,
|
|
40
|
+
days: flags.days,
|
|
33
41
|
});
|
|
34
42
|
}
|
|
35
43
|
}
|
|
@@ -20,6 +20,11 @@ import {
|
|
|
20
20
|
UNK_CONTEXT_WINDOW,
|
|
21
21
|
UNK_MAX_TOKENS,
|
|
22
22
|
} from "@oh-my-pi/pi-catalog/provider-models";
|
|
23
|
+
import {
|
|
24
|
+
collapseBuiltModelVariants,
|
|
25
|
+
getVariantAliasSources,
|
|
26
|
+
resolveVariantAlias,
|
|
27
|
+
} from "@oh-my-pi/pi-catalog/variant-collapse";
|
|
23
28
|
|
|
24
29
|
// Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
|
|
25
30
|
// any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
|
|
@@ -542,7 +547,37 @@ function normalizeSuppressedSelector(selector: string): string {
|
|
|
542
547
|
if (!trimmed) return trimmed;
|
|
543
548
|
const parsed = parseModelString(trimmed);
|
|
544
549
|
if (!parsed) return trimmed;
|
|
545
|
-
|
|
550
|
+
// Retired effort-tier variant ids normalize to their collapsed logical id
|
|
551
|
+
// so persisted suppressions keyed by raw member ids still bind.
|
|
552
|
+
const aliasId = resolveVariantAlias(parsed.provider, parsed.id);
|
|
553
|
+
return `${parsed.provider}/${aliasId ?? parsed.id}`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Look up a model's override, falling back to entries keyed by retired
|
|
558
|
+
* effort-tier variant ids (models.yml authored before collapsing). A raw key
|
|
559
|
+
* only re-binds when no live model holds that id.
|
|
560
|
+
*/
|
|
561
|
+
function resolveModelOverrideWithAliases(
|
|
562
|
+
overrides: Map<string, ModelOverride>,
|
|
563
|
+
model: Model<Api>,
|
|
564
|
+
hasLiveModel: (provider: string, id: string) => boolean,
|
|
565
|
+
): ModelOverride | undefined {
|
|
566
|
+
const direct = overrides.get(model.id);
|
|
567
|
+
if (direct) return direct;
|
|
568
|
+
for (const rawId of getVariantAliasSources(model.provider, model.id)) {
|
|
569
|
+
if (hasLiveModel(model.provider, rawId)) continue;
|
|
570
|
+
const remapped = overrides.get(rawId);
|
|
571
|
+
if (remapped) {
|
|
572
|
+
logger.debug("model override re-keyed through variant alias", {
|
|
573
|
+
provider: model.provider,
|
|
574
|
+
from: rawId,
|
|
575
|
+
to: model.id,
|
|
576
|
+
});
|
|
577
|
+
return remapped;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return undefined;
|
|
546
581
|
}
|
|
547
582
|
|
|
548
583
|
function getDisabledProviderIdsFromSettings(): Set<string> {
|
|
@@ -799,7 +834,9 @@ export class ModelRegistry {
|
|
|
799
834
|
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
|
|
800
835
|
// Merge runtime extension models so they survive refresh() cycles
|
|
801
836
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
802
|
-
|
|
837
|
+
// Custom/config providers bypass the model-manager merge point —
|
|
838
|
+
// collapse effort-tier variants here so X/X-thinking twins fold.
|
|
839
|
+
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
803
840
|
this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
|
|
804
841
|
this.#rebuildCanonicalIndex();
|
|
805
842
|
this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
|
|
@@ -1152,7 +1189,7 @@ export class ModelRegistry {
|
|
|
1152
1189
|
const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
|
|
1153
1190
|
// Merge runtime extension models so they survive online discovery completion
|
|
1154
1191
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1155
|
-
const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
1192
|
+
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
1156
1193
|
this.#models = this.#applyRuntimeProviderOverrides(withModelOverrides);
|
|
1157
1194
|
this.#rebuildCanonicalIndex();
|
|
1158
1195
|
}
|
|
@@ -1398,8 +1435,13 @@ export class ModelRegistry {
|
|
|
1398
1435
|
#applyProviderModelOverrides(provider: string, models: Model<Api>[]): Model<Api>[] {
|
|
1399
1436
|
const overrides = this.#modelOverrides.get(provider);
|
|
1400
1437
|
if (!overrides || overrides.size === 0) return models;
|
|
1438
|
+
let liveIds: Set<string> | null = null;
|
|
1439
|
+
const hasLiveModel = (_provider: string, id: string) => {
|
|
1440
|
+
liveIds ??= new Set(models.map(m => m.id));
|
|
1441
|
+
return liveIds.has(id);
|
|
1442
|
+
};
|
|
1401
1443
|
return models.map(model => {
|
|
1402
|
-
const override = overrides
|
|
1444
|
+
const override = resolveModelOverrideWithAliases(overrides, model, hasLiveModel);
|
|
1403
1445
|
if (!override) return model;
|
|
1404
1446
|
return applyModelOverride(model, override);
|
|
1405
1447
|
});
|
|
@@ -1443,10 +1485,15 @@ export class ModelRegistry {
|
|
|
1443
1485
|
}
|
|
1444
1486
|
#applyModelOverrides(models: Model<Api>[], overrides: Map<string, Map<string, ModelOverride>>): Model<Api>[] {
|
|
1445
1487
|
if (overrides.size === 0) return models;
|
|
1488
|
+
let liveKeys: Set<string> | null = null;
|
|
1489
|
+
const hasLiveModel = (provider: string, id: string) => {
|
|
1490
|
+
liveKeys ??= new Set(models.map(m => `${m.provider}\u0000${m.id}`));
|
|
1491
|
+
return liveKeys.has(`${provider}\u0000${id}`);
|
|
1492
|
+
};
|
|
1446
1493
|
return models.map(model => {
|
|
1447
1494
|
const providerOverrides = overrides.get(model.provider);
|
|
1448
1495
|
if (!providerOverrides) return model;
|
|
1449
|
-
const override = providerOverrides
|
|
1496
|
+
const override = resolveModelOverrideWithAliases(providerOverrides, model, hasLiveModel);
|
|
1450
1497
|
if (!override) return model;
|
|
1451
1498
|
return applyModelOverride(model, override);
|
|
1452
1499
|
});
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Layering:
|
|
5
5
|
* - `matchModel` is the single matching engine. Order: exact `provider/id`
|
|
6
|
-
* reference (with OpenRouter routed/date fallbacks) →
|
|
7
|
-
* exact
|
|
6
|
+
* reference (with variant-alias and OpenRouter routed/date fallbacks) →
|
|
7
|
+
* exact canonical id → exact bare id → retired variant alias →
|
|
8
|
+
* provider-scoped fuzzy → substring with alias-vs-dated pick.
|
|
8
9
|
* - `parseModelPatternWithContext`/`parseModelPattern` layer the selector
|
|
9
10
|
* grammar on top: trailing `:level` thinking suffixes (`splitThinkingSuffix`)
|
|
10
11
|
* and `@upstream` provider routing (`splitUpstreamRouting`).
|
|
@@ -19,9 +20,11 @@ import type { Api, Effort, KnownProvider, Model, ModelSpec } from "@oh-my-pi/pi-
|
|
|
19
20
|
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
20
21
|
import { modelMatchesHost } from "@oh-my-pi/pi-catalog/hosts";
|
|
21
22
|
import { buildModelProviderPriorityRank } from "@oh-my-pi/pi-catalog/identity";
|
|
23
|
+
import { stripThinkingVariantToken } from "@oh-my-pi/pi-catalog/identity/family";
|
|
22
24
|
import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
23
25
|
import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
24
26
|
import { DEFAULT_MODEL_PER_PROVIDER } from "@oh-my-pi/pi-catalog/provider-models";
|
|
27
|
+
import { resolveBareVariantAlias, resolveVariantAlias } from "@oh-my-pi/pi-catalog/variant-collapse";
|
|
25
28
|
import { fuzzyMatch } from "@oh-my-pi/pi-tui";
|
|
26
29
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
27
30
|
import chalk from "chalk";
|
|
@@ -228,6 +231,18 @@ export function resolveProviderModelReference(
|
|
|
228
231
|
return exact;
|
|
229
232
|
}
|
|
230
233
|
|
|
234
|
+
// Retired effort-tier variant ids resolve to their collapsed logical
|
|
235
|
+
// model: hand-table aliases first, then the `X-thinking` → `X` grammar
|
|
236
|
+
// for auto-derived pairs. Exact lookup above always wins while raw is live.
|
|
237
|
+
const variantAliasId =
|
|
238
|
+
resolveVariantAlias(normalizedProvider, normalizedModelId) ?? stripThinkingVariantToken(normalizedModelId);
|
|
239
|
+
if (variantAliasId) {
|
|
240
|
+
const aliased = index.get(`${normalizedProvider}\u0000${variantAliasId.toLowerCase()}`);
|
|
241
|
+
if (aliased) {
|
|
242
|
+
return aliased;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
231
246
|
if (normalizedProvider !== "openrouter") {
|
|
232
247
|
return undefined;
|
|
233
248
|
}
|
|
@@ -407,11 +422,13 @@ function findExactCanonicalModelMatch(
|
|
|
407
422
|
|
|
408
423
|
/**
|
|
409
424
|
* The single model-matching engine. Tries, in order:
|
|
410
|
-
* 1. exact `provider/id` reference (OpenRouter routed/date
|
|
425
|
+
* 1. exact `provider/id` reference (variant-alias and OpenRouter routed/date
|
|
426
|
+
* fallbacks included),
|
|
411
427
|
* 2. exact canonical id (coalesces provider variants),
|
|
412
428
|
* 3. exact bare id (preference-ranked),
|
|
413
|
-
* 4.
|
|
414
|
-
* 5.
|
|
429
|
+
* 4. retired effort-tier variant alias (collapsed catalog entries),
|
|
430
|
+
* 5. provider-scoped fuzzy match,
|
|
431
|
+
* 6. substring match with the alias-vs-dated pick.
|
|
415
432
|
* Returns the matched model or undefined if no match found.
|
|
416
433
|
*/
|
|
417
434
|
function matchModel(
|
|
@@ -440,6 +457,20 @@ function matchModel(
|
|
|
440
457
|
if (exactMatches.length > 0) {
|
|
441
458
|
return pickPreferredModel(exactMatches, context);
|
|
442
459
|
}
|
|
460
|
+
|
|
461
|
+
// Retired effort-tier variant ids (bare, no provider prefix) resolve to
|
|
462
|
+
// their collapsed logical model; models from the providers whose table
|
|
463
|
+
// declared the alias win ties. Auto-derived `X-thinking` pairs resolve
|
|
464
|
+
// through the grammar fallback.
|
|
465
|
+
const bareAlias = resolveBareVariantAlias(modelPattern);
|
|
466
|
+
const bareAliasTargetId = bareAlias?.id ?? stripThinkingVariantToken(modelPattern);
|
|
467
|
+
if (bareAliasTargetId) {
|
|
468
|
+
const aliasMatches = availableModels.filter(m => m.id.toLowerCase() === bareAliasTargetId.toLowerCase());
|
|
469
|
+
if (aliasMatches.length > 0) {
|
|
470
|
+
const preferred = bareAlias ? aliasMatches.filter(m => bareAlias.providers.includes(m.provider)) : [];
|
|
471
|
+
return pickPreferredModel(preferred.length > 0 ? preferred : aliasMatches, context);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
443
474
|
// Check for provider/modelId format — fuzzy match within provider only.
|
|
444
475
|
const slashIndex = modelPattern.indexOf("/");
|
|
445
476
|
if (slashIndex !== -1) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { SHAPE_VARIANT_NAMES } from "@oh-my-pi/snapcompact";
|
|
2
3
|
import { AUTO_THINKING, getConfiguredThinkingLevelMetadata, getThinkingLevelMetadata } from "../thinking";
|
|
3
4
|
import {
|
|
4
5
|
TINY_MODEL_DEVICE_DEFAULT,
|
|
@@ -1572,14 +1573,28 @@ export const SETTINGS_SCHEMA = {
|
|
|
1572
1573
|
|
|
1573
1574
|
// Experimental: snapcompact inline imaging (transient, per-request; never persisted)
|
|
1574
1575
|
"snapcompact.systemPrompt": {
|
|
1575
|
-
type: "
|
|
1576
|
-
|
|
1576
|
+
type: "enum",
|
|
1577
|
+
values: ["none", "agents-md", "all"] as const,
|
|
1578
|
+
default: "none",
|
|
1577
1579
|
ui: {
|
|
1578
1580
|
tab: "context",
|
|
1579
1581
|
group: "Experimental",
|
|
1580
1582
|
label: "Snapcompact System Prompt",
|
|
1581
1583
|
description:
|
|
1582
|
-
"Experimental: render
|
|
1584
|
+
"Experimental: render selected system prompt text as dense PNG image(s) and attach to the first user message (vision models only). Saves tokens; loses prompt caching for imaged text.",
|
|
1585
|
+
options: [
|
|
1586
|
+
{ value: "none", label: "None", description: "Keep the system prompt as text." },
|
|
1587
|
+
{
|
|
1588
|
+
value: "agents-md",
|
|
1589
|
+
label: "AGENTS.md",
|
|
1590
|
+
description: "Only move loaded context-file instructions to images, when that saves tokens.",
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
value: "all",
|
|
1594
|
+
label: "All",
|
|
1595
|
+
description: "Move the full system prompt to images, when that saves tokens.",
|
|
1596
|
+
},
|
|
1597
|
+
],
|
|
1583
1598
|
},
|
|
1584
1599
|
},
|
|
1585
1600
|
|
|
@@ -1595,6 +1610,97 @@ export const SETTINGS_SCHEMA = {
|
|
|
1595
1610
|
},
|
|
1596
1611
|
},
|
|
1597
1612
|
|
|
1613
|
+
"snapcompact.shape": {
|
|
1614
|
+
type: "enum",
|
|
1615
|
+
values: ["auto", ...SHAPE_VARIANT_NAMES] as const,
|
|
1616
|
+
default: "auto",
|
|
1617
|
+
ui: {
|
|
1618
|
+
tab: "context",
|
|
1619
|
+
group: "Experimental",
|
|
1620
|
+
label: "Snapcompact Shape",
|
|
1621
|
+
description:
|
|
1622
|
+
"Frame shape snapcompact prints text with (compaction archive and inline imaging). Auto picks a shape tuned for the current model.",
|
|
1623
|
+
options: [
|
|
1624
|
+
{
|
|
1625
|
+
value: "auto",
|
|
1626
|
+
label: "Auto",
|
|
1627
|
+
description: "Picks a shape tuned for the current model, falling back to its provider family.",
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
value: "8x8r-bw",
|
|
1631
|
+
label: "8x8 repeated, black",
|
|
1632
|
+
description:
|
|
1633
|
+
"unscii square cell, black ink, every line printed twice with the copy on a pale highlight band.",
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
value: "8x8r-sent",
|
|
1637
|
+
label: "8x8 repeated, sentence hues",
|
|
1638
|
+
description: "Repeated grid with ink cycling six hues at sentence boundaries.",
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
value: "8x8u-bw",
|
|
1642
|
+
label: "8x8, black",
|
|
1643
|
+
description: "Plain unscii square cell, single-printed lines, black ink.",
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
value: "8x8u-sent",
|
|
1647
|
+
label: "8x8, sentence hues",
|
|
1648
|
+
description: "Plain unscii square cell with sentence-hue ink.",
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
value: "6x6u-bw",
|
|
1652
|
+
label: "6x6 dense, black",
|
|
1653
|
+
description: "unscii squeezed to 6x6 — densest readable cell, fewest frames — in black ink.",
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
value: "6x6u-sent",
|
|
1657
|
+
label: "6x6 dense, sentence hues",
|
|
1658
|
+
description: "Densest cell with sentence-hue ink.",
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
value: "5x8-bw",
|
|
1662
|
+
label: "5x8 legacy, black",
|
|
1663
|
+
description: "Original X.org 5x8 glyphs on the 2576px frame, black ink.",
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
value: "5x8-sent",
|
|
1667
|
+
label: "5x8 legacy, sentence hues",
|
|
1668
|
+
description: "The original snapcompact shape (pre-shape-table sessions rendered this).",
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
value: "6x12-dim",
|
|
1672
|
+
label: "6x12, dimmed stopwords",
|
|
1673
|
+
description: "X.org 6x12 glyphs, black ink, function words dimmed gray.",
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
value: "8x13-bw",
|
|
1677
|
+
label: "8x13, black",
|
|
1678
|
+
description: "X.org 8x13 glyphs, black ink.",
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
value: "8on16-bw",
|
|
1682
|
+
label: "8x13 on 16px pitch, black",
|
|
1683
|
+
description: "8x13 glyphs on an 8x16 cell (extra leading), black ink.",
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
value: "doc-8on16-bw",
|
|
1687
|
+
label: "Doc 8on16, black",
|
|
1688
|
+
description: "Two word-wrapped newspaper columns of 8x13 glyphs on a 16px pitch, black ink.",
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
value: "doc-8on16-sent",
|
|
1692
|
+
label: "Doc 8on16, sentence hues",
|
|
1693
|
+
description: "Two-column doc layout with sentence-hue ink.",
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
value: "doc-8on16-sent-dim",
|
|
1697
|
+
label: "Doc 8on16, sentence hues + dimmed stopwords",
|
|
1698
|
+
description: "Two-column doc layout, sentence-hue ink, function words dimmed gray.",
|
|
1699
|
+
},
|
|
1700
|
+
],
|
|
1701
|
+
},
|
|
1702
|
+
},
|
|
1703
|
+
|
|
1598
1704
|
// Branch summaries
|
|
1599
1705
|
"branchSummary.enabled": {
|
|
1600
1706
|
type: "boolean",
|
|
@@ -3617,6 +3723,39 @@ export const SETTINGS_SCHEMA = {
|
|
|
3617
3723
|
],
|
|
3618
3724
|
},
|
|
3619
3725
|
},
|
|
3726
|
+
// Codex saved rate-limit resets (auto-redeem)
|
|
3727
|
+
"codexResets.autoRedeem": {
|
|
3728
|
+
type: "boolean",
|
|
3729
|
+
default: false,
|
|
3730
|
+
ui: {
|
|
3731
|
+
tab: "providers",
|
|
3732
|
+
group: "Services",
|
|
3733
|
+
label: "Codex Auto-Redeem Saved Resets",
|
|
3734
|
+
description:
|
|
3735
|
+
"When a turn is blocked by the Codex weekly limit on the active account and no other account is available, automatically spend one saved rate-limit reset (ChatGPT 'save rate limit resets'). Conservative: never fires for 5-hour-only or Spark limits, near a natural reset, or twice for the same block. Requires retries enabled.",
|
|
3736
|
+
},
|
|
3737
|
+
},
|
|
3738
|
+
"codexResets.minBlockedMinutes": {
|
|
3739
|
+
type: "number",
|
|
3740
|
+
default: 60,
|
|
3741
|
+
ui: {
|
|
3742
|
+
tab: "providers",
|
|
3743
|
+
group: "Services",
|
|
3744
|
+
label: "Codex Auto-Redeem Min Block",
|
|
3745
|
+
description:
|
|
3746
|
+
"Only auto-redeem when the natural weekly reset is at least this many minutes away (don't spend a ~30-day credit to save a short wait).",
|
|
3747
|
+
},
|
|
3748
|
+
},
|
|
3749
|
+
"codexResets.keepCredits": {
|
|
3750
|
+
type: "number",
|
|
3751
|
+
default: 0,
|
|
3752
|
+
ui: {
|
|
3753
|
+
tab: "providers",
|
|
3754
|
+
group: "Services",
|
|
3755
|
+
label: "Codex Auto-Redeem Reserve",
|
|
3756
|
+
description: "Never auto-spend below this many saved resets (0 = the last credit may be spent automatically).",
|
|
3757
|
+
},
|
|
3758
|
+
},
|
|
3620
3759
|
"provider.appendOnlyContext": {
|
|
3621
3760
|
type: "enum",
|
|
3622
3761
|
values: ["auto", "on", "off"] as const,
|
|
@@ -4005,6 +4144,11 @@ export interface ShellMinimizerSettings {
|
|
|
4005
4144
|
sourceOutlineLevel: "default" | "aggressive";
|
|
4006
4145
|
legacyFilters: boolean | undefined;
|
|
4007
4146
|
}
|
|
4147
|
+
export interface CodexResetsSettings {
|
|
4148
|
+
autoRedeem: boolean;
|
|
4149
|
+
minBlockedMinutes: number;
|
|
4150
|
+
keepCredits: number;
|
|
4151
|
+
}
|
|
4008
4152
|
|
|
4009
4153
|
/** Map group prefix -> typed settings interface */
|
|
4010
4154
|
export interface GroupTypeMap {
|
|
@@ -4024,6 +4168,7 @@ export interface GroupTypeMap {
|
|
|
4024
4168
|
modelTags: ModelTagsSettings;
|
|
4025
4169
|
cycleOrder: string[];
|
|
4026
4170
|
shellMinimizer: ShellMinimizerSettings;
|
|
4171
|
+
codexResets: CodexResetsSettings;
|
|
4027
4172
|
}
|
|
4028
4173
|
|
|
4029
4174
|
export type GroupPrefix = keyof GroupTypeMap;
|
package/src/config/settings.ts
CHANGED
|
@@ -798,6 +798,15 @@ export class Settings {
|
|
|
798
798
|
raw["compaction.strategy"] = "shake";
|
|
799
799
|
}
|
|
800
800
|
|
|
801
|
+
// snapcompact.systemPrompt: boolean -> scoped enum.
|
|
802
|
+
const snapcompactObj = raw.snapcompact as Record<string, unknown> | undefined;
|
|
803
|
+
if (snapcompactObj && typeof snapcompactObj.systemPrompt === "boolean") {
|
|
804
|
+
snapcompactObj.systemPrompt = snapcompactObj.systemPrompt ? "all" : "none";
|
|
805
|
+
}
|
|
806
|
+
if (typeof raw["snapcompact.systemPrompt"] === "boolean") {
|
|
807
|
+
raw["snapcompact.systemPrompt"] = raw["snapcompact.systemPrompt"] ? "all" : "none";
|
|
808
|
+
}
|
|
809
|
+
|
|
801
810
|
// statusLine: rename "plan_mode" segment to "mode"
|
|
802
811
|
const statusLineObj = raw.statusLine as Record<string, unknown> | undefined;
|
|
803
812
|
if (statusLineObj) {
|
package/src/edit/renderer.ts
CHANGED
|
@@ -579,6 +579,11 @@ function wrapEditRendererLine(line: string, width: number): string[] {
|
|
|
579
579
|
|
|
580
580
|
export const editToolRenderer = {
|
|
581
581
|
mergeCallAndResult: true,
|
|
582
|
+
// Pending preview is a TAIL window of the streamed diff ("… N more lines
|
|
583
|
+
// above" + last rows); the result render re-anchors the block top-first, so
|
|
584
|
+
// committing the preview's settled head would strand a stale call-box
|
|
585
|
+
// fragment in native scrollback.
|
|
586
|
+
provisionalPendingPreview: true,
|
|
582
587
|
|
|
583
588
|
renderCall(
|
|
584
589
|
args: EditRenderArgs,
|
package/src/hindsight/client.ts
CHANGED
|
@@ -546,7 +546,7 @@ interface BuiltMemoryItem {
|
|
|
546
546
|
function buildMemoryItem(item: MemoryItemInput): BuiltMemoryItem {
|
|
547
547
|
const out: BuiltMemoryItem = { content: item.content };
|
|
548
548
|
if (item.timestamp !== undefined) {
|
|
549
|
-
out.timestamp = item.timestamp instanceof Date ? item.timestamp
|
|
549
|
+
out.timestamp = item.timestamp instanceof Date ? formatDateWithLocalOffset(item.timestamp) : item.timestamp;
|
|
550
550
|
}
|
|
551
551
|
if (item.context !== undefined) out.context = item.context;
|
|
552
552
|
if (item.metadata !== undefined) out.metadata = item.metadata;
|
|
@@ -558,6 +558,31 @@ function buildMemoryItem(item: MemoryItemInput): BuiltMemoryItem {
|
|
|
558
558
|
return out;
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
function formatDateWithLocalOffset(date: Date): string {
|
|
562
|
+
const offsetMinutes = date.getTimezoneOffset();
|
|
563
|
+
const offsetSign = offsetMinutes <= 0 ? "+" : "-";
|
|
564
|
+
const absoluteOffset = Math.abs(offsetMinutes);
|
|
565
|
+
const offsetHours = Math.floor(absoluteOffset / 60);
|
|
566
|
+
const offsetRemainderMinutes = absoluteOffset % 60;
|
|
567
|
+
const milliseconds = date.getMilliseconds();
|
|
568
|
+
const millisecondsPart = milliseconds === 0 ? "" : `.${pad3(milliseconds)}`;
|
|
569
|
+
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}T${pad2(
|
|
570
|
+
date.getHours(),
|
|
571
|
+
)}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}${millisecondsPart}${offsetSign}${pad2(
|
|
572
|
+
offsetHours,
|
|
573
|
+
)}:${pad2(offsetRemainderMinutes)}`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function pad2(value: number): string {
|
|
577
|
+
return value < 10 ? `0${value}` : String(value);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function pad3(value: number): string {
|
|
581
|
+
if (value < 10) return `00${value}`;
|
|
582
|
+
if (value < 100) return `0${value}`;
|
|
583
|
+
return String(value);
|
|
584
|
+
}
|
|
585
|
+
|
|
561
586
|
function buildQueryString(query: Record<string, unknown>): string {
|
|
562
587
|
const params = new URLSearchParams();
|
|
563
588
|
for (const [key, value] of Object.entries(query)) {
|
package/src/hindsight/state.ts
CHANGED
|
@@ -26,6 +26,7 @@ const RETAIN_FLUSH_INTERVAL_MS = 5_000;
|
|
|
26
26
|
interface PendingRetainItem {
|
|
27
27
|
content: string;
|
|
28
28
|
context?: string;
|
|
29
|
+
timestamp: Date;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
interface RecallOutcome {
|
|
@@ -84,7 +85,7 @@ export class HindsightRetainQueue {
|
|
|
84
85
|
if (this.#closed) {
|
|
85
86
|
throw new Error("Hindsight retain queue is closed.");
|
|
86
87
|
}
|
|
87
|
-
this.#items.push({ content, context });
|
|
88
|
+
this.#items.push({ content, context, timestamp: new Date() });
|
|
88
89
|
|
|
89
90
|
if (this.#items.length >= RETAIN_FLUSH_BATCH_SIZE) {
|
|
90
91
|
void this.flush();
|
|
@@ -154,6 +155,7 @@ export class HindsightRetainQueue {
|
|
|
154
155
|
context: item.context ?? state.config.retainContext,
|
|
155
156
|
metadata: { session_id: sessionId },
|
|
156
157
|
tags: state.retainTags,
|
|
158
|
+
timestamp: item.timestamp,
|
|
157
159
|
}));
|
|
158
160
|
await state.client.retainBatch(state.bankId, batch, { async: true });
|
|
159
161
|
if (state.config.debug) {
|
|
@@ -281,6 +283,7 @@ export class HindsightSessionState {
|
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
async retainSession(messages: HindsightMessage[]): Promise<void> {
|
|
286
|
+
const retainedAt = new Date();
|
|
284
287
|
const retainFullWindow = this.config.retainMode === "full-session";
|
|
285
288
|
let target: HindsightMessage[];
|
|
286
289
|
let documentId: string;
|
|
@@ -291,7 +294,7 @@ export class HindsightSessionState {
|
|
|
291
294
|
} else {
|
|
292
295
|
const windowTurns = this.config.retainEveryNTurns + this.config.retainOverlapTurns;
|
|
293
296
|
target = sliceLastTurnsByUserBoundary(messages, windowTurns);
|
|
294
|
-
documentId = `${this.sessionId}-${
|
|
297
|
+
documentId = `${this.sessionId}-${retainedAt.getTime()}`;
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
const { transcript } = prepareRetentionTranscript(target, true);
|
|
@@ -303,6 +306,7 @@ export class HindsightSessionState {
|
|
|
303
306
|
context: this.config.retainContext,
|
|
304
307
|
metadata: { session_id: this.sessionId },
|
|
305
308
|
tags: this.retainTags,
|
|
309
|
+
timestamp: retainedAt,
|
|
306
310
|
async: true,
|
|
307
311
|
});
|
|
308
312
|
}
|