@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-ITQEGO5A.js";
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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 (overseer?.wasKilled() || errorLoopDetector.wasKilled()) {
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-QZ5NRPJ5.js");
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-QZ5NRPJ5.js");
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-ITQEGO5A.js";
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: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarcisiopgs/lisa",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Autonomous issue resolver",
5
5
  "keywords": [
6
6
  "loop",