ai_co_discuss 0.3.71 → 0.3.73
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/backend/free_room.py +81 -9
- package/backend/schema.sql +1675 -1645
- package/backend/server.py +19 -8
- package/docs/runner.md +12 -1
- package/package.json +1 -1
- package/runner/adapters/antigravity_cli.mjs +37 -7
- package/runner/adapters/api.mjs +25 -4
- package/runner/adapters/api_files.mjs +36 -19
- package/runner/adapters/claude.mjs +448 -385
- package/runner/adapters/cli.mjs +175 -155
- package/runner/adapters/codex_app.mjs +19 -3
- package/runner/adapters/codex_bridge.mjs +123 -0
- package/runner/adapters/codex_cli.mjs +47 -10
- package/runner/adapters/index.mjs +34 -2
- package/runner/adapters/profiles.mjs +5 -3
- package/runner/device_auth.mjs +39 -21
- package/runner/discussion_launch.mjs +83 -21
- package/runner/discussion_local.mjs +2 -0
- package/runner/discussion_runner.mjs +34 -1
- package/runner/discussion_service.mjs +6 -3
- package/runner/expert_prompts.mjs +21 -11
- package/runner/free_runner.mjs +73 -14
- package/runner/project_files.mjs +57 -5
- package/runner/relay_channel.mjs +4 -3
- package/runner/remote_launch.mjs +268 -268
- package/runner/remote_policy.mjs +2 -1
- package/web/app.js +4955 -4862
- package/web/backup.js +1 -1
- package/web/modern.css +1 -1
- package/web/modern.js +9793 -9187
package/backend/free_room.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
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
|
|