prizmkit 1.1.106 → 1.1.108
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/bin/create-prizmkit.js +4 -0
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/config.py +81 -14
- package/bundled/dev-pipeline/prizmkit_runtime/heartbeat.py +19 -8
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
- package/bundled/dev-pipeline/prizmkit_runtime/launch_profiles.py +18 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +22 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +56 -5
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -8
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +11 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +298 -63
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +243 -37
- package/bundled/dev-pipeline/scripts/continuation.py +0 -5
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +10 -82
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +7 -64
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +40 -63
- package/bundled/dev-pipeline/scripts/utils.py +123 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +4 -3
- package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +5 -6
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +36 -35
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +53 -80
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +43 -78
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +5 -17
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +6 -18
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +14 -48
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +7 -14
- package/bundled/dev-pipeline/templates/sections/feature-context.md +0 -4
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +0 -14
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -14
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +0 -2
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +6 -8
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +3 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +4 -16
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +4 -17
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +2 -9
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +1 -8
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +15 -16
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +3 -10
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +3 -10
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +2 -9
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +2 -15
- package/bundled/dev-pipeline/tests/conftest.py +0 -1
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +395 -21
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +50 -14
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +154 -2
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +431 -3
- package/bundled/dev-pipeline/tests/test_unified_cli.py +857 -5
- package/bundled/skills/_metadata.json +4 -4
- package/bundled/skills/app-planner/SKILL.md +1 -1
- package/bundled/skills/app-planner/references/architecture-decisions.md +1 -1
- package/bundled/skills/bug-fix-workflow/SKILL.md +174 -128
- package/bundled/skills/bug-planner/SKILL.md +101 -15
- package/bundled/skills/bug-planner/references/severity-rules.md +20 -8
- package/bundled/skills/feature-planner/SKILL.md +87 -8
- package/bundled/skills/feature-planner/assets/planning-guide.md +34 -10
- package/bundled/skills/feature-workflow/SKILL.md +100 -81
- package/bundled/skills/{prizm-kit → prizmkit}/SKILL.md +1 -1
- package/bundled/skills/prizmkit-code-review/SKILL.md +12 -13
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +10 -10
- package/bundled/skills/refactor-planner/SKILL.md +88 -7
- package/bundled/skills/refactor-planner/references/fast-path.md +2 -1
- package/bundled/skills/refactor-planner/references/planning-phases.md +29 -11
- package/bundled/skills/refactor-workflow/SKILL.md +116 -57
- package/bundled/templates/project-memory-template.md +1 -1
- package/package.json +1 -1
- package/src/ai-cli-launch.js +194 -0
- package/src/config.js +26 -14
- package/src/index.js +11 -44
- package/src/platforms.js +10 -3
- package/src/prompts.js +60 -2
- package/src/scaffold.js +26 -14
- package/bundled/dev-pipeline/scripts/prizmkit-test-gate.py +0 -446
- package/bundled/dev-pipeline/templates/agent-prompts/dev-fix.md +0 -7
- package/bundled/dev-pipeline/templates/agent-prompts/dev-resume.md +0 -5
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +0 -20
- package/bundled/dev-pipeline/tests/test_prizmkit_test_gate.py +0 -107
- package/bundled/skills/prizmkit-code-review/references/dev-agent-prompt.md +0 -30
|
@@ -6,6 +6,7 @@ import importlib.util
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import queue
|
|
9
|
+
import shlex
|
|
9
10
|
import shutil
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
@@ -16,7 +17,8 @@ from collections.abc import Callable, Mapping, Sequence
|
|
|
16
17
|
from dataclasses import dataclass, field
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
|
|
19
|
-
from .heartbeat import HeartbeatConfig, HeartbeatMonitor, write_stale_marker
|
|
20
|
+
from .heartbeat import HeartbeatConfig, HeartbeatMonitor, HeartbeatState, observe_heartbeat_state, write_stale_marker
|
|
21
|
+
from .launch_profiles import HeadlessLaunchProfile
|
|
20
22
|
from .processes import ProcessHandle, ProcessSpec, launch_process, terminate_process_tree
|
|
21
23
|
from .status import ProgressSummary
|
|
22
24
|
|
|
@@ -52,12 +54,14 @@ class AISessionConfig:
|
|
|
52
54
|
permission_mode: str = "dangerously-skip-permissions"
|
|
53
55
|
total_timeout_seconds: float | None = None
|
|
54
56
|
stale_kill_threshold_seconds: float = 0
|
|
57
|
+
heartbeat_stale_threshold_seconds: float = 0
|
|
55
58
|
stale_kill_grace_seconds: float = 10
|
|
56
59
|
live_progress_interval_seconds: float = 30
|
|
57
60
|
live_banner: bool = True
|
|
58
61
|
suppress_stream_output: bool = False
|
|
59
62
|
codex_subagent_timeout_seconds: int = 3300
|
|
60
63
|
extra_args: tuple[str, ...] = ()
|
|
64
|
+
launch_profile: HeadlessLaunchProfile | None = None
|
|
61
65
|
environment_overrides: Mapping[str, str] = field(default_factory=dict)
|
|
62
66
|
|
|
63
67
|
|
|
@@ -69,6 +73,7 @@ class AISessionCommand:
|
|
|
69
73
|
stdin_text: str | None
|
|
70
74
|
provider: str
|
|
71
75
|
prompt_mode: str
|
|
76
|
+
prompt_text: str | None = None
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
@dataclass(frozen=True)
|
|
@@ -106,7 +111,7 @@ HelpRunner = Callable[[Sequence[str]], str]
|
|
|
106
111
|
|
|
107
112
|
def _normalize_platform(platform: str | None) -> str:
|
|
108
113
|
normalized = (platform or "").strip().lower()
|
|
109
|
-
return normalized if normalized in {"claude", "codex", "codebuddy"} else "codebuddy"
|
|
114
|
+
return normalized if normalized in {"claude", "codex", "codebuddy", "custom"} else "codebuddy"
|
|
110
115
|
|
|
111
116
|
|
|
112
117
|
def _prompt_text(path: Path) -> str:
|
|
@@ -156,6 +161,11 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
156
161
|
"""Build provider-specific AI CLI argv without spawning a process."""
|
|
157
162
|
platform = _normalize_platform(config.platform)
|
|
158
163
|
prompt = _prompt_text(config.prompt_path)
|
|
164
|
+
if config.launch_profile is not None and config.launch_profile.is_custom:
|
|
165
|
+
argv = [config.cli_command, config.launch_profile.prompt_arg, prompt]
|
|
166
|
+
argv.extend(config.launch_profile.extra_args)
|
|
167
|
+
argv.extend(config.extra_args)
|
|
168
|
+
return AISessionCommand(tuple(argv), None, "custom", "argument", prompt)
|
|
159
169
|
if platform == "claude":
|
|
160
170
|
argv = [
|
|
161
171
|
config.cli_command,
|
|
@@ -174,7 +184,7 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
174
184
|
if config.model:
|
|
175
185
|
argv.extend(["--model", config.model])
|
|
176
186
|
argv.extend(config.extra_args)
|
|
177
|
-
return AISessionCommand(tuple(argv), None, "claude", "argument")
|
|
187
|
+
return AISessionCommand(tuple(argv), None, "claude", "argument", prompt)
|
|
178
188
|
|
|
179
189
|
if platform == "codex":
|
|
180
190
|
argv = [
|
|
@@ -197,7 +207,7 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
197
207
|
argv.extend(["--config", f"model_reasoning_effort={config.effort}"])
|
|
198
208
|
argv.extend(config.extra_args)
|
|
199
209
|
argv.append("-")
|
|
200
|
-
return AISessionCommand(tuple(argv), prompt, "codex", "stdin")
|
|
210
|
+
return AISessionCommand(tuple(argv), prompt, "codex", "stdin", prompt)
|
|
201
211
|
|
|
202
212
|
argv = [config.cli_command, "--print", "-y", "--disallowedTools", "EnterWorktree"]
|
|
203
213
|
if config.verbose:
|
|
@@ -209,7 +219,18 @@ def build_ai_session_command(config: AISessionConfig) -> AISessionCommand:
|
|
|
209
219
|
if config.model:
|
|
210
220
|
argv.extend(["--model", config.model])
|
|
211
221
|
argv.extend(config.extra_args)
|
|
212
|
-
return AISessionCommand(tuple(argv), prompt, "codebuddy", "stdin")
|
|
222
|
+
return AISessionCommand(tuple(argv), prompt, "codebuddy", "stdin", prompt)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def format_start_command(command: AISessionCommand) -> str:
|
|
226
|
+
"""Return a readable command string with the injected prompt redacted."""
|
|
227
|
+
redacted: list[str] = []
|
|
228
|
+
for token in command.argv:
|
|
229
|
+
if command.prompt_text is not None and token == command.prompt_text:
|
|
230
|
+
redacted.append("<prompt>")
|
|
231
|
+
else:
|
|
232
|
+
redacted.append(token)
|
|
233
|
+
return " ".join("<prompt>" if token == "<prompt>" else shlex.quote(token) for token in redacted)
|
|
213
234
|
|
|
214
235
|
|
|
215
236
|
def default_backup_log_path(session_log: Path, home: Path | None = None) -> Path:
|
|
@@ -220,15 +241,16 @@ def default_backup_log_path(session_log: Path, home: Path | None = None) -> Path
|
|
|
220
241
|
return root / ".prizmkit" / "session-backups" / f"{session_id}.log"
|
|
221
242
|
|
|
222
243
|
|
|
223
|
-
def write_session_preamble(session_log: Path, backup_log: Path,
|
|
244
|
+
def write_session_preamble(session_log: Path, backup_log: Path, command: AISessionCommand, model: str | None) -> None:
|
|
224
245
|
"""Write the session preamble to both primary and backup logs."""
|
|
225
246
|
session_log.parent.mkdir(parents=True, exist_ok=True)
|
|
226
247
|
backup_log.parent.mkdir(parents=True, exist_ok=True)
|
|
227
248
|
preamble = "\n".join(
|
|
228
249
|
[
|
|
229
250
|
"=== PRIZMKIT SESSION START ===",
|
|
230
|
-
|
|
251
|
+
"Prompt: <prompt>",
|
|
231
252
|
f"Model: {model or 'default'}",
|
|
253
|
+
f"StartCommand: {format_start_command(command)}",
|
|
232
254
|
f"Started at epoch: {time.time():.6f}",
|
|
233
255
|
"--- OUTPUT BELOW ---",
|
|
234
256
|
"",
|
|
@@ -368,6 +390,60 @@ def _write_progress_summary(progress_path: Path | None, summary: ProgressSummary
|
|
|
368
390
|
progress_path.write_text(json.dumps(summary.to_dict(), indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
369
391
|
|
|
370
392
|
|
|
393
|
+
def _progress_refresh_key(data: Mapping[str, object]) -> tuple[object, ...]:
|
|
394
|
+
event_counts = data.get("event_type_counts")
|
|
395
|
+
return (
|
|
396
|
+
int(data.get("message_count") or 0),
|
|
397
|
+
tuple(sorted(event_counts.items())) if isinstance(event_counts, Mapping) else (),
|
|
398
|
+
str(data.get("current_tool") or ""),
|
|
399
|
+
str(data.get("current_phase") or ""),
|
|
400
|
+
int(data.get("total_tool_calls") or 0),
|
|
401
|
+
int(data.get("active_subagent_count") or 0),
|
|
402
|
+
int(data.get("subagent_spawn_count") or 0),
|
|
403
|
+
str(data.get("child_activity_signature") or ""),
|
|
404
|
+
int(data.get("child_total_bytes") or 0),
|
|
405
|
+
str(data.get("fatal_error_code") or ""),
|
|
406
|
+
str(data.get("terminal_result_text") or ""),
|
|
407
|
+
tuple(str(item) for item in data.get("errors", []) if item) if isinstance(data.get("errors"), list) else (),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _write_progress_data(progress_path: Path, data: Mapping[str, object]) -> None:
|
|
412
|
+
progress_path.parent.mkdir(parents=True, exist_ok=True)
|
|
413
|
+
progress_path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _start_idle_progress_refresh(
|
|
417
|
+
progress_path: Path,
|
|
418
|
+
tracker: object,
|
|
419
|
+
tracker_lock: threading.Lock,
|
|
420
|
+
stop_event: threading.Event,
|
|
421
|
+
interval_seconds: float = 1.0,
|
|
422
|
+
) -> tuple[threading.Thread, Callable[[bool], None]]:
|
|
423
|
+
"""Refresh progress.json from child activity while parent output is idle."""
|
|
424
|
+
last_key: tuple[object, ...] | None = None
|
|
425
|
+
|
|
426
|
+
def maybe_write(force: bool = False) -> None:
|
|
427
|
+
nonlocal last_key
|
|
428
|
+
with tracker_lock:
|
|
429
|
+
data = tracker.to_dict()
|
|
430
|
+
key = _progress_refresh_key(data)
|
|
431
|
+
if force or key != last_key:
|
|
432
|
+
_write_progress_data(progress_path, data)
|
|
433
|
+
last_key = key
|
|
434
|
+
|
|
435
|
+
def refresh_loop() -> None:
|
|
436
|
+
while not stop_event.wait(max(0.1, interval_seconds)):
|
|
437
|
+
try:
|
|
438
|
+
maybe_write(False)
|
|
439
|
+
except Exception:
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
thread = threading.Thread(target=refresh_loop, name="prizmkit-progress-refresh", daemon=True)
|
|
443
|
+
thread.start()
|
|
444
|
+
return thread, maybe_write
|
|
445
|
+
|
|
446
|
+
|
|
371
447
|
def _emit_live_line(message: str) -> None:
|
|
372
448
|
"""Print foreground runner status without interfering with file logging."""
|
|
373
449
|
print(message, file=sys.stderr, flush=True)
|
|
@@ -393,6 +469,16 @@ def _format_elapsed(seconds: float) -> str:
|
|
|
393
469
|
return f"{minutes}m{remainder:02d}s"
|
|
394
470
|
|
|
395
471
|
|
|
472
|
+
def _format_stale_minutes(seconds: float) -> str:
|
|
473
|
+
total = max(0, int(seconds))
|
|
474
|
+
minutes, remainder = divmod(total, 60)
|
|
475
|
+
if minutes == 0:
|
|
476
|
+
return f"{remainder}s"
|
|
477
|
+
if remainder == 0:
|
|
478
|
+
return f"{minutes}m"
|
|
479
|
+
return f"{minutes}m{remainder:02d}s"
|
|
480
|
+
|
|
481
|
+
|
|
396
482
|
def _read_json_object(path: Path | None) -> dict[str, object]:
|
|
397
483
|
if path is None or not path.is_file():
|
|
398
484
|
return {}
|
|
@@ -403,14 +489,55 @@ def _read_json_object(path: Path | None) -> dict[str, object]:
|
|
|
403
489
|
return data if isinstance(data, dict) else {}
|
|
404
490
|
|
|
405
491
|
|
|
406
|
-
def
|
|
492
|
+
def _display_heartbeat_state(
|
|
493
|
+
session_log: Path,
|
|
494
|
+
progress_path: Path | None,
|
|
495
|
+
monitor_state: HeartbeatState | None,
|
|
496
|
+
fallback_state: HeartbeatState | None,
|
|
497
|
+
*,
|
|
498
|
+
interval_seconds: float,
|
|
499
|
+
stale_threshold_seconds: float,
|
|
500
|
+
) -> HeartbeatState:
|
|
501
|
+
if monitor_state is not None and monitor_state.last_seen_epoch is not None:
|
|
502
|
+
return monitor_state
|
|
503
|
+
return observe_heartbeat_state(
|
|
504
|
+
HeartbeatConfig(
|
|
505
|
+
heartbeat_file=session_log.with_name("heartbeat.json"),
|
|
506
|
+
session_log=session_log,
|
|
507
|
+
progress_file=progress_path,
|
|
508
|
+
interval_seconds=interval_seconds,
|
|
509
|
+
stale_after_seconds=stale_threshold_seconds,
|
|
510
|
+
kill_after_seconds=0,
|
|
511
|
+
),
|
|
512
|
+
fallback_state,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _compact_progress_line(
|
|
517
|
+
started_epoch: float,
|
|
518
|
+
session_log: Path,
|
|
519
|
+
progress_path: Path | None,
|
|
520
|
+
heartbeat_state: HeartbeatState | None = None,
|
|
521
|
+
*,
|
|
522
|
+
stale_threshold_seconds: float = 0,
|
|
523
|
+
) -> str:
|
|
407
524
|
data = _read_json_object(progress_path)
|
|
408
|
-
|
|
409
|
-
log_size =
|
|
410
|
-
|
|
411
|
-
|
|
525
|
+
if heartbeat_state is not None:
|
|
526
|
+
log_size = heartbeat_state.log_size
|
|
527
|
+
else:
|
|
528
|
+
try:
|
|
529
|
+
log_size = session_log.stat().st_size
|
|
530
|
+
except OSError:
|
|
531
|
+
log_size = 0
|
|
532
|
+
stale_seconds = heartbeat_state.stale_seconds if heartbeat_state is not None else 0
|
|
533
|
+
show_stale = bool(
|
|
534
|
+
heartbeat_state is not None
|
|
535
|
+
and heartbeat_state.details == "no progress"
|
|
536
|
+
and (heartbeat_state.stale or (stale_threshold_seconds > 0 and stale_seconds >= stale_threshold_seconds))
|
|
537
|
+
)
|
|
538
|
+
glyph = "⏸" if show_stale else "▶"
|
|
412
539
|
parts = [
|
|
413
|
-
f"
|
|
540
|
+
f" {glyph} [HEARTBEAT] {_format_elapsed(time.time() - started_epoch)}",
|
|
414
541
|
f"log: {_format_bytes(log_size)}",
|
|
415
542
|
]
|
|
416
543
|
child_bytes = int(data.get("child_total_bytes") or 0)
|
|
@@ -432,15 +559,42 @@ def _compact_progress_line(started_epoch: float, session_log: Path, progress_pat
|
|
|
432
559
|
tool_calls = int(data.get("total_tool_calls") or 0)
|
|
433
560
|
if tool_calls:
|
|
434
561
|
parts.append(f"{tool_calls} tool calls")
|
|
562
|
+
if show_stale:
|
|
563
|
+
threshold = f"/{_format_stale_minutes(stale_threshold_seconds)}" if stale_threshold_seconds > 0 else ""
|
|
564
|
+
parts.append(f"stale: {_format_stale_minutes(stale_seconds)}{threshold}")
|
|
435
565
|
return " | ".join(parts)
|
|
436
566
|
|
|
437
567
|
|
|
438
|
-
def _emit_progress_tick(
|
|
439
|
-
|
|
568
|
+
def _emit_progress_tick(
|
|
569
|
+
started_epoch: float,
|
|
570
|
+
session_log: Path,
|
|
571
|
+
progress_path: Path | None,
|
|
572
|
+
last_line: str,
|
|
573
|
+
*,
|
|
574
|
+
monitor_state: HeartbeatState | None = None,
|
|
575
|
+
fallback_state: HeartbeatState | None = None,
|
|
576
|
+
interval_seconds: float = 30,
|
|
577
|
+
stale_threshold_seconds: float = 0,
|
|
578
|
+
) -> tuple[str, HeartbeatState]:
|
|
579
|
+
heartbeat_state = _display_heartbeat_state(
|
|
580
|
+
session_log,
|
|
581
|
+
progress_path,
|
|
582
|
+
monitor_state,
|
|
583
|
+
fallback_state,
|
|
584
|
+
interval_seconds=interval_seconds,
|
|
585
|
+
stale_threshold_seconds=stale_threshold_seconds,
|
|
586
|
+
)
|
|
587
|
+
line = _compact_progress_line(
|
|
588
|
+
started_epoch,
|
|
589
|
+
session_log,
|
|
590
|
+
progress_path,
|
|
591
|
+
heartbeat_state,
|
|
592
|
+
stale_threshold_seconds=stale_threshold_seconds,
|
|
593
|
+
)
|
|
440
594
|
if line != last_line:
|
|
441
595
|
_emit_live_line(line)
|
|
442
|
-
return line
|
|
443
|
-
return last_line
|
|
596
|
+
return line, heartbeat_state
|
|
597
|
+
return last_line, heartbeat_state
|
|
444
598
|
|
|
445
599
|
|
|
446
600
|
def _emit_session_start(config: AISessionConfig, command: AISessionCommand, session_log: Path, backup_log: Path) -> None:
|
|
@@ -474,12 +628,23 @@ def _reader_thread(
|
|
|
474
628
|
) -> None:
|
|
475
629
|
lines: list[str] = []
|
|
476
630
|
tracker = None
|
|
631
|
+
tracker_lock = threading.Lock()
|
|
632
|
+
progress_refresh_stop = threading.Event()
|
|
633
|
+
progress_refresh_thread: threading.Thread | None = None
|
|
634
|
+
refresh_progress: Callable[[bool], None] | None = None
|
|
477
635
|
tracker_class = _load_progress_tracker_class() if progress_path is not None else None
|
|
478
636
|
if tracker_class is not None:
|
|
479
637
|
try:
|
|
480
638
|
tracker = tracker_class(session_log)
|
|
481
639
|
except Exception:
|
|
482
640
|
tracker = None
|
|
641
|
+
if progress_path is not None and tracker is not None:
|
|
642
|
+
progress_refresh_thread, refresh_progress = _start_idle_progress_refresh(
|
|
643
|
+
progress_path,
|
|
644
|
+
tracker,
|
|
645
|
+
tracker_lock,
|
|
646
|
+
progress_refresh_stop,
|
|
647
|
+
)
|
|
483
648
|
try:
|
|
484
649
|
for line in iter(pipe.readline, ""):
|
|
485
650
|
if not line:
|
|
@@ -495,18 +660,26 @@ def _reader_thread(
|
|
|
495
660
|
if progress_path is not None:
|
|
496
661
|
if tracker is not None:
|
|
497
662
|
stripped = line.strip()
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
progress_path
|
|
663
|
+
with tracker_lock:
|
|
664
|
+
if stripped:
|
|
665
|
+
try:
|
|
666
|
+
tracker.process_event(json.loads(stripped))
|
|
667
|
+
except json.JSONDecodeError:
|
|
668
|
+
tracker.last_text_snippet = stripped[:120]
|
|
669
|
+
tracker._detect_terminal_error(stripped, require_error_context=True)
|
|
670
|
+
data = tracker.to_dict()
|
|
671
|
+
_write_progress_data(progress_path, data)
|
|
507
672
|
else:
|
|
508
673
|
_write_progress_summary(progress_path, parse_stream_progress_lines(lines))
|
|
509
674
|
finally:
|
|
675
|
+
if refresh_progress is not None:
|
|
676
|
+
try:
|
|
677
|
+
refresh_progress(True)
|
|
678
|
+
except Exception:
|
|
679
|
+
pass
|
|
680
|
+
progress_refresh_stop.set()
|
|
681
|
+
if progress_refresh_thread is not None:
|
|
682
|
+
progress_refresh_thread.join(timeout=2)
|
|
510
683
|
try:
|
|
511
684
|
pipe.close()
|
|
512
685
|
except Exception:
|
|
@@ -567,7 +740,7 @@ class AISessionLauncher:
|
|
|
567
740
|
command = build_ai_session_command(self.config)
|
|
568
741
|
session_log = self.config.log_path or self.config.cwd / ".prizmkit" / "state" / "session.log"
|
|
569
742
|
backup_log = self.config.backup_log_path or default_backup_log_path(session_log)
|
|
570
|
-
write_session_preamble(session_log, backup_log,
|
|
743
|
+
write_session_preamble(session_log, backup_log, command, self.config.model)
|
|
571
744
|
|
|
572
745
|
spec = ProcessSpec(
|
|
573
746
|
command=command.argv,
|
|
@@ -601,8 +774,13 @@ class AISessionLauncher:
|
|
|
601
774
|
handle.popen.stdin.close()
|
|
602
775
|
|
|
603
776
|
heartbeat = None
|
|
777
|
+
live_progress_interval = max(1.0, float(self.config.live_progress_interval_seconds or 30))
|
|
778
|
+
kill_threshold_seconds = max(0.0, float(self.config.stale_kill_threshold_seconds or 0))
|
|
779
|
+
stale_threshold_seconds = max(0.0, float(self.config.heartbeat_stale_threshold_seconds or 0))
|
|
780
|
+
if stale_threshold_seconds <= 0:
|
|
781
|
+
stale_threshold_seconds = kill_threshold_seconds
|
|
604
782
|
heartbeat_path = self.config.heartbeat_path
|
|
605
|
-
if heartbeat_path is None and
|
|
783
|
+
if heartbeat_path is None and kill_threshold_seconds > 0:
|
|
606
784
|
heartbeat_path = session_log.with_name("heartbeat.json")
|
|
607
785
|
if heartbeat_path is not None:
|
|
608
786
|
heartbeat = HeartbeatMonitor(
|
|
@@ -613,8 +791,8 @@ class AISessionLauncher:
|
|
|
613
791
|
progress_file=self.config.progress_path,
|
|
614
792
|
marker_dir=session_log.parent,
|
|
615
793
|
interval_seconds=0.1,
|
|
616
|
-
stale_after_seconds=
|
|
617
|
-
kill_after_seconds=
|
|
794
|
+
stale_after_seconds=stale_threshold_seconds,
|
|
795
|
+
kill_after_seconds=kill_threshold_seconds,
|
|
618
796
|
grace_seconds=self.config.stale_kill_grace_seconds,
|
|
619
797
|
),
|
|
620
798
|
)
|
|
@@ -626,6 +804,7 @@ class AISessionLauncher:
|
|
|
626
804
|
stale_marker = None
|
|
627
805
|
last_progress_line = ""
|
|
628
806
|
last_progress_emit = 0.0
|
|
807
|
+
display_heartbeat_state = None
|
|
629
808
|
try:
|
|
630
809
|
while True:
|
|
631
810
|
timeout = 0.2
|
|
@@ -642,27 +821,54 @@ class AISessionLauncher:
|
|
|
642
821
|
break
|
|
643
822
|
except subprocess.TimeoutExpired:
|
|
644
823
|
if self.config.live_output and (not self.config.verbose or self.config.suppress_stream_output):
|
|
645
|
-
interval = max(1.0, float(self.config.live_progress_interval_seconds or 30))
|
|
646
824
|
now = time.time()
|
|
647
|
-
if last_progress_emit == 0.0 or now - last_progress_emit >=
|
|
648
|
-
last_progress_line = _emit_progress_tick(
|
|
825
|
+
if last_progress_emit == 0.0 or now - last_progress_emit >= live_progress_interval:
|
|
826
|
+
last_progress_line, display_heartbeat_state = _emit_progress_tick(
|
|
827
|
+
started,
|
|
828
|
+
session_log,
|
|
829
|
+
self.config.progress_path,
|
|
830
|
+
last_progress_line,
|
|
831
|
+
monitor_state=heartbeat.state if heartbeat is not None else None,
|
|
832
|
+
fallback_state=display_heartbeat_state,
|
|
833
|
+
interval_seconds=live_progress_interval,
|
|
834
|
+
stale_threshold_seconds=stale_threshold_seconds,
|
|
835
|
+
)
|
|
649
836
|
last_progress_emit = now
|
|
650
837
|
except KeyboardInterrupt:
|
|
651
838
|
terminate_process_tree(handle, "interrupted", grace_seconds=self.config.stale_kill_grace_seconds)
|
|
652
839
|
raise
|
|
653
840
|
if self.config.live_output and (not self.config.verbose or self.config.suppress_stream_output):
|
|
654
|
-
|
|
841
|
+
final_heartbeat_state = _display_heartbeat_state(
|
|
842
|
+
session_log,
|
|
843
|
+
self.config.progress_path,
|
|
844
|
+
heartbeat.state if heartbeat is not None else None,
|
|
845
|
+
display_heartbeat_state,
|
|
846
|
+
interval_seconds=live_progress_interval,
|
|
847
|
+
stale_threshold_seconds=stale_threshold_seconds,
|
|
848
|
+
)
|
|
849
|
+
display_heartbeat_state = final_heartbeat_state
|
|
850
|
+
if final_heartbeat_state.details == "progress":
|
|
851
|
+
final_line = _compact_progress_line(
|
|
852
|
+
started,
|
|
853
|
+
session_log,
|
|
854
|
+
self.config.progress_path,
|
|
855
|
+
final_heartbeat_state,
|
|
856
|
+
stale_threshold_seconds=stale_threshold_seconds,
|
|
857
|
+
)
|
|
858
|
+
if final_line != last_progress_line:
|
|
859
|
+
_emit_live_line(final_line)
|
|
860
|
+
last_progress_line = final_line
|
|
655
861
|
reader.join(timeout=5)
|
|
656
862
|
heartbeat_result = heartbeat.stop() if heartbeat is not None else None
|
|
657
863
|
if heartbeat_result and heartbeat_result.killed:
|
|
658
864
|
termination_reason = heartbeat_result.reason
|
|
659
865
|
stale_marker = heartbeat_result.marker_path
|
|
660
|
-
if termination_reason is None and
|
|
866
|
+
if termination_reason is None and kill_threshold_seconds > 0 and handle.popen.returncode is None:
|
|
661
867
|
stale_marker = write_stale_marker(
|
|
662
868
|
session_log.parent,
|
|
663
869
|
reason="stale_session",
|
|
664
|
-
stale_seconds=
|
|
665
|
-
threshold=
|
|
870
|
+
stale_seconds=kill_threshold_seconds,
|
|
871
|
+
threshold=kill_threshold_seconds,
|
|
666
872
|
)
|
|
667
873
|
termination_reason = "stale_session"
|
|
668
874
|
recovery = recover_session_log(session_log, backup_log)
|
|
@@ -158,8 +158,6 @@ def runtime_paths(args, project_root, task_type, task_id, previous_session_id):
|
|
|
158
158
|
"bootstrap_prompt": _rel_path(project_root, output_path),
|
|
159
159
|
"session_log": _rel_path(project_root, os.path.join(logs_dir, "session.log")),
|
|
160
160
|
"progress_json": _rel_path(project_root, os.path.join(logs_dir, "progress.json")),
|
|
161
|
-
"log_monitor_status": _rel_path(project_root, os.path.join(logs_dir, "log-monitor-status.txt")),
|
|
162
|
-
"log_monitor_baseline": _rel_path(project_root, os.path.join(logs_dir, "log-monitor-baseline.txt")),
|
|
163
161
|
"pid_file": _rel_path(project_root, os.path.join(logs_dir, "ai.pid")),
|
|
164
162
|
"session_status": _rel_path(project_root, session_status),
|
|
165
163
|
"previous_session_log": _rel_path(project_root, previous_log),
|
|
@@ -296,7 +294,6 @@ def _build_addendum(context):
|
|
|
296
294
|
"- Continue from the first pending/in-progress checkpoint step: `{}`.".format(cursor["first_cursor"]),
|
|
297
295
|
"- Do not read the previous full `session.log` unless explicitly necessary.",
|
|
298
296
|
"- If previous runtime context is needed, prefer synthesized artifacts and summaries over logs.",
|
|
299
|
-
"- Do not attempt `/compact` as the recovery mechanism.",
|
|
300
297
|
"",
|
|
301
298
|
"### Preserved Durable Context",
|
|
302
299
|
"",
|
|
@@ -321,8 +318,6 @@ def _build_addendum(context):
|
|
|
321
318
|
"- Generated bootstrap prompt path: `{}`".format(runtime["bootstrap_prompt"]),
|
|
322
319
|
"- Session log path: `{}`".format(runtime["session_log"]),
|
|
323
320
|
"- Progress JSON path: `{}`".format(runtime["progress_json"]),
|
|
324
|
-
"- Log monitor status path: `{}`".format(runtime["log_monitor_status"]),
|
|
325
|
-
"- Log monitor baseline path: `{}`".format(runtime["log_monitor_baseline"]),
|
|
326
321
|
"- PID file: `{}`".format(runtime["pid_file"]),
|
|
327
322
|
"- Session status file: `{}`".format(runtime["session_status"]),
|
|
328
323
|
"",
|
|
@@ -27,12 +27,11 @@ import os
|
|
|
27
27
|
import re
|
|
28
28
|
import sys
|
|
29
29
|
|
|
30
|
-
from utils import enrich_global_context, load_json_file,
|
|
30
|
+
from utils import enrich_global_context, load_json_file, resolve_prompt_platform, setup_logging
|
|
31
31
|
from continuation import add_continuation_args, append_continuation_handoff, is_continuation
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
DEFAULT_MAX_RETRIES = 3
|
|
35
|
-
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
36
35
|
|
|
37
36
|
LOGGER = setup_logging("generate-bootstrap-prompt")
|
|
38
37
|
|
|
@@ -161,27 +160,6 @@ def compute_feature_slug(feature_id, title):
|
|
|
161
160
|
return "{}-{}".format(numeric, slug)
|
|
162
161
|
|
|
163
162
|
|
|
164
|
-
def _format_bytes(n):
|
|
165
|
-
"""Format a byte count to a human-readable string."""
|
|
166
|
-
if n >= 1048576:
|
|
167
|
-
return "{}MB".format(n // 1048576)
|
|
168
|
-
elif n >= 1024:
|
|
169
|
-
return "{}KB".format(n // 1024)
|
|
170
|
-
return "{}B".format(n)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _parse_non_negative_int_env(name, default):
|
|
174
|
-
"""Parse a non-negative integer environment variable."""
|
|
175
|
-
value = os.environ.get(name, str(default))
|
|
176
|
-
try:
|
|
177
|
-
parsed = int(value)
|
|
178
|
-
except ValueError:
|
|
179
|
-
emit_failure("{} must be a non-negative integer".format(name))
|
|
180
|
-
if parsed < 0:
|
|
181
|
-
emit_failure("{} must be a non-negative integer".format(name))
|
|
182
|
-
return parsed
|
|
183
|
-
|
|
184
|
-
|
|
185
163
|
def _parse_positive_int_env(name, default):
|
|
186
164
|
"""Parse a positive integer environment variable."""
|
|
187
165
|
value = os.environ.get(name, str(default))
|
|
@@ -195,20 +173,6 @@ def _parse_positive_int_env(name, default):
|
|
|
195
173
|
|
|
196
174
|
|
|
197
175
|
|
|
198
|
-
def load_log_size_section(script_dir):
|
|
199
|
-
"""Load the optional log-size awareness prompt section."""
|
|
200
|
-
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
201
|
-
if max_log_size == 0:
|
|
202
|
-
return ""
|
|
203
|
-
|
|
204
|
-
section_path = os.path.join(
|
|
205
|
-
script_dir, "..", "templates", "sections", "log-size-awareness.md",
|
|
206
|
-
)
|
|
207
|
-
content, err = read_text_file(section_path)
|
|
208
|
-
if err:
|
|
209
|
-
return ""
|
|
210
|
-
return content
|
|
211
|
-
|
|
212
176
|
def format_acceptance_criteria(criteria):
|
|
213
177
|
"""Format acceptance criteria as a markdown bullet list."""
|
|
214
178
|
if not criteria:
|
|
@@ -676,7 +640,7 @@ def determine_pipeline_mode(complexity):
|
|
|
676
640
|
SECTION_TO_SKILL = {
|
|
677
641
|
"phase0-init": ("prizmkit-init", "Project Bootstrap",
|
|
678
642
|
[".prizmkit/prizm-docs/root.prizm", ".prizmkit/config.json"]),
|
|
679
|
-
"phase0-test-baseline":
|
|
643
|
+
"phase0-test-baseline": None,
|
|
680
644
|
"phase-context-snapshot": ("context-snapshot", "Build Context Snapshot",
|
|
681
645
|
[".prizmkit/specs/{slug}/context-snapshot.md"]),
|
|
682
646
|
"phase-specify-plan": ("context-snapshot-and-plan", "Specify & Plan",
|
|
@@ -1299,17 +1263,16 @@ def load_section(sections_dir, name):
|
|
|
1299
1263
|
ACTIVE_AGENT_PROMPTS = {
|
|
1300
1264
|
"critic-plan-challenge.md": "{{AGENT_PROMPT_CRITIC_PLAN_CHALLENGE}}",
|
|
1301
1265
|
"reviewer-review.md": "{{AGENT_PROMPT_REVIEWER_REVIEW}}",
|
|
1302
|
-
"dev-fix.md": "{{AGENT_PROMPT_DEV_FIX}}",
|
|
1303
|
-
"dev-resume.md": "{{AGENT_PROMPT_DEV_RESUME}}",
|
|
1304
1266
|
}
|
|
1305
1267
|
|
|
1306
1268
|
|
|
1307
1269
|
def load_active_agent_prompts(templates_dir):
|
|
1308
1270
|
"""Load explicit active agent prompt templates.
|
|
1309
1271
|
|
|
1310
|
-
|
|
1311
|
-
feature implementation
|
|
1312
|
-
prompt placeholders support Critic
|
|
1272
|
+
Retired Dev implementation and review-fix prompts are intentionally not
|
|
1273
|
+
loaded; feature implementation and accepted review fixes are performed by
|
|
1274
|
+
the main orchestrator. Remaining prompt placeholders support Critic and
|
|
1275
|
+
read-only Reviewer handoffs.
|
|
1313
1276
|
"""
|
|
1314
1277
|
agent_prompts_dir = os.path.join(templates_dir, "agent-prompts")
|
|
1315
1278
|
if not os.path.isdir(agent_prompts_dir):
|
|
@@ -1470,12 +1433,6 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1470
1433
|
sections.append(("context-budget-rules",
|
|
1471
1434
|
load_section(sections_dir, "context-budget-rules.md")))
|
|
1472
1435
|
|
|
1473
|
-
# --- Log Size Awareness (only disabled when MAX_LOG_SIZE is explicitly 0) ---
|
|
1474
|
-
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
1475
|
-
if max_log_size != 0 and os.path.isfile(os.path.join(sections_dir, "log-size-awareness.md")):
|
|
1476
|
-
sections.append(("log-size-awareness",
|
|
1477
|
-
load_section(sections_dir, "log-size-awareness.md")))
|
|
1478
|
-
|
|
1479
1436
|
# --- Directory Convention (tier-specific) ---
|
|
1480
1437
|
if pipeline_mode == "lite":
|
|
1481
1438
|
dc_file = "directory-convention-lite.md"
|
|
@@ -1720,27 +1677,12 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1720
1677
|
"""Build the full dict of placeholder -> replacement value."""
|
|
1721
1678
|
project_root = _args_project_root(args, script_dir)
|
|
1722
1679
|
|
|
1723
|
-
# Resolve paths - platform-aware agent/team resolution
|
|
1724
|
-
platform
|
|
1680
|
+
# Resolve paths - platform-aware agent/team resolution.
|
|
1681
|
+
# Mixed `--platform all` installs must render commands for the actual AI
|
|
1682
|
+
# environment, not whichever platform directory happens to be checked first.
|
|
1683
|
+
platform = resolve_prompt_platform(project_root)
|
|
1725
1684
|
home_dir = os.path.expanduser("~")
|
|
1726
1685
|
|
|
1727
|
-
# Auto-detect platform if not set
|
|
1728
|
-
if not platform:
|
|
1729
|
-
has_codex = os.path.isdir(os.path.join(project_root, ".codex", "agents"))
|
|
1730
|
-
has_claude = os.path.isdir(os.path.join(project_root, ".claude", "agents"))
|
|
1731
|
-
has_codebuddy = os.path.isdir(os.path.join(project_root, ".codebuddy", "agents"))
|
|
1732
|
-
if has_codex:
|
|
1733
|
-
platform = "codex"
|
|
1734
|
-
elif has_claude:
|
|
1735
|
-
platform = "claude"
|
|
1736
|
-
elif has_codebuddy:
|
|
1737
|
-
platform = "codebuddy"
|
|
1738
|
-
else:
|
|
1739
|
-
raise RuntimeError(
|
|
1740
|
-
"PrizmKit agents not found. None of .codex/agents/, .claude/agents/, or .codebuddy/agents/ exists. "
|
|
1741
|
-
"Run `npx prizmkit install` first, or set PRIZMKIT_PLATFORM=codex|claude|codebuddy explicitly."
|
|
1742
|
-
)
|
|
1743
|
-
|
|
1744
1686
|
if platform == "claude":
|
|
1745
1687
|
# Claude Code: agents in .claude/agents/, no native team config
|
|
1746
1688
|
agents_dir = os.path.join(project_root, ".claude", "agents")
|
|
@@ -1949,10 +1891,7 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1949
1891
|
pass # Keep default 3000 on any error
|
|
1950
1892
|
dev_url = f"http://localhost:{dev_port}"
|
|
1951
1893
|
|
|
1952
|
-
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
1953
|
-
|
|
1954
1894
|
replacements = {
|
|
1955
|
-
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
1956
1895
|
"{{RUN_ID}}": args.run_id,
|
|
1957
1896
|
"{{SESSION_ID}}": args.session_id,
|
|
1958
1897
|
"{{FEATURE_ID}}": args.feature_id,
|
|
@@ -1968,7 +1907,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1968
1907
|
),
|
|
1969
1908
|
"{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
|
|
1970
1909
|
"{{PROJECT_BRIEF}}": _read_project_brief(project_root),
|
|
1971
|
-
"{{PLATFORM_CONVENTIONS}}": read_platform_conventions(project_root),
|
|
1972
1910
|
"{{TEAM_CONFIG_PATH}}": team_config_path,
|
|
1973
1911
|
"{{DEV_SUBAGENT_PATH}}": dev_subagent,
|
|
1974
1912
|
"{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
|
|
@@ -1997,8 +1935,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1997
1935
|
"{{COVERAGE_TARGET}}": coverage_target,
|
|
1998
1936
|
"{{DEV_PORT}}": dev_port,
|
|
1999
1937
|
"{{DEV_URL}}": dev_url,
|
|
2000
|
-
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
2001
|
-
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
2002
1938
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
2003
1939
|
".prizmkit", "state", "features", args.feature_id,
|
|
2004
1940
|
"sessions", args.session_id, "logs", "session.log",
|
|
@@ -2090,9 +2026,6 @@ def main():
|
|
|
2090
2026
|
agent_prompt_replacements = load_active_agent_prompts(templates_dir)
|
|
2091
2027
|
replacements.update(agent_prompt_replacements)
|
|
2092
2028
|
|
|
2093
|
-
if is_continuation(args):
|
|
2094
|
-
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
2095
|
-
|
|
2096
2029
|
# Extract state needed for assembly
|
|
2097
2030
|
pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
|
|
2098
2031
|
init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
|
|
@@ -2112,11 +2045,6 @@ def main():
|
|
|
2112
2045
|
retry_count=int(args.retry_count),
|
|
2113
2046
|
browser_tool=browser_tool,
|
|
2114
2047
|
)
|
|
2115
|
-
if is_continuation(args):
|
|
2116
|
-
sections = [
|
|
2117
|
-
(name, content) for name, content in sections
|
|
2118
|
-
if name != "log-size-awareness"
|
|
2119
|
-
]
|
|
2120
2048
|
rendered = render_from_sections(sections, replacements)
|
|
2121
2049
|
except FileNotFoundError as exc:
|
|
2122
2050
|
LOGGER.warning(
|