@tarcisiopgs/lisa 1.16.0 → 1.17.0
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
CHANGED
|
@@ -189,6 +189,7 @@ repos:
|
|
|
189
189
|
loop:
|
|
190
190
|
cooldown: 10 # seconds between issues
|
|
191
191
|
max_sessions: 0 # 0 = unlimited
|
|
192
|
+
session_timeout: 0 # seconds per provider run (0 = disabled)
|
|
192
193
|
|
|
193
194
|
# Optional — kill stuck providers
|
|
194
195
|
overseer:
|
|
@@ -227,6 +228,19 @@ When `--concurrency` is greater than 1, worktree mode is enforced automatically.
|
|
|
227
228
|
|
|
228
229
|
---
|
|
229
230
|
|
|
231
|
+
### Session Timeout
|
|
232
|
+
|
|
233
|
+
If a provider hangs (e.g. misconfigured model, network issue), Lisa can kill it after a configurable duration:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
loop:
|
|
237
|
+
session_timeout: 300 # kill provider after 5 minutes (0 = disabled, default)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
When the timeout fires, the provider process is killed and the error is eligible for fallback — Lisa will try the next model in your chain. This is disabled by default so long-running sessions work uninterrupted.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
230
244
|
## Writing Issues
|
|
231
245
|
|
|
232
246
|
Issue quality is the single biggest factor in PR quality. Lisa validates issues before accepting them — vague tickets without clear criteria are skipped and labelled `needs-spec`.
|
|
@@ -743,6 +743,9 @@ function useKanbanState(bellEnabled) {
|
|
|
743
743
|
})
|
|
744
744
|
);
|
|
745
745
|
};
|
|
746
|
+
const onLogFile = (issueId, logFile) => {
|
|
747
|
+
setCards((prev) => prev.map((c) => c.id === issueId ? { ...c, logFile } : c));
|
|
748
|
+
};
|
|
746
749
|
const onOutput = (issueId, text) => {
|
|
747
750
|
setCards(
|
|
748
751
|
(prev) => prev.map((c) => c.id === issueId ? { ...c, outputLog: c.outputLog + text } : c)
|
|
@@ -757,6 +760,7 @@ function useKanbanState(bellEnabled) {
|
|
|
757
760
|
kanbanEmitter.on("issue:killed", onKilled);
|
|
758
761
|
kanbanEmitter.on("provider:paused", onProviderPaused);
|
|
759
762
|
kanbanEmitter.on("provider:resumed", onProviderResumed);
|
|
763
|
+
kanbanEmitter.on("issue:log-file", onLogFile);
|
|
760
764
|
kanbanEmitter.on("issue:output", onOutput);
|
|
761
765
|
const onModelChanged = (model) => setModelInUse(model);
|
|
762
766
|
kanbanEmitter.on("provider:model-changed", onModelChanged);
|
|
@@ -786,6 +790,7 @@ function useKanbanState(bellEnabled) {
|
|
|
786
790
|
kanbanEmitter.off("issue:killed", onKilled);
|
|
787
791
|
kanbanEmitter.off("provider:paused", onProviderPaused);
|
|
788
792
|
kanbanEmitter.off("provider:resumed", onProviderResumed);
|
|
793
|
+
kanbanEmitter.off("issue:log-file", onLogFile);
|
|
789
794
|
kanbanEmitter.off("issue:output", onOutput);
|
|
790
795
|
kanbanEmitter.off("provider:model-changed", onModelChanged);
|
|
791
796
|
kanbanEmitter.off("work:empty", onEmpty);
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
ok,
|
|
33
33
|
setOutputMode,
|
|
34
34
|
warn
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-WDGLMZB7.js";
|
|
36
36
|
import {
|
|
37
37
|
notify,
|
|
38
38
|
resetTitle,
|
|
@@ -522,6 +522,28 @@ function spawnWithPty(command, options = {}) {
|
|
|
522
522
|
return { proc, isPty: false };
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
// src/providers/timeout.ts
|
|
526
|
+
var TIMEOUT_MESSAGE = "\n[lisa-timeout] Provider killed: exceeded session_timeout. Eligible for fallback.\n";
|
|
527
|
+
function createSessionTimeout(proc, timeoutSeconds) {
|
|
528
|
+
if (!timeoutSeconds || timeoutSeconds <= 0) {
|
|
529
|
+
return { stop() {
|
|
530
|
+
}, wasTimedOut: () => false };
|
|
531
|
+
}
|
|
532
|
+
let timedOut = false;
|
|
533
|
+
const timer = setTimeout(() => {
|
|
534
|
+
timedOut = true;
|
|
535
|
+
proc.kill("SIGTERM");
|
|
536
|
+
}, timeoutSeconds * 1e3);
|
|
537
|
+
return {
|
|
538
|
+
stop() {
|
|
539
|
+
clearTimeout(timer);
|
|
540
|
+
},
|
|
541
|
+
wasTimedOut() {
|
|
542
|
+
return timedOut;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
525
547
|
// src/providers/aider.ts
|
|
526
548
|
var AIDER_API_KEY_ENV_VARS = [
|
|
527
549
|
"OPENAI_API_KEY",
|
|
@@ -561,12 +583,25 @@ var AiderProvider = class {
|
|
|
561
583
|
try {
|
|
562
584
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
563
585
|
const command = `aider --message-file '${promptFile}' --yes-always ${modelFlag}`;
|
|
586
|
+
log(
|
|
587
|
+
`[aider] Running: aider --message-file --yes-always ${modelFlag || "(default model)"}`.trim()
|
|
588
|
+
);
|
|
589
|
+
if (opts.issueId) {
|
|
590
|
+
kanbanEmitter.emit(
|
|
591
|
+
"issue:output",
|
|
592
|
+
opts.issueId,
|
|
593
|
+
`${`$ aider --message-file --yes-always ${modelFlag || "(default model)"}
|
|
594
|
+
`.trim()}
|
|
595
|
+
`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
564
598
|
const { proc, isPty } = spawnWithPty(command, {
|
|
565
599
|
cwd: opts.cwd,
|
|
566
600
|
env: { ...process.env, ...opts.env }
|
|
567
601
|
});
|
|
568
602
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
569
603
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
604
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
570
605
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
571
606
|
const chunks = [];
|
|
572
607
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -595,14 +630,17 @@ var AiderProvider = class {
|
|
|
595
630
|
const exitCode = await new Promise((resolve13) => {
|
|
596
631
|
proc.on("close", (code) => {
|
|
597
632
|
overseer?.stop();
|
|
633
|
+
sessionTimeout.stop();
|
|
598
634
|
resolve13(code ?? 1);
|
|
599
635
|
});
|
|
600
636
|
});
|
|
601
|
-
if (
|
|
637
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
638
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
639
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
602
640
|
chunks.push(STUCK_MESSAGE);
|
|
603
641
|
}
|
|
604
642
|
return {
|
|
605
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
643
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
606
644
|
output: chunks.join(""),
|
|
607
645
|
duration: Date.now() - start
|
|
608
646
|
};
|
|
@@ -649,6 +687,11 @@ var ClaudeProvider = class {
|
|
|
649
687
|
flags.push("--model", opts.model);
|
|
650
688
|
}
|
|
651
689
|
const command = `claude ${flags.join(" ")} "$(cat '${promptFile}')"`;
|
|
690
|
+
log(`[claude] Running: claude ${flags.join(" ")}`.trim());
|
|
691
|
+
if (opts.issueId) {
|
|
692
|
+
kanbanEmitter.emit("issue:output", opts.issueId, `$ claude ${flags.join(" ")}
|
|
693
|
+
`);
|
|
694
|
+
}
|
|
652
695
|
const spawnEnv = { ...process.env, ...opts.env, CLAUDECODE: void 0 };
|
|
653
696
|
const isNestedInClaude = Boolean(process.env.CLAUDECODE);
|
|
654
697
|
let proc;
|
|
@@ -665,6 +708,7 @@ var ClaudeProvider = class {
|
|
|
665
708
|
}
|
|
666
709
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
667
710
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
711
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
668
712
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
669
713
|
const chunks = [];
|
|
670
714
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -693,14 +737,17 @@ var ClaudeProvider = class {
|
|
|
693
737
|
const exitCode = await new Promise((resolve13) => {
|
|
694
738
|
proc.on("close", (code) => {
|
|
695
739
|
overseer?.stop();
|
|
740
|
+
sessionTimeout.stop();
|
|
696
741
|
resolve13(code ?? 1);
|
|
697
742
|
});
|
|
698
743
|
});
|
|
699
|
-
if (
|
|
744
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
745
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
746
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
700
747
|
chunks.push(STUCK_MESSAGE);
|
|
701
748
|
}
|
|
702
749
|
return {
|
|
703
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
750
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
704
751
|
output: chunks.join(""),
|
|
705
752
|
duration: Date.now() - start
|
|
706
753
|
};
|
|
@@ -742,12 +789,24 @@ var CodexProvider = class {
|
|
|
742
789
|
try {
|
|
743
790
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
744
791
|
const command = `codex exec --dangerously-bypass-approvals-and-sandbox --ephemeral ${modelFlag} "$(cat '${promptFile}')"`;
|
|
792
|
+
log(
|
|
793
|
+
`[codex] Running: codex exec --dangerously-bypass-approvals-and-sandbox --ephemeral ${modelFlag || "(default model)"}`.trim()
|
|
794
|
+
);
|
|
795
|
+
if (opts.issueId) {
|
|
796
|
+
kanbanEmitter.emit(
|
|
797
|
+
"issue:output",
|
|
798
|
+
opts.issueId,
|
|
799
|
+
`$ codex exec --dangerously-bypass-approvals-and-sandbox --ephemeral ${modelFlag || "(default model)"}
|
|
800
|
+
`.trim() + "\n"
|
|
801
|
+
);
|
|
802
|
+
}
|
|
745
803
|
const { proc, isPty } = spawnWithPty(command, {
|
|
746
804
|
cwd: opts.cwd,
|
|
747
805
|
env: { ...process.env, ...opts.env, CODEX_QUIET_MODE: "1" }
|
|
748
806
|
});
|
|
749
807
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
750
808
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
809
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
751
810
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
752
811
|
const chunks = [];
|
|
753
812
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -776,14 +835,17 @@ var CodexProvider = class {
|
|
|
776
835
|
const exitCode = await new Promise((resolve13) => {
|
|
777
836
|
proc.on("close", (code) => {
|
|
778
837
|
overseer?.stop();
|
|
838
|
+
sessionTimeout.stop();
|
|
779
839
|
resolve13(code ?? 1);
|
|
780
840
|
});
|
|
781
841
|
});
|
|
782
|
-
if (
|
|
842
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
843
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
844
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
783
845
|
chunks.push(STUCK_MESSAGE);
|
|
784
846
|
}
|
|
785
847
|
return {
|
|
786
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
848
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
787
849
|
output: chunks.join(""),
|
|
788
850
|
duration: Date.now() - start
|
|
789
851
|
};
|
|
@@ -825,12 +887,25 @@ var CopilotProvider = class {
|
|
|
825
887
|
try {
|
|
826
888
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
827
889
|
const command = `copilot --allow-all ${modelFlag} -p "$(cat '${promptFile}')"`;
|
|
890
|
+
log(
|
|
891
|
+
`[copilot] Running: copilot --allow-all ${modelFlag || "(default model)"} -p`.trim()
|
|
892
|
+
);
|
|
893
|
+
if (opts.issueId) {
|
|
894
|
+
kanbanEmitter.emit(
|
|
895
|
+
"issue:output",
|
|
896
|
+
opts.issueId,
|
|
897
|
+
`${`$ copilot --allow-all ${modelFlag || "(default model)"} -p
|
|
898
|
+
`.trim()}
|
|
899
|
+
`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
828
902
|
const { proc, isPty } = spawnWithPty(command, {
|
|
829
903
|
cwd: opts.cwd,
|
|
830
904
|
env: { ...process.env, ...opts.env }
|
|
831
905
|
});
|
|
832
906
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
833
907
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
908
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
834
909
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
835
910
|
const chunks = [];
|
|
836
911
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -859,14 +934,17 @@ var CopilotProvider = class {
|
|
|
859
934
|
const exitCode = await new Promise((resolve13) => {
|
|
860
935
|
proc.on("close", (code) => {
|
|
861
936
|
overseer?.stop();
|
|
937
|
+
sessionTimeout.stop();
|
|
862
938
|
resolve13(code ?? 1);
|
|
863
939
|
});
|
|
864
940
|
});
|
|
865
|
-
if (
|
|
941
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
942
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
943
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
866
944
|
chunks.push(STUCK_MESSAGE);
|
|
867
945
|
}
|
|
868
946
|
return {
|
|
869
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
947
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
870
948
|
output: chunks.join(""),
|
|
871
949
|
duration: Date.now() - start
|
|
872
950
|
};
|
|
@@ -926,12 +1004,24 @@ var CursorProvider = class {
|
|
|
926
1004
|
try {
|
|
927
1005
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
928
1006
|
const command = `${bin} -p "$(cat '${promptFile}')" --output-format text --force ${modelFlag}`;
|
|
1007
|
+
log(
|
|
1008
|
+
`[cursor] Running: ${bin} -p --output-format text --force ${modelFlag || "(default model)"}`.trim()
|
|
1009
|
+
);
|
|
1010
|
+
if (opts.issueId) {
|
|
1011
|
+
kanbanEmitter.emit(
|
|
1012
|
+
"issue:output",
|
|
1013
|
+
opts.issueId,
|
|
1014
|
+
`$ ${bin} -p --output-format text --force ${modelFlag || "(default model)"}
|
|
1015
|
+
`.trim() + "\n"
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
929
1018
|
const { proc, isPty } = spawnWithPty(command, {
|
|
930
1019
|
cwd: opts.cwd,
|
|
931
1020
|
env: { ...process.env, ...opts.env }
|
|
932
1021
|
});
|
|
933
1022
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
934
1023
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
1024
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
935
1025
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
936
1026
|
const chunks = [];
|
|
937
1027
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -960,14 +1050,17 @@ var CursorProvider = class {
|
|
|
960
1050
|
const exitCode = await new Promise((resolve13) => {
|
|
961
1051
|
proc.on("close", (code) => {
|
|
962
1052
|
overseer?.stop();
|
|
1053
|
+
sessionTimeout.stop();
|
|
963
1054
|
resolve13(code ?? 1);
|
|
964
1055
|
});
|
|
965
1056
|
});
|
|
966
|
-
if (
|
|
1057
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
1058
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
1059
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
967
1060
|
chunks.push(STUCK_MESSAGE);
|
|
968
1061
|
}
|
|
969
1062
|
return {
|
|
970
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
1063
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
971
1064
|
output: chunks.join(""),
|
|
972
1065
|
duration: Date.now() - start
|
|
973
1066
|
};
|
|
@@ -1010,12 +1103,23 @@ var GeminiProvider = class {
|
|
|
1010
1103
|
try {
|
|
1011
1104
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1012
1105
|
const command = `gemini --yolo ${modelFlag} -p "$(cat '${promptFile}')"`;
|
|
1106
|
+
log(`[gemini] Running: gemini --yolo ${modelFlag || "(default model)"} -p`.trim());
|
|
1107
|
+
if (opts.issueId) {
|
|
1108
|
+
kanbanEmitter.emit(
|
|
1109
|
+
"issue:output",
|
|
1110
|
+
opts.issueId,
|
|
1111
|
+
`${`$ gemini --yolo ${modelFlag || "(default model)"} -p
|
|
1112
|
+
`.trim()}
|
|
1113
|
+
`
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1013
1116
|
const { proc, isPty } = spawnWithPty(command, {
|
|
1014
1117
|
cwd: opts.cwd,
|
|
1015
1118
|
env: { ...process.env, ...opts.env }
|
|
1016
1119
|
});
|
|
1017
1120
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
1018
1121
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
1122
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
1019
1123
|
const errorLoopDetector = createErrorLoopDetector(proc, GEMINI_ERROR_PATTERN);
|
|
1020
1124
|
const chunks = [];
|
|
1021
1125
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -1044,14 +1148,17 @@ var GeminiProvider = class {
|
|
|
1044
1148
|
const exitCode = await new Promise((resolve13) => {
|
|
1045
1149
|
proc.on("close", (code) => {
|
|
1046
1150
|
overseer?.stop();
|
|
1151
|
+
sessionTimeout.stop();
|
|
1047
1152
|
resolve13(code ?? 1);
|
|
1048
1153
|
});
|
|
1049
1154
|
});
|
|
1050
|
-
if (
|
|
1155
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
1156
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
1157
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
1051
1158
|
chunks.push(STUCK_MESSAGE);
|
|
1052
1159
|
}
|
|
1053
1160
|
return {
|
|
1054
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
1161
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
1055
1162
|
output: chunks.join(""),
|
|
1056
1163
|
duration: Date.now() - start
|
|
1057
1164
|
};
|
|
@@ -1094,12 +1201,25 @@ var GooseProvider = class {
|
|
|
1094
1201
|
const providerFlag = process.env.GOOSE_PROVIDER ? `--provider ${process.env.GOOSE_PROVIDER}` : "";
|
|
1095
1202
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1096
1203
|
const command = `goose run ${providerFlag} ${modelFlag} --text "$(cat '${promptFile}')"`;
|
|
1204
|
+
log(
|
|
1205
|
+
`[goose] Running: goose run ${providerFlag} ${modelFlag || "(default model)"} --text`.trim()
|
|
1206
|
+
);
|
|
1207
|
+
if (opts.issueId) {
|
|
1208
|
+
kanbanEmitter.emit(
|
|
1209
|
+
"issue:output",
|
|
1210
|
+
opts.issueId,
|
|
1211
|
+
`${`$ goose run ${providerFlag} ${modelFlag || "(default model)"} --text
|
|
1212
|
+
`.trim()}
|
|
1213
|
+
`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1097
1216
|
const { proc, isPty } = spawnWithPty(command, {
|
|
1098
1217
|
cwd: opts.cwd,
|
|
1099
1218
|
env: { ...process.env, ...opts.env }
|
|
1100
1219
|
});
|
|
1101
1220
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
1102
1221
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
1222
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
1103
1223
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
1104
1224
|
const chunks = [];
|
|
1105
1225
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -1128,14 +1248,17 @@ var GooseProvider = class {
|
|
|
1128
1248
|
const exitCode = await new Promise((resolve13) => {
|
|
1129
1249
|
proc.on("close", (code) => {
|
|
1130
1250
|
overseer?.stop();
|
|
1251
|
+
sessionTimeout.stop();
|
|
1131
1252
|
resolve13(code ?? 1);
|
|
1132
1253
|
});
|
|
1133
1254
|
});
|
|
1134
|
-
if (
|
|
1255
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
1256
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
1257
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
1135
1258
|
chunks.push(STUCK_MESSAGE);
|
|
1136
1259
|
}
|
|
1137
1260
|
return {
|
|
1138
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
1261
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
1139
1262
|
output: chunks.join(""),
|
|
1140
1263
|
duration: Date.now() - start
|
|
1141
1264
|
};
|
|
@@ -1177,12 +1300,23 @@ var OpenCodeProvider = class {
|
|
|
1177
1300
|
try {
|
|
1178
1301
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1179
1302
|
const command = `opencode run ${modelFlag} "$(cat '${promptFile}')"`;
|
|
1303
|
+
log(`[opencode] Running: opencode run ${modelFlag || "(default model)"}`.trim());
|
|
1304
|
+
if (opts.issueId) {
|
|
1305
|
+
kanbanEmitter.emit(
|
|
1306
|
+
"issue:output",
|
|
1307
|
+
opts.issueId,
|
|
1308
|
+
`${`$ opencode run ${modelFlag || "(default model)"}
|
|
1309
|
+
`.trim()}
|
|
1310
|
+
`
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1180
1313
|
const { proc, isPty } = spawnWithPty(command, {
|
|
1181
1314
|
cwd: opts.cwd,
|
|
1182
1315
|
env: { ...process.env, ...opts.env }
|
|
1183
1316
|
});
|
|
1184
1317
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
1185
1318
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
1319
|
+
const sessionTimeout = createSessionTimeout(proc, opts.sessionTimeout);
|
|
1186
1320
|
const errorLoopDetector = createErrorLoopDetector(proc, /^Error /);
|
|
1187
1321
|
const chunks = [];
|
|
1188
1322
|
proc.stdout?.on("data", (chunk) => {
|
|
@@ -1211,14 +1345,17 @@ var OpenCodeProvider = class {
|
|
|
1211
1345
|
const exitCode = await new Promise((resolve13) => {
|
|
1212
1346
|
proc.on("close", (code) => {
|
|
1213
1347
|
overseer?.stop();
|
|
1348
|
+
sessionTimeout.stop();
|
|
1214
1349
|
resolve13(code ?? 1);
|
|
1215
1350
|
});
|
|
1216
1351
|
});
|
|
1217
|
-
if (
|
|
1352
|
+
if (sessionTimeout.wasTimedOut()) {
|
|
1353
|
+
chunks.push(TIMEOUT_MESSAGE);
|
|
1354
|
+
} else if (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
|
|
1218
1355
|
chunks.push(STUCK_MESSAGE);
|
|
1219
1356
|
}
|
|
1220
1357
|
return {
|
|
1221
|
-
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled(),
|
|
1358
|
+
success: exitCode === 0 && !overseer?.wasKilled() && !errorLoopDetector.wasKilled() && !sessionTimeout.wasTimedOut(),
|
|
1222
1359
|
output: chunks.join(""),
|
|
1223
1360
|
duration: Date.now() - start
|
|
1224
1361
|
};
|
|
@@ -1282,6 +1419,7 @@ var ELIGIBLE_ERROR_PATTERNS = [
|
|
|
1282
1419
|
/not in PATH/i,
|
|
1283
1420
|
/command not found/i,
|
|
1284
1421
|
/lisa-overseer/i,
|
|
1422
|
+
/lisa-timeout/i,
|
|
1285
1423
|
/named models unavailable/i,
|
|
1286
1424
|
/free plans can only use/i,
|
|
1287
1425
|
/empty commit/i
|
|
@@ -3946,6 +4084,13 @@ function resolveModels(config2) {
|
|
|
3946
4084
|
);
|
|
3947
4085
|
}
|
|
3948
4086
|
}
|
|
4087
|
+
for (const m of providerModels) {
|
|
4088
|
+
if (m.includes("/") && m.startsWith(`${config2.provider}/`)) {
|
|
4089
|
+
warn(
|
|
4090
|
+
`Model "${m}" starts with the provider name "${config2.provider}/". Most provider CLIs expect just the model name (e.g. "${m.slice(config2.provider.length + 1)}"). If the provider fails silently, try removing the "${config2.provider}/" prefix.`
|
|
4091
|
+
);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
3949
4094
|
if (config2.provider === "cursor") {
|
|
3950
4095
|
const hasAuto = providerModels.some((m) => m.toLowerCase() === "auto");
|
|
3951
4096
|
if (!hasAuto) {
|
|
@@ -5754,6 +5899,7 @@ async function runWorktreeMultiRepoSession(config2, issue2, logFile, session, mo
|
|
|
5754
5899
|
} catch {
|
|
5755
5900
|
}
|
|
5756
5901
|
initLogFile(logFile);
|
|
5902
|
+
kanbanEmitter.emit("issue:log-file", issue2.id, logFile);
|
|
5757
5903
|
startSpinner(`${issue2.id} \u2014 analyzing issue...`);
|
|
5758
5904
|
log(`Multi-repo planning phase for ${issue2.id}`);
|
|
5759
5905
|
const repoGenerators = /* @__PURE__ */ new Map();
|
|
@@ -5769,6 +5915,7 @@ async function runWorktreeMultiRepoSession(config2, issue2, logFile, session, mo
|
|
|
5769
5915
|
guardrailsDir: workspace,
|
|
5770
5916
|
issueId: issue2.id,
|
|
5771
5917
|
overseer: config2.overseer,
|
|
5918
|
+
sessionTimeout: config2.loop.session_timeout,
|
|
5772
5919
|
onProcess: (pid) => {
|
|
5773
5920
|
activeProviderPids.set(issue2.id, pid);
|
|
5774
5921
|
},
|
|
@@ -5928,6 +6075,7 @@ async function runMultiRepoStep(config2, issue2, step, previousResults, logFile,
|
|
|
5928
6075
|
guardrailsDir: workspace,
|
|
5929
6076
|
issueId: issue2.id,
|
|
5930
6077
|
overseer: config2.overseer,
|
|
6078
|
+
sessionTimeout: config2.loop.session_timeout,
|
|
5931
6079
|
env: Object.keys(lifecycleEnv).length > 0 ? lifecycleEnv : void 0,
|
|
5932
6080
|
onProcess: (pid) => {
|
|
5933
6081
|
activeProviderPids.set(issue2.id, pid);
|
|
@@ -6069,6 +6217,7 @@ async function runNativeWorktreeSession(config2, issue2, logFile, session, model
|
|
|
6069
6217
|
config2.platform
|
|
6070
6218
|
);
|
|
6071
6219
|
initLogFile(logFile);
|
|
6220
|
+
kanbanEmitter.emit("issue:log-file", issue2.id, logFile);
|
|
6072
6221
|
startSpinner(`${issue2.id} \u2014 implementing (native worktree)...`);
|
|
6073
6222
|
log(`Implementing with native worktree... (log: ${logFile})`);
|
|
6074
6223
|
const result = await runWithFallback(models, prompt, {
|
|
@@ -6077,6 +6226,7 @@ async function runNativeWorktreeSession(config2, issue2, logFile, session, model
|
|
|
6077
6226
|
guardrailsDir: workspace,
|
|
6078
6227
|
issueId: issue2.id,
|
|
6079
6228
|
overseer: config2.overseer,
|
|
6229
|
+
sessionTimeout: config2.loop.session_timeout,
|
|
6080
6230
|
useNativeWorktree: true,
|
|
6081
6231
|
env: Object.keys(lifecycleEnv).length > 0 ? lifecycleEnv : void 0,
|
|
6082
6232
|
onProcess: (pid) => {
|
|
@@ -6226,6 +6376,7 @@ async function runManualWorktreeSession(config2, issue2, logFile, session, model
|
|
|
6226
6376
|
manifestPath
|
|
6227
6377
|
);
|
|
6228
6378
|
initLogFile(logFile);
|
|
6379
|
+
kanbanEmitter.emit("issue:log-file", issue2.id, logFile);
|
|
6229
6380
|
startSpinner(`${issue2.id} \u2014 implementing...`);
|
|
6230
6381
|
log(`Implementing in worktree... (log: ${logFile})`);
|
|
6231
6382
|
const result = await runWithFallback(models, prompt, {
|
|
@@ -6234,6 +6385,7 @@ async function runManualWorktreeSession(config2, issue2, logFile, session, model
|
|
|
6234
6385
|
guardrailsDir: workspace,
|
|
6235
6386
|
issueId: issue2.id,
|
|
6236
6387
|
overseer: config2.overseer,
|
|
6388
|
+
sessionTimeout: config2.loop.session_timeout,
|
|
6237
6389
|
env: Object.keys(lifecycleEnv).length > 0 ? lifecycleEnv : void 0,
|
|
6238
6390
|
onProcess: (pid) => {
|
|
6239
6391
|
activeProviderPids.set(issue2.id, pid);
|
|
@@ -6597,6 +6749,7 @@ async function runBranchSession(config2, issue2, logFile, session, models) {
|
|
|
6597
6749
|
manifestPath
|
|
6598
6750
|
);
|
|
6599
6751
|
initLogFile(logFile);
|
|
6752
|
+
kanbanEmitter.emit("issue:log-file", issue2.id, logFile);
|
|
6600
6753
|
startSpinner(`${issue2.id} \u2014 implementing...`);
|
|
6601
6754
|
log(`Implementing... (log: ${logFile})`);
|
|
6602
6755
|
const result = await runWithFallback(models, prompt, {
|
|
@@ -6605,6 +6758,7 @@ async function runBranchSession(config2, issue2, logFile, session, models) {
|
|
|
6605
6758
|
guardrailsDir: workspace,
|
|
6606
6759
|
issueId: issue2.id,
|
|
6607
6760
|
overseer: config2.overseer,
|
|
6761
|
+
sessionTimeout: config2.loop.session_timeout,
|
|
6608
6762
|
env: Object.keys(lifecycleEnv).length > 0 ? lifecycleEnv : void 0,
|
|
6609
6763
|
onProcess: (pid) => {
|
|
6610
6764
|
activeProviderPids.set(issue2.id, pid);
|
|
@@ -7115,7 +7269,7 @@ var run = defineCommand5({
|
|
|
7115
7269
|
if (isTTY) {
|
|
7116
7270
|
const { render } = await import("ink");
|
|
7117
7271
|
const { createElement } = await import("react");
|
|
7118
|
-
const { KanbanApp } = await import("./kanban-
|
|
7272
|
+
const { KanbanApp } = await import("./kanban-LG26AUFK.js");
|
|
7119
7273
|
const demoConfig = {
|
|
7120
7274
|
provider: "claude",
|
|
7121
7275
|
source: "linear",
|
|
@@ -7190,7 +7344,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
7190
7344
|
if (isTTY) {
|
|
7191
7345
|
const { render } = await import("ink");
|
|
7192
7346
|
const { createElement } = await import("react");
|
|
7193
|
-
const { KanbanApp } = await import("./kanban-
|
|
7347
|
+
const { KanbanApp } = await import("./kanban-LG26AUFK.js");
|
|
7194
7348
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
7195
7349
|
}
|
|
7196
7350
|
await runLoop(merged, {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
kanbanEmitter,
|
|
4
4
|
useKanbanState
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WDGLMZB7.js";
|
|
6
6
|
import {
|
|
7
7
|
resetTitle,
|
|
8
8
|
startSpinner,
|
|
@@ -532,6 +532,10 @@ function IssueDetail({ card, onBack }) {
|
|
|
532
532
|
/* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: card.prUrls.length === 1 ? "PR: " : `PR ${i + 1}: ` }),
|
|
533
533
|
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: hyperlink(url, url) })
|
|
534
534
|
] }, url)),
|
|
535
|
+
card.logFile && /* @__PURE__ */ jsxs4(Box4, { marginTop: 0, children: [
|
|
536
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "LOG: " }),
|
|
537
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: card.logFile })
|
|
538
|
+
] }),
|
|
535
539
|
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: separator }) }),
|
|
536
540
|
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
537
541
|
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
|