@sickr/cli 0.9.13 → 0.9.15
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/run.js +51 -4
- package/package.json +1 -1
package/dist/run.js
CHANGED
|
@@ -24,7 +24,7 @@ import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, rea
|
|
|
24
24
|
import { homedir } from 'node:os';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
26
|
import { randomUUID } from 'node:crypto';
|
|
27
|
-
import { execFileSync } from 'node:child_process';
|
|
27
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
28
28
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
29
29
|
import { readCredentials } from './auth.js';
|
|
30
30
|
import { runsDir } from './recorder.js';
|
|
@@ -160,6 +160,10 @@ export function cleanPtyResponse(input, lastPrompt = '') {
|
|
|
160
160
|
.join('\n')
|
|
161
161
|
.trim();
|
|
162
162
|
}
|
|
163
|
+
function hasPtyPromptMarker(input) {
|
|
164
|
+
const clean = stripAnsi(input).replace(/[\u2800-\u28ff]/g, '');
|
|
165
|
+
return clean.includes('>>> Send a message (/? for help)') || /(?:^|\n)\s*>>>\s*$/.test(clean);
|
|
166
|
+
}
|
|
163
167
|
export class HooklessPtyEventSynth {
|
|
164
168
|
identity;
|
|
165
169
|
send;
|
|
@@ -169,7 +173,7 @@ export class HooklessPtyEventSynth {
|
|
|
169
173
|
lastPrompt = '';
|
|
170
174
|
awaitingResponse = false;
|
|
171
175
|
responseTimer = null;
|
|
172
|
-
constructor(identity, send, responseDelayMs =
|
|
176
|
+
constructor(identity, send, responseDelayMs = 2500) {
|
|
173
177
|
this.identity = identity;
|
|
174
178
|
this.send = send;
|
|
175
179
|
this.responseDelayMs = responseDelayMs;
|
|
@@ -191,18 +195,25 @@ export class HooklessPtyEventSynth {
|
|
|
191
195
|
this.recordPrompt(submitted);
|
|
192
196
|
}
|
|
193
197
|
recordPrompt(text) {
|
|
198
|
+
this.prepareResponse(text);
|
|
199
|
+
this.sendEvent('prompt', 'Prompt', this.lastPrompt);
|
|
200
|
+
}
|
|
201
|
+
prepareResponse(text) {
|
|
194
202
|
const prompt = stripAnsi(text).replace(/\r/g, '\n').trim();
|
|
195
203
|
if (!prompt)
|
|
196
204
|
return;
|
|
197
205
|
this.lastPrompt = prompt;
|
|
198
206
|
this.outputBuffer = '';
|
|
199
207
|
this.awaitingResponse = true;
|
|
200
|
-
this.sendEvent('prompt', 'Prompt', prompt);
|
|
201
208
|
}
|
|
202
209
|
observeOutput(chunk) {
|
|
203
210
|
if (!this.awaitingResponse)
|
|
204
211
|
return;
|
|
205
212
|
this.outputBuffer += chunk;
|
|
213
|
+
if (hasPtyPromptMarker(this.outputBuffer)) {
|
|
214
|
+
this.flushResponse();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
206
217
|
if (this.responseTimer)
|
|
207
218
|
clearTimeout(this.responseTimer);
|
|
208
219
|
this.responseTimer = setTimeout(() => this.flushResponse(), this.responseDelayMs);
|
|
@@ -403,6 +414,40 @@ export function providerArgsFor(agent, existingArgs) {
|
|
|
403
414
|
return ['run', model, ...existingArgs];
|
|
404
415
|
return existingArgs;
|
|
405
416
|
}
|
|
417
|
+
function ollamaHost() {
|
|
418
|
+
return process.env.OLLAMA_HOST || 'http://127.0.0.1:11434';
|
|
419
|
+
}
|
|
420
|
+
async function ollamaServerReady() {
|
|
421
|
+
try {
|
|
422
|
+
const res = await fetch(`${ollamaHost().replace(/\/$/, '')}/api/tags`, { signal: AbortSignal.timeout(1000) });
|
|
423
|
+
return res.ok;
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
export async function ensureOllamaServer(agentBin, opts = {}) {
|
|
430
|
+
if (await ollamaServerReady())
|
|
431
|
+
return;
|
|
432
|
+
const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(agentBin);
|
|
433
|
+
const child = spawn(agentBin, ['serve'], {
|
|
434
|
+
cwd: process.cwd(),
|
|
435
|
+
env: process.env,
|
|
436
|
+
detached: true,
|
|
437
|
+
stdio: 'ignore',
|
|
438
|
+
windowsHide: true,
|
|
439
|
+
shell: needsShell,
|
|
440
|
+
});
|
|
441
|
+
child.unref();
|
|
442
|
+
const attempts = opts.attempts ?? 20;
|
|
443
|
+
const delayMs = opts.delayMs ?? 250;
|
|
444
|
+
for (let i = 0; i < attempts; i++) {
|
|
445
|
+
await sleep(delayMs);
|
|
446
|
+
if (await ollamaServerReady())
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
throw new Error('Ollama server did not start. Try running `ollama serve` in another terminal, then retry `sickr run ollama`.');
|
|
450
|
+
}
|
|
406
451
|
export async function startRun(opts) {
|
|
407
452
|
const creds = readCredentials();
|
|
408
453
|
if (!creds) {
|
|
@@ -424,6 +469,8 @@ export async function startRun(opts) {
|
|
|
424
469
|
ensureRecordingHooks(opts.agent);
|
|
425
470
|
const agentBin = resolveAgent(opts.agent);
|
|
426
471
|
const provider = providerForAgent(opts.agent);
|
|
472
|
+
if (provider === 'ollama')
|
|
473
|
+
await ensureOllamaServer(agentBin);
|
|
427
474
|
const agentLabel = provider ? PROVIDERS[provider].recordLabel : opts.agent;
|
|
428
475
|
const runnerId = randomUUID();
|
|
429
476
|
const runnerIdentity = { agent: agentLabel, runner: runnerId, sessions: new Set() };
|
|
@@ -604,7 +651,7 @@ export async function startRun(opts) {
|
|
|
604
651
|
pty.write('\r');
|
|
605
652
|
}
|
|
606
653
|
catch { /* pty exited */ } }, 80);
|
|
607
|
-
hooklessSynth?.
|
|
654
|
+
hooklessSynth?.prepareResponse(m.text);
|
|
608
655
|
}
|
|
609
656
|
catch { /* PTY may have exited */ }
|
|
610
657
|
if (opts.verbose)
|