clouds-coder 2026.4.5__tar.gz → 2026.4.6__tar.gz
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.
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/Clouds_Coder.py +116 -20
- {clouds_coder-2026.4.5/clouds_coder.egg-info → clouds_coder-2026.4.6}/PKG-INFO +1 -1
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6/clouds_coder.egg-info}/PKG-INFO +1 -1
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/pyproject.toml +1 -1
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/LICENSE +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/README.md +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/clouds_coder.egg-info/SOURCES.txt +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/clouds_coder.egg-info/dependency_links.txt +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/clouds_coder.egg-info/entry_points.txt +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/clouds_coder.egg-info/requires.txt +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/clouds_coder.egg-info/top_level.txt +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/setup.cfg +0 -0
- {clouds_coder-2026.4.5 → clouds_coder-2026.4.6}/tests/test_smoke.py +0 -0
|
@@ -216,6 +216,22 @@ DEFAULT_TIMEOUT_SECONDS = max(
|
|
|
216
216
|
),
|
|
217
217
|
)
|
|
218
218
|
DEFAULT_REQUEST_TIMEOUT = DEFAULT_TIMEOUT_SECONDS
|
|
219
|
+
# Patterns that indicate the shell process is waiting for interactive confirmation.
|
|
220
|
+
# Checked against the tail of combined stdout+stderr (lowercased bytes).
|
|
221
|
+
_SHELL_AUTO_CONFIRM_PATTERNS: tuple[bytes, ...] = (
|
|
222
|
+
b"ok to proceed? (y)",
|
|
223
|
+
b"proceed? (y)",
|
|
224
|
+
b"? (y)",
|
|
225
|
+
b"[y/n]",
|
|
226
|
+
b"[y/n]:",
|
|
227
|
+
b"[yes/no]",
|
|
228
|
+
b"(y/n)",
|
|
229
|
+
b"(yes/no)",
|
|
230
|
+
b"continue? (y/n)",
|
|
231
|
+
b"do you want to continue",
|
|
232
|
+
b"press enter to continue",
|
|
233
|
+
b"enter to continue",
|
|
234
|
+
)
|
|
219
235
|
MIN_SHELL_COMMAND_TIMEOUT_SECONDS = 10
|
|
220
236
|
MAX_SHELL_COMMAND_TIMEOUT_SECONDS = 86_400
|
|
221
237
|
DEFAULT_SHELL_COMMAND_TIMEOUT_SECONDS = max(
|
|
@@ -22521,6 +22537,7 @@ body{padding:18px}
|
|
|
22521
22537
|
_spawn_reader("stdout", proc.stdout)
|
|
22522
22538
|
_spawn_reader("stderr", proc.stderr)
|
|
22523
22539
|
|
|
22540
|
+
_auto_confirmed: set[bytes] = set()
|
|
22524
22541
|
while True:
|
|
22525
22542
|
now = time.time()
|
|
22526
22543
|
elapsed = now - start
|
|
@@ -22566,6 +22583,18 @@ body{padding:18px}
|
|
|
22566
22583
|
},
|
|
22567
22584
|
)
|
|
22568
22585
|
next_progress_emit = now + 0.8
|
|
22586
|
+
# Auto-confirm interactive prompts (Windows reader thread path)
|
|
22587
|
+
if proc.stdin and not proc.stdin.closed:
|
|
22588
|
+
_tail = (bytes(out_buf[-300:]) + bytes(err_buf[-300:])).lower()
|
|
22589
|
+
for _pat in _SHELL_AUTO_CONFIRM_PATTERNS:
|
|
22590
|
+
if _pat in _tail and _pat not in _auto_confirmed:
|
|
22591
|
+
try:
|
|
22592
|
+
proc.stdin.write(b"y\n")
|
|
22593
|
+
proc.stdin.flush()
|
|
22594
|
+
except Exception:
|
|
22595
|
+
pass
|
|
22596
|
+
_auto_confirmed.add(_pat)
|
|
22597
|
+
break
|
|
22569
22598
|
if (proc.poll() is not None) and (not active_readers) and io_queue.empty():
|
|
22570
22599
|
break
|
|
22571
22600
|
|
|
@@ -22606,6 +22635,7 @@ body{padding:18px}
|
|
|
22606
22635
|
popen_kwargs = {
|
|
22607
22636
|
"shell": True,
|
|
22608
22637
|
"cwd": cwd,
|
|
22638
|
+
"stdin": subprocess.PIPE,
|
|
22609
22639
|
"stdout": subprocess.PIPE,
|
|
22610
22640
|
"stderr": subprocess.PIPE,
|
|
22611
22641
|
"text": False,
|
|
@@ -22637,6 +22667,7 @@ body{padding:18px}
|
|
|
22637
22667
|
except Exception:
|
|
22638
22668
|
pass
|
|
22639
22669
|
sel.register(proc.stderr, selectors.EVENT_READ, data="stderr")
|
|
22670
|
+
_auto_confirmed: set[bytes] = set()
|
|
22640
22671
|
while True:
|
|
22641
22672
|
now = time.time()
|
|
22642
22673
|
elapsed = now - start
|
|
@@ -22680,6 +22711,18 @@ body{padding:18px}
|
|
|
22680
22711
|
},
|
|
22681
22712
|
)
|
|
22682
22713
|
next_progress_emit = now + 0.8
|
|
22714
|
+
# Auto-confirm interactive prompts (e.g. "Ok to proceed? (y)")
|
|
22715
|
+
if proc.stdin and not proc.stdin.closed:
|
|
22716
|
+
_tail = (bytes(out_buf[-300:]) + bytes(err_buf[-300:])).lower()
|
|
22717
|
+
for _pat in _SHELL_AUTO_CONFIRM_PATTERNS:
|
|
22718
|
+
if _pat in _tail and _pat not in _auto_confirmed:
|
|
22719
|
+
try:
|
|
22720
|
+
proc.stdin.write(b"y\n")
|
|
22721
|
+
proc.stdin.flush()
|
|
22722
|
+
except Exception:
|
|
22723
|
+
pass
|
|
22724
|
+
_auto_confirmed.add(_pat)
|
|
22725
|
+
break
|
|
22683
22726
|
if (proc.poll() is not None) and (not sel.get_map()):
|
|
22684
22727
|
break
|
|
22685
22728
|
merged_raw = _merge_output_text()
|
|
@@ -25416,6 +25459,12 @@ body{padding:18px}
|
|
|
25416
25459
|
board["updated_at"] = float(now_ts())
|
|
25417
25460
|
self.blackboard = board
|
|
25418
25461
|
|
|
25462
|
+
def _save_blackboard(self, bb: dict):
|
|
25463
|
+
"""Persist a blackboard dict as the current blackboard and touch updated_at."""
|
|
25464
|
+
if isinstance(bb, dict):
|
|
25465
|
+
bb["updated_at"] = float(now_ts())
|
|
25466
|
+
self.blackboard = bb
|
|
25467
|
+
|
|
25419
25468
|
def _blackboard_reset_for_goal(self, goal: str):
|
|
25420
25469
|
# Preserve plan state when safe, but refresh loaded skills on goal change.
|
|
25421
25470
|
old_bb = self._ensure_blackboard()
|
|
@@ -26621,13 +26670,6 @@ body{padding:18px}
|
|
|
26621
26670
|
all_marked_done = all(str(r.get("status", "")).lower() == "completed" for r in worker_items)
|
|
26622
26671
|
if not all_marked_done:
|
|
26623
26672
|
return False
|
|
26624
|
-
# Acceptance verification: check that each "completed" subtask has real evidence
|
|
26625
|
-
# Don't just trust the model's TodoWrite status — verify against accumulated tool outputs
|
|
26626
|
-
if worker_items:
|
|
26627
|
-
bb = self._ensure_blackboard()
|
|
26628
|
-
unverified = self._verify_subtasks_acceptance(worker_items, step_id, bb)
|
|
26629
|
-
if unverified:
|
|
26630
|
-
return False
|
|
26631
26673
|
return True
|
|
26632
26674
|
|
|
26633
26675
|
def _verify_subtasks_acceptance(self, subtasks: list[dict], step_id: str, bb: dict) -> list[str]:
|
|
@@ -27907,9 +27949,12 @@ body{padding:18px}
|
|
|
27907
27949
|
return False
|
|
27908
27950
|
|
|
27909
27951
|
def _single_mode_validation_gate(self, plan_step: dict, tool_results: list[dict]) -> bool:
|
|
27910
|
-
"""Gate
|
|
27911
|
-
|
|
27912
|
-
|
|
27952
|
+
"""Gate passes when:
|
|
27953
|
+
1. Phase is research/design (no execution needed)
|
|
27954
|
+
2. Model emitted <step-verified/> since step activation
|
|
27955
|
+
3. Blackboard shows phase-appropriate accumulated evidence (has_write+has_exec for implement)
|
|
27956
|
+
4. Escape hatch: 10 consecutive blocks
|
|
27957
|
+
Research/design phases exempt. Escape hatch prevents permanent stall."""
|
|
27913
27958
|
step_id = str(plan_step.get("id", "") or "")
|
|
27914
27959
|
_flag = f"_smvg_{step_id}"
|
|
27915
27960
|
if getattr(self, _flag, False):
|
|
@@ -27925,10 +27970,17 @@ body{padding:18px}
|
|
|
27925
27970
|
if _n_blocked >= 10:
|
|
27926
27971
|
setattr(self, _flag, True)
|
|
27927
27972
|
return True
|
|
27928
|
-
#
|
|
27973
|
+
# Path A: model explicit tag
|
|
27929
27974
|
if self._check_step_verified_tag(plan_step):
|
|
27930
27975
|
setattr(self, _flag, True)
|
|
27931
27976
|
return True
|
|
27977
|
+
# Path B: blackboard shows phase-appropriate accumulated evidence
|
|
27978
|
+
# implement: has_write AND (has_exec OR ...) — wrote files + ran bash
|
|
27979
|
+
# test/review: has_exec OR has_test_pass — ran tests
|
|
27980
|
+
# other: use generic evidence check
|
|
27981
|
+
if self._plan_step_has_blackboard_evidence(plan_step):
|
|
27982
|
+
setattr(self, _flag, True)
|
|
27983
|
+
return True
|
|
27932
27984
|
# Gate blocked — increment counter and inject hint
|
|
27933
27985
|
setattr(self, _n_flag, _n_blocked + 1)
|
|
27934
27986
|
self._inject_single_mode_validation_hint(plan_step)
|
|
@@ -28068,16 +28120,12 @@ body{padding:18px}
|
|
|
28068
28120
|
# Priority 1: Check if worker subtasks are all completed (most reliable signal)
|
|
28069
28121
|
subtasks_done = self._step_subtasks_all_completed(current)
|
|
28070
28122
|
if subtasks_done:
|
|
28071
|
-
# Validation gate
|
|
28072
|
-
#
|
|
28073
|
-
#
|
|
28123
|
+
# Validation gate: passes when model emitted <step-verified/>, blackboard has
|
|
28124
|
+
# phase-appropriate evidence, or escape hatch (10 blocks). Gate is the authoritative
|
|
28125
|
+
# "step done" check — no additional validation_ok required after gate passes.
|
|
28074
28126
|
_gate_ok = self._single_mode_validation_gate(current, tool_results)
|
|
28075
28127
|
if _gate_ok:
|
|
28076
|
-
|
|
28077
|
-
should_advance = True
|
|
28078
|
-
elif todo_progress_signal and self._step_has_accumulated_evidence(current, bb):
|
|
28079
|
-
# Accumulated evidence path: subtasks done + TodoWrite progress + history
|
|
28080
|
-
should_advance = True
|
|
28128
|
+
should_advance = True
|
|
28081
28129
|
else:
|
|
28082
28130
|
_gate_blocked = True # Gate blocked — disable ALL remaining advancement paths
|
|
28083
28131
|
# Priority 2: Phase-based heuristics — BUT gate by subtask completion when subtasks exist
|
|
@@ -28111,7 +28159,8 @@ body{padding:18px}
|
|
|
28111
28159
|
last_text = str(msg.get("content", "") or "").lower()
|
|
28112
28160
|
break
|
|
28113
28161
|
step_done_signals = ("step completed", "步骤完成", "step done", "完成了", "已完成",
|
|
28114
|
-
"next step", "下一步", "proceed to step", "进入下一"
|
|
28162
|
+
"next step", "下一步", "proceed to step", "进入下一",
|
|
28163
|
+
"全部完成", "✅", "all subtasks")
|
|
28115
28164
|
if validation_ok and any(sig in last_text for sig in step_done_signals):
|
|
28116
28165
|
should_advance = True
|
|
28117
28166
|
if should_advance:
|
|
@@ -32974,6 +33023,36 @@ body{padding:18px}
|
|
|
32974
33023
|
if budget_forced
|
|
32975
33024
|
else ""
|
|
32976
33025
|
)
|
|
33026
|
+
# If in plan mode, include the current in-progress subtask and the <step-verified/> escape path
|
|
33027
|
+
plan_subtask_hint = ""
|
|
33028
|
+
try:
|
|
33029
|
+
bb = self._ensure_blackboard()
|
|
33030
|
+
todos = bb.get("project_todos", [])
|
|
33031
|
+
active_step = next(
|
|
33032
|
+
(t for t in todos if t.get("category") == "plan_step" and t.get("status") == "in_progress"),
|
|
33033
|
+
None,
|
|
33034
|
+
)
|
|
33035
|
+
if active_step:
|
|
33036
|
+
step_id = str(active_step.get("id", "") or "")
|
|
33037
|
+
active_subtask = next(
|
|
33038
|
+
(
|
|
33039
|
+
t for t in todos
|
|
33040
|
+
if t.get("category") != "plan_step"
|
|
33041
|
+
and t.get("status") == "in_progress"
|
|
33042
|
+
and str(t.get("parent_step_id", "") or "") == step_id
|
|
33043
|
+
),
|
|
33044
|
+
None,
|
|
33045
|
+
)
|
|
33046
|
+
if active_subtask:
|
|
33047
|
+
subtask_text = trim(str(active_subtask.get("content", "") or ""), 120)
|
|
33048
|
+
plan_subtask_hint = (
|
|
33049
|
+
f"\n当前子任务: {subtask_text}\n"
|
|
33050
|
+
"如果此子任务需要视觉/浏览器验证而无法通过 bash 完整执行,"
|
|
33051
|
+
"请创建相关文件,通过代码审查确认实现正确,"
|
|
33052
|
+
"然后在回复中发出 <step-verified/> 并调用 TodoWrite 将子任务标记为 completed。"
|
|
33053
|
+
)
|
|
33054
|
+
except Exception:
|
|
33055
|
+
pass
|
|
32977
33056
|
self.messages.append(
|
|
32978
33057
|
{
|
|
32979
33058
|
"role": "user",
|
|
@@ -32982,6 +33061,7 @@ body{padding:18px}
|
|
|
32982
33061
|
f"系统检测到空动作回合(consecutive_empty_action={int(streak)})。"
|
|
32983
33062
|
"你刚才进行了深入思考,但没有输出任何最终结果或工具调用。"
|
|
32984
33063
|
f"{budget_note}"
|
|
33064
|
+
f"{plan_subtask_hint}"
|
|
32985
33065
|
"请结束思考,立即基于现有推导输出最终结论,或发起一个明确、可执行的工具调用。 "
|
|
32986
33066
|
"System notice: you returned thinking-only content without final answer or tool calls. "
|
|
32987
33067
|
"Stop internal reasoning now and immediately output either the final conclusion or exactly one "
|
|
@@ -38074,6 +38154,22 @@ body{padding:18px}
|
|
|
38074
38154
|
"ok": not str(output).startswith("Error:"),
|
|
38075
38155
|
}
|
|
38076
38156
|
)
|
|
38157
|
+
# Update blackboard signals (step_files, execution_logs) for plan+single mode.
|
|
38158
|
+
# In plan+sync this is handled by _blackboard_update_from_worker_step, but in
|
|
38159
|
+
# plan+single there is no worker turn — we must update inline so that
|
|
38160
|
+
# _plan_step_has_blackboard_evidence() can see the evidence when the gate fires.
|
|
38161
|
+
try:
|
|
38162
|
+
self._blackboard_update_from_tool_result(
|
|
38163
|
+
"developer",
|
|
38164
|
+
{
|
|
38165
|
+
"name": dispatched_name or name,
|
|
38166
|
+
"args": args if isinstance(args, dict) else {},
|
|
38167
|
+
"output": trim(str(output or ""), 3000),
|
|
38168
|
+
"ok": not str(output).startswith("Error:"),
|
|
38169
|
+
},
|
|
38170
|
+
)
|
|
38171
|
+
except Exception:
|
|
38172
|
+
pass
|
|
38077
38173
|
# Failure ledger: record tool call and detect errors (single-agent, unified)
|
|
38078
38174
|
self._ledger_record_tool_call(name, args if isinstance(args, dict) else {})
|
|
38079
38175
|
_sa_ok = not str(output or "").startswith("Error")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "clouds-coder"
|
|
7
|
-
version = "2026.4.
|
|
7
|
+
version = "2026.4.6"
|
|
8
8
|
description = "Clouds Coder: local-first Cloud CLI coder runtime with Web UI and Skills Studio."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|