ai_co_discuss 0.3.70 → 0.3.72

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.
@@ -107,6 +107,10 @@ MAX_DENIAL_FIELD = 300
107
107
  # A manual member's delivery is retried at most this often before the execution fails.
108
108
  MAX_RETRIES = 3
109
109
  MAX_RETRY_REASON = 500
110
+ # One interjection the owner adds to a running execution, and how many one execution keeps.
111
+ MAX_INTERJECTION_LENGTH = 2000
112
+ MAX_INTERJECTIONS = 20
113
+ INTERJECTION_ENDED = "送达前本次执行已结束"
110
114
  ACTIVE = ("dispatched", "started")
111
115
  ACCESS_MODES = ("runner", "manual")
112
116
  SCHEDULER_PROVIDERS = ("jev", "decisions")
@@ -145,19 +149,24 @@ REWIND_EFFECTS = ("cut", "fresh", "kept")
145
149
  # The reported capabilities and stop result read alongside an execution aliased `e`.
146
150
  CAPABILITY_COLUMNS = """(SELECT stoppable FROM execution_capabilities c WHERE c.execution_id=e.id) AS stoppable,
147
151
  (SELECT progress FROM execution_capabilities c WHERE c.execution_id=e.id) AS progress,
148
- (SELECT stop_result FROM execution_capabilities c WHERE c.execution_id=e.id) AS stop_result"""
152
+ (SELECT stop_result FROM execution_capabilities c WHERE c.execution_id=e.id) AS stop_result,
153
+ (SELECT steer FROM execution_capabilities c WHERE c.execution_id=e.id) AS steer"""
149
154
 
150
155
 
151
156
  def reported_capabilities(row):
152
- """`{stoppable, progress}` as the runner reported them for one execution, or None before it did (or an older runner)."""
153
- return None if row["stoppable"] is None else {"stoppable": bool(row["stoppable"]), "progress": bool(row["progress"])}
157
+ """`{stoppable, progress, steer}` as the runner reported them for one execution, or None before it did (or an older runner).
158
+ `steer`: the owner can add an interjection the running software takes without ending its turn."""
159
+ if row["stoppable"] is None:
160
+ return None
161
+ return {"stoppable": bool(row["stoppable"]), "progress": bool(row["progress"]), "steer": bool(row["steer"])}
154
162
  # Columns added after the first free-room tables; Python databases created by that build gain them here.
155
163
  LATER_COLUMNS = {"room_members": {"access": "TEXT NOT NULL DEFAULT 'runner'", "session_mode": "TEXT NOT NULL DEFAULT 'new'"},
156
164
  "member_executions": {"changes": "TEXT NOT NULL DEFAULT ''", "edited": "TEXT NOT NULL DEFAULT ''", "cwd": "TEXT NOT NULL DEFAULT ''",
157
165
  "activity": "TEXT NOT NULL DEFAULT ''", "activity_at": "INTEGER NOT NULL DEFAULT 0"},
158
166
  "member_requests": {"chain_id": "INTEGER NOT NULL DEFAULT 0", "batch_id": "INTEGER NOT NULL DEFAULT 0",
159
167
  "purpose": "TEXT NOT NULL DEFAULT 'ask'", "created_by": "TEXT NOT NULL DEFAULT 'user'"},
160
- "free_chain_holds": {"asked_rounds": "INTEGER NOT NULL DEFAULT 0", "asked_reason": "TEXT NOT NULL DEFAULT ''"}}
168
+ "free_chain_holds": {"asked_rounds": "INTEGER NOT NULL DEFAULT 0", "asked_reason": "TEXT NOT NULL DEFAULT ''"},
169
+ "execution_capabilities": {"steer": "INTEGER"}}
161
170
 
162
171
 
163
172
  def migrate_free_tables(connection):
@@ -719,6 +728,7 @@ def recent_executions(connection, room_id):
719
728
  "notes": row["note_count"], "denials": json.loads(row["denials"] or "[]"),
720
729
  "capabilities": reported_capabilities(row), "stop_result": row["stop_result"] or "",
721
730
  "late_answer": json.loads(row["late_answer"]) if row["late_answer"] else None,
731
+ "interjections": execution_interjections(connection, row["id"], row["status"]),
722
732
  "retry": execution_retry(connection, row["id"])})
723
733
  return executions
724
734
 
@@ -1877,7 +1887,11 @@ def inbox(connection, room_id, token):
1877
1887
  member_view["rewind"] = reported[0] if reported else ""
1878
1888
  pending = [{**request, **review_request_context(connection, request), **sequence_request_context(connection, request)} for request in pending]
1879
1889
  (members if row["access"] == "runner" else observers).append({**member_view, "pending": pending})
1880
- executions = [{**public_execution(row), "member": row["name"], "retry": execution_retry(connection, row["id"])} for row in connection.execute(
1890
+ # Pending interjections ride along so the runner hands them to the running software at its next check.
1891
+ executions = [{**public_execution(row), "member": row["name"], "retry": execution_retry(connection, row["id"]),
1892
+ "interjections": [dict(item) for item in connection.execute(
1893
+ "SELECT id,text FROM execution_interjections WHERE execution_id=? AND status='pending' ORDER BY id", (row["id"],))]}
1894
+ for row in connection.execute(
1881
1895
  """SELECT member_executions.*,room_members.name FROM member_executions JOIN room_members ON room_members.id=member_id
1882
1896
  WHERE member_executions.room_id=? AND member_executions.status IN ('dispatched','started') ORDER BY member_executions.id""", (room_id,))]
1883
1897
  return {"room_id": room_id, "archived": bool(archived), "members": members, "observers": observers, "executions": executions}
@@ -2231,6 +2245,54 @@ def record_stop_result(connection, room_id, execution_id, result):
2231
2245
  return True
2232
2246
 
2233
2247
 
2248
+ def add_interjection(connection, room_id, row, payload, now):
2249
+ """The owner's interjection into one running execution, kept for the runner to deliver; the same submission_id returns the earlier one."""
2250
+ text, submission_id = payload.get("text"), payload.get("submission_id")
2251
+ if not isinstance(text, str) or not 0 < len(text.strip()) <= MAX_INTERJECTION_LENGTH:
2252
+ raise ValueError(f"插话须为 1–{MAX_INTERJECTION_LENGTH} 字")
2253
+ if not isinstance(submission_id, str) or not 0 < len(submission_id) <= 100:
2254
+ raise ValueError("插话须带 submission_id;重试沿用原编号")
2255
+ earlier = connection.execute("SELECT * FROM execution_interjections WHERE room_id=? AND submission_id=?", (room_id, submission_id)).fetchone()
2256
+ if earlier:
2257
+ if earlier["execution_id"] != row["id"] or earlier["text"] != text:
2258
+ raise FileExistsError("submission_id 已用于另一条插话")
2259
+ return public_interjection(earlier, row["status"])
2260
+ capability = connection.execute("SELECT steer FROM execution_capabilities WHERE execution_id=?", (row["id"],)).fetchone()
2261
+ if not capability or not capability["steer"]:
2262
+ raise FileExistsError("这位成员的软件不支持执行中插话,可停止后带上这句话重问")
2263
+ if connection.execute("SELECT COUNT(*) FROM execution_interjections WHERE execution_id=?", (row["id"],)).fetchone()[0] >= MAX_INTERJECTIONS:
2264
+ raise FileExistsError(f"一次执行最多插话 {MAX_INTERJECTIONS} 条")
2265
+ cursor = connection.execute("""INSERT INTO execution_interjections (execution_id,room_id,submission_id,text,created_at,updated_at)
2266
+ VALUES (?,?,?,?,?,?)""", (row["id"], room_id, submission_id, text, now, now))
2267
+ return public_interjection(connection.execute("SELECT * FROM execution_interjections WHERE id=?", (cursor.lastrowid,)).fetchone(), row["status"])
2268
+
2269
+
2270
+ def record_interjection_result(connection, room_id, execution_id, payload, now):
2271
+ """Mark one pending interjection delivered or failed, as the runner reports it."""
2272
+ status, reason = payload.get("status"), payload.get("reason", "")
2273
+ if status not in ("delivered", "failed") or not isinstance(reason, str) or len(reason) > MAX_RETRY_REASON:
2274
+ raise ValueError(f"interjection 须带 status(delivered 或 failed)与最多 {MAX_RETRY_REASON} 字的 reason")
2275
+ updated = connection.execute("""UPDATE execution_interjections SET status=?,reason=?,updated_at=?
2276
+ WHERE id=? AND room_id=? AND execution_id=? AND status='pending'""",
2277
+ (status, reason, now, payload.get("interjection_id"), room_id, execution_id))
2278
+ if not updated.rowcount and not connection.execute("SELECT 1 FROM execution_interjections WHERE id=? AND room_id=? AND execution_id=?",
2279
+ (payload.get("interjection_id"), room_id, execution_id)).fetchone():
2280
+ raise LookupError("插话不存在")
2281
+
2282
+
2283
+ def public_interjection(row, execution_status):
2284
+ """One interjection as the page shows it; one still pending when its execution ended never reached the software."""
2285
+ ended = row["status"] == "pending" and execution_status not in ACTIVE
2286
+ return {"id": row["id"], "text": row["text"], "status": "failed" if ended else row["status"],
2287
+ "reason": INTERJECTION_ENDED if ended else row["reason"], "created_at": row["created_at"]}
2288
+
2289
+
2290
+ def execution_interjections(connection, execution_id, execution_status):
2291
+ """Every interjection of one execution in the order the owner sent them."""
2292
+ return [public_interjection(row, execution_status) for row in connection.execute(
2293
+ "SELECT * FROM execution_interjections WHERE execution_id=? ORDER BY id", (execution_id,))]
2294
+
2295
+
2234
2296
  def execution_action(connection, room_id, execution_id, token, payload):
2235
2297
  """Runner-side lifecycle: renew, fail, release undelivered work, report how the software was reached, whether a voided one stopped, or observed file changes."""
2236
2298
  begin_write(connection)
@@ -2284,9 +2346,15 @@ def execution_action(connection, room_id, execution_id, token, payload):
2284
2346
  # What this delivery can do, as the runner's adapter reported; a retried delivery may differ, so the latest counts.
2285
2347
  if type(payload.get("stoppable")) is not bool or type(payload.get("progress")) is not bool:
2286
2348
  raise ValueError("delivery 须带布尔值 stoppable 与 progress")
2287
- connection.execute("""INSERT INTO execution_capabilities (execution_id,room_id,stoppable,progress) VALUES (?,?,?,?)
2288
- ON CONFLICT(execution_id) DO UPDATE SET stoppable=excluded.stoppable,progress=excluded.progress""",
2289
- (execution_id, room_id, int(payload["stoppable"]), int(payload["progress"])))
2349
+ # Older runners report no `steer`; their deliveries take no interjection.
2350
+ if type(payload.get("steer", False)) is not bool:
2351
+ raise ValueError("delivery 的 steer 须为布尔值")
2352
+ connection.execute("""INSERT INTO execution_capabilities (execution_id,room_id,stoppable,progress,steer) VALUES (?,?,?,?,?)
2353
+ ON CONFLICT(execution_id) DO UPDATE SET stoppable=excluded.stoppable,progress=excluded.progress,steer=excluded.steer""",
2354
+ (execution_id, room_id, int(payload["stoppable"]), int(payload["progress"]), int(payload.get("steer", False))))
2355
+ elif action == "interjection":
2356
+ # The runner's report on one interjection: it reached the software, or why it could not; a repeated report changes nothing.
2357
+ record_interjection_result(connection, room_id, execution_id, payload, now)
2290
2358
  elif action == "retry":
2291
2359
  retry_failed(connection, room_id, row)
2292
2360
  elif row["status"] not in ACTIVE:
@@ -2302,6 +2370,10 @@ def execution_action(connection, room_id, execution_id, token, payload):
2302
2370
  (now + LEASE_SECONDS, activity, activity_at, execution_id))
2303
2371
  connection.executemany("INSERT OR IGNORE INTO execution_notes (execution_id,seq,room_id,text,created_at) VALUES (?,?,?,?,?)",
2304
2372
  [(execution_id, note["seq"], room_id, note["text"], now) for note in notes])
2373
+ elif action == "interject":
2374
+ interjection = add_interjection(connection, room_id, row, payload, now)
2375
+ detail = execution_detail(connection, connection.execute("SELECT * FROM member_executions WHERE id=?", (execution_id,)).fetchone())
2376
+ return {**detail, "interjection": interjection}
2305
2377
  elif action == "retry_wait":
2306
2378
  # A manual member's session refused the delivery: the runner keeps the execution and delivers again at retry_at.
2307
2379
  attempt, retry_at, reason = payload.get("attempt"), payload.get("retry_at"), payload.get("reason")
@@ -2335,7 +2407,7 @@ def execution_action(connection, room_id, execution_id, token, payload):
2335
2407
  # Only for proven non-delivery: the requests return to the queue unchanged.
2336
2408
  finish_execution(connection, execution_id, "cancelled", str(payload.get("reason") or "未送达"), "pending")
2337
2409
  else:
2338
- raise ValueError("action 须为 renew、fail、release、report、segments、retry_wait、retry_start、retry_now、retry_stop 或 retry")
2410
+ raise ValueError("action 须为 renew、fail、release、report、segments、interject、interjection、retry_wait、retry_start、retry_now、retry_stop 或 retry")
2339
2411
  detail = execution_detail(connection, connection.execute("SELECT * FROM member_executions WHERE id=?", (execution_id,)).fetchone())
2340
2412
  return {**detail, "duplicate": duplicate} if action == "stopped" else detail
2341
2413