prompt-language-shell 0.8.2 → 0.8.4
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/services/anthropic.js +42 -55
- package/dist/services/colors.js +1 -0
- package/dist/services/components.js +5 -13
- package/dist/services/configuration.js +23 -23
- package/dist/services/filesystem.js +114 -0
- package/dist/services/loader.js +8 -5
- package/dist/services/logger.js +24 -0
- package/dist/services/parser.js +3 -1
- package/dist/services/refinement.js +9 -9
- package/dist/services/router.js +25 -25
- package/dist/services/skills.js +12 -11
- package/dist/services/validator.js +4 -3
- package/dist/types/guards.js +4 -6
- package/dist/types/handlers.js +1 -0
- package/dist/types/schemas.js +103 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Answer.js +10 -9
- package/dist/ui/Command.js +20 -14
- package/dist/ui/Config.js +8 -7
- package/dist/ui/Confirm.js +4 -4
- package/dist/ui/Execute.js +263 -69
- package/dist/ui/Feedback.js +1 -0
- package/dist/ui/Introspect.js +12 -10
- package/dist/ui/Main.js +5 -5
- package/dist/ui/Schedule.js +8 -8
- package/dist/ui/Validate.js +10 -8
- package/dist/ui/Workflow.js +102 -30
- package/package.json +3 -2
|
@@ -3,6 +3,7 @@ import { getAvailableConfigStructure, getConfiguredKeys, } from './configuration
|
|
|
3
3
|
import { logPrompt, logResponse } from './logger.js';
|
|
4
4
|
import { formatSkillsForPrompt, loadSkillsWithValidation } from './skills.js';
|
|
5
5
|
import { toolRegistry } from './registry.js';
|
|
6
|
+
import { CommandResultSchema, IntrospectResultSchema, } from '../types/schemas.js';
|
|
6
7
|
/**
|
|
7
8
|
* Wraps text to ensure no line exceeds the specified width.
|
|
8
9
|
* Breaks at word boundaries to maintain readability.
|
|
@@ -45,6 +46,19 @@ export function cleanAnswerText(text) {
|
|
|
45
46
|
cleaned = wrapText(cleaned, 80);
|
|
46
47
|
return cleaned;
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Formats Zod validation errors into readable error messages.
|
|
51
|
+
* Provides detailed information about what failed validation.
|
|
52
|
+
*/
|
|
53
|
+
function formatValidationError(error) {
|
|
54
|
+
const issues = error.issues
|
|
55
|
+
.map((issue) => {
|
|
56
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
57
|
+
return ` - ${path}: ${issue.message}`;
|
|
58
|
+
})
|
|
59
|
+
.join('\n');
|
|
60
|
+
return `LLM response validation failed:\n${issues}`;
|
|
61
|
+
}
|
|
48
62
|
export class AnthropicService {
|
|
49
63
|
client;
|
|
50
64
|
model;
|
|
@@ -148,88 +162,61 @@ export class AnthropicService {
|
|
|
148
162
|
const input = content.input;
|
|
149
163
|
// Handle execute tool response
|
|
150
164
|
if (toolName === 'execute') {
|
|
151
|
-
|
|
152
|
-
throw new Error('Invalid tool response: missing or invalid message field');
|
|
153
|
-
}
|
|
154
|
-
if (!input.commands || !Array.isArray(input.commands)) {
|
|
155
|
-
throw new Error('Invalid tool response: missing or invalid commands array');
|
|
156
|
-
}
|
|
157
|
-
// Validate each command has required fields
|
|
158
|
-
input.commands.forEach((cmd, i) => {
|
|
159
|
-
if (!cmd.description || typeof cmd.description !== 'string') {
|
|
160
|
-
throw new Error(`Invalid command at index ${String(i)}: missing or invalid 'description' field`);
|
|
161
|
-
}
|
|
162
|
-
if (!cmd.command || typeof cmd.command !== 'string') {
|
|
163
|
-
throw new Error(`Invalid command at index ${String(i)}: missing or invalid 'command' field`);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
return {
|
|
165
|
+
const validation = CommandResultSchema.safeParse({
|
|
167
166
|
message: input.message,
|
|
168
167
|
summary: input.summary,
|
|
169
168
|
tasks: [],
|
|
170
169
|
commands: input.commands,
|
|
171
170
|
debug,
|
|
172
|
-
};
|
|
171
|
+
});
|
|
172
|
+
if (!validation.success) {
|
|
173
|
+
throw new Error(`I received an unexpected response while preparing to execute commands:\n${formatValidationError(validation.error)}`);
|
|
174
|
+
}
|
|
175
|
+
return validation.data;
|
|
173
176
|
}
|
|
174
177
|
// Handle answer tool response
|
|
175
178
|
if (toolName === 'answer') {
|
|
179
|
+
// Validate question and answer fields exist
|
|
176
180
|
if (!input.question || typeof input.question !== 'string') {
|
|
177
|
-
throw new Error('
|
|
181
|
+
throw new Error('I received an unexpected response while answering your question:\nLLM response validation failed:\n - question: missing or invalid');
|
|
178
182
|
}
|
|
179
183
|
if (!input.answer || typeof input.answer !== 'string') {
|
|
180
|
-
throw new Error('
|
|
184
|
+
throw new Error('I received an unexpected response while answering your question:\nLLM response validation failed:\n - answer: missing or invalid');
|
|
181
185
|
}
|
|
182
|
-
|
|
186
|
+
// Validate the result structure with Zod
|
|
187
|
+
const validation = CommandResultSchema.safeParse({
|
|
183
188
|
message: '',
|
|
184
189
|
tasks: [],
|
|
185
190
|
answer: cleanAnswerText(input.answer),
|
|
186
191
|
debug,
|
|
187
|
-
};
|
|
192
|
+
});
|
|
193
|
+
if (!validation.success) {
|
|
194
|
+
throw new Error(`I received an unexpected response while answering your question:\n${formatValidationError(validation.error)}`);
|
|
195
|
+
}
|
|
196
|
+
return validation.data;
|
|
188
197
|
}
|
|
189
198
|
// Handle introspect tool response
|
|
190
199
|
if (toolName === 'introspect') {
|
|
191
|
-
|
|
192
|
-
throw new Error('Invalid tool response: missing or invalid message field');
|
|
193
|
-
}
|
|
194
|
-
if (!input.capabilities || !Array.isArray(input.capabilities)) {
|
|
195
|
-
throw new Error('Invalid tool response: missing or invalid capabilities array');
|
|
196
|
-
}
|
|
197
|
-
// Validate each capability has required fields
|
|
198
|
-
input.capabilities.forEach((cap, i) => {
|
|
199
|
-
if (!cap.name || typeof cap.name !== 'string') {
|
|
200
|
-
throw new Error(`Invalid capability at index ${String(i)}: missing or invalid 'name' field`);
|
|
201
|
-
}
|
|
202
|
-
if (!cap.description || typeof cap.description !== 'string') {
|
|
203
|
-
throw new Error(`Invalid capability at index ${String(i)}: missing or invalid 'description' field`);
|
|
204
|
-
}
|
|
205
|
-
if (typeof cap.origin !== 'string') {
|
|
206
|
-
throw new Error(`Invalid capability at index ${String(i)}: invalid 'origin' field`);
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
return {
|
|
200
|
+
const validation = IntrospectResultSchema.safeParse({
|
|
210
201
|
message: input.message,
|
|
211
202
|
capabilities: input.capabilities,
|
|
212
203
|
debug,
|
|
213
|
-
};
|
|
204
|
+
});
|
|
205
|
+
if (!validation.success) {
|
|
206
|
+
throw new Error(`I received an unexpected response while listing capabilities:\n${formatValidationError(validation.error)}`);
|
|
207
|
+
}
|
|
208
|
+
return validation.data;
|
|
214
209
|
}
|
|
215
210
|
// Handle schedule tool responses
|
|
216
|
-
|
|
217
|
-
throw new Error('Invalid tool response: missing or invalid message field');
|
|
218
|
-
}
|
|
219
|
-
if (!input.tasks || !Array.isArray(input.tasks)) {
|
|
220
|
-
throw new Error('Invalid tool response: missing or invalid tasks array');
|
|
221
|
-
}
|
|
222
|
-
// Validate each task has required action field
|
|
223
|
-
input.tasks.forEach((task, i) => {
|
|
224
|
-
if (!task.action || typeof task.action !== 'string') {
|
|
225
|
-
throw new Error(`Invalid task at index ${String(i)}: missing or invalid 'action' field`);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
return {
|
|
211
|
+
const validation = CommandResultSchema.safeParse({
|
|
229
212
|
message: input.message,
|
|
230
213
|
tasks: input.tasks,
|
|
231
214
|
debug,
|
|
232
|
-
};
|
|
215
|
+
});
|
|
216
|
+
if (!validation.success) {
|
|
217
|
+
throw new Error(`I received an unexpected response while planning tasks:\n${formatValidationError(validation.error)}`);
|
|
218
|
+
}
|
|
219
|
+
return validation.data;
|
|
233
220
|
}
|
|
234
221
|
}
|
|
235
222
|
export function createAnthropicService(config) {
|
package/dist/services/colors.js
CHANGED
|
@@ -128,6 +128,7 @@ const taskColors = {
|
|
|
128
128
|
*/
|
|
129
129
|
const feedbackColors = {
|
|
130
130
|
[FeedbackType.Info]: Colors.Status.Info,
|
|
131
|
+
[FeedbackType.Warning]: Palette.Yellow,
|
|
131
132
|
[FeedbackType.Succeeded]: Colors.Status.Success,
|
|
132
133
|
[FeedbackType.Aborted]: Palette.MediumOrange,
|
|
133
134
|
[FeedbackType.Failed]: Colors.Status.Error,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
3
2
|
import { parse as parseYaml } from 'yaml';
|
|
4
3
|
import { ComponentStatus, } from '../types/components.js';
|
|
5
4
|
import { ComponentName } from '../types/types.js';
|
|
6
5
|
import { ConfigDefinitionType, getConfigPath, getConfigSchema, loadConfig, } from './configuration.js';
|
|
6
|
+
import { defaultFileSystem } from './filesystem.js';
|
|
7
7
|
import { getConfirmationMessage } from './messages.js';
|
|
8
8
|
import { StepType } from '../ui/Config.js';
|
|
9
9
|
export function createWelcomeDefinition(app) {
|
|
@@ -55,13 +55,13 @@ function getValidator(definition) {
|
|
|
55
55
|
/**
|
|
56
56
|
* Create config steps from schema for specified keys
|
|
57
57
|
*/
|
|
58
|
-
export function createConfigStepsFromSchema(keys) {
|
|
58
|
+
export function createConfigStepsFromSchema(keys, fs = defaultFileSystem) {
|
|
59
59
|
const schema = getConfigSchema();
|
|
60
60
|
let currentConfig = null;
|
|
61
61
|
let rawConfig = null;
|
|
62
62
|
// Load validated config (may fail if config has validation errors)
|
|
63
63
|
try {
|
|
64
|
-
currentConfig = loadConfig();
|
|
64
|
+
currentConfig = loadConfig(fs);
|
|
65
65
|
}
|
|
66
66
|
catch {
|
|
67
67
|
// Config doesn't exist or has validation errors, use defaults
|
|
@@ -69,8 +69,8 @@ export function createConfigStepsFromSchema(keys) {
|
|
|
69
69
|
// Load raw config separately (for discovered keys not in schema)
|
|
70
70
|
try {
|
|
71
71
|
const configFile = getConfigPath();
|
|
72
|
-
if (
|
|
73
|
-
const content =
|
|
72
|
+
if (fs.exists(configFile)) {
|
|
73
|
+
const content = fs.readFile(configFile, 'utf-8');
|
|
74
74
|
rawConfig = parseYaml(content);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -382,11 +382,3 @@ export function createValidateDefinition(missingConfig, userRequest, service, on
|
|
|
382
382
|
},
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
|
-
/**
|
|
386
|
-
* Add debug components to timeline if present in result
|
|
387
|
-
*/
|
|
388
|
-
export function addDebugToTimeline(debugComponents, handlers) {
|
|
389
|
-
if (debugComponents && debugComponents.length > 0 && handlers) {
|
|
390
|
-
handlers.addToTimeline(...debugComponents);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
1
|
import { homedir } from 'os';
|
|
3
2
|
import { join } from 'path';
|
|
4
3
|
import YAML from 'yaml';
|
|
5
4
|
import { getConfigLabel } from './config-labels.js';
|
|
6
5
|
import { flattenConfig } from './config-utils.js';
|
|
6
|
+
import { defaultFileSystem } from './filesystem.js';
|
|
7
7
|
/**
|
|
8
8
|
* Convert a dotted config key to a readable label
|
|
9
9
|
* Example: "project.alpha.repo" -> "Project Alpha Repo"
|
|
@@ -96,20 +96,20 @@ function validateConfig(parsed) {
|
|
|
96
96
|
}
|
|
97
97
|
return validatedConfig;
|
|
98
98
|
}
|
|
99
|
-
export function loadConfig() {
|
|
99
|
+
export function loadConfig(fs = defaultFileSystem) {
|
|
100
100
|
const configFile = getConfigFile();
|
|
101
|
-
if (!
|
|
101
|
+
if (!fs.exists(configFile)) {
|
|
102
102
|
throw new ConfigError('Configuration not found');
|
|
103
103
|
}
|
|
104
|
-
const content =
|
|
104
|
+
const content = fs.readFile(configFile, 'utf-8');
|
|
105
105
|
const parsed = parseYamlConfig(content);
|
|
106
106
|
return validateConfig(parsed);
|
|
107
107
|
}
|
|
108
108
|
export function getConfigPath() {
|
|
109
109
|
return getConfigFile();
|
|
110
110
|
}
|
|
111
|
-
export function configExists() {
|
|
112
|
-
return
|
|
111
|
+
export function configExists(fs = defaultFileSystem) {
|
|
112
|
+
return fs.exists(getConfigFile());
|
|
113
113
|
}
|
|
114
114
|
export function isValidAnthropicApiKey(key) {
|
|
115
115
|
// Anthropic API keys format: sk-ant-api03-XXXXX (108 chars total)
|
|
@@ -149,24 +149,24 @@ export function mergeConfig(existingContent, sectionName, newValues) {
|
|
|
149
149
|
// Convert back to YAML
|
|
150
150
|
return YAML.stringify(sortedConfig);
|
|
151
151
|
}
|
|
152
|
-
export function saveConfig(section, config) {
|
|
152
|
+
export function saveConfig(section, config, fs = defaultFileSystem) {
|
|
153
153
|
const configFile = getConfigFile();
|
|
154
|
-
const existingContent =
|
|
155
|
-
?
|
|
154
|
+
const existingContent = fs.exists(configFile)
|
|
155
|
+
? fs.readFile(configFile, 'utf-8')
|
|
156
156
|
: '';
|
|
157
157
|
const newContent = mergeConfig(existingContent, section, config);
|
|
158
|
-
|
|
158
|
+
fs.writeFile(configFile, newContent);
|
|
159
159
|
}
|
|
160
|
-
export function saveAnthropicConfig(config) {
|
|
161
|
-
saveConfig('anthropic', config);
|
|
162
|
-
return loadConfig();
|
|
160
|
+
export function saveAnthropicConfig(config, fs = defaultFileSystem) {
|
|
161
|
+
saveConfig('anthropic', config, fs);
|
|
162
|
+
return loadConfig(fs);
|
|
163
163
|
}
|
|
164
|
-
export function saveDebugSetting(debug) {
|
|
165
|
-
saveConfig('settings', { debug });
|
|
164
|
+
export function saveDebugSetting(debug, fs = defaultFileSystem) {
|
|
165
|
+
saveConfig('settings', { debug }, fs);
|
|
166
166
|
}
|
|
167
|
-
export function loadDebugSetting() {
|
|
167
|
+
export function loadDebugSetting(fs = defaultFileSystem) {
|
|
168
168
|
try {
|
|
169
|
-
const config = loadConfig();
|
|
169
|
+
const config = loadConfig(fs);
|
|
170
170
|
return config.settings?.debug ?? DebugLevel.None;
|
|
171
171
|
}
|
|
172
172
|
catch {
|
|
@@ -304,13 +304,13 @@ export function getMissingConfigKeys() {
|
|
|
304
304
|
* Get list of configured keys from config file
|
|
305
305
|
* Returns array of dot-notation keys that exist in the config file
|
|
306
306
|
*/
|
|
307
|
-
export function getConfiguredKeys() {
|
|
307
|
+
export function getConfiguredKeys(fs = defaultFileSystem) {
|
|
308
308
|
try {
|
|
309
309
|
const configFile = getConfigFile();
|
|
310
|
-
if (!
|
|
310
|
+
if (!fs.exists(configFile)) {
|
|
311
311
|
return [];
|
|
312
312
|
}
|
|
313
|
-
const content =
|
|
313
|
+
const content = fs.readFile(configFile, 'utf-8');
|
|
314
314
|
const parsed = YAML.parse(content);
|
|
315
315
|
// Flatten nested config to dot notation
|
|
316
316
|
const flatConfig = flattenConfig(parsed);
|
|
@@ -325,15 +325,15 @@ export function getConfiguredKeys() {
|
|
|
325
325
|
* Returns keys with descriptions only (no values for privacy)
|
|
326
326
|
* Marks optional keys as "(optional)"
|
|
327
327
|
*/
|
|
328
|
-
export function getAvailableConfigStructure() {
|
|
328
|
+
export function getAvailableConfigStructure(fs = defaultFileSystem) {
|
|
329
329
|
const schema = getConfigSchema();
|
|
330
330
|
const structure = {};
|
|
331
331
|
// Try to load existing config to see which keys are already set
|
|
332
332
|
let flatConfig = {};
|
|
333
333
|
try {
|
|
334
334
|
const configFile = getConfigFile();
|
|
335
|
-
if (
|
|
336
|
-
const content =
|
|
335
|
+
if (fs.exists(configFile)) {
|
|
336
|
+
const content = fs.readFile(configFile, 'utf-8');
|
|
337
337
|
const parsed = YAML.parse(content);
|
|
338
338
|
// Flatten nested config to dot notation
|
|
339
339
|
flatConfig = flattenConfig(parsed);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Real filesystem implementation using Node's fs module
|
|
5
|
+
*/
|
|
6
|
+
export class RealFileSystem {
|
|
7
|
+
exists(path) {
|
|
8
|
+
return existsSync(path);
|
|
9
|
+
}
|
|
10
|
+
readFile(path, encoding) {
|
|
11
|
+
return readFileSync(path, encoding);
|
|
12
|
+
}
|
|
13
|
+
writeFile(path, data) {
|
|
14
|
+
writeFileSync(path, data, 'utf-8');
|
|
15
|
+
}
|
|
16
|
+
readDirectory(path) {
|
|
17
|
+
return readdirSync(path);
|
|
18
|
+
}
|
|
19
|
+
createDirectory(path, options) {
|
|
20
|
+
mkdirSync(path, options);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* In-memory filesystem implementation for testing
|
|
25
|
+
* Simulates filesystem behavior without touching disk
|
|
26
|
+
*/
|
|
27
|
+
export class MemoryFileSystem {
|
|
28
|
+
files = new Map();
|
|
29
|
+
directories = new Set();
|
|
30
|
+
exists(path) {
|
|
31
|
+
return this.files.has(path) || this.directories.has(path);
|
|
32
|
+
}
|
|
33
|
+
readFile(path, _encoding) {
|
|
34
|
+
const content = this.files.get(path);
|
|
35
|
+
if (content === undefined) {
|
|
36
|
+
throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
37
|
+
}
|
|
38
|
+
return content;
|
|
39
|
+
}
|
|
40
|
+
writeFile(path, data) {
|
|
41
|
+
// Auto-create parent directories
|
|
42
|
+
const dir = dirname(path);
|
|
43
|
+
if (dir !== '.' && dir !== path) {
|
|
44
|
+
this.createDirectory(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
this.files.set(path, data);
|
|
47
|
+
}
|
|
48
|
+
readDirectory(path) {
|
|
49
|
+
if (!this.directories.has(path)) {
|
|
50
|
+
throw new Error(`ENOENT: no such file or directory, scandir '${path}'`);
|
|
51
|
+
}
|
|
52
|
+
const results = [];
|
|
53
|
+
const prefix = path.endsWith('/') ? path : `${path}/`;
|
|
54
|
+
// Find all direct children (files and directories)
|
|
55
|
+
for (const filePath of this.files.keys()) {
|
|
56
|
+
if (filePath.startsWith(prefix)) {
|
|
57
|
+
const relative = filePath.slice(prefix.length);
|
|
58
|
+
const firstSlash = relative.indexOf('/');
|
|
59
|
+
if (firstSlash === -1) {
|
|
60
|
+
// Direct file child
|
|
61
|
+
results.push(relative);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const dirPath of this.directories) {
|
|
66
|
+
if (dirPath.startsWith(prefix) && dirPath !== path) {
|
|
67
|
+
const relative = dirPath.slice(prefix.length);
|
|
68
|
+
const firstSlash = relative.indexOf('/');
|
|
69
|
+
if (firstSlash === -1) {
|
|
70
|
+
// Direct directory child
|
|
71
|
+
results.push(relative);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
createDirectory(path, options) {
|
|
78
|
+
if (options?.recursive) {
|
|
79
|
+
// Create all parent directories
|
|
80
|
+
const parts = path.split('/').filter((p) => p);
|
|
81
|
+
let current = path.startsWith('/') ? '/' : '';
|
|
82
|
+
for (const part of parts) {
|
|
83
|
+
current = current === '/' ? `/${part}` : `${current}/${part}`;
|
|
84
|
+
this.directories.add(current);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Non-recursive: parent must exist
|
|
89
|
+
const parent = dirname(path);
|
|
90
|
+
if (parent !== '.' && parent !== path && !this.directories.has(parent)) {
|
|
91
|
+
throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`);
|
|
92
|
+
}
|
|
93
|
+
this.directories.add(path);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all files and directories (useful for test cleanup)
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this.files.clear();
|
|
101
|
+
this.directories.clear();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get all files for debugging
|
|
105
|
+
*/
|
|
106
|
+
getFiles() {
|
|
107
|
+
return new Map(this.files);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Default filesystem instance (uses real fs)
|
|
112
|
+
* Services can accept optional FileSystem parameter for testing
|
|
113
|
+
*/
|
|
114
|
+
export const defaultFileSystem = new RealFileSystem();
|
package/dist/services/loader.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'fs';
|
|
2
1
|
import { homedir } from 'os';
|
|
3
2
|
import { join } from 'path';
|
|
4
3
|
import YAML from 'yaml';
|
|
4
|
+
import { defaultFileSystem } from './filesystem.js';
|
|
5
|
+
import { displayWarning } from './logger.js';
|
|
5
6
|
/**
|
|
6
7
|
* Load user config from ~/.plsrc
|
|
7
8
|
*/
|
|
8
|
-
export function loadUserConfig() {
|
|
9
|
+
export function loadUserConfig(fs = defaultFileSystem) {
|
|
9
10
|
const configPath = join(homedir(), '.plsrc');
|
|
10
|
-
if (!
|
|
11
|
+
if (!fs.exists(configPath)) {
|
|
11
12
|
return {};
|
|
12
13
|
}
|
|
13
14
|
try {
|
|
14
|
-
const content =
|
|
15
|
+
const content = fs.readFile(configPath, 'utf-8');
|
|
15
16
|
const parsed = YAML.parse(content);
|
|
16
17
|
if (parsed && typeof parsed === 'object') {
|
|
17
18
|
return parsed;
|
|
18
19
|
}
|
|
20
|
+
displayWarning('User config file exists but is not a valid object');
|
|
19
21
|
return {};
|
|
20
22
|
}
|
|
21
|
-
catch {
|
|
23
|
+
catch (error) {
|
|
24
|
+
displayWarning('Failed to load user config', error);
|
|
22
25
|
return {};
|
|
23
26
|
}
|
|
24
27
|
}
|
package/dist/services/logger.js
CHANGED
|
@@ -6,6 +6,10 @@ import { Palette } from './colors.js';
|
|
|
6
6
|
* Logs information based on the current debug level setting
|
|
7
7
|
*/
|
|
8
8
|
let currentDebugLevel = DebugLevel.None;
|
|
9
|
+
/**
|
|
10
|
+
* Accumulated warnings to be displayed in the timeline
|
|
11
|
+
*/
|
|
12
|
+
const warnings = [];
|
|
9
13
|
/**
|
|
10
14
|
* Initialize the logger with the current debug level from config
|
|
11
15
|
*/
|
|
@@ -24,6 +28,26 @@ export function setDebugLevel(debug) {
|
|
|
24
28
|
export function getDebugLevel() {
|
|
25
29
|
return currentDebugLevel;
|
|
26
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Store a warning message to be displayed in the timeline
|
|
33
|
+
* Only stores warnings at Info or Verbose debug levels
|
|
34
|
+
*/
|
|
35
|
+
export function displayWarning(message, error) {
|
|
36
|
+
if (currentDebugLevel === DebugLevel.None) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const errorDetails = error instanceof Error ? `: ${error.message}` : '';
|
|
40
|
+
warnings.push(`${message}${errorDetails}`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get all accumulated warnings and clear the list
|
|
44
|
+
* Returns array of warning messages
|
|
45
|
+
*/
|
|
46
|
+
export function getWarnings() {
|
|
47
|
+
const result = [...warnings];
|
|
48
|
+
warnings.length = 0;
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
27
51
|
/**
|
|
28
52
|
* Create debug component for system prompts sent to the LLM
|
|
29
53
|
* Only creates at Verbose level
|
package/dist/services/parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
|
+
import { displayWarning } from './logger.js';
|
|
2
3
|
/**
|
|
3
4
|
* Validate a skill without parsing it fully
|
|
4
5
|
* Returns validation error if skill is invalid, null if valid
|
|
@@ -188,7 +189,8 @@ function parseConfigSchema(content) {
|
|
|
188
189
|
}
|
|
189
190
|
return parsed;
|
|
190
191
|
}
|
|
191
|
-
catch {
|
|
192
|
+
catch (error) {
|
|
193
|
+
displayWarning('Failed to parse config schema in skill', error);
|
|
192
194
|
return undefined;
|
|
193
195
|
}
|
|
194
196
|
}
|
|
@@ -5,12 +5,12 @@ import { routeTasksWithConfirm } from './router.js';
|
|
|
5
5
|
* Handle refinement flow for DEFINE tasks
|
|
6
6
|
* Called when user selects options from a plan with DEFINE tasks
|
|
7
7
|
*/
|
|
8
|
-
export async function handleRefinement(selectedTasks, service, originalCommand,
|
|
8
|
+
export async function handleRefinement(selectedTasks, service, originalCommand, queueHandlers, lifecycleHandlers, workflowHandlers, errorHandlers) {
|
|
9
9
|
// Create and add refinement component to queue
|
|
10
10
|
const refinementDef = createRefinement(getRefiningMessage(), (operation) => {
|
|
11
|
-
|
|
11
|
+
errorHandlers.onAborted(operation);
|
|
12
12
|
});
|
|
13
|
-
|
|
13
|
+
queueHandlers.addToQueue(refinementDef);
|
|
14
14
|
try {
|
|
15
15
|
// Build refined command from selected tasks
|
|
16
16
|
const refinedCommand = selectedTasks
|
|
@@ -23,18 +23,18 @@ export async function handleRefinement(selectedTasks, service, originalCommand,
|
|
|
23
23
|
// Call LLM to refine plan with selected tasks
|
|
24
24
|
const refinedResult = await service.processWithTool(refinedCommand, 'schedule');
|
|
25
25
|
// Complete the Refinement component
|
|
26
|
-
|
|
26
|
+
lifecycleHandlers.completeActive();
|
|
27
27
|
// Add debug components to timeline if present
|
|
28
|
-
if (refinedResult.debug
|
|
29
|
-
|
|
28
|
+
if (refinedResult.debug?.length) {
|
|
29
|
+
workflowHandlers.addToTimeline(...refinedResult.debug);
|
|
30
30
|
}
|
|
31
31
|
// Route refined tasks to appropriate components
|
|
32
|
-
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand,
|
|
32
|
+
routeTasksWithConfirm(refinedResult.tasks, refinedResult.message, service, originalCommand, queueHandlers, workflowHandlers, errorHandlers, false // No DEFINE tasks in refined result
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
catch (err) {
|
|
36
|
-
|
|
36
|
+
lifecycleHandlers.completeActive();
|
|
37
37
|
const errorMessage = formatErrorMessage(err);
|
|
38
|
-
|
|
38
|
+
errorHandlers.onError(errorMessage);
|
|
39
39
|
}
|
|
40
40
|
}
|