Commit ebbc162399232750edcd562d7a6ee83c3e8cde80
1 parent
98d86e47
graphlemon: auto-commit uncommitted changes for judgment-92eccdb1933c-main-checkout-drift
Showing
2 changed files
with
75 additions
and
1 deletions
src/task_query.py
| ... | ... | @@ -206,9 +206,10 @@ def get_task(conn, task_id): |
| 206 | 206 | task["work_graph_runs"] = [] |
| 207 | 207 | for w in runs: |
| 208 | 208 | nodes = _work_nodes_for_run(conn, w[0]) |
| 209 | + graph_manifest = json.loads(w[1]) if w[1] else None | |
| 209 | 210 | task["work_graph_runs"].append({ |
| 210 | 211 | "run_id": w[0], |
| 211 | - "graph_manifest": json.loads(w[1]) if w[1] else None, | |
| 212 | + "graph_manifest": _display_graph_manifest(graph_manifest, nodes), | |
| 212 | 213 | "status": w[2], "started_at": w[3], "completed_at": w[4], |
| 213 | 214 | # D (2026-08-06): both None for a normal run; both set when this |
| 214 | 215 | # run was forked from a historical node (src/work_graph_fork.py). | ... | ... |
src/work_graph_plan.py
| ... | ... | @@ -29,6 +29,13 @@ import json |
| 29 | 29 | import subprocess |
| 30 | 30 | import uuid |
| 31 | 31 | |
| 32 | +from src import execution_log, paths | |
| 33 | + | |
| 34 | +# node_id reserved for the synthetic work_node_execution _record_planning_node | |
| 35 | +# creates below -- task_query.py matches on this exact string to recognize | |
| 36 | +# "this run has a real planning node to display", so keep the two in sync. | |
| 37 | +PLANNING_NODE_ID = "__planning__" | |
| 38 | + | |
| 32 | 39 | GRAPH_MANIFEST_SCHEMA = { |
| 33 | 40 | "type": "object", |
| 34 | 41 | "required": ["nodes", "edges"], |
| ... | ... | @@ -571,6 +578,71 @@ def _normalize_graph_manifest(manifest, additional_valid_loop_back_target_ids=() |
| 571 | 578 | return manifest |
| 572 | 579 | |
| 573 | 580 | |
| 581 | +def _record_planning_node(conn, run_id, task_id, org_node, prompt, raw_response, graph_manifest): | |
| 582 | + """Persists the planning call itself (the `claude --agent <org_node> -p | |
| 583 | + <prompt>` subprocess call in plan_work_graph() below, NOT any node it | |
| 584 | + planned) as a real, clickable work_node_execution -- previously this | |
| 585 | + call's own raw output was discarded the moment _extract_graph_manifest | |
| 586 | + pulled the JSON back out of it, and the call itself left no trace | |
| 587 | + anywhere the console renders (v_task_detail's to-do tree/DAG only ever | |
| 588 | + show the nodes a planning call PRODUCED, never the act of planning | |
| 589 | + itself). Deliberately does NOT touch work_graph_run.graph_manifest -- | |
| 590 | + that column stays the pure, LLM-authored manifest run_graph()/fork/ | |
| 591 | + append-mode all still execute and merge against unchanged; task_query.py | |
| 592 | + synthesizes a display-only node from THIS row (matched by node_id == | |
| 593 | + PLANNING_NODE_ID) when building the console's API response instead, so | |
| 594 | + execution/fork/append logic never has to know this row exists. | |
| 595 | + | |
| 596 | + Only called from the fresh (non-append) branch of plan_work_graph() -- | |
| 597 | + one real "how was this run planned" entry per run is enough to answer | |
| 598 | + the question; Coder/Validator's own append_to_run_id planning calls | |
| 599 | + aren't given one (YAGNI until someone actually needs to inspect those | |
| 600 | + too).""" | |
| 601 | + project_id = conn.execute("SELECT project_id FROM task WHERE task_id = ?", (task_id,)).fetchone()[0] | |
| 602 | + | |
| 603 | + # work_node_execution.skill_ref is a required FK to a real skill row. | |
| 604 | + # design-dynamic-work-graph is the actual Skill documenting this | |
| 605 | + # self-planning mechanism (skills/design-dynamic-work-graph/SKILL.md) -- | |
| 606 | + # reused here rather than inventing a placeholder skill_id nothing else | |
| 607 | + # would ever reference. | |
| 608 | + skill_id = "design-dynamic-work-graph" | |
| 609 | + if conn.execute("SELECT 1 FROM skill WHERE skill_id = ?", (skill_id,)).fetchone() is None: | |
| 610 | + conn.execute( | |
| 611 | + "INSERT INTO skill (skill_id, owner_org_node, file_path, current_git_commit_sha, source, status) " | |
| 612 | + "VALUES (?, ?, ?, 'unknown', 'new', 'active')", | |
| 613 | + (skill_id, org_node, "skills/" + skill_id + "/"), | |
| 614 | + ) | |
| 615 | + | |
| 616 | + node_exec_id = str(uuid.uuid4()) | |
| 617 | + conn.execute( | |
| 618 | + "INSERT INTO work_node_execution (node_exec_id, run_id, node_id, subagent_type, skill_ref, " | |
| 619 | + "input_schema_ref, output_schema_ref, input_payload, output_payload, status) " | |
| 620 | + "VALUES (?, ?, ?, ?, ?, 'work_graph_plan.goal_text', 'work_graph_plan.graph_manifest', ?, ?, 'done')", | |
| 621 | + ( | |
| 622 | + node_exec_id, run_id, PLANNING_NODE_ID, org_node, skill_id, | |
| 623 | + json.dumps({"prompt": prompt}), json.dumps({"graph_manifest": graph_manifest}), | |
| 624 | + ), | |
| 625 | + ) | |
| 626 | + | |
| 627 | + log_file_id = str(uuid.uuid4()) | |
| 628 | + log_path = paths.log_file_path(project_id, task_id, run_id, node_exec_id) | |
| 629 | + conn.execute( | |
| 630 | + "INSERT INTO execution_log_file (log_file_id, node_exec_id, project_id, file_path, format, status) " | |
| 631 | + "VALUES (?, ?, ?, ?, 'jsonl', 'closed')", | |
| 632 | + (log_file_id, node_exec_id, project_id, log_path), | |
| 633 | + ) | |
| 634 | + # execution_log.append_log does its own conn.commit() -- covers the two | |
| 635 | + # INSERTs above too, same connection/transaction. | |
| 636 | + execution_log.append_log( | |
| 637 | + conn, node_exec_id, | |
| 638 | + json.dumps({"event": "node_start", "org_node": org_node, "prompt": prompt}, ensure_ascii=False) + "\n" | |
| 639 | + + json.dumps( | |
| 640 | + {"event": "node_completed", "raw_response": raw_response, "graph_manifest": graph_manifest}, | |
| 641 | + ensure_ascii=False, | |
| 642 | + ) + "\n", | |
| 643 | + ) | |
| 644 | + | |
| 645 | + | |
| 574 | 646 | def plan_work_graph(conn, org_node, task_id, goal_text, target_folder_path, timeout=300, append_to_run_id=None): |
| 575 | 647 | """Returns {"run_id", "graph_manifest", "new_node_ids"}. Creates one |
| 576 | 648 | `work_graph_run` row (status='planning') -- moving it to 'running'/ |
| ... | ... | @@ -672,6 +744,7 @@ def plan_work_graph(conn, org_node, task_id, goal_text, target_folder_path, time |
| 672 | 744 | (run_id, task_id, json.dumps(new_manifest)), |
| 673 | 745 | ) |
| 674 | 746 | conn.commit() |
| 747 | + _record_planning_node(conn, run_id, task_id, org_node, prompt, payload.get("result", ""), new_manifest) | |
| 675 | 748 | return {"run_id": run_id, "graph_manifest": new_manifest, "new_node_ids": []} |
| 676 | 749 | |
| 677 | 750 | new_node_ids = [n["node_id"] for n in new_manifest["nodes"]] | ... | ... |