whitesmith 0.0.0 ā 0.0.2
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/README.md +228 -0
- package/dist/auto-work.d.ts +11 -0
- package/dist/auto-work.d.ts.map +1 -0
- package/dist/auto-work.js +22 -0
- package/dist/auto-work.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +108 -1
- package/dist/cli.js.map +1 -1
- package/dist/comment.d.ts +29 -0
- package/dist/comment.d.ts.map +1 -0
- package/dist/comment.js +390 -0
- package/dist/comment.js.map +1 -0
- package/dist/git.d.ts +12 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +57 -14
- package/dist/git.js.map +1 -1
- package/dist/harnesses/agent-harness.d.ts +13 -0
- package/dist/harnesses/agent-harness.d.ts.map +1 -1
- package/dist/harnesses/index.d.ts +1 -1
- package/dist/harnesses/index.d.ts.map +1 -1
- package/dist/harnesses/pi.d.ts +7 -5
- package/dist/harnesses/pi.d.ts.map +1 -1
- package/dist/harnesses/pi.js +122 -9
- package/dist/harnesses/pi.js.map +1 -1
- package/dist/install-ci.d.ts +7 -0
- package/dist/install-ci.d.ts.map +1 -0
- package/dist/install-ci.js +760 -0
- package/dist/install-ci.js.map +1 -0
- package/dist/orchestrator.d.ts +24 -4
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +254 -63
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +1 -0
- package/dist/prompts.js.map +1 -1
- package/dist/providers/github-ci.d.ts +16 -0
- package/dist/providers/github-ci.d.ts.map +1 -0
- package/dist/providers/github-ci.js +733 -0
- package/dist/providers/github-ci.js.map +1 -0
- package/dist/providers/github.d.ts +21 -0
- package/dist/providers/github.d.ts.map +1 -1
- package/dist/providers/github.js +88 -3
- package/dist/providers/github.js.map +1 -1
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/issue-provider.d.ts +26 -0
- package/dist/providers/issue-provider.d.ts.map +1 -1
- package/dist/task-manager.d.ts +4 -0
- package/dist/task-manager.d.ts.map +1 -1
- package/dist/task-manager.js +6 -0
- package/dist/task-manager.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -1
- package/src/auto-work.ts +26 -0
- package/src/cli.ts +123 -1
- package/src/comment.ts +531 -0
- package/src/git.ts +58 -12
- package/src/harnesses/agent-harness.ts +15 -0
- package/src/harnesses/index.ts +1 -1
- package/src/harnesses/pi.ts +146 -10
- package/src/orchestrator.ts +290 -72
- package/src/prompts.ts +1 -0
- package/src/providers/github-ci.ts +840 -0
- package/src/providers/github.ts +118 -5
- package/src/providers/index.ts +1 -0
- package/src/providers/issue-provider.ts +25 -1
- package/src/task-manager.ts +7 -0
- package/src/types.ts +11 -0
package/src/harnesses/pi.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import {exec} from 'node:child_process';
|
|
1
|
+
import {exec, execSync} from 'node:child_process';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
|
+
import {homedir} from 'node:os';
|
|
3
4
|
import * as path from 'node:path';
|
|
4
|
-
import type {AgentHarness} from './agent-harness.js';
|
|
5
|
+
import type {AgentHarness, AgentHarnessConfig} from './agent-harness.js';
|
|
6
|
+
|
|
7
|
+
/** Subset of pi JSON event fields we care about */
|
|
8
|
+
interface PiEvent {
|
|
9
|
+
type: string;
|
|
10
|
+
toolName?: string;
|
|
11
|
+
args?: any;
|
|
12
|
+
result?: any;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
assistantMessageEvent?: {type: string; delta?: string};
|
|
15
|
+
reason?: string;
|
|
16
|
+
attempt?: number;
|
|
17
|
+
maxAttempts?: number;
|
|
18
|
+
delayMs?: number;
|
|
19
|
+
errorMessage?: string;
|
|
20
|
+
success?: boolean;
|
|
21
|
+
finalError?: string;
|
|
22
|
+
}
|
|
5
23
|
|
|
6
24
|
/**
|
|
7
25
|
* Agent harness for @mariozechner/pi-coding-agent.
|
|
@@ -10,12 +28,72 @@ import type {AgentHarness} from './agent-harness.js';
|
|
|
10
28
|
*/
|
|
11
29
|
export class PiHarness implements AgentHarness {
|
|
12
30
|
private cmd: string;
|
|
31
|
+
private provider: string;
|
|
32
|
+
private model: string;
|
|
13
33
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
constructor(config: AgentHarnessConfig) {
|
|
35
|
+
this.cmd = config.cmd;
|
|
36
|
+
this.provider = config.provider;
|
|
37
|
+
this.model = config.model;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async validate(): Promise<void> {
|
|
41
|
+
// Check if the command exists
|
|
42
|
+
try {
|
|
43
|
+
execSync(`which ${this.cmd}`, {stdio: 'pipe'});
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Agent command '${this.cmd}' not found. ` +
|
|
47
|
+
`Make sure it is installed and available in PATH. ` +
|
|
48
|
+
`For pi-coding-agent: npm install -g @mariozechner/pi-coding-agent`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check auth.json exists and has the expected provider
|
|
53
|
+
const homeDir = process.env.HOME || homedir();
|
|
54
|
+
const authJsonPath = path.join(homeDir, '.pi', 'agent', 'auth.json');
|
|
55
|
+
if (fs.existsSync(authJsonPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const authData = JSON.parse(fs.readFileSync(authJsonPath, 'utf-8'));
|
|
58
|
+
const providers = Object.keys(authData);
|
|
59
|
+
console.log(`Auth file found at ${authJsonPath} with providers: ${providers.join(', ')}`);
|
|
60
|
+
if (!authData[this.provider]) {
|
|
61
|
+
console.warn(
|
|
62
|
+
`WARNING: Provider '${this.provider}' not found in auth.json (has: ${providers.join(', ')})`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} catch (e: any) {
|
|
66
|
+
console.warn(`WARNING: Could not parse auth.json: ${e.message}`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.warn(`WARNING: No auth.json found at ${authJsonPath}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Validate auth by making a minimal API call
|
|
73
|
+
try {
|
|
74
|
+
const result = execSync(
|
|
75
|
+
`${this.cmd} --print --no-tools --no-session --provider ${this.provider} --model ${this.model} "respond with OK"`,
|
|
76
|
+
{stdio: 'pipe', timeout: 30_000},
|
|
77
|
+
);
|
|
78
|
+
const output = result.toString().trim();
|
|
79
|
+
if (!output) {
|
|
80
|
+
throw new Error('Empty response');
|
|
81
|
+
}
|
|
82
|
+
console.log(`Auth check passed (response: ${output.slice(0, 20)})`);
|
|
83
|
+
} catch (error: any) {
|
|
84
|
+
const stderr = error.stderr?.toString() || '';
|
|
85
|
+
const stdout = error.stdout?.toString() || '';
|
|
86
|
+
const details =
|
|
87
|
+
[stderr, stdout].filter(Boolean).join('\n') || error.message || 'unknown error';
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Agent auth validation failed. Ensure valid credentials are configured.\n` +
|
|
90
|
+
`Set ANTHROPIC_API_KEY or configure OAuth via ~/.pi/agent/auth.json\n` +
|
|
91
|
+
`Auth file path: ${authJsonPath}\n` +
|
|
92
|
+
`Auth file exists: ${fs.existsSync(authJsonPath)}\n` +
|
|
93
|
+
`HOME: ${homeDir}\n` +
|
|
94
|
+
`Details: ${details.slice(0, 800)}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
19
97
|
}
|
|
20
98
|
|
|
21
99
|
async run(options: {
|
|
@@ -23,13 +101,13 @@ export class PiHarness implements AgentHarness {
|
|
|
23
101
|
workDir: string;
|
|
24
102
|
logFile?: string;
|
|
25
103
|
}): Promise<{output: string; exitCode: number}> {
|
|
26
|
-
// Write prompt to a temp file
|
|
104
|
+
// Write prompt to a temp file and use @file syntax so pi reads contents
|
|
27
105
|
const promptFile = path.join(options.workDir, '.whitesmith-prompt.md');
|
|
28
106
|
fs.writeFileSync(promptFile, options.prompt, 'utf-8');
|
|
29
107
|
|
|
30
108
|
try {
|
|
31
109
|
const result = await this.exec(
|
|
32
|
-
`${this.cmd} --
|
|
110
|
+
`${this.cmd} --print --mode json --no-session --provider ${this.provider} --model ${this.model} @"${promptFile}"`,
|
|
33
111
|
options.workDir,
|
|
34
112
|
options.logFile,
|
|
35
113
|
);
|
|
@@ -44,6 +122,43 @@ export class PiHarness implements AgentHarness {
|
|
|
44
122
|
}
|
|
45
123
|
}
|
|
46
124
|
|
|
125
|
+
/** Format a pi JSON event as a human-readable log line (null = skip) */
|
|
126
|
+
private formatEvent(event: PiEvent): string | null {
|
|
127
|
+
switch (event.type) {
|
|
128
|
+
case 'agent_start':
|
|
129
|
+
return '\nš¤ Agent started';
|
|
130
|
+
case 'agent_end':
|
|
131
|
+
return '\nš Agent finished';
|
|
132
|
+
case 'turn_start':
|
|
133
|
+
return '\n--- turn ---';
|
|
134
|
+
case 'message_update': {
|
|
135
|
+
const evt = event.assistantMessageEvent;
|
|
136
|
+
if (evt?.type === 'text_delta' && evt.delta) return evt.delta;
|
|
137
|
+
if (evt?.type === 'thinking_delta' && evt.delta) return evt.delta;
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
case 'tool_execution_start': {
|
|
141
|
+
const argsStr = JSON.stringify(event.args);
|
|
142
|
+
const truncArgs = argsStr.length > 200 ? argsStr.slice(0, 200) + 'ā¦' : argsStr;
|
|
143
|
+
return `\nš§ ${event.toolName}(${truncArgs})`;
|
|
144
|
+
}
|
|
145
|
+
case 'tool_execution_end': {
|
|
146
|
+
const icon = event.isError ? 'ā' : 'ā
';
|
|
147
|
+
const res = typeof event.result === 'string' ? event.result : JSON.stringify(event.result);
|
|
148
|
+
const truncRes = res.length > 500 ? res.slice(0, 500) + 'ā¦' : res;
|
|
149
|
+
return `${icon} ${event.toolName} ā ${truncRes}`;
|
|
150
|
+
}
|
|
151
|
+
case 'compaction_start':
|
|
152
|
+
return `\nš¦ Compaction (${event.reason})`;
|
|
153
|
+
case 'auto_retry_start':
|
|
154
|
+
return `\nš Retry ${event.attempt}/${event.maxAttempts}: ${event.errorMessage}`;
|
|
155
|
+
case 'auto_retry_end':
|
|
156
|
+
return event.success ? 'š Retry succeeded' : `š Retry failed: ${event.finalError}`;
|
|
157
|
+
default:
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
47
162
|
private exec(
|
|
48
163
|
cmd: string,
|
|
49
164
|
workDir: string,
|
|
@@ -56,15 +171,35 @@ export class PiHarness implements AgentHarness {
|
|
|
56
171
|
timeout: 30 * 60 * 1000, // 30 minute timeout
|
|
57
172
|
});
|
|
58
173
|
|
|
174
|
+
// Close stdin immediately so pi doesn't hang waiting for piped input
|
|
175
|
+
child.stdin?.end();
|
|
176
|
+
|
|
59
177
|
let output = '';
|
|
178
|
+
let lineBuffer = '';
|
|
60
179
|
const logStream = logFile
|
|
61
180
|
? fs.createWriteStream(path.resolve(workDir, logFile), {flags: 'a'})
|
|
62
181
|
: null;
|
|
63
182
|
|
|
183
|
+
const processLine = (line: string) => {
|
|
184
|
+
if (!line.trim()) return;
|
|
185
|
+
try {
|
|
186
|
+
const event: PiEvent = JSON.parse(line);
|
|
187
|
+
const formatted = this.formatEvent(event);
|
|
188
|
+
if (formatted !== null) {
|
|
189
|
+
process.stdout.write(formatted);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
process.stdout.write(line + '\n');
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
64
196
|
child.stdout?.on('data', (data: string) => {
|
|
65
197
|
output += data;
|
|
66
|
-
process.stdout.write(data);
|
|
67
198
|
logStream?.write(data);
|
|
199
|
+
lineBuffer += data;
|
|
200
|
+
const lines = lineBuffer.split('\n');
|
|
201
|
+
lineBuffer = lines.pop() ?? '';
|
|
202
|
+
for (const line of lines) processLine(line);
|
|
68
203
|
});
|
|
69
204
|
|
|
70
205
|
child.stderr?.on('data', (data: string) => {
|
|
@@ -74,6 +209,7 @@ export class PiHarness implements AgentHarness {
|
|
|
74
209
|
});
|
|
75
210
|
|
|
76
211
|
child.on('close', (code) => {
|
|
212
|
+
if (lineBuffer.trim()) processLine(lineBuffer);
|
|
77
213
|
logStream?.end();
|
|
78
214
|
resolve({output, exitCode: code ?? 1});
|
|
79
215
|
});
|