wydcode 0.4.3
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/LICENSE +100 -0
- package/NOTICE +5 -0
- package/README.md +434 -0
- package/SECURITY.md +64 -0
- package/SOCRA_SOURCE.md +36 -0
- package/TRADEMARKS.md +12 -0
- package/bin/wydcode.js +74 -0
- package/examples/behavior-preference-card.json +12 -0
- package/examples/exact-recurrence-card.json +11 -0
- package/examples/heldout-gene-evidence.json +6 -0
- package/examples/humaneval-ornith.md +29 -0
- package/examples/large_context_json_diagnostic.py +187 -0
- package/fixtures/failing-node-repo/package.json +9 -0
- package/fixtures/failing-node-repo/src/math.js +3 -0
- package/fixtures/failing-node-repo/test/math.test.js +8 -0
- package/package.json +55 -0
- package/pyproject.toml +24 -0
- package/socra_harness/__init__.py +3 -0
- package/socra_harness/attempts.py +76 -0
- package/socra_harness/central_traces.py +627 -0
- package/socra_harness/cli.py +265 -0
- package/socra_harness/eval/__init__.py +1 -0
- package/socra_harness/eval/humaneval.py +388 -0
- package/socra_harness/events.py +39 -0
- package/socra_harness/init.py +206 -0
- package/socra_harness/job/__init__.py +2 -0
- package/socra_harness/job/events.py +132 -0
- package/socra_harness/job/lock.py +90 -0
- package/socra_harness/job/manager.py +3785 -0
- package/socra_harness/job/planner.py +253 -0
- package/socra_harness/job/state.py +106 -0
- package/socra_harness/mumpix_trace_bridge.cjs +32 -0
- package/socra_harness/product.py +533 -0
- package/socra_harness/recurrence.py +263 -0
- package/socra_harness/scan/__init__.py +1 -0
- package/socra_harness/scan/repo.py +85 -0
- package/socra_harness/serve/__init__.py +1 -0
- package/socra_harness/serve/local_proxy.py +293 -0
- package/socra_harness/start.py +331 -0
- package/socra_harness/tui/__init__.py +1 -0
- package/socra_harness/tui/app.py +1114 -0
package/bin/wydcode.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
8
|
+
const cliName = path.basename(process.argv[1] || '').includes('wydcode') ? 'wydcode' : 'socra-harness';
|
|
9
|
+
const candidates = [
|
|
10
|
+
process.env.SOCRA_PYTHON,
|
|
11
|
+
'python3.12',
|
|
12
|
+
'python3.11',
|
|
13
|
+
'python3',
|
|
14
|
+
].filter(Boolean);
|
|
15
|
+
|
|
16
|
+
function run(candidate, args, options = {}) {
|
|
17
|
+
return spawnSync(candidate, args, {
|
|
18
|
+
cwd: process.cwd(),
|
|
19
|
+
env: {
|
|
20
|
+
...process.env,
|
|
21
|
+
SOCRA_CLI_NAME: process.env.SOCRA_CLI_NAME || cliName,
|
|
22
|
+
SOCRA_CLI_NAME: 'wydcode',
|
|
23
|
+
PYTHONPATH: [packageRoot, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter),
|
|
24
|
+
},
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
...options,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let selected = null;
|
|
31
|
+
let lastError = '';
|
|
32
|
+
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
const probe = run(candidate, [
|
|
35
|
+
'-c',
|
|
36
|
+
'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 42)',
|
|
37
|
+
]);
|
|
38
|
+
if (probe.status === 0) {
|
|
39
|
+
selected = candidate;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (probe.error) {
|
|
43
|
+
lastError = String(probe.error.message || probe.error);
|
|
44
|
+
} else if (probe.status === 42) {
|
|
45
|
+
lastError = `${candidate} is older than Python 3.11`;
|
|
46
|
+
} else if (probe.stderr) {
|
|
47
|
+
lastError = probe.stderr.trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!selected) {
|
|
52
|
+
console.error(`${cliName} requires Python 3.11+.`);
|
|
53
|
+
console.error('Install Python 3.11+ or set SOCRA_PYTHON=/path/to/python.');
|
|
54
|
+
if (lastError) {
|
|
55
|
+
console.error(`Last probe: ${lastError}`);
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = run(selected, ['-m', 'socra_harness.cli', ...process.argv.slice(2)], {
|
|
61
|
+
stdio: 'inherit',
|
|
62
|
+
encoding: undefined,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (result.error) {
|
|
66
|
+
console.error(`Failed to run ${cliName} with ${selected}: ${result.error.message}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (result.signal) {
|
|
71
|
+
process.kill(process.pid, result.signal);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"approach": "Preserve the arithmetic contract while correcting the failing branch.",
|
|
3
|
+
"invariants_to_preserve": [
|
|
4
|
+
"Inputs remain immutable and ordinary numeric addition remains unchanged."
|
|
5
|
+
],
|
|
6
|
+
"checks_to_run": [
|
|
7
|
+
"Exercise positive, negative, and zero operands through the public API."
|
|
8
|
+
],
|
|
9
|
+
"known_nonmatches": [
|
|
10
|
+
"Do not apply this behavior to multiplication or string concatenation failures."
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"card_id": "math-add-terminal-assertion-v1",
|
|
3
|
+
"repository_identity": "github.com/mumpix/wydcode",
|
|
4
|
+
"owning_path": "src/math.js",
|
|
5
|
+
"owning_symbol_or_api": "add",
|
|
6
|
+
"invariant": {
|
|
7
|
+
"error_or_assertion_class": "AssertionError",
|
|
8
|
+
"operation": "add two numeric operands",
|
|
9
|
+
"expected_relation": "result equals the arithmetic sum"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Example: Ornith HumanEval with wydcode
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
MODEL_PATH="$HOME/models/ornith-1.0-35b-Q6_K.gguf"
|
|
5
|
+
LLAMA_CLI="$HOME/src/llama.cpp/build/bin/llama-cli"
|
|
6
|
+
TASKS_PATH="$HOME/benchmarks/humaneval-openai-full-164.jsonl"
|
|
7
|
+
|
|
8
|
+
wydcode eval humaneval \
|
|
9
|
+
--name ornith \
|
|
10
|
+
--model "$MODEL_PATH" \
|
|
11
|
+
--llama-cli "$LLAMA_CLI" \
|
|
12
|
+
--tasks "$TASKS_PATH" \
|
|
13
|
+
--mode socra \
|
|
14
|
+
--gpu-layers 999 \
|
|
15
|
+
--no-cpu-fallback
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Known local result from the corrected harness:
|
|
19
|
+
|
|
20
|
+
- `145/164`
|
|
21
|
+
- pass@1 `0.8841`
|
|
22
|
+
- GPU generations `164/164`
|
|
23
|
+
- CPU fallback `0/164`
|
|
24
|
+
|
|
25
|
+
Open the terminal UI in another shell:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
wydcode tui --state-dir .socra
|
|
29
|
+
```
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from socra_harness.job.events import JobEventLog, read_events
|
|
10
|
+
from socra_harness.job.lock import JobLock
|
|
11
|
+
from socra_harness.job.manager import (
|
|
12
|
+
CREATOR_PROMPT_LAYOUT,
|
|
13
|
+
build_creator_messages,
|
|
14
|
+
call_creator_model,
|
|
15
|
+
declared_file_set,
|
|
16
|
+
endpoint_health,
|
|
17
|
+
latest_passed_artifacts_by_path,
|
|
18
|
+
load_plan,
|
|
19
|
+
parse_creator_json,
|
|
20
|
+
read_declared_file_context,
|
|
21
|
+
sha256_file,
|
|
22
|
+
sha256_text,
|
|
23
|
+
validate_creator_declared_files,
|
|
24
|
+
)
|
|
25
|
+
from socra_harness.job.planner import materialize_plan_from_events
|
|
26
|
+
from socra_harness.job.state import replay
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def elapsed_ms(start: float) -> int:
|
|
30
|
+
return int(round((time.monotonic() - start) * 1000))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
34
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> int:
|
|
39
|
+
parser = argparse.ArgumentParser(description="Run a large-context JSON-contract diagnostic against a Socra job.")
|
|
40
|
+
parser.add_argument("--project", required=True, type=Path)
|
|
41
|
+
parser.add_argument("--job-id", required=True)
|
|
42
|
+
parser.add_argument("--diagnostic-id", default="large-context-json-v1")
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--declared-file",
|
|
45
|
+
action="append",
|
|
46
|
+
dest="declared_files",
|
|
47
|
+
default=[],
|
|
48
|
+
help="Relative file to include. Repeatable. Defaults to the server decomposition stress shape.",
|
|
49
|
+
)
|
|
50
|
+
args = parser.parse_args()
|
|
51
|
+
|
|
52
|
+
project = args.project.expanduser().resolve()
|
|
53
|
+
job_dir = project / ".socra" / "jobs" / args.job_id
|
|
54
|
+
declared_files = args.declared_files or [
|
|
55
|
+
"server.js",
|
|
56
|
+
"server/admin/artifactReaders.js",
|
|
57
|
+
"server/admin/walAdapter.js",
|
|
58
|
+
"server/admin/geneRouteAdapter.js",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
with JobLock(job_dir / ".lock"):
|
|
62
|
+
events = read_events(job_dir / "events.jsonl")
|
|
63
|
+
state = replay(events)
|
|
64
|
+
plan = materialize_plan_from_events(events, load_plan(job_dir))
|
|
65
|
+
creator = state.creator_config or {}
|
|
66
|
+
log = JobEventLog(job_dir / "events.jsonl", state.job_id or args.job_id)
|
|
67
|
+
|
|
68
|
+
health = endpoint_health(str(creator.get("endpoint") or "http://127.0.0.1:11439/v1"))
|
|
69
|
+
expected_model = str(creator.get("expected_served_model_id") or Path(str(creator.get("model") or "")).name)
|
|
70
|
+
if expected_model:
|
|
71
|
+
served_ids = [str(item) for item in health.get("model_ids") or []]
|
|
72
|
+
health["expected_served_model_id"] = expected_model
|
|
73
|
+
health["model_identity_ok"] = expected_model in served_ids
|
|
74
|
+
health["served_model_ids"] = served_ids
|
|
75
|
+
|
|
76
|
+
task = {
|
|
77
|
+
"id": args.diagnostic_id,
|
|
78
|
+
"description": (
|
|
79
|
+
"Diagnostic only: test whether the current Creator configuration can return valid JSON "
|
|
80
|
+
"for a large multi-file context. Preserve existing behavior and return full file contents."
|
|
81
|
+
),
|
|
82
|
+
"acceptance": [
|
|
83
|
+
"response is valid Creator JSON",
|
|
84
|
+
"all returned paths stay within declared files",
|
|
85
|
+
"finish_reason indicates a complete response",
|
|
86
|
+
],
|
|
87
|
+
"risk_tier": 0,
|
|
88
|
+
"declared_files": declared_files,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
effective_sources = latest_passed_artifacts_by_path(job_dir, plan)
|
|
92
|
+
file_context = read_declared_file_context(project, task, effective_sources)
|
|
93
|
+
context_sources = [
|
|
94
|
+
{
|
|
95
|
+
"path": item.get("path"),
|
|
96
|
+
"source": item.get("source"),
|
|
97
|
+
"source_task_id": item.get("source_task_id"),
|
|
98
|
+
"sandbox_path": item.get("sandbox_path"),
|
|
99
|
+
"sha256": item.get("sha256"),
|
|
100
|
+
"exists": item.get("exists"),
|
|
101
|
+
"content_chars": len(str(item.get("content") or "")),
|
|
102
|
+
}
|
|
103
|
+
for item in file_context
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
artifact_dir = job_dir / "artifacts" / "diagnostics" / args.diagnostic_id
|
|
107
|
+
artifact_dir.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
messages = build_creator_messages(job_dir, project, task, file_context, retry_feedback=[])
|
|
109
|
+
request_path = artifact_dir / "creator-request.json"
|
|
110
|
+
write_json(request_path, {"messages": messages})
|
|
111
|
+
|
|
112
|
+
start_event = log.append(
|
|
113
|
+
"large_context_json_diagnostic_started",
|
|
114
|
+
{
|
|
115
|
+
"diagnostic_id": args.diagnostic_id,
|
|
116
|
+
"declared_files": declared_files,
|
|
117
|
+
"context_sources": context_sources,
|
|
118
|
+
"prompt_layout": CREATOR_PROMPT_LAYOUT,
|
|
119
|
+
"request_path": str(request_path.relative_to(job_dir)),
|
|
120
|
+
"request_sha256": sha256_file(request_path),
|
|
121
|
+
"endpoint_health": health,
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
response: dict[str, Any] | None = None
|
|
126
|
+
parse_ok = False
|
|
127
|
+
violations: list[str] = []
|
|
128
|
+
parse_error = ""
|
|
129
|
+
creator_call_start = time.monotonic()
|
|
130
|
+
try:
|
|
131
|
+
if not health.get("ok") or not health.get("model_identity_ok"):
|
|
132
|
+
raise RuntimeError("diagnostic_endpoint_unavailable_or_model_mismatch")
|
|
133
|
+
response = call_creator_model(creator, messages, json_mode=True)
|
|
134
|
+
payload = parse_creator_json(str(response.get("content") or ""))
|
|
135
|
+
violations = validate_creator_declared_files(payload, declared_file_set(task))
|
|
136
|
+
parse_ok = not violations
|
|
137
|
+
except Exception as exc: # noqa: BLE001 - diagnostic records failures instead of raising.
|
|
138
|
+
parse_error = str(exc)
|
|
139
|
+
creator_call_ms = elapsed_ms(creator_call_start)
|
|
140
|
+
|
|
141
|
+
response_path = artifact_dir / "creator-response.json"
|
|
142
|
+
write_json(response_path, response or {"error": parse_error})
|
|
143
|
+
|
|
144
|
+
finish_reason = response.get("finish_reason") if response else None
|
|
145
|
+
if parse_ok and finish_reason == "stop":
|
|
146
|
+
verdict = "pass_large_context_json_current_config"
|
|
147
|
+
elif finish_reason == "length":
|
|
148
|
+
verdict = "fail_probable_truncation"
|
|
149
|
+
elif finish_reason == "stop":
|
|
150
|
+
verdict = "fail_large_context_json_contract_despite_complete_response"
|
|
151
|
+
else:
|
|
152
|
+
verdict = "fail_incomplete_or_unclassified_response"
|
|
153
|
+
|
|
154
|
+
result = {
|
|
155
|
+
"diagnostic_id": args.diagnostic_id,
|
|
156
|
+
"started_event_seq": start_event.seq,
|
|
157
|
+
"declared_files": declared_files,
|
|
158
|
+
"context_sources": context_sources,
|
|
159
|
+
"prompt_layout": CREATOR_PROMPT_LAYOUT,
|
|
160
|
+
"request_path": str(request_path.relative_to(job_dir)),
|
|
161
|
+
"request_sha256": sha256_file(request_path),
|
|
162
|
+
"response_path": str(response_path.relative_to(job_dir)),
|
|
163
|
+
"response_sha256": sha256_file(response_path),
|
|
164
|
+
"creator_call_ms": creator_call_ms,
|
|
165
|
+
"request_model": response.get("request_model") if response else None,
|
|
166
|
+
"response_model": response.get("response_model") if response else None,
|
|
167
|
+
"content_sha256": sha256_text(str(response.get("content") or "")) if response else None,
|
|
168
|
+
"content_chars": len(str(response.get("content") or "")) if response else 0,
|
|
169
|
+
"reasoning_chars": len(str(response.get("reasoning_content") or "")) if response else 0,
|
|
170
|
+
"finish_reason": finish_reason,
|
|
171
|
+
"usage": response.get("usage") if response and isinstance(response.get("usage"), dict) else {},
|
|
172
|
+
"parse_ok": parse_ok,
|
|
173
|
+
"declared_file_violations": violations,
|
|
174
|
+
"parse_error": parse_error,
|
|
175
|
+
"verdict": verdict,
|
|
176
|
+
}
|
|
177
|
+
result_path = artifact_dir / "result.json"
|
|
178
|
+
write_json(result_path, result)
|
|
179
|
+
log.append("large_context_json_diagnostic_completed", {**result, "result_path": str(result_path.relative_to(job_dir)), "result_sha256": sha256_file(result_path)})
|
|
180
|
+
|
|
181
|
+
print(json.dumps(result, indent=2))
|
|
182
|
+
return 0 if parse_ok else 2
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
raise SystemExit(main())
|
|
187
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wydcode",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Local-first long-running bug fixer: staged AI patches behind hash-bound human review.",
|
|
5
|
+
"license": "BUSL-1.1",
|
|
6
|
+
"author": "Mumpix",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"wydcode": "bin/wydcode.js",
|
|
10
|
+
"socra-harness": "bin/wydcode.js"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/wydcode.js",
|
|
17
|
+
"SOCRA_SOURCE.md",
|
|
18
|
+
"examples/*.json",
|
|
19
|
+
"examples/*.md",
|
|
20
|
+
"examples/*.py",
|
|
21
|
+
"fixtures",
|
|
22
|
+
"socra_harness/**/*.py",
|
|
23
|
+
"socra_harness/**/*.cjs",
|
|
24
|
+
"README.md",
|
|
25
|
+
"SECURITY.md",
|
|
26
|
+
"NOTICE",
|
|
27
|
+
"TRADEMARKS.md",
|
|
28
|
+
"pyproject.toml",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"coding-agent",
|
|
33
|
+
"bug-fixer",
|
|
34
|
+
"local-first",
|
|
35
|
+
"code-review",
|
|
36
|
+
"ai",
|
|
37
|
+
"automation",
|
|
38
|
+
"socra",
|
|
39
|
+
"mumpix"
|
|
40
|
+
],
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/mumpix/wydcode.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/mumpix/wydcode",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/mumpix/wydcode/issues"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"mumpix": "^1.1.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wydcode"
|
|
7
|
+
version = "0.4.3"
|
|
8
|
+
description = "Local-first long-running bug fixer with hash-bound human review."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
authors = [{name = "Mumpix"}]
|
|
12
|
+
license = {text = "BUSL-1.1"}
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
wydcode = "socra_harness.cli:main"
|
|
17
|
+
socra-harness = "socra_harness.cli:main"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["."]
|
|
21
|
+
include = ["socra_harness*"]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.package-data]
|
|
24
|
+
socra_harness = ["*.cjs"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .job.events import JobEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AttemptFailure(str, Enum):
|
|
10
|
+
LENGTH_DEATH = "length_death"
|
|
11
|
+
MALFORMED_OUTPUT = "malformed_output"
|
|
12
|
+
GATE_FAILED = "gate_failed"
|
|
13
|
+
ENDPOINT_ERROR = "endpoint_error"
|
|
14
|
+
INTERRUPTED = "interrupted"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
IN_PROGRESS_EVENTS = {
|
|
18
|
+
"task_started",
|
|
19
|
+
"creator_request_written",
|
|
20
|
+
"creator_call_started",
|
|
21
|
+
"creator_output_received",
|
|
22
|
+
"patch_written",
|
|
23
|
+
"gate_started",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
TERMINAL_EVENTS = {
|
|
27
|
+
"task_passed",
|
|
28
|
+
"task_failed",
|
|
29
|
+
"task_blocked",
|
|
30
|
+
"job_paused",
|
|
31
|
+
"job_completed",
|
|
32
|
+
"job_failed",
|
|
33
|
+
"job_interrupted",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def attempt_failure_payload(kind: AttemptFailure, **extra: Any) -> dict[str, Any]:
|
|
38
|
+
payload = {"failure_type": kind.value}
|
|
39
|
+
payload.update(extra)
|
|
40
|
+
return payload
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def classify_creator_finish(finish_reason: str | None) -> AttemptFailure | None:
|
|
44
|
+
if finish_reason == "length":
|
|
45
|
+
return AttemptFailure.LENGTH_DEATH
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def detect_interrupted_attempt(events: list[JobEvent]) -> dict[str, Any] | None:
|
|
50
|
+
task_id: str | None = None
|
|
51
|
+
start_seq: int | None = None
|
|
52
|
+
last_in_progress: JobEvent | None = None
|
|
53
|
+
|
|
54
|
+
for event in events:
|
|
55
|
+
if event.type == "task_started":
|
|
56
|
+
task_id = str(event.payload.get("task_id") or "")
|
|
57
|
+
start_seq = event.seq
|
|
58
|
+
last_in_progress = event
|
|
59
|
+
elif task_id and event.type in IN_PROGRESS_EVENTS:
|
|
60
|
+
last_in_progress = event
|
|
61
|
+
elif event.type in TERMINAL_EVENTS:
|
|
62
|
+
event_task = str(event.payload.get("task_id") or "")
|
|
63
|
+
if not event_task or event_task == task_id:
|
|
64
|
+
task_id = None
|
|
65
|
+
start_seq = None
|
|
66
|
+
last_in_progress = None
|
|
67
|
+
|
|
68
|
+
if not task_id or not start_seq or last_in_progress is None:
|
|
69
|
+
return None
|
|
70
|
+
return {
|
|
71
|
+
"failure_type": AttemptFailure.INTERRUPTED.value,
|
|
72
|
+
"task_id": task_id,
|
|
73
|
+
"started_seq": start_seq,
|
|
74
|
+
"last_event_type": last_in_progress.type,
|
|
75
|
+
"last_event_seq": last_in_progress.seq,
|
|
76
|
+
}
|