Browse Source

More task log

Billy Barrow 1 month ago
parent
commit
92c1eb2d7f
2 changed files with 66 additions and 16 deletions
  1. 11 4
      src/pages/task_log.js
  2. 55 12
      test/test_task_log_js.py

+ 11 - 4
src/pages/task_log.js

@@ -1053,8 +1053,14 @@
             }
 
             // One call per turn: the first tool_result after a call
-            // is its own (or the answer never came yet). Answers
-            // -1 when the scan hits another call first.
+            // is its own (or the answer never came yet). The live
+            // wire's result payloads carry no turn of their own -
+            // matching rides on order, tool name and session, with
+            // a turn mismatch (both present) the only rejection.
+            function sameSession(a, b) {
+                return a === b || a == null || b == null;
+            }
+
             function matchResultIndex(rows, index) {
                 var evt = rows[index];
                 var p = evt.payload || {};
@@ -1063,7 +1069,8 @@
                     var other = rows[j];
                     if (other.kind === 'tool_result') {
                         var q = other.payload || {};
-                        if (q.turn === p.turn && other.session_id === evt.session_id
+                        if ((q.turn === p.turn || q.turn == null)
+                                && sameSession(other.session_id, evt.session_id)
                                 && strText(q.tool) === strText(p.tool)) {
                             return j;
                         }
@@ -1087,7 +1094,7 @@
                     var hasCompanion = false;
                     for (var k = index + 1; k < resultIndex; k++) {
                         if (rows[k].kind === companion
-                                && rows[k].session_id === evt.session_id) {
+                                && sameSession(rows[k].session_id, evt.session_id)) {
                             hasCompanion = true;
                             break;
                         }

+ 55 - 12
test/test_task_log_js.py

@@ -796,9 +796,17 @@ def quiet_result(evt):
     return True
 
 
+def same_session(a, b):
+    """PORT of sameSession: equal, or either side unlabelled."""
+    return a == b or a is None or b is None
+
+
 def match_result_index(rows, index):
     """PORT of matchResultIndex: the first tool_result after the
-    call when it matches turn/session/tool; -1 otherwise."""
+    call is its own (one call per turn). The live wire's result
+    payloads carry no turn of their own - matching rides on order,
+    tool name and session; a turn mismatch (both present) is the
+    only rejection."""
     evt = rows[index]
     payload = evt.get("payload") or {}
     if not isinstance(payload.get("turn"), int):
@@ -807,8 +815,9 @@ def match_result_index(rows, index):
         other = rows[j]
         if other.get("kind") == "tool_result":
             other_payload = other.get("payload") or {}
-            if other_payload.get("turn") == payload.get("turn") \
-                    and other.get("session_id") == evt.get("session_id") \
+            other_turn = other_payload.get("turn")
+            if (other_turn is None or other_turn == payload.get("turn")) \
+                    and same_session(other.get("session_id"), evt.get("session_id")) \
                     and str_text(other_payload.get("tool")) == str_text(payload.get("tool")):
                 return j
             return -1
@@ -834,7 +843,7 @@ def folded_quiet_calls(rows):
             continue
         has_companion = any(
             rows[k].get("kind") == companion
-            and rows[k].get("session_id") == evt.get("session_id")
+            and same_session(rows[k].get("session_id"), evt.get("session_id"))
             for k in range(index + 1, result_index))
         if has_companion and quiet_result(rows[result_index]):
             folded.add(index)
@@ -1236,18 +1245,24 @@ QUIET_ACK = json.dumps({"ok": True, "recorded": True})
 FAILED_ACK = json.dumps({"ok": False, "error": "web callback unreachable"})
 
 
-def progress_trio(result_inner=QUIET_ACK, with_companion=True):
+def progress_trio(result_inner=QUIET_ACK, with_companion=True,
+                  result_turn=None):
     """The live-tail's repetitive trio: an update_progress call,
     the progress event carrying the same text, then the ack
-    result."""
+    result. The live wire's result payloads carry NO turn of their
+    own (the payload is {tool, result}); pass result_turn to model
+    a turn-stamped result."""
     rows = [{"id": 1, "kind": "tool_call", "session_id": 201, "payload": {
         "turn": 1551, "tool": "update_progress",
         "args": json.dumps({"text": "Segfault located"})}}]
     if with_companion:
         rows.append({"id": 2, "kind": "progress", "session_id": 201,
                      "payload": {"text": "Segfault located"}})
-    rows.append({"id": 3, "kind": "tool_result", "session_id": 201, "payload": {
-        "turn": 1551, "tool": "update_progress", "result": result_inner}})
+    result_payload = {"tool": "update_progress", "result": result_inner}
+    if result_turn is not None:
+        result_payload["turn"] = result_turn
+    rows.append({"id": 3, "kind": "tool_result", "session_id": 201,
+                 "payload": result_payload})
     return rows
 
 
@@ -1257,9 +1272,20 @@ class QuietFoldingTests(unittest.TestCase):
     unfolds both."""
 
     def test_quiet_trio_folds_call_and_result(self):
+        """The live wire's shape: the result carries no turn."""
         folded = folded_quiet_calls(progress_trio())
         self.assertEqual(folded, {0, 2})
 
+    def test_turn_stamped_result_also_folds(self):
+        folded = folded_quiet_calls(progress_trio(result_turn=1551))
+        self.assertEqual(folded, {0, 2})
+
+    def test_turn_mismatch_never_folds(self):
+        """A result stamped with another turn is not this call's
+        answer."""
+        self.assertEqual(
+            folded_quiet_calls(progress_trio(result_turn=9999)), set())
+
     def test_failed_ack_unfolds_the_pair(self):
         self.assertEqual(folded_quiet_calls(progress_trio(FAILED_ACK)), set())
 
@@ -1279,14 +1305,14 @@ class QuietFoldingTests(unittest.TestCase):
                 "turn": 5, "tool": "run_command",
                 "args": json.dumps({"command": "ls"})}},
             {"id": 2, "kind": "tool_result", "session_id": 201, "payload": {
-                "turn": 5, "tool": "run_command", "result": QUIET_ACK}},
+                "tool": "run_command", "result": QUIET_ACK}},
         ]
         self.assertEqual(folded_quiet_calls(rows), set())
 
 
 class ResultAttachmentTests(unittest.TestCase):
-    """A non-folded call carries its result as an attached block;
-    the result never renders as a separate row."""
+    """A non-folded call carries its result as an attached block
+    INSIDE its row; the result never renders as a separate row."""
 
     def test_call_and_result_pair_up(self):
         rows = [
@@ -1294,7 +1320,7 @@ class ResultAttachmentTests(unittest.TestCase):
                 "turn": 5, "tool": "run_command",
                 "args": json.dumps({"command": "make"})}},
             {"id": 11, "kind": "tool_result", "session_id": 7, "payload": {
-                "turn": 5, "tool": "run_command",
+                "tool": "run_command",
                 "result": json.dumps({"ok": True, "exit_code": 0,
                                       "stdout": "built\n", "stderr": "",
                                       "duration_ms": 900})}},
@@ -1303,6 +1329,23 @@ class ResultAttachmentTests(unittest.TestCase):
         self.assertEqual(calls_by_id, {10: 11})
         self.assertEqual(rides_by_id, {11: True})
 
+    def test_turn_less_live_result_pairs_up(self):
+        """The live wire's shape: the result carries no turn, so
+        matching rides on order, tool and session."""
+        rows = [
+            {"id": 20, "kind": "tool_call", "session_id": 201, "payload": {
+                "turn": 1731, "tool": "run_command",
+                "args": json.dumps({"command": "make test"})}},
+            {"id": 21, "kind": "tool_result", "session_id": 201, "payload": {
+                "tool": "run_command",
+                "result": json.dumps({"ok": True, "exit_code": 0,
+                                      "stdout": "ok\n", "stderr": "",
+                                      "duration_ms": 25000})}},
+        ]
+        calls_by_id, rides_by_id = attach_pairs(rows, set())
+        self.assertEqual(calls_by_id, {20: 21})
+        self.assertEqual(rides_by_id, {21: True})
+
     def test_folded_pair_attaches_nothing(self):
         calls_by_id, rides_by_id = attach_pairs(
             progress_trio(), folded_quiet_calls(progress_trio()))