@denizokcu/haze 0.0.2 → 0.0.3
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 +10 -0
- package/README.md +87 -33
- package/dist/cli/commands/chat.d.ts +3 -1
- package/dist/cli/commands/chat.js +442 -52
- package/dist/cli/commands/commands.d.ts +5 -0
- package/dist/cli/commands/commands.js +114 -29
- package/dist/cli/commands/formatters.js +5 -2
- package/dist/cli/commands/streaming.d.ts +5 -1
- package/dist/cli/commands/streaming.js +193 -86
- package/dist/cli/index.js +5 -2
- package/dist/config/inputHistory.js +8 -0
- package/dist/config/providers.d.ts +26 -0
- package/dist/config/providers.js +88 -0
- package/dist/config/settings.d.ts +9 -2
- package/dist/core/agent/compaction.d.ts +13 -0
- package/dist/core/agent/compaction.js +34 -0
- package/dist/core/agent/errors.d.ts +3 -0
- package/dist/core/agent/errors.js +13 -0
- package/dist/core/agent/events.d.ts +58 -0
- package/dist/core/agent/events.js +3 -0
- package/dist/core/goal/completionPolicy.d.ts +27 -0
- package/dist/core/goal/completionPolicy.js +67 -0
- package/dist/core/goal/requestClassifier.d.ts +6 -0
- package/dist/core/goal/requestClassifier.js +31 -0
- package/dist/core/goal/sessionGoal.d.ts +30 -0
- package/dist/core/goal/sessionGoal.js +88 -0
- package/dist/core/session/sessionStore.d.ts +37 -0
- package/dist/core/session/sessionStore.js +59 -0
- package/dist/llm/client.d.ts +1 -1
- package/dist/llm/client.js +6 -6
- package/dist/llm/hazeTools.d.ts +38 -0
- package/dist/llm/hazeTools.js +196 -92
- package/dist/llm/initPrompt.js +6 -4
- package/dist/llm/systemPrompt.js +3 -3
- package/dist/skills/builder/SkillBuilder.d.ts +6 -0
- package/dist/skills/builder/SkillBuilder.js +146 -24
- package/dist/ui/components/ErrorView.d.ts +2 -1
- package/dist/ui/components/Header.d.ts +2 -1
- package/dist/ui/components/Header.js +1 -11
- package/dist/ui/components/MarkdownText.d.ts +2 -1
- package/dist/ui/components/TextInput.d.ts +7 -3
- package/dist/ui/components/TextInput.js +112 -27
- package/dist/ui/theme.d.ts +1 -0
- package/dist/ui/theme.js +2 -1
- package/package.json +8 -8
|
@@ -5,8 +5,13 @@ export type CommandContext = {
|
|
|
5
5
|
settings: HazeSettings;
|
|
6
6
|
contextFiles: ContextFile[];
|
|
7
7
|
setMode: (mode: Mode) => void;
|
|
8
|
+
setModelProviderFilter?: (providerName: string | undefined) => void;
|
|
8
9
|
addSystemMessage: (text: string) => void;
|
|
9
10
|
clearConversation: () => void;
|
|
11
|
+
newSession?: () => Promise<void>;
|
|
12
|
+
resumeSession?: () => Promise<void>;
|
|
13
|
+
sessionInfo?: () => string;
|
|
14
|
+
compactConversation?: (instructions?: string) => boolean;
|
|
10
15
|
runAgentTurn: (prompt: string, displayValue?: string) => Promise<void>;
|
|
11
16
|
refreshContextFiles: () => Promise<ContextFile[]>;
|
|
12
17
|
updateSettings: (patch: Partial<HazeSettings>) => Promise<HazeSettings>;
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { buildInitPrompt } from '../../llm/initPrompt.js';
|
|
4
|
+
import { activeProvider, configuredProviders, modelSelector, providerHasKey, resolveModelSelector, upsertProvider } from '../../config/providers.js';
|
|
4
5
|
function skillHelp() {
|
|
5
6
|
return [
|
|
6
7
|
'Skill commands:',
|
|
7
|
-
'/skill
|
|
8
|
+
'/create-skill <description>',
|
|
8
9
|
' Create a Markdown skill in ~/.haze/skills.',
|
|
9
|
-
'/
|
|
10
|
+
'/list-skills',
|
|
10
11
|
' List installed skills.',
|
|
11
|
-
'/skill
|
|
12
|
+
'/skill-info <name>',
|
|
12
13
|
' Show a skill description and path.',
|
|
13
|
-
'/skill
|
|
14
|
+
'/validate-skill <name-or-dir>',
|
|
14
15
|
' Validate a skill directory containing SKILL.md.',
|
|
15
|
-
'/skill
|
|
16
|
+
'/remove-skill <name> --yes',
|
|
16
17
|
' Remove an installed skill. Requires --yes because it deletes files.',
|
|
17
18
|
].join('\n');
|
|
18
19
|
}
|
|
20
|
+
async function skillOverview() {
|
|
21
|
+
const { loadSkillRegistry } = await import('../../skills/SkillRegistry.js');
|
|
22
|
+
const registry = await loadSkillRegistry();
|
|
23
|
+
const skills = [...registry.skills.values()];
|
|
24
|
+
const installed = skills.length === 0
|
|
25
|
+
? ['Installed skills:', '- None yet. Create one with /create-skill <description>.']
|
|
26
|
+
: ['Installed skills:', ...skills.map(s => `- /${s.name} — ${s.description}`)];
|
|
27
|
+
return `${skillHelp()}\n\n${installed.join('\n')}`;
|
|
28
|
+
}
|
|
19
29
|
async function handleSkillCommand(value, ctx) {
|
|
20
30
|
const parts = value.trim().split(/\s+/).filter(Boolean);
|
|
21
31
|
const subcommand = parts[1];
|
|
22
32
|
if (!subcommand || subcommand === 'help') {
|
|
23
|
-
ctx.addSystemMessage(
|
|
33
|
+
ctx.addSystemMessage(await skillOverview());
|
|
24
34
|
return 'handled';
|
|
25
35
|
}
|
|
26
36
|
if (subcommand === 'create') {
|
|
27
37
|
const description = parts.slice(2).join(' ');
|
|
28
38
|
if (!description) {
|
|
29
|
-
ctx.addSystemMessage('Usage: /skill
|
|
39
|
+
ctx.addSystemMessage('Usage: /create-skill <description>');
|
|
30
40
|
return 'handled';
|
|
31
41
|
}
|
|
32
42
|
const { createSkill } = await import('../../skills/builder/SkillBuilder.js');
|
|
33
43
|
const result = await createSkill(description);
|
|
34
|
-
ctx.addSystemMessage(`Created skill ${result.name} at ${result.file}. Edit SKILL.md to refine its workflow.`);
|
|
44
|
+
ctx.addSystemMessage(`Created skill ${result.name} at ${result.file}. Invoke it with /${result.name}. Edit SKILL.md to refine its workflow.`);
|
|
35
45
|
return 'handled';
|
|
36
46
|
}
|
|
37
47
|
if (subcommand === 'list') {
|
|
@@ -39,14 +49,14 @@ async function handleSkillCommand(value, ctx) {
|
|
|
39
49
|
const registry = await loadSkillRegistry();
|
|
40
50
|
const skills = [...registry.skills.values()];
|
|
41
51
|
ctx.addSystemMessage(skills.length === 0
|
|
42
|
-
? 'No installed skills found.'
|
|
43
|
-
: ['Installed skills:', ...skills.map(s => `-
|
|
52
|
+
? 'No installed skills found. Create one with /create-skill <description>.'
|
|
53
|
+
: ['Installed skills:', ...skills.map(s => `- /${s.name} — ${s.description}`)].join('\n'));
|
|
44
54
|
return 'handled';
|
|
45
55
|
}
|
|
46
56
|
if (subcommand === 'info') {
|
|
47
57
|
const name = parts[2];
|
|
48
58
|
if (!name) {
|
|
49
|
-
ctx.addSystemMessage('Usage: /skill
|
|
59
|
+
ctx.addSystemMessage('Usage: /skill-info <name>');
|
|
50
60
|
return 'handled';
|
|
51
61
|
}
|
|
52
62
|
const { loadSkillRegistry } = await import('../../skills/SkillRegistry.js');
|
|
@@ -68,7 +78,7 @@ async function handleSkillCommand(value, ctx) {
|
|
|
68
78
|
if (subcommand === 'validate') {
|
|
69
79
|
const target = parts[2];
|
|
70
80
|
if (!target) {
|
|
71
|
-
ctx.addSystemMessage('Usage: /skill
|
|
81
|
+
ctx.addSystemMessage('Usage: /validate-skill <name-or-dir>');
|
|
72
82
|
return 'handled';
|
|
73
83
|
}
|
|
74
84
|
const { GLOBAL_SKILLS_DIR } = await import('../../config/paths.js');
|
|
@@ -82,7 +92,7 @@ async function handleSkillCommand(value, ctx) {
|
|
|
82
92
|
if (subcommand === 'remove') {
|
|
83
93
|
const name = parts[2];
|
|
84
94
|
if (!name || !parts.includes('--yes')) {
|
|
85
|
-
ctx.addSystemMessage('Usage: /skill
|
|
95
|
+
ctx.addSystemMessage('Usage: /remove-skill <name> --yes');
|
|
86
96
|
return 'handled';
|
|
87
97
|
}
|
|
88
98
|
const { loadSkillRegistry } = await import('../../skills/SkillRegistry.js');
|
|
@@ -107,18 +117,28 @@ export async function handleSlashCommand(value, ctx) {
|
|
|
107
117
|
'Commands:',
|
|
108
118
|
'/help',
|
|
109
119
|
' Show all available slash commands and what they do.',
|
|
110
|
-
'/
|
|
111
|
-
'
|
|
120
|
+
'/provider',
|
|
121
|
+
' Choose a provider, then use it or add models; choose add provider at the bottom to create one.',
|
|
112
122
|
'/model',
|
|
113
|
-
'
|
|
114
|
-
'/model <name>',
|
|
115
|
-
' Set
|
|
123
|
+
' Choose a model from all configured providers.',
|
|
124
|
+
'/model <name-or-provider:name>',
|
|
125
|
+
' Set a model directly. Selecting a model also sets its provider.',
|
|
116
126
|
'/settings',
|
|
117
127
|
' Show the configured provider, model, API key status, and loaded context files.',
|
|
118
|
-
'/skill
|
|
119
|
-
'
|
|
128
|
+
'/create-skill <description>',
|
|
129
|
+
' Create a reusable Markdown workflow from how you work.',
|
|
130
|
+
'/skills',
|
|
131
|
+
' Show Markdown skill commands and installed skill slash commands.',
|
|
120
132
|
'/init',
|
|
121
133
|
' Inspect the current workspace and create or update AGENTS.md project instructions.',
|
|
134
|
+
'/session',
|
|
135
|
+
' Show the current durable session file.',
|
|
136
|
+
'/resume',
|
|
137
|
+
' Resume the latest saved session for this workspace.',
|
|
138
|
+
'/new',
|
|
139
|
+
' Start a fresh durable session.',
|
|
140
|
+
'/compact [instructions]',
|
|
141
|
+
' Summarize older model context and keep recent messages.',
|
|
122
142
|
'/clear',
|
|
123
143
|
' Clear the current chat conversation history.',
|
|
124
144
|
'/exit',
|
|
@@ -128,29 +148,82 @@ export async function handleSlashCommand(value, ctx) {
|
|
|
128
148
|
].join('\n'));
|
|
129
149
|
return 'handled';
|
|
130
150
|
}
|
|
151
|
+
if (value === '/session') {
|
|
152
|
+
ctx.addSystemMessage(ctx.sessionInfo?.() ?? 'Session persistence is unavailable.');
|
|
153
|
+
return 'handled';
|
|
154
|
+
}
|
|
155
|
+
if (value === '/resume') {
|
|
156
|
+
if (ctx.resumeSession)
|
|
157
|
+
await ctx.resumeSession();
|
|
158
|
+
else
|
|
159
|
+
ctx.addSystemMessage('Session persistence is unavailable.');
|
|
160
|
+
return 'handled';
|
|
161
|
+
}
|
|
162
|
+
if (value === '/new') {
|
|
163
|
+
if (ctx.newSession)
|
|
164
|
+
await ctx.newSession();
|
|
165
|
+
else
|
|
166
|
+
ctx.addSystemMessage('Session persistence is unavailable.');
|
|
167
|
+
return 'handled';
|
|
168
|
+
}
|
|
169
|
+
if (value === '/compact' || value.startsWith('/compact ')) {
|
|
170
|
+
if (ctx.compactConversation)
|
|
171
|
+
ctx.compactConversation(value.slice('/compact'.length).trim() || undefined);
|
|
172
|
+
else
|
|
173
|
+
ctx.addSystemMessage('Compaction is unavailable.');
|
|
174
|
+
return 'handled';
|
|
175
|
+
}
|
|
131
176
|
if (value === '/clear') {
|
|
132
177
|
ctx.clearConversation();
|
|
133
178
|
ctx.addSystemMessage('Cleared. The void is productive.');
|
|
134
179
|
return 'handled';
|
|
135
180
|
}
|
|
136
181
|
if (value === '/settings') {
|
|
137
|
-
|
|
182
|
+
const providers = configuredProviders(ctx.settings);
|
|
183
|
+
const activeProvider = providers.find(provider => provider.name === ctx.settings.provider) ?? providers[0];
|
|
184
|
+
ctx.addSystemMessage([
|
|
185
|
+
`Provider: ${activeProvider?.name ?? 'not configured'}`,
|
|
186
|
+
`Model: ${ctx.settings.model ?? 'not set'}`,
|
|
187
|
+
`Base URL: ${activeProvider?.url ?? ctx.settings.baseURL ?? 'not configured'}`,
|
|
188
|
+
`API key: ${activeProvider && providerHasKey(ctx.settings, activeProvider) ? 'saved' : 'missing'}`,
|
|
189
|
+
`Configured providers: ${providers.map(provider => provider.name).join(', ') || 'none'}`,
|
|
190
|
+
`Context files: ${ctx.contextFiles.length ? ctx.contextFiles.map(file => file.path).join(', ') : 'none'}`,
|
|
191
|
+
].join(' | '));
|
|
138
192
|
return 'handled';
|
|
139
193
|
}
|
|
140
|
-
if (value === '/
|
|
141
|
-
ctx.
|
|
142
|
-
ctx.
|
|
194
|
+
if (value === '/provider') {
|
|
195
|
+
ctx.setModelProviderFilter?.(undefined);
|
|
196
|
+
ctx.setMode('provider');
|
|
197
|
+
ctx.addSystemMessage('Choose a provider. Selecting one opens provider actions. Choose “add provider” at the bottom to add a new provider.');
|
|
143
198
|
return 'handled';
|
|
144
199
|
}
|
|
145
200
|
if (value === '/model') {
|
|
201
|
+
ctx.setModelProviderFilter?.(undefined);
|
|
146
202
|
ctx.setMode('model');
|
|
147
|
-
ctx.addSystemMessage('
|
|
203
|
+
ctx.addSystemMessage('Choose a model. Selecting a model also sets its provider.');
|
|
204
|
+
return 'handled';
|
|
205
|
+
}
|
|
206
|
+
if (value === '/model list') {
|
|
207
|
+
const providers = configuredProviders(ctx.settings);
|
|
208
|
+
ctx.addSystemMessage(['Configured models:', ...providers.flatMap(provider => provider.models.map(model => `- ${modelSelector(provider, model)} — ${provider.name}`))].join('\n'));
|
|
148
209
|
return 'handled';
|
|
149
210
|
}
|
|
150
211
|
if (value.startsWith('/model ')) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
212
|
+
const selector = value.slice('/model '.length).trim();
|
|
213
|
+
const resolved = resolveModelSelector(ctx.settings, selector);
|
|
214
|
+
if (resolved.status === 'ambiguous') {
|
|
215
|
+
ctx.addSystemMessage(`Model ${resolved.model} exists on multiple providers: ${resolved.providers.map(provider => modelSelector(provider, resolved.model)).join(', ')}`);
|
|
216
|
+
return 'handled';
|
|
217
|
+
}
|
|
218
|
+
if (resolved.status === 'missing') {
|
|
219
|
+
const provider = activeProvider(ctx.settings);
|
|
220
|
+
const nextProvider = provider.models.includes(selector) ? provider : { ...provider, models: [...provider.models, selector] };
|
|
221
|
+
await ctx.updateSettings({ provider: provider.name, model: selector, providers: upsertProvider(ctx.settings, nextProvider) });
|
|
222
|
+
ctx.addSystemMessage(`Model set to ${selector} on ${provider.name}. Saved to ~/.haze/settings.json.`);
|
|
223
|
+
return 'handled';
|
|
224
|
+
}
|
|
225
|
+
await ctx.updateSettings({ provider: resolved.provider.name, model: resolved.model });
|
|
226
|
+
ctx.addSystemMessage(`Model set to ${resolved.model} on ${resolved.provider.name}. Saved to ~/.haze/settings.json.`);
|
|
154
227
|
return 'handled';
|
|
155
228
|
}
|
|
156
229
|
if (value === '/init') {
|
|
@@ -158,7 +231,19 @@ export async function handleSlashCommand(value, ctx) {
|
|
|
158
231
|
await ctx.refreshContextFiles();
|
|
159
232
|
return 'handled';
|
|
160
233
|
}
|
|
161
|
-
if (value === '/
|
|
234
|
+
if (value === '/skills')
|
|
235
|
+
return await handleSkillCommand('/skill help', ctx);
|
|
236
|
+
if (value === '/list-skills')
|
|
237
|
+
return await handleSkillCommand('/skill list', ctx);
|
|
238
|
+
if (value === '/create-skill' || value.startsWith('/create-skill '))
|
|
239
|
+
return await handleSkillCommand(`/skill create${value.slice('/create-skill'.length)}`, ctx);
|
|
240
|
+
if (value === '/skill-info' || value.startsWith('/skill-info '))
|
|
241
|
+
return await handleSkillCommand(`/skill info${value.slice('/skill-info'.length)}`, ctx);
|
|
242
|
+
if (value === '/validate-skill' || value.startsWith('/validate-skill '))
|
|
243
|
+
return await handleSkillCommand(`/skill validate${value.slice('/validate-skill'.length)}`, ctx);
|
|
244
|
+
if (value === '/remove-skill' || value.startsWith('/remove-skill '))
|
|
245
|
+
return await handleSkillCommand(`/skill remove${value.slice('/remove-skill'.length)}`, ctx);
|
|
246
|
+
if (value === '/skill' || value.startsWith('/skill ') || value.startsWith('/skills ')) {
|
|
162
247
|
const normalized = value.replace(/^\/skills\b/, '/skill');
|
|
163
248
|
return await handleSkillCommand(normalized, ctx);
|
|
164
249
|
}
|
|
@@ -39,8 +39,11 @@ export function toolResultSummary(event) {
|
|
|
39
39
|
return 'skipped duplicate';
|
|
40
40
|
if (typeof output?.code === 'number')
|
|
41
41
|
return `exited with code ${output.code}`;
|
|
42
|
-
if (typeof output?.ok === 'boolean')
|
|
43
|
-
|
|
42
|
+
if (typeof output?.ok === 'boolean') {
|
|
43
|
+
if (output.ok)
|
|
44
|
+
return 'completed';
|
|
45
|
+
return typeof output.error === 'string' ? `failed: ${compact(output.error)}` : 'failed';
|
|
46
|
+
}
|
|
44
47
|
return 'completed';
|
|
45
48
|
}
|
|
46
49
|
export function formatSeconds(milliseconds) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ModelMessage } from 'ai';
|
|
2
2
|
import type { ContextFile } from '../../config/contextFiles.js';
|
|
3
|
+
import { type AgentEventSink } from '../../core/agent/events.js';
|
|
3
4
|
export type Message = {
|
|
4
5
|
id?: string;
|
|
5
6
|
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
@@ -17,5 +18,8 @@ export interface StreamCallbacks {
|
|
|
17
18
|
getLastAssistantText: () => string;
|
|
18
19
|
setLastAssistantText: (text: string) => void;
|
|
19
20
|
setAbortController?: (controller: AbortController | null) => void;
|
|
21
|
+
setGoalStatus?: (status: string | undefined) => void;
|
|
22
|
+
onEvent?: AgentEventSink;
|
|
23
|
+
compactConversation?: (instructions?: string) => boolean;
|
|
20
24
|
}
|
|
21
|
-
export declare function runAgentTurn(value: string, displayValue: string | undefined, contextFiles: ContextFile[], callbacks: StreamCallbacks): Promise<void>;
|
|
25
|
+
export declare function runAgentTurn(value: string, displayValue: string | undefined, contextFiles: ContextFile[], callbacks: StreamCallbacks, retryAttempt?: number, retryingExistingRequest?: boolean, contextOverflowRecovered?: boolean): Promise<void>;
|