trismegistus 1.1.3 → 1.1.5
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/cli.js +59 -46
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { join } from "path";
|
|
|
14
14
|
var DEFAULT_CONFIG = {
|
|
15
15
|
maxRetries: 3,
|
|
16
16
|
timeoutMinutes: 30,
|
|
17
|
+
idleTimeoutSeconds: 15,
|
|
17
18
|
idlePollSeconds: 10,
|
|
18
19
|
taskDelaySeconds: 5
|
|
19
20
|
};
|
|
@@ -33,6 +34,7 @@ var NOTES_TEMPLATE = `# Notes for Claude \u2014 write here, cleared after each r
|
|
|
33
34
|
var CONFIG_TEMPLATE = `# Trismegistus Configuration
|
|
34
35
|
MAX_RETRIES=3
|
|
35
36
|
TIMEOUT_MINUTES=30
|
|
37
|
+
IDLE_TIMEOUT_SECONDS=15
|
|
36
38
|
IDLE_POLL_SECONDS=10
|
|
37
39
|
TASK_DELAY_SECONDS=5
|
|
38
40
|
`;
|
|
@@ -360,6 +362,7 @@ import { join as join3 } from "path";
|
|
|
360
362
|
var KEY_MAP = {
|
|
361
363
|
MAX_RETRIES: "maxRetries",
|
|
362
364
|
TIMEOUT_MINUTES: "timeoutMinutes",
|
|
365
|
+
IDLE_TIMEOUT_SECONDS: "idleTimeoutSeconds",
|
|
363
366
|
IDLE_POLL_SECONDS: "idlePollSeconds",
|
|
364
367
|
TASK_DELAY_SECONDS: "taskDelaySeconds"
|
|
365
368
|
};
|
|
@@ -393,16 +396,24 @@ function loadConfig(projectDir) {
|
|
|
393
396
|
|
|
394
397
|
// src/runner.ts
|
|
395
398
|
import * as pty from "node-pty";
|
|
396
|
-
function stripAnsi(str) {
|
|
397
|
-
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
|
|
398
|
-
}
|
|
399
399
|
function runClaude(opts) {
|
|
400
|
-
const {
|
|
400
|
+
const {
|
|
401
|
+
prompt,
|
|
402
|
+
timeoutMs,
|
|
403
|
+
projectDir,
|
|
404
|
+
idleTimeoutMs = 6e4,
|
|
405
|
+
spawnFn = pty.spawn,
|
|
406
|
+
startupDelayMs = 5e3
|
|
407
|
+
} = opts;
|
|
401
408
|
return new Promise((resolve) => {
|
|
402
409
|
let timedOut = false;
|
|
410
|
+
let idledOut = false;
|
|
403
411
|
let settled = false;
|
|
404
|
-
let outputBuffer = "";
|
|
405
412
|
let promptSent = false;
|
|
413
|
+
let gotFirstData = false;
|
|
414
|
+
let idleTimer = null;
|
|
415
|
+
let outputAfterPrompt = 0;
|
|
416
|
+
const MIN_OUTPUT_FOR_SUCCESS = 500;
|
|
406
417
|
const child = spawnFn("claude", ["--dangerously-skip-permissions"], {
|
|
407
418
|
cols: 120,
|
|
408
419
|
rows: 40,
|
|
@@ -417,43 +428,43 @@ function runClaude(opts) {
|
|
|
417
428
|
if (!settled) {
|
|
418
429
|
settled = true;
|
|
419
430
|
clearTimeout(timer);
|
|
431
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
420
432
|
resolve(result);
|
|
421
433
|
}
|
|
422
434
|
}
|
|
435
|
+
function resetIdleTimer() {
|
|
436
|
+
if (!promptSent || settled) return;
|
|
437
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
438
|
+
idleTimer = setTimeout(() => {
|
|
439
|
+
idledOut = true;
|
|
440
|
+
child.kill();
|
|
441
|
+
}, idleTimeoutMs);
|
|
442
|
+
}
|
|
423
443
|
child.onData((data) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
444
|
+
process.stdout.write(data);
|
|
445
|
+
if (promptSent) outputAfterPrompt += data.length;
|
|
446
|
+
resetIdleTimer();
|
|
447
|
+
if (!gotFirstData) {
|
|
448
|
+
gotFirstData = true;
|
|
449
|
+
setTimeout(() => {
|
|
450
|
+
if (!promptSent && !settled) {
|
|
451
|
+
promptSent = true;
|
|
452
|
+
child.write(prompt);
|
|
453
|
+
setTimeout(() => child.write("\r"), 500);
|
|
454
|
+
}
|
|
455
|
+
}, startupDelayMs);
|
|
436
456
|
}
|
|
437
457
|
});
|
|
438
458
|
child.onExit(({ exitCode }) => {
|
|
459
|
+
const idleSuccess = idledOut && outputAfterPrompt >= MIN_OUTPUT_FOR_SUCCESS;
|
|
439
460
|
settle({
|
|
440
|
-
success: !timedOut && exitCode === 0,
|
|
461
|
+
success: idleSuccess || !timedOut && !idledOut && exitCode === 0,
|
|
441
462
|
exitCode: timedOut ? 124 : exitCode,
|
|
442
463
|
timedOut
|
|
443
464
|
});
|
|
444
465
|
});
|
|
445
466
|
});
|
|
446
467
|
}
|
|
447
|
-
function isReadyForInput(output) {
|
|
448
|
-
const lines = output.split("\n");
|
|
449
|
-
const tail = lines.slice(-5).join("\n");
|
|
450
|
-
return /[>❯]\s*$/.test(tail) || /╰─\s*$/.test(tail) || /\$\s*$/.test(tail);
|
|
451
|
-
}
|
|
452
|
-
function isTaskComplete(output) {
|
|
453
|
-
const lines = output.split("\n");
|
|
454
|
-
const tail = lines.slice(-5).join("\n");
|
|
455
|
-
return /[>❯]\s*$/.test(tail) || /╰─\s*$/.test(tail);
|
|
456
|
-
}
|
|
457
468
|
|
|
458
469
|
// src/daemon.ts
|
|
459
470
|
import { createRequire } from "module";
|
|
@@ -489,9 +500,15 @@ function preflight(projectDir) {
|
|
|
489
500
|
return { ok: errors.length === 0, errors, warnings };
|
|
490
501
|
}
|
|
491
502
|
function buildPrompt(taskText, attempt, maxRetries, notes, handoff) {
|
|
492
|
-
let prompt = `You are
|
|
503
|
+
let prompt = `You are an autonomous agent (attempt ${attempt}/${maxRetries}). Complete this task fully without asking questions.
|
|
504
|
+
|
|
505
|
+
WORKFLOW:
|
|
506
|
+
1. Break the task into subtasks. Use the Agent tool to spawn sub-agents for independent work when it makes sense.
|
|
507
|
+
2. Plan before coding \u2014 understand the codebase first, then implement.
|
|
508
|
+
3. Run tests and verify your work before finishing.
|
|
509
|
+
4. Commit your changes with a clear commit message.
|
|
493
510
|
|
|
494
|
-
If you
|
|
511
|
+
If you cannot finish in time, write a summary of what you did and what remains to .trismegistus/handoff so the next session can continue.`;
|
|
495
512
|
if (notes) {
|
|
496
513
|
prompt += `
|
|
497
514
|
|
|
@@ -513,12 +530,12 @@ function sleep(ms) {
|
|
|
513
530
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
514
531
|
}
|
|
515
532
|
async function runDaemon(opts) {
|
|
516
|
-
const { projectDir, spawnFn, maxIterations, onLog } = opts;
|
|
533
|
+
const { projectDir, spawnFn, maxIterations, onLog, startupDelayMs } = opts;
|
|
517
534
|
const log = onLog ?? ((msg) => console.log(msg));
|
|
518
535
|
const config = loadConfig(projectDir);
|
|
519
536
|
log(` TMG v${VERSION} \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
|
|
520
537
|
log(` Project: ${projectDir}`);
|
|
521
|
-
log(` Timeout: ${config.timeoutMinutes}m | Max retries: ${config.maxRetries}`);
|
|
538
|
+
log(` Timeout: ${config.timeoutMinutes}m | Idle: ${config.idleTimeoutSeconds}s | Max retries: ${config.maxRetries}`);
|
|
522
539
|
log("");
|
|
523
540
|
let iterations = 0;
|
|
524
541
|
while (true) {
|
|
@@ -548,8 +565,10 @@ async function runDaemon(opts) {
|
|
|
548
565
|
const result = await runClaude({
|
|
549
566
|
prompt,
|
|
550
567
|
timeoutMs: config.timeoutMinutes * 60 * 1e3,
|
|
568
|
+
idleTimeoutMs: config.idleTimeoutSeconds * 1e3,
|
|
551
569
|
projectDir,
|
|
552
|
-
spawnFn
|
|
570
|
+
spawnFn,
|
|
571
|
+
startupDelayMs
|
|
553
572
|
});
|
|
554
573
|
if (result.success) {
|
|
555
574
|
setTaskStatus(projectDir, task.text, "x");
|
|
@@ -615,10 +634,11 @@ function startTunnel(name, options = {}) {
|
|
|
615
634
|
)
|
|
616
635
|
);
|
|
617
636
|
}
|
|
637
|
+
const safeName = name.replace(/[^\w-=]/g, "").slice(0, 50) || "tmg-tunnel";
|
|
618
638
|
return new Promise((resolve, reject) => {
|
|
619
639
|
const child = spawn2(
|
|
620
640
|
"code",
|
|
621
|
-
["tunnel", "--name",
|
|
641
|
+
["tunnel", "--name", safeName, "--accept-server-license-terms"],
|
|
622
642
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
623
643
|
);
|
|
624
644
|
let stderr = "";
|
|
@@ -636,17 +656,8 @@ function startTunnel(name, options = {}) {
|
|
|
636
656
|
${stderr}` : `Timed out waiting for tunnel URL. You may need to authenticate \u2014 run \`code tunnel\` manually first.`;
|
|
637
657
|
settle(() => reject(new Error(msg)));
|
|
638
658
|
}, TIMEOUT_MS);
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
stderr += text;
|
|
642
|
-
if (options.onOutput) {
|
|
643
|
-
for (const line of text.split("\n").filter(Boolean)) {
|
|
644
|
-
options.onOutput(line);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
child.stdout?.on("data", (chunk) => {
|
|
649
|
-
const text = chunk.toString();
|
|
659
|
+
function handleOutput(text, isStderr) {
|
|
660
|
+
if (isStderr) stderr += text;
|
|
650
661
|
if (options.onOutput) {
|
|
651
662
|
for (const line of text.split("\n").filter(Boolean)) {
|
|
652
663
|
options.onOutput(line);
|
|
@@ -657,7 +668,9 @@ ${stderr}` : `Timed out waiting for tunnel URL. You may need to authenticate \u2
|
|
|
657
668
|
clearTimeout(timer);
|
|
658
669
|
settle(() => resolve({ url: match[1], process: child }));
|
|
659
670
|
}
|
|
660
|
-
}
|
|
671
|
+
}
|
|
672
|
+
child.stderr?.on("data", (chunk) => handleOutput(chunk.toString(), true));
|
|
673
|
+
child.stdout?.on("data", (chunk) => handleOutput(chunk.toString(), false));
|
|
661
674
|
child.on("error", (err) => {
|
|
662
675
|
clearTimeout(timer);
|
|
663
676
|
settle(() => reject(err));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trismegistus",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "A local persistent daemon that runs AI sessions from a task queue, with mobile support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"commander": "^13.0.0",
|
|
42
|
-
"node-pty": "^1.
|
|
42
|
+
"node-pty": "^1.2.0-beta.11",
|
|
43
43
|
"qrcode-terminal": "^0.12.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|