@compilr-dev/cli 0.8.7 → 0.8.9
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/dist/agent.js +7 -3
- package/dist/commands-v2/handlers/settings.js +22 -3
- package/dist/commands-v2/types.d.ts +10 -0
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +68 -1
- package/dist/models/model-registry.js +1 -0
- package/dist/models/providers.js +1 -0
- package/dist/repl-v2.d.ts +19 -1
- package/dist/repl-v2.js +37 -0
- package/dist/settings/index.d.ts +1 -1
- package/dist/ui/overlay/impl/model-overlay-v2.js +33 -20
- package/dist/ui/overlay-manager.js +10 -2
- package/dist/utils/credentials.d.ts +1 -1
- package/dist/utils/credentials.js +1 -0
- package/dist/utils/model-tiers.js +14 -9
- package/package.json +3 -3
- package/dist/episodes/recorder.d.ts +0 -51
- package/dist/episodes/recorder.js +0 -195
- package/dist/episodes/significant-work.d.ts +0 -21
- package/dist/episodes/significant-work.js +0 -56
- package/dist/episodes/store.d.ts +0 -38
- package/dist/episodes/store.js +0 -199
- package/dist/episodes/types.d.ts +0 -35
- package/dist/episodes/types.js +0 -6
- package/dist/episodes/work-at-risk.d.ts +0 -12
- package/dist/episodes/work-at-risk.js +0 -38
- package/dist/episodes/work-summary-anchor.d.ts +0 -23
- package/dist/episodes/work-summary-anchor.js +0 -73
- package/dist/tools/consult.d.ts +0 -14
- package/dist/tools/consult.js +0 -73
- package/dist/tools/delegate-background.d.ts +0 -27
- package/dist/tools/delegate-background.js +0 -115
- package/dist/tools/delegate.d.ts +0 -28
- package/dist/tools/delegate.js +0 -113
- package/dist/tools/handoff.d.ts +0 -17
- package/dist/tools/handoff.js +0 -79
- package/dist/tools/meta-tools.d.ts +0 -31
- package/dist/tools/meta-tools.js +0 -78
- package/dist/ui/overlay/impl/skill-editor-overlay.d.ts +0 -56
- package/dist/ui/overlay/impl/skill-editor-overlay.js +0 -493
package/dist/agent.js
CHANGED
|
@@ -157,15 +157,19 @@ function resolveProviderConfig(options) {
|
|
|
157
157
|
`Run /keys to set it, or: export ${meta.envVar}=...` +
|
|
158
158
|
(providerType === 'claude' ? '\nOr use --provider ollama for local models.' : ''));
|
|
159
159
|
}
|
|
160
|
-
|
|
161
|
-
const
|
|
160
|
+
// Both Ollama variants hit the same local server; only the endpoint differs.
|
|
161
|
+
const isOllama = providerType === 'ollama' || providerType === 'ollama-anthropic';
|
|
162
|
+
// ollama-anthropic has no registry rows — default to classic ollama's balanced local model.
|
|
163
|
+
const tierSource = providerType === 'ollama-anthropic' ? 'ollama' : providerType;
|
|
164
|
+
const model = options.model ?? getDefaultModelForTier(tierSource, 'balanced')?.id;
|
|
165
|
+
const baseUrl = isOllama
|
|
162
166
|
? ((typeof getSetting('ollamaBaseUrl') === 'string'
|
|
163
167
|
? getSetting('ollamaBaseUrl')
|
|
164
168
|
: undefined) ?? process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434')
|
|
165
169
|
: undefined;
|
|
166
170
|
if (!options.quiet) {
|
|
167
171
|
log.info({ component: 'agent', provider: meta.displayName, model: model ?? 'default' }, 'Provider configured');
|
|
168
|
-
if (
|
|
172
|
+
if (isOllama && baseUrl !== 'http://localhost:11434') {
|
|
169
173
|
log.info({ component: 'agent', baseUrl }, 'Ollama server');
|
|
170
174
|
}
|
|
171
175
|
}
|
|
@@ -146,11 +146,30 @@ export const modelCommand = {
|
|
|
146
146
|
currentProvider: currentProvider === 'auto' ? undefined : currentProvider,
|
|
147
147
|
});
|
|
148
148
|
await ctx.ui.showOverlay(modelOverlay);
|
|
149
|
-
// After overlay closes,
|
|
150
|
-
//
|
|
149
|
+
// After overlay closes, apply the selection (the overlay writes both
|
|
150
|
+
// defaultModel and defaultProvider to settings).
|
|
151
151
|
if (agent) {
|
|
152
152
|
const newModel = getSetting('defaultModel');
|
|
153
|
-
|
|
153
|
+
const newProvider = getSetting('defaultProvider');
|
|
154
|
+
const providerChanged = !!newProvider && !!ctx.provider && newProvider !== ctx.provider;
|
|
155
|
+
if (providerChanged) {
|
|
156
|
+
// setModel() only swaps the model string on the existing provider
|
|
157
|
+
// client — it can't cross providers. Rebuild the agent on the new
|
|
158
|
+
// provider (history preserved) instead.
|
|
159
|
+
if (ctx.rebuildActiveAgent) {
|
|
160
|
+
const res = await ctx.rebuildActiveAgent(newProvider, newModel ?? '');
|
|
161
|
+
if (res.ok) {
|
|
162
|
+
ctx.ui.print({ type: 'success', message: `Switched to ${newProvider}${newModel ? ` · ${newModel}` : ''}` });
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
ctx.ui.print({ type: 'error', message: res.error ?? 'Failed to switch provider' });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
ctx.ui.print({ type: 'error', message: 'Provider switch takes effect on the next restart in this mode.' });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (newModel && newModel !== agentModel) {
|
|
154
173
|
try {
|
|
155
174
|
agent.setModel(newModel);
|
|
156
175
|
const agentId = ctx.team ? ctx.team.getActive().id : 'default';
|
|
@@ -52,6 +52,16 @@ export interface CommandContextV2 {
|
|
|
52
52
|
* Used by /resume command to restore from a saved session.
|
|
53
53
|
*/
|
|
54
54
|
restoreAgent?: (agent: Agent) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Rebuild the active agent on a new provider (+ model), preserving history.
|
|
57
|
+
* Used by /model when the provider changes: setModel() only swaps the model
|
|
58
|
+
* string on the existing provider client, so it can't cross providers.
|
|
59
|
+
* Resolves { ok: false, error } if the target provider has no API key.
|
|
60
|
+
*/
|
|
61
|
+
rebuildActiveAgent?: (provider: string, model: string) => Promise<{
|
|
62
|
+
ok: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
}>;
|
|
55
65
|
/** Current LLM model ID */
|
|
56
66
|
model?: string;
|
|
57
67
|
/** Current LLM provider (claude, openai, gemini, ollama) */
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -595,6 +595,63 @@ async function main() {
|
|
|
595
595
|
sharedState.grantSession = (toolName) => {
|
|
596
596
|
defaultAgentRef?.grantSessionPermission(toolName);
|
|
597
597
|
};
|
|
598
|
+
// Build a full-fidelity default agent for a given provider+model. Mirrors the
|
|
599
|
+
// initial createAgent() above (incl. plan-mode callbacks) so a hot-switched
|
|
600
|
+
// agent keeps every capability. Used by the REPL's rebuildActiveAgent() when
|
|
601
|
+
// /model changes the provider — setModel() can't cross providers. Throws if
|
|
602
|
+
// the target provider has no API key (surfaced to the user by the REPL).
|
|
603
|
+
const buildDefaultAgent = (agentProvider, agentModel) => {
|
|
604
|
+
const built = createAgent({
|
|
605
|
+
provider: agentProvider,
|
|
606
|
+
model: agentModel,
|
|
607
|
+
minimal: options.minimal,
|
|
608
|
+
quiet: true,
|
|
609
|
+
enableMetaTools: !options.minimal && getProviderMetadata(agentProvider).supportsMetaTools,
|
|
610
|
+
onPermissionRequest,
|
|
611
|
+
onIterationLimitReached,
|
|
612
|
+
onSuggest: (event) => {
|
|
613
|
+
sharedState.pendingSuggestion = event.action;
|
|
614
|
+
},
|
|
615
|
+
projectName: liveProjectState.dbProject?.displayName,
|
|
616
|
+
projectSlug: liveProjectState.dbProject?.name,
|
|
617
|
+
projectId: liveProjectState.dbProject?.id,
|
|
618
|
+
projectContext: liveProjectState.projectContext,
|
|
619
|
+
guidedModeContext: liveProjectState.guidedModeContext,
|
|
620
|
+
enableAnchors: true,
|
|
621
|
+
persistedAnchors: persistedAnchors.length > 0 ? persistedAnchors : undefined,
|
|
622
|
+
enableGuardrails: true,
|
|
623
|
+
onGuardrailTriggered,
|
|
624
|
+
onSubagentStart: (toolUseId, agentType, description) => {
|
|
625
|
+
sharedState.onSubagentStart?.(toolUseId, agentType, description);
|
|
626
|
+
},
|
|
627
|
+
onSubagentEnd: (toolUseId, success, tokenCount, error) => {
|
|
628
|
+
sharedState.onSubagentEnd?.(toolUseId, success, tokenCount, error);
|
|
629
|
+
},
|
|
630
|
+
onSubagentToolUse: (toolUseId, toolName, summary) => {
|
|
631
|
+
sharedState.onSubagentToolUse?.(toolUseId, toolName, summary);
|
|
632
|
+
},
|
|
633
|
+
onSubagentTrackingReady: (clearTracking) => {
|
|
634
|
+
sharedState.clearSubagentTracking = clearTracking;
|
|
635
|
+
},
|
|
636
|
+
episodeRecorder: defaultEpisodeRecorder,
|
|
637
|
+
planModeCallbacks: {
|
|
638
|
+
onPlanSubmit: async (info) => {
|
|
639
|
+
if (sharedState.onPlanSubmit)
|
|
640
|
+
return sharedState.onPlanSubmit(info);
|
|
641
|
+
return { action: 'reject' };
|
|
642
|
+
},
|
|
643
|
+
onPlanModeExit: async (info) => {
|
|
644
|
+
if (sharedState.onPlanModeExit)
|
|
645
|
+
await sharedState.onPlanModeExit(info);
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
defaultAgentRef = built;
|
|
650
|
+
sharedState.grantSession = (toolName) => {
|
|
651
|
+
defaultAgentRef?.grantSessionPermission(toolName);
|
|
652
|
+
};
|
|
653
|
+
return built;
|
|
654
|
+
};
|
|
598
655
|
// ==========================================================================
|
|
599
656
|
// Create AgentTeam with the default agent (or restore from persistence)
|
|
600
657
|
// ==========================================================================
|
|
@@ -828,6 +885,16 @@ async function main() {
|
|
|
828
885
|
onDefaultAgentSwapped: (newAgent) => {
|
|
829
886
|
defaultAgentRef = newAgent;
|
|
830
887
|
},
|
|
888
|
+
// Rebuild the default agent on a new provider (+ model) for /model's
|
|
889
|
+
// provider hot-switch. Throws if the provider has no API key. Also
|
|
890
|
+
// updates the outer provider/model so later team-agent creation and
|
|
891
|
+
// status reflect the switch.
|
|
892
|
+
onRebuildAgent: (rebuildProvider, rebuildModel) => {
|
|
893
|
+
const built = buildDefaultAgent(rebuildProvider, rebuildModel);
|
|
894
|
+
provider = rebuildProvider;
|
|
895
|
+
model = rebuildModel;
|
|
896
|
+
return Promise.resolve(built);
|
|
897
|
+
},
|
|
831
898
|
// Set session prefix for VS Code diff IPC after session registration
|
|
832
899
|
onSessionRegistered: (sessionId) => {
|
|
833
900
|
sharedState.sessionPrefix = sessionId.slice(0, 8);
|
|
@@ -968,7 +1035,7 @@ function getDefaultModel(provider) {
|
|
|
968
1035
|
return model.id;
|
|
969
1036
|
// Fallback to any supported model for this provider
|
|
970
1037
|
const anyModel = getDefaultModelForTier(provider, 'fast') ?? getDefaultModelForTier(provider, 'powerful');
|
|
971
|
-
return anyModel?.id ?? 'claude-sonnet-
|
|
1038
|
+
return anyModel?.id ?? 'claude-sonnet-5';
|
|
972
1039
|
}
|
|
973
1040
|
// =============================================================================
|
|
974
1041
|
// Run
|
package/dist/models/providers.js
CHANGED
package/dist/repl-v2.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export interface ReplV2Options {
|
|
|
34
34
|
team?: AgentTeam;
|
|
35
35
|
/** Agent factory function for creating new agents (for team switching) */
|
|
36
36
|
agentFactory?: (config: Record<string, unknown>, systemPromptAddition?: string, useMinimalSystemPrompt?: boolean, noTools?: boolean) => Promise<Agent>;
|
|
37
|
+
/**
|
|
38
|
+
* Rebuild the default agent on a new provider (+ model), full fidelity.
|
|
39
|
+
* Implemented by index.ts (which holds the full createAgent context).
|
|
40
|
+
* Throws if the provider has no API key. Used by rebuildActiveAgent() to
|
|
41
|
+
* hot-switch providers without a restart.
|
|
42
|
+
*/
|
|
43
|
+
onRebuildAgent?: (provider: string, model: string) => Promise<Agent>;
|
|
37
44
|
/** Current model ID */
|
|
38
45
|
model?: string;
|
|
39
46
|
/** Current provider */
|
|
@@ -235,8 +242,9 @@ export declare class ReplV2 {
|
|
|
235
242
|
private agent?;
|
|
236
243
|
private team?;
|
|
237
244
|
private readonly agentFactory?;
|
|
245
|
+
private readonly onRebuildAgent?;
|
|
238
246
|
private readonly model;
|
|
239
|
-
private
|
|
247
|
+
private provider;
|
|
240
248
|
private readonly version;
|
|
241
249
|
private readonly updateAvailable;
|
|
242
250
|
private readonly projectName;
|
|
@@ -461,6 +469,16 @@ export declare class ReplV2 {
|
|
|
461
469
|
* Called by /resume command to swap in a restored agent.
|
|
462
470
|
*/
|
|
463
471
|
restoreAgent(newAgent: Agent): void;
|
|
472
|
+
/**
|
|
473
|
+
* Hot-switch the active agent to a new provider (+ model), preserving the
|
|
474
|
+
* conversation history. setModel() only swaps the model string on the
|
|
475
|
+
* existing provider client, so a provider change needs a full rebuild.
|
|
476
|
+
* Returns { ok: false, error } if rebuild fails (e.g. no API key).
|
|
477
|
+
*/
|
|
478
|
+
rebuildActiveAgent(provider: string, model: string): Promise<{
|
|
479
|
+
ok: boolean;
|
|
480
|
+
error?: string;
|
|
481
|
+
}>;
|
|
464
482
|
/**
|
|
465
483
|
* Get the current agent instance.
|
|
466
484
|
* Used by commands that need to serialize/checkpoint the agent state.
|
package/dist/repl-v2.js
CHANGED
|
@@ -171,6 +171,7 @@ export class ReplV2 {
|
|
|
171
171
|
agent;
|
|
172
172
|
team;
|
|
173
173
|
agentFactory;
|
|
174
|
+
onRebuildAgent;
|
|
174
175
|
model;
|
|
175
176
|
provider;
|
|
176
177
|
version;
|
|
@@ -235,6 +236,7 @@ export class ReplV2 {
|
|
|
235
236
|
this.agent = options.agent;
|
|
236
237
|
this.team = options.team;
|
|
237
238
|
this.agentFactory = options.agentFactory;
|
|
239
|
+
this.onRebuildAgent = options.onRebuildAgent;
|
|
238
240
|
this.model = options.model ?? 'simulation';
|
|
239
241
|
this.provider = options.provider ?? 'simulation';
|
|
240
242
|
this.version = options.version ?? STANDALONE_VERSION;
|
|
@@ -933,6 +935,38 @@ export class ReplV2 {
|
|
|
933
935
|
this.agent = newAgent;
|
|
934
936
|
this.onDefaultAgentSwapped?.(newAgent);
|
|
935
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Hot-switch the active agent to a new provider (+ model), preserving the
|
|
940
|
+
* conversation history. setModel() only swaps the model string on the
|
|
941
|
+
* existing provider client, so a provider change needs a full rebuild.
|
|
942
|
+
* Returns { ok: false, error } if rebuild fails (e.g. no API key).
|
|
943
|
+
*/
|
|
944
|
+
async rebuildActiveAgent(provider, model) {
|
|
945
|
+
if (!this.onRebuildAgent) {
|
|
946
|
+
return { ok: false, error: 'Provider switching is not available in this mode.' };
|
|
947
|
+
}
|
|
948
|
+
let fresh;
|
|
949
|
+
try {
|
|
950
|
+
fresh = await this.onRebuildAgent(provider, model);
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
954
|
+
}
|
|
955
|
+
// Carry the conversation history across the provider swap.
|
|
956
|
+
if (this.agent) {
|
|
957
|
+
try {
|
|
958
|
+
const history = this.agent.getHistory();
|
|
959
|
+
if (history.length > 0)
|
|
960
|
+
await fresh.setHistory(history);
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
// Best-effort — a fresh agent on the right provider still beats a broken one.
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
this.restoreAgent(fresh);
|
|
967
|
+
this.provider = provider;
|
|
968
|
+
return { ok: true };
|
|
969
|
+
}
|
|
936
970
|
/**
|
|
937
971
|
* Get the current agent instance.
|
|
938
972
|
* Used by commands that need to serialize/checkpoint the agent state.
|
|
@@ -2000,6 +2034,9 @@ export class ReplV2 {
|
|
|
2000
2034
|
restoreAgent: (newAgent) => {
|
|
2001
2035
|
this.restoreAgent(newAgent);
|
|
2002
2036
|
},
|
|
2037
|
+
rebuildActiveAgent: (provider, model) => {
|
|
2038
|
+
return this.rebuildActiveAgent(provider, model);
|
|
2039
|
+
},
|
|
2003
2040
|
model: this.model,
|
|
2004
2041
|
provider: this.provider,
|
|
2005
2042
|
getContextManager: () => {
|
package/dist/settings/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export type { PermissionRule, PermissionMode, PermissionLevel } from '@compilr-dev/sdk';
|
|
8
8
|
import type { PermissionRule, PermissionMode, PermissionLevel } from '@compilr-dev/sdk';
|
|
9
9
|
export type NotificationMode = 'auto' | 'bell' | 'disabled';
|
|
10
|
-
export type ProviderType = 'auto' | 'claude' | 'openai' | 'gemini' | 'ollama' | 'together' | 'groq' | 'fireworks' | 'perplexity' | 'openrouter' | 'custom';
|
|
10
|
+
export type ProviderType = 'auto' | 'claude' | 'openai' | 'gemini' | 'ollama' | 'ollama-anthropic' | 'together' | 'groq' | 'fireworks' | 'perplexity' | 'openrouter' | 'custom';
|
|
11
11
|
export type StartupMode = 'menu' | 'repl';
|
|
12
12
|
export type ProjectStartupMode = 'last' | 'off';
|
|
13
13
|
export type MascotSetting = 'none' | 'random' | 'neutral' | 'thinking' | 'looking_left' | 'looking_right' | 'sleeping' | 'alert' | 'error' | 'success' | 'success_minimal' | 'searching' | 'skeptical';
|
|
@@ -30,14 +30,25 @@ const TIER_WIDTH = 14;
|
|
|
30
30
|
const LABEL_WIDTH = 14;
|
|
31
31
|
const MODEL_NAME_WIDTH = 28;
|
|
32
32
|
/** Primary providers that show tier-based selection */
|
|
33
|
-
const PRIMARY_PROVIDERS = ['claude', 'openai', 'gemini', 'ollama'];
|
|
33
|
+
const PRIMARY_PROVIDERS = ['claude', 'openai', 'gemini', 'ollama', 'ollama-anthropic'];
|
|
34
34
|
const PROVIDER_TABS = [
|
|
35
35
|
{ id: 'claude', label: 'Claude' },
|
|
36
36
|
{ id: 'openai', label: 'OpenAI' },
|
|
37
37
|
{ id: 'gemini', label: 'Gemini' },
|
|
38
38
|
{ id: 'ollama', label: 'Ollama (local)' },
|
|
39
|
+
{ id: 'ollama-anthropic', label: 'Ollama (Claude API)' },
|
|
39
40
|
{ id: 'others', label: 'Others' },
|
|
40
41
|
];
|
|
42
|
+
/** Both Ollama providers share local-model behavior + installed-model discovery
|
|
43
|
+
* (same server/port); only the API endpoint differs. */
|
|
44
|
+
function isOllamaProvider(p) {
|
|
45
|
+
return p === 'ollama' || p === 'ollama-anthropic';
|
|
46
|
+
}
|
|
47
|
+
/** `ollama-anthropic` has no registry rows of its own — it borrows the classic
|
|
48
|
+
* `ollama` local-model tiers (same local models, different endpoint). */
|
|
49
|
+
function registryProviderFor(p) {
|
|
50
|
+
return p === 'ollama-anthropic' ? 'ollama' : p;
|
|
51
|
+
}
|
|
41
52
|
/** All model tiers in display order */
|
|
42
53
|
const ALL_TIERS = [...MODEL_TIERS];
|
|
43
54
|
/**
|
|
@@ -47,11 +58,13 @@ function getListItems() {
|
|
|
47
58
|
const items = [];
|
|
48
59
|
// For each primary provider, add 3 tier items
|
|
49
60
|
for (const provider of PRIMARY_PROVIDERS) {
|
|
61
|
+
// ollama-anthropic has no registry rows — resolve tiers from classic ollama.
|
|
62
|
+
const tierSource = registryProviderFor(provider);
|
|
50
63
|
for (const tier of ALL_TIERS) {
|
|
51
64
|
// Get default model from registry
|
|
52
|
-
const defaultModel = getDefaultModelForTier(
|
|
65
|
+
const defaultModel = getDefaultModelForTier(tierSource, tier);
|
|
53
66
|
// Get current model (may have user override)
|
|
54
|
-
const currentModelId = getModelForTier(
|
|
67
|
+
const currentModelId = getModelForTier(tierSource, tier);
|
|
55
68
|
const currentModelInfo = getModelInfo(currentModelId);
|
|
56
69
|
items.push({
|
|
57
70
|
type: 'tier',
|
|
@@ -446,7 +459,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
446
459
|
this.state = {
|
|
447
460
|
editMode: false,
|
|
448
461
|
// For Ollama, start with URL field; otherwise just model
|
|
449
|
-
selectedField: tierItem.provider
|
|
462
|
+
selectedField: isOllamaProvider(tierItem.provider) ? 'url' : 'model',
|
|
450
463
|
editValue: '',
|
|
451
464
|
cursorPos: 0,
|
|
452
465
|
testing: false,
|
|
@@ -462,7 +475,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
462
475
|
*/
|
|
463
476
|
buildAvailableModels() {
|
|
464
477
|
const models = [];
|
|
465
|
-
const registryModels = getModelsForProvider(this.tierItem.provider);
|
|
478
|
+
const registryModels = getModelsForProvider(registryProviderFor(this.tierItem.provider));
|
|
466
479
|
// Add registry models (supported ones)
|
|
467
480
|
for (const model of registryModels) {
|
|
468
481
|
if (model.status !== 'supported')
|
|
@@ -470,7 +483,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
470
483
|
const isDefault = model.id === this.tierItem.defaultModelId;
|
|
471
484
|
// For Ollama: check if locally available
|
|
472
485
|
let isLocal;
|
|
473
|
-
if (this.tierItem.provider
|
|
486
|
+
if (isOllamaProvider(this.tierItem.provider)) {
|
|
474
487
|
const modelName = model.id.toLowerCase();
|
|
475
488
|
const baseName = modelName.split(':')[0];
|
|
476
489
|
isLocal = this.ollamaState.availableModels.has(modelName) ||
|
|
@@ -484,7 +497,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
484
497
|
});
|
|
485
498
|
}
|
|
486
499
|
// For Ollama: add any local models not in registry
|
|
487
|
-
if (this.tierItem.provider
|
|
500
|
+
if (isOllamaProvider(this.tierItem.provider)) {
|
|
488
501
|
const registryIds = new Set(models.map(m => m.id.toLowerCase()));
|
|
489
502
|
for (const localModel of this.ollamaState.availableModels) {
|
|
490
503
|
// Skip base names (we only want full model:tag names)
|
|
@@ -574,7 +587,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
574
587
|
lines.push(` ${s.muted('Tier:'.padEnd(LABEL_WIDTH))}${tierInfo.label} ${s.muted(`- ${tierInfo.shortDescription}`)}`);
|
|
575
588
|
lines.push('');
|
|
576
589
|
// API Key Status (not shown for Ollama - no auth needed)
|
|
577
|
-
if (this.tierItem.provider
|
|
590
|
+
if (!isOllamaProvider(this.tierItem.provider)) {
|
|
578
591
|
const keyStatus = checkApiKeyStatus(this.tierItem.provider);
|
|
579
592
|
const keyIndicator = keyStatus.hasKey ? s.success('● Set') : s.error('○ Not set');
|
|
580
593
|
const keyHint = !keyStatus.hasKey && keyStatus.hint ? s.warning(` (${keyStatus.hint})`) : '';
|
|
@@ -582,7 +595,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
582
595
|
lines.push('');
|
|
583
596
|
}
|
|
584
597
|
// Ollama Server URL (only for Ollama provider)
|
|
585
|
-
if (this.tierItem.provider
|
|
598
|
+
if (isOllamaProvider(this.tierItem.provider)) {
|
|
586
599
|
const isUrlSelected = this.state.editMode && this.state.selectedField === 'url';
|
|
587
600
|
const sectionHeader = isUrlSelected ? s.primary('─── Server URL ───') : s.muted('─── Server URL ───');
|
|
588
601
|
lines.push(` ${sectionHeader}`);
|
|
@@ -648,7 +661,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
648
661
|
const displayName = currentModelInfo?.displayName ?? this.currentModelId;
|
|
649
662
|
// For Ollama: show local availability
|
|
650
663
|
let localBadge = '';
|
|
651
|
-
if (this.tierItem.provider
|
|
664
|
+
if (isOllamaProvider(this.tierItem.provider) && currentModelInfo) {
|
|
652
665
|
localBadge = currentModelInfo.isLocal ? s.success(' ●') : s.warning(' ○');
|
|
653
666
|
}
|
|
654
667
|
lines.push(` ${s.muted('Current:'.padEnd(LABEL_WIDTH))}${currentStyle(displayName)}${localBadge}${cycleIndicator}`);
|
|
@@ -676,7 +689,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
676
689
|
lines.push(` ${s.warning('Testing model...')}`);
|
|
677
690
|
}
|
|
678
691
|
else if (this.state.editMode) {
|
|
679
|
-
const arrowHint = this.tierItem.provider
|
|
692
|
+
const arrowHint = isOllamaProvider(this.tierItem.provider) ? '↑↓ Switch field · ' : '';
|
|
680
693
|
lines.push(` ${s.muted(`${arrowHint}Enter Save · Esc Cancel · Clear = Reset to default`)}`);
|
|
681
694
|
}
|
|
682
695
|
else {
|
|
@@ -737,7 +750,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
737
750
|
if (char === 'e') {
|
|
738
751
|
this.state.editMode = true;
|
|
739
752
|
// For Ollama, start with URL field; otherwise just model
|
|
740
|
-
if (this.tierItem.provider
|
|
753
|
+
if (isOllamaProvider(this.tierItem.provider)) {
|
|
741
754
|
this.state.selectedField = 'url';
|
|
742
755
|
this.state.editValue = this.currentOllamaUrl;
|
|
743
756
|
this.state.cursorPos = this.currentOllamaUrl.length;
|
|
@@ -805,7 +818,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
805
818
|
return stay(true);
|
|
806
819
|
}
|
|
807
820
|
// Up/Down arrows - switch between fields (only for Ollama)
|
|
808
|
-
if (this.tierItem.provider
|
|
821
|
+
if (isOllamaProvider(this.tierItem.provider)) {
|
|
809
822
|
// Up arrow
|
|
810
823
|
if (data[2] === 0x41) {
|
|
811
824
|
if (this.state.selectedField === 'model') {
|
|
@@ -915,7 +928,7 @@ class TierDetailScreen extends BaseScreen {
|
|
|
915
928
|
}
|
|
916
929
|
getMinHeight() {
|
|
917
930
|
// Ollama needs more height for the URL section
|
|
918
|
-
return this.tierItem.provider
|
|
931
|
+
return isOllamaProvider(this.tierItem.provider) ? 26 : 22;
|
|
919
932
|
}
|
|
920
933
|
}
|
|
921
934
|
// =============================================================================
|
|
@@ -986,7 +999,7 @@ export class ModelOverlayV2 extends TabbedListOverlayV2 {
|
|
|
986
999
|
const isActive = item.currentModelId === activeModel && item.provider === activeProvider;
|
|
987
1000
|
// For Ollama, show availability status; for others, show API key status
|
|
988
1001
|
let statusIndicator;
|
|
989
|
-
if (item.provider
|
|
1002
|
+
if (isOllamaProvider(item.provider)) {
|
|
990
1003
|
const isLocallyAvailable = this.ollamaState.availableModels.has(item.currentModelId.toLowerCase()) ||
|
|
991
1004
|
this.ollamaState.availableModels.has(item.currentModelId.split(':')[0].toLowerCase());
|
|
992
1005
|
statusIndicator = isLocallyAvailable ? styles.success('●') : styles.warning('○');
|
|
@@ -1014,8 +1027,8 @@ export class ModelOverlayV2 extends TabbedListOverlayV2 {
|
|
|
1014
1027
|
if (currentTabId === 'others') {
|
|
1015
1028
|
return 'Tab · ↑↓ · / Search · Enter Details · q Close';
|
|
1016
1029
|
}
|
|
1017
|
-
// Ollama doesn't need API key
|
|
1018
|
-
if (currentTabId === 'ollama') {
|
|
1030
|
+
// Ollama (both variants) doesn't need an API key
|
|
1031
|
+
if (currentTabId === 'ollama' || currentTabId === 'ollama-anthropic') {
|
|
1019
1032
|
return 'Tab · ↑↓ · / Search · Enter Details · q Close';
|
|
1020
1033
|
}
|
|
1021
1034
|
// Standard providers: show s Set Key option
|
|
@@ -1026,8 +1039,8 @@ export class ModelOverlayV2 extends TabbedListOverlayV2 {
|
|
|
1026
1039
|
// Skip for "Others" tab (has its own detail screen)
|
|
1027
1040
|
if (tabId === 'others')
|
|
1028
1041
|
return [];
|
|
1029
|
-
// Ollama: show connection status
|
|
1030
|
-
if (tabId === 'ollama') {
|
|
1042
|
+
// Ollama (both variants): show connection status
|
|
1043
|
+
if (tabId === 'ollama' || tabId === 'ollama-anthropic') {
|
|
1031
1044
|
const status = this.ollamaState.status;
|
|
1032
1045
|
let indicator;
|
|
1033
1046
|
switch (status) {
|
|
@@ -1057,7 +1070,7 @@ export class ModelOverlayV2 extends TabbedListOverlayV2 {
|
|
|
1057
1070
|
// Handle 's' key to set API key for current provider
|
|
1058
1071
|
handleCustomKey: (key, tabId) => {
|
|
1059
1072
|
const char = key.toString();
|
|
1060
|
-
if (char === 's' && tabId !== 'others' && tabId !== 'ollama') {
|
|
1073
|
+
if (char === 's' && tabId !== 'others' && tabId !== 'ollama' && tabId !== 'ollama-anthropic') {
|
|
1061
1074
|
const providerName = PROVIDER_TABS.find(t => t.id === tabId)?.label ?? tabId;
|
|
1062
1075
|
return {
|
|
1063
1076
|
handled: true,
|
|
@@ -38,8 +38,14 @@ export class OverlayManager {
|
|
|
38
38
|
this.clearOverlayRender();
|
|
39
39
|
}
|
|
40
40
|
this.stack.push(overlay);
|
|
41
|
-
overlay
|
|
42
|
-
|
|
41
|
+
// Enter overlay mode (resets renderState + clears the footer) BEFORE wiring
|
|
42
|
+
// the render callback and mounting. Otherwise an async render triggered
|
|
43
|
+
// during the `await onMount` microtask gap — e.g. ModelOverlay's
|
|
44
|
+
// fetchOllamaModels() resolving and calling requestRender() — draws a frame
|
|
45
|
+
// and sets renderState.maxLineCount, then enterInlineOverlayMode() wipes
|
|
46
|
+
// that count to 0, so the final renderOverlay() clears nothing and draws a
|
|
47
|
+
// second frame below the first (the "ghost" overlay). Resetting first means
|
|
48
|
+
// any gap render is tracked and the final render clears it correctly.
|
|
43
49
|
if (!overlay.usesAlternateScreen) {
|
|
44
50
|
if (overlay.type === 'fullscreen') {
|
|
45
51
|
this.enterFullscreenOverlayMode();
|
|
@@ -48,6 +54,8 @@ export class OverlayManager {
|
|
|
48
54
|
this.enterInlineOverlayMode();
|
|
49
55
|
}
|
|
50
56
|
}
|
|
57
|
+
overlay.setRenderCallback?.(() => { this.requestOverlayRender(); });
|
|
58
|
+
await overlay.onMount?.();
|
|
51
59
|
this.renderOverlay();
|
|
52
60
|
return new Promise((resolve) => {
|
|
53
61
|
this.resolvers.set(overlay.id, resolve);
|
|
@@ -71,4 +71,4 @@ export declare function getProvidersByCategory(category: ProviderCategory): Prov
|
|
|
71
71
|
/**
|
|
72
72
|
* Map provider type from settings to credential provider key
|
|
73
73
|
*/
|
|
74
|
-
export declare function settingsProviderToCredentialKey(provider: 'auto' | 'claude' | 'openai' | 'gemini' | 'ollama' | 'together' | 'groq' | 'fireworks' | 'perplexity' | 'openrouter' | 'custom'): ProviderKey;
|
|
74
|
+
export declare function settingsProviderToCredentialKey(provider: 'auto' | 'claude' | 'openai' | 'gemini' | 'ollama' | 'ollama-anthropic' | 'together' | 'groq' | 'fireworks' | 'perplexity' | 'openrouter' | 'custom'): ProviderKey;
|
|
@@ -18,19 +18,23 @@
|
|
|
18
18
|
// Model Tier Mappings
|
|
19
19
|
// =============================================================================
|
|
20
20
|
const MODEL_TIERS = {
|
|
21
|
-
// Claude (Anthropic) -
|
|
21
|
+
// Claude (Anthropic) - current
|
|
22
|
+
'claude-fable-5': { tier: 'large', provider: 'anthropic' },
|
|
23
|
+
'claude-opus-4-8': { tier: 'large', provider: 'anthropic' },
|
|
24
|
+
'claude-sonnet-5': { tier: 'large', provider: 'anthropic' },
|
|
25
|
+
'claude-haiku-4-5': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-5' },
|
|
26
|
+
// Claude legacy (for backward compatibility)
|
|
27
|
+
'claude-opus-4-7': { tier: 'large', provider: 'anthropic' },
|
|
22
28
|
'claude-opus-4-6': { tier: 'large', provider: 'anthropic' },
|
|
23
29
|
'claude-sonnet-4-6': { tier: 'large', provider: 'anthropic' },
|
|
24
|
-
'claude-haiku-4-5': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-4-6' },
|
|
25
|
-
// Claude legacy (for backward compatibility)
|
|
26
30
|
'claude-opus-4-5': { tier: 'large', provider: 'anthropic' },
|
|
27
31
|
'claude-sonnet-4-5': { tier: 'large', provider: 'anthropic' },
|
|
28
32
|
'claude-opus-4-1': { tier: 'large', provider: 'anthropic' },
|
|
29
33
|
'claude-opus-4': { tier: 'large', provider: 'anthropic' },
|
|
30
34
|
'claude-sonnet-4': { tier: 'large', provider: 'anthropic' },
|
|
31
35
|
'claude-3-5-sonnet': { tier: 'large', provider: 'anthropic' },
|
|
32
|
-
'claude-3-5-haiku': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-
|
|
33
|
-
'claude-3-haiku': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-
|
|
36
|
+
'claude-3-5-haiku': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-5' },
|
|
37
|
+
'claude-3-haiku': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-5' },
|
|
34
38
|
// OpenAI - GPT-5 series (current)
|
|
35
39
|
'gpt-5.2': { tier: 'large', provider: 'openai' },
|
|
36
40
|
'gpt-5-mini': { tier: 'large', provider: 'openai' },
|
|
@@ -56,10 +60,11 @@ const MODEL_TIERS = {
|
|
|
56
60
|
'gemini-1.5-pro': { tier: 'large', provider: 'google' },
|
|
57
61
|
'gemini-1.5-flash': { tier: 'medium', provider: 'google', suggestedUpgrade: 'gemini-3-flash-preview' },
|
|
58
62
|
// Ollama / Local models (common ones)
|
|
59
|
-
'llama3.
|
|
60
|
-
'llama3.
|
|
61
|
-
'llama3.2': { tier: 'small', provider: 'ollama', suggestedUpgrade: 'llama3.
|
|
62
|
-
'llama3.
|
|
63
|
+
'llama3.3:70b': { tier: 'large', provider: 'ollama' },
|
|
64
|
+
'llama3.3': { tier: 'large', provider: 'ollama' },
|
|
65
|
+
'llama3.2:3b': { tier: 'small', provider: 'ollama', suggestedUpgrade: 'llama3.3:70b' },
|
|
66
|
+
'llama3.2': { tier: 'small', provider: 'ollama', suggestedUpgrade: 'llama3.3:70b' },
|
|
67
|
+
'llama3.1': { tier: 'medium', provider: 'ollama', suggestedUpgrade: 'llama3.3:70b' },
|
|
63
68
|
'llama3.1:70b': { tier: 'large', provider: 'ollama' },
|
|
64
69
|
'mistral': { tier: 'small', provider: 'ollama' },
|
|
65
70
|
'mixtral': { tier: 'medium', provider: 'ollama' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compilr-dev/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9",
|
|
4
4
|
"description": "AI-powered coding assistant CLI using @compilr-dev/agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@anthropic-ai/sdk": "^0.74.0",
|
|
58
|
-
"@compilr-dev/agents": "^0.6.
|
|
58
|
+
"@compilr-dev/agents": "^0.6.8",
|
|
59
59
|
"@compilr-dev/agents-coding": "^1.0.4",
|
|
60
60
|
"@compilr-dev/editor-core": "^0.0.3",
|
|
61
61
|
"@compilr-dev/factory": "^0.1.35",
|
|
62
62
|
"@compilr-dev/logger": "^0.1.0",
|
|
63
|
-
"@compilr-dev/sdk": "^0.18.
|
|
63
|
+
"@compilr-dev/sdk": "^0.18.10",
|
|
64
64
|
"@compilr-dev/ui-core": "^0.0.1",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
66
66
|
"ansi-escapes": "^7.3.0",
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Episode Recorder
|
|
3
|
-
*
|
|
4
|
-
* Collects tool call signals via an AfterToolHook, batches them within
|
|
5
|
-
* a time window, and persists as WorkEpisode records.
|
|
6
|
-
*/
|
|
7
|
-
import type { AfterToolHook, Effort, EpisodeStore } from '@compilr-dev/agents';
|
|
8
|
-
export interface EpisodeRecorderConfig {
|
|
9
|
-
/** Episode store for persistence */
|
|
10
|
-
store: EpisodeStore;
|
|
11
|
-
/** Agent ID (e.g., 'default', 'backend') */
|
|
12
|
-
agentId: string;
|
|
13
|
-
/** Terminal session prefix (first 8 chars) */
|
|
14
|
-
terminalPrefix: string;
|
|
15
|
-
/** Session ID for grouping */
|
|
16
|
-
sessionId: string;
|
|
17
|
-
/** Enable automatic recording via hook (default: true) */
|
|
18
|
-
autoRecord?: boolean;
|
|
19
|
-
/** Minimum effort level to record (default: 'low') */
|
|
20
|
-
minEffortToRecord?: Effort;
|
|
21
|
-
/** Batch window in milliseconds (default: 5000) */
|
|
22
|
-
batchWindowMs?: number;
|
|
23
|
-
/** Called after each batch is flushed as a WorkEpisode */
|
|
24
|
-
onBatchFlushed?: () => void;
|
|
25
|
-
}
|
|
26
|
-
export declare class EpisodeRecorder {
|
|
27
|
-
private readonly store;
|
|
28
|
-
private readonly agentId;
|
|
29
|
-
private readonly terminalPrefix;
|
|
30
|
-
private readonly sessionId;
|
|
31
|
-
private readonly autoRecord;
|
|
32
|
-
private readonly minEffortToRecord;
|
|
33
|
-
private readonly batchWindowMs;
|
|
34
|
-
private readonly onBatchFlushed?;
|
|
35
|
-
private pendingSignals;
|
|
36
|
-
private batchTimer;
|
|
37
|
-
private batchStartTime;
|
|
38
|
-
constructor(config: EpisodeRecorderConfig);
|
|
39
|
-
/**
|
|
40
|
-
* Create an AfterToolHook for observing tool calls.
|
|
41
|
-
* Always returns undefined (observation only — never modifies results).
|
|
42
|
-
*/
|
|
43
|
-
createHook(): AfterToolHook;
|
|
44
|
-
/**
|
|
45
|
-
* Force-flush all pending signals as a WorkEpisode.
|
|
46
|
-
* Called on shutdown to avoid losing data.
|
|
47
|
-
*/
|
|
48
|
-
flush(): void;
|
|
49
|
-
private scheduleBatch;
|
|
50
|
-
private buildAndSaveEpisode;
|
|
51
|
-
}
|