prizmkit 1.1.99 → 1.1.100
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/lib/common.sh +244 -25
- package/bundled/dev-pipeline/reset-bug.sh +30 -0
- package/bundled/dev-pipeline/reset-feature.sh +30 -0
- package/bundled/dev-pipeline/reset-refactor.sh +30 -0
- package/bundled/dev-pipeline/run-bugfix.sh +128 -13
- package/bundled/dev-pipeline/run-feature.sh +130 -14
- package/bundled/dev-pipeline/run-refactor.sh +128 -13
- package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -30
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/conftest.py +1 -0
- package/bundled/dev-pipeline/tests/test_auto_skip.py +510 -0
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
- package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +93 -9
- package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
- package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
- package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
- package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
- package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +27 -30
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
- package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
- package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
|
@@ -20,11 +20,11 @@ import re
|
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
22
|
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
23
|
+
from continuation import add_continuation_args, append_continuation_handoff, is_continuation
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
DEFAULT_MAX_RETRIES = 3
|
|
26
27
|
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
27
|
-
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
28
28
|
|
|
29
29
|
LOGGER = setup_logging("generate-bugfix-prompt")
|
|
30
30
|
|
|
@@ -48,6 +48,8 @@ def parse_args():
|
|
|
48
48
|
parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
|
|
49
49
|
parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
|
|
50
50
|
parser.add_argument("--no-checkpoint", action="store_true", help="Do not write workflow-checkpoint.json (used by pipeline dry-run)")
|
|
51
|
+
add_continuation_args(parser, "bugfix")
|
|
52
|
+
|
|
51
53
|
return parser.parse_args()
|
|
52
54
|
|
|
53
55
|
|
|
@@ -358,33 +360,15 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
358
360
|
browser_verify_steps = " # (reproduce bug and verify fix)"
|
|
359
361
|
|
|
360
362
|
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
361
|
-
if max_log_size == 0:
|
|
362
|
-
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
363
|
-
else:
|
|
364
|
-
poll_interval = _parse_positive_int_env(
|
|
365
|
-
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
366
|
-
)
|
|
367
363
|
|
|
368
364
|
replacements = {
|
|
369
365
|
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
370
366
|
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
371
367
|
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
372
|
-
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
373
|
-
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
374
|
-
".prizmkit", "state", "bugfix", args.bug_id,
|
|
375
|
-
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
376
|
-
),
|
|
377
|
-
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
378
|
-
".prizmkit", "state", "bugfix", args.bug_id,
|
|
379
|
-
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
380
|
-
),
|
|
381
368
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
382
369
|
".prizmkit", "state", "bugfix", args.bug_id,
|
|
383
370
|
"sessions", args.session_id, "logs", "session.log",
|
|
384
371
|
),
|
|
385
|
-
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
386
|
-
".prizmkit", "dev-pipeline", "scripts", "monitor-log.ps1",
|
|
387
|
-
),
|
|
388
372
|
"{{RUN_ID}}": args.run_id,
|
|
389
373
|
"{{SESSION_ID}}": args.session_id,
|
|
390
374
|
"{{BUG_ID}}": args.bug_id,
|
|
@@ -662,6 +646,8 @@ def main():
|
|
|
662
646
|
|
|
663
647
|
# Build replacements
|
|
664
648
|
replacements = build_replacements(args, bug, global_context, script_dir)
|
|
649
|
+
if is_continuation(args):
|
|
650
|
+
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
665
651
|
|
|
666
652
|
# Add checkpoint path to replacements
|
|
667
653
|
checkpoint_rel = os.path.join(
|
|
@@ -680,6 +666,7 @@ def main():
|
|
|
680
666
|
# Generate checkpoint file
|
|
681
667
|
project_root = resolve_project_root(script_dir)
|
|
682
668
|
checkpoint_path = os.path.join(project_root, checkpoint_rel)
|
|
669
|
+
checkpoint = {}
|
|
683
670
|
if not args.no_checkpoint:
|
|
684
671
|
checkpoint_dir = os.path.dirname(checkpoint_path)
|
|
685
672
|
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
@@ -687,7 +674,7 @@ def main():
|
|
|
687
674
|
checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
|
|
688
675
|
|
|
689
676
|
is_resume = args.resume_phase != "null"
|
|
690
|
-
if is_resume and os.path.exists(checkpoint_path):
|
|
677
|
+
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
691
678
|
try:
|
|
692
679
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
693
680
|
existing = json.load(f)
|
|
@@ -706,6 +693,15 @@ def main():
|
|
|
706
693
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
707
694
|
LOGGER.info("Wrote bugfix checkpoint to %s", checkpoint_path)
|
|
708
695
|
|
|
696
|
+
if is_continuation(args):
|
|
697
|
+
rendered = append_continuation_handoff(
|
|
698
|
+
rendered, args, project_root, args.task_type, args.bug_id,
|
|
699
|
+
args.bug_id, checkpoint,
|
|
700
|
+
)
|
|
701
|
+
err = write_output(args.output, rendered)
|
|
702
|
+
if err:
|
|
703
|
+
emit_failure(err)
|
|
704
|
+
|
|
709
705
|
# Resolve critic and mode
|
|
710
706
|
bug_critic = bug.get("critic", False)
|
|
711
707
|
if args.critic is not None:
|
|
@@ -20,11 +20,11 @@ import re
|
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
22
|
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
23
|
+
from continuation import add_continuation_args, append_continuation_handoff, is_continuation
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
DEFAULT_MAX_RETRIES = 3
|
|
26
27
|
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
27
|
-
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
28
28
|
|
|
29
29
|
LOGGER = setup_logging("generate-refactor-prompt")
|
|
30
30
|
|
|
@@ -139,6 +139,8 @@ def parse_args():
|
|
|
139
139
|
parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
|
|
140
140
|
parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
|
|
141
141
|
parser.add_argument("--no-checkpoint", action="store_true", help="Do not write workflow-checkpoint.json (used by pipeline dry-run)")
|
|
142
|
+
add_continuation_args(parser, "refactor")
|
|
143
|
+
|
|
142
144
|
return parser.parse_args()
|
|
143
145
|
|
|
144
146
|
|
|
@@ -518,33 +520,15 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
518
520
|
browser_verify_steps = " # (validate UI renders correctly and feature still works)"
|
|
519
521
|
|
|
520
522
|
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
521
|
-
if max_log_size == 0:
|
|
522
|
-
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
523
|
-
else:
|
|
524
|
-
poll_interval = _parse_positive_int_env(
|
|
525
|
-
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
526
|
-
)
|
|
527
523
|
|
|
528
524
|
replacements = {
|
|
529
525
|
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
530
526
|
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
531
527
|
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
532
|
-
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
533
|
-
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
534
|
-
".prizmkit", "state", "refactor", args.refactor_id,
|
|
535
|
-
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
536
|
-
),
|
|
537
|
-
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
538
|
-
".prizmkit", "state", "refactor", args.refactor_id,
|
|
539
|
-
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
540
|
-
),
|
|
541
528
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
542
529
|
".prizmkit", "state", "refactor", args.refactor_id,
|
|
543
530
|
"sessions", args.session_id, "logs", "session.log",
|
|
544
531
|
),
|
|
545
|
-
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
546
|
-
".prizmkit", "dev-pipeline", "scripts", "monitor-log.ps1",
|
|
547
|
-
),
|
|
548
532
|
"{{RUN_ID}}": args.run_id,
|
|
549
533
|
"{{SESSION_ID}}": args.session_id,
|
|
550
534
|
"{{REFACTOR_ID}}": args.refactor_id,
|
|
@@ -744,6 +728,8 @@ def main():
|
|
|
744
728
|
|
|
745
729
|
# Build replacements
|
|
746
730
|
replacements = build_replacements(args, refactor, refactors, global_context, script_dir)
|
|
731
|
+
if is_continuation(args):
|
|
732
|
+
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
747
733
|
|
|
748
734
|
# Render the template
|
|
749
735
|
rendered = render_template(template_content, replacements, args.resume_phase, refactor)
|
|
@@ -759,6 +745,7 @@ def main():
|
|
|
759
745
|
".prizmkit", "refactor", args.refactor_id, "workflow-checkpoint.json",
|
|
760
746
|
)
|
|
761
747
|
checkpoint_path = os.path.join(project_root, checkpoint_rel)
|
|
748
|
+
checkpoint = {}
|
|
762
749
|
if not args.no_checkpoint:
|
|
763
750
|
checkpoint_dir = os.path.dirname(checkpoint_path)
|
|
764
751
|
os.makedirs(checkpoint_dir, exist_ok=True)
|
|
@@ -766,7 +753,7 @@ def main():
|
|
|
766
753
|
checkpoint = generate_refactor_checkpoint(args.refactor_id, args.session_id)
|
|
767
754
|
|
|
768
755
|
is_resume = args.resume_phase != "null"
|
|
769
|
-
if is_resume and os.path.exists(checkpoint_path):
|
|
756
|
+
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
770
757
|
try:
|
|
771
758
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
772
759
|
existing = json.load(f)
|
|
@@ -785,6 +772,15 @@ def main():
|
|
|
785
772
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
786
773
|
LOGGER.info("Wrote refactor checkpoint to %s", checkpoint_path)
|
|
787
774
|
|
|
775
|
+
if is_continuation(args):
|
|
776
|
+
rendered = append_continuation_handoff(
|
|
777
|
+
rendered, args, project_root, args.task_type, args.refactor_id,
|
|
778
|
+
args.refactor_id, checkpoint,
|
|
779
|
+
)
|
|
780
|
+
err = write_output(args.output, rendered)
|
|
781
|
+
if err:
|
|
782
|
+
emit_failure(err)
|
|
783
|
+
|
|
788
784
|
# Resolve critic and mode
|
|
789
785
|
refactor_critic = refactor.get("critic", False)
|
|
790
786
|
if args.critic is not None:
|
|
@@ -60,30 +60,66 @@ PHASE_KEYWORDS = {
|
|
|
60
60
|
},
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
CONTEXT_OVERFLOW_CODE = "context_overflow"
|
|
64
|
+
|
|
65
|
+
# Exact machine/wrapper signals. These are only treated as self-sufficient when
|
|
66
|
+
# they appear in structured error-code fields or as explicit wrapper markers.
|
|
67
|
+
CONTEXT_CODE_PATTERNS = [
|
|
68
|
+
re.compile(pattern, re.IGNORECASE)
|
|
69
|
+
for pattern in (
|
|
70
|
+
r"\bcontext_overflow\b",
|
|
71
|
+
r"\bcontext_too_large\b",
|
|
72
|
+
r"\bcontext_length_exceeded\b",
|
|
73
|
+
r"\bmodel_context_window_exceeded\b",
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
WRAPPER_CONTEXT_ERROR_PATTERNS = [
|
|
78
|
+
re.compile(pattern, re.IGNORECASE)
|
|
79
|
+
for pattern in (
|
|
80
|
+
r"\bPRIZMKIT_FATAL_ERROR\s*=\s*context_overflow\b",
|
|
81
|
+
)
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Natural-language provider messages are weak signals: they require an adjacent
|
|
85
|
+
# runtime/API error context unless a structured field already proves the result
|
|
86
|
+
# is an error.
|
|
87
|
+
WEAK_CONTEXT_ERROR_PATTERNS = [
|
|
64
88
|
re.compile(pattern, re.IGNORECASE)
|
|
65
89
|
for pattern in (
|
|
66
|
-
r"context_too_large",
|
|
67
|
-
r"model_context_window_exceeded",
|
|
68
90
|
r"Your input exceeds the context window",
|
|
69
|
-
r"input exceeds the context window",
|
|
91
|
+
r"input exceeds (?:the )?(?:model )?context window",
|
|
70
92
|
r"context window of this model",
|
|
71
|
-
r"context window exceeded",
|
|
72
|
-
r"
|
|
73
|
-
r"context
|
|
93
|
+
r"context window (?:was )?exceeded",
|
|
94
|
+
r"exceeded (?:the )?context window",
|
|
95
|
+
r"maximum context length exceeded",
|
|
96
|
+
r"prompt is too long",
|
|
97
|
+
r"too many tokens",
|
|
98
|
+
r"input token count exceeds",
|
|
99
|
+
r"input tokens? exceeds?",
|
|
100
|
+
r"token count exceeds",
|
|
74
101
|
)
|
|
75
102
|
]
|
|
76
103
|
|
|
104
|
+
REQUEST_TOO_LARGE_PATTERN = re.compile(r"\brequest (?:is )?too large\b", re.IGNORECASE)
|
|
105
|
+
REQUEST_TOO_LARGE_SEMANTIC_PATTERN = re.compile(
|
|
106
|
+
r"\b(context|token|tokens|prompt|input|model)\b",
|
|
107
|
+
re.IGNORECASE,
|
|
108
|
+
)
|
|
109
|
+
|
|
77
110
|
ERROR_CONTEXT_PATTERNS = [
|
|
78
111
|
re.compile(pattern, re.IGNORECASE)
|
|
79
112
|
for pattern in (
|
|
80
113
|
r"\bapi error\b",
|
|
81
114
|
r"invalid_request_error",
|
|
115
|
+
r"\bbad request\b",
|
|
82
116
|
r"\bstatus\s*[:=]?\s*(400|413)\b",
|
|
83
117
|
r"\bapi_error_status\b",
|
|
84
118
|
r"\bapi_error_code\b",
|
|
85
119
|
r"\blast_result_is_error\b\s*[\"':=]*\s*true\b",
|
|
86
120
|
r"\bis_error\b\s*[\"':=]*\s*true\b",
|
|
121
|
+
r"\berror\s*[:=]",
|
|
122
|
+
r"\bfatal\s*[:=]",
|
|
87
123
|
)
|
|
88
124
|
]
|
|
89
125
|
|
|
@@ -95,21 +131,41 @@ def _has_error_context(text):
|
|
|
95
131
|
return any(pattern.search(text) for pattern in ERROR_CONTEXT_PATTERNS)
|
|
96
132
|
|
|
97
133
|
|
|
98
|
-
def
|
|
134
|
+
def _has_context_overflow_signal(text, structured_code=False):
|
|
135
|
+
"""Return true when text contains a context-window/token overflow signal."""
|
|
136
|
+
if not text:
|
|
137
|
+
return False
|
|
138
|
+
if any(pattern.search(text) for pattern in WRAPPER_CONTEXT_ERROR_PATTERNS):
|
|
139
|
+
return True
|
|
140
|
+
if structured_code and any(pattern.search(text) for pattern in CONTEXT_CODE_PATTERNS):
|
|
141
|
+
return True
|
|
142
|
+
if any(pattern.search(text) for pattern in CONTEXT_CODE_PATTERNS):
|
|
143
|
+
return True
|
|
144
|
+
if any(pattern.search(text) for pattern in WEAK_CONTEXT_ERROR_PATTERNS):
|
|
145
|
+
return True
|
|
146
|
+
if REQUEST_TOO_LARGE_PATTERN.search(text) and REQUEST_TOO_LARGE_SEMANTIC_PATTERN.search(text):
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def detect_api_error_code(text, require_error_context=False, structured_code=False):
|
|
99
152
|
"""Return a normalized fatal/runtime error code from terminal text.
|
|
100
153
|
|
|
101
|
-
Structured
|
|
102
|
-
directly.
|
|
103
|
-
|
|
104
|
-
|
|
154
|
+
Structured error-code fields and explicit wrapper markers can be matched
|
|
155
|
+
directly. Natural-language provider messages require runtime/API context to
|
|
156
|
+
avoid false positives from prompts, tests, or documentation that merely
|
|
157
|
+
mention context windows or token limits.
|
|
105
158
|
"""
|
|
106
159
|
if not text:
|
|
107
160
|
return ""
|
|
161
|
+
if any(pattern.search(text) for pattern in WRAPPER_CONTEXT_ERROR_PATTERNS):
|
|
162
|
+
return CONTEXT_OVERFLOW_CODE
|
|
163
|
+
if structured_code and any(pattern.search(text) for pattern in CONTEXT_CODE_PATTERNS):
|
|
164
|
+
return CONTEXT_OVERFLOW_CODE
|
|
108
165
|
if require_error_context and not _has_error_context(text):
|
|
109
166
|
return ""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return "context_too_large"
|
|
167
|
+
if _has_context_overflow_signal(text, structured_code=structured_code):
|
|
168
|
+
return CONTEXT_OVERFLOW_CODE
|
|
113
169
|
return ""
|
|
114
170
|
|
|
115
171
|
|
|
@@ -171,6 +227,7 @@ class ProgressTracker:
|
|
|
171
227
|
with sessionId/cwd metadata, function_call for tool invocations).
|
|
172
228
|
"""
|
|
173
229
|
event_type = event.get("type", "")
|
|
230
|
+
self._detect_structured_terminal_fields(event)
|
|
174
231
|
|
|
175
232
|
# ── Codex exec --json JSONL format ──────────────────────────
|
|
176
233
|
if event_type in (
|
|
@@ -491,6 +548,7 @@ class ProgressTracker:
|
|
|
491
548
|
is_error=bool(event.get("is_error")),
|
|
492
549
|
api_error_status=event.get("api_error_status"),
|
|
493
550
|
api_error_code=str(api_error_code or ""),
|
|
551
|
+
stop_reason=str(event.get("stop_reason") or ""),
|
|
494
552
|
)
|
|
495
553
|
|
|
496
554
|
# ── Claude API raw stream format ────────────────────────────
|
|
@@ -579,6 +637,78 @@ class ProgressTracker:
|
|
|
579
637
|
# This is a sub-agent event; tool name is still tracked normally
|
|
580
638
|
pass
|
|
581
639
|
|
|
640
|
+
|
|
641
|
+
def _detect_structured_terminal_fields(self, event):
|
|
642
|
+
"""Detect provider-neutral terminal/context errors before format routing."""
|
|
643
|
+
if not isinstance(event, dict):
|
|
644
|
+
return
|
|
645
|
+
|
|
646
|
+
code_parts = []
|
|
647
|
+
text_parts = []
|
|
648
|
+
terminal_parts = []
|
|
649
|
+
|
|
650
|
+
for key in ("fatal_error_code", "api_error_code", "error_code", "stop_reason"):
|
|
651
|
+
value = event.get(key)
|
|
652
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
653
|
+
code_parts.append(str(value))
|
|
654
|
+
|
|
655
|
+
for key in ("terminal_result_text", "message", "result"):
|
|
656
|
+
value = event.get(key)
|
|
657
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
658
|
+
text_parts.append(str(value))
|
|
659
|
+
terminal_parts.append(str(value))
|
|
660
|
+
|
|
661
|
+
error_obj = event.get("error")
|
|
662
|
+
if isinstance(error_obj, dict):
|
|
663
|
+
for key in ("type", "code", "message"):
|
|
664
|
+
value = error_obj.get(key)
|
|
665
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
666
|
+
text_parts.append(str(value))
|
|
667
|
+
terminal_parts.append(str(value))
|
|
668
|
+
elif isinstance(error_obj, (str, int, float)) and str(error_obj).strip():
|
|
669
|
+
text_parts.append(str(error_obj))
|
|
670
|
+
terminal_parts.append(str(error_obj))
|
|
671
|
+
|
|
672
|
+
combined_code_text = " ".join(code_parts)
|
|
673
|
+
combined_text = " ".join(code_parts + text_parts)
|
|
674
|
+
if not combined_text:
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
error_like = (
|
|
678
|
+
bool(code_parts)
|
|
679
|
+
or event.get("api_error_status") not in (None, "")
|
|
680
|
+
or bool(event.get("last_result_is_error"))
|
|
681
|
+
or bool(event.get("is_error"))
|
|
682
|
+
or _has_error_context(combined_text)
|
|
683
|
+
)
|
|
684
|
+
normalized_code = detect_api_error_code(
|
|
685
|
+
combined_code_text,
|
|
686
|
+
require_error_context=False,
|
|
687
|
+
structured_code=True,
|
|
688
|
+
) or detect_api_error_code(
|
|
689
|
+
combined_text,
|
|
690
|
+
require_error_context=not error_like,
|
|
691
|
+
structured_code=bool(code_parts),
|
|
692
|
+
)
|
|
693
|
+
if not normalized_code:
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
self.last_result_is_error = True
|
|
697
|
+
api_error_status = event.get("api_error_status") or event.get("status")
|
|
698
|
+
if api_error_status not in (None, ""):
|
|
699
|
+
try:
|
|
700
|
+
self.api_error_status = int(api_error_status)
|
|
701
|
+
except (TypeError, ValueError):
|
|
702
|
+
self.api_error_status = api_error_status
|
|
703
|
+
self.api_error_code = normalized_code
|
|
704
|
+
self.fatal_error_code = normalized_code
|
|
705
|
+
terminal_text = " ".join(terminal_parts) or combined_text
|
|
706
|
+
self.terminal_result_text = terminal_text[:1000]
|
|
707
|
+
if terminal_text.strip():
|
|
708
|
+
self.last_text_snippet = terminal_text.strip()[:120]
|
|
709
|
+
if not self.errors or self.errors[-1] != normalized_code:
|
|
710
|
+
self.errors.append(normalized_code)
|
|
711
|
+
|
|
582
712
|
def _record_cb_agent_tool_call(self, tool_name, raw_args):
|
|
583
713
|
"""Record a CodeBuddy Agent/Task* tool invocation for sub-agent tracking.
|
|
584
714
|
|
|
@@ -616,7 +746,7 @@ class ProgressTracker:
|
|
|
616
746
|
elif tool_name == "TaskCreate":
|
|
617
747
|
self._subagent_spawn_count += 1
|
|
618
748
|
|
|
619
|
-
def _record_terminal_result(self, text="", is_error=False, api_error_status=None, api_error_code=""):
|
|
749
|
+
def _record_terminal_result(self, text="", is_error=False, api_error_status=None, api_error_code="", stop_reason=""):
|
|
620
750
|
"""Record a Claude Code terminal result event."""
|
|
621
751
|
terminal_text = str(text or "")
|
|
622
752
|
self.last_result_is_error = bool(is_error)
|