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.
@@ -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: after subtasks complete, require model to explicitly emit <step-verified/>
27911
- in a message since this step was activated. Research/design phases exempt.
27912
- Escape hatch: after 10 consecutive blocks, auto-pass to prevent permanent stall."""
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
- # Model must explicitly emit <step-verified/> after evaluating results
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 always fires when subtasks are done even if validation_ok is False.
28072
- # For research/design phases the gate passes immediately; for implement/test it requires
28073
- # a successful bash run. This ensures single mode proactively requests verification.
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
- if validation_ok:
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")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clouds-coder
3
- Version: 2026.4.5
3
+ Version: 2026.4.6
4
4
  Summary: Clouds Coder: local-first Cloud CLI coder runtime with Web UI and Skills Studio.
5
5
  Author: Clouds Coder Contributors
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clouds-coder
3
- Version: 2026.4.5
3
+ Version: 2026.4.6
4
4
  Summary: Clouds Coder: local-first Cloud CLI coder runtime with Web UI and Skills Studio.
5
5
  Author: Clouds Coder Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clouds-coder"
7
- version = "2026.4.5"
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