@oneciel-ai/claude-any 0.1.62 → 0.1.64
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/README.md +22 -1
- package/claude-any-tool-guard.py +136 -7
- package/claude_any.py +36 -15
- package/docs/README.ja.md +20 -1
- package/docs/README.ko.md +20 -1
- package/docs/README.zh.md +19 -1
- package/docs/manual.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ arguments through unchanged.
|
|
|
48
48
|
|
|
49
49
|
Credits: One Ciel LLC
|
|
50
50
|
|
|
51
|
-
Current version: `0.1.
|
|
51
|
+
Current version: `0.1.64`
|
|
52
52
|
|
|
53
53
|
## Why This Exists
|
|
54
54
|
|
|
@@ -385,6 +385,27 @@ steps under that larger model's supervision.
|
|
|
385
385
|
|
|
386
386
|
## Changelog
|
|
387
387
|
|
|
388
|
+
### 0.1.64
|
|
389
|
+
|
|
390
|
+
- **Model-aware native auto-compact**: claude-any now injects
|
|
391
|
+
`CLAUDE_CODE_AUTO_COMPACT_WINDOW` at launch using the selected provider/model
|
|
392
|
+
context window, including the cached Ollama/Ollama Cloud model catalog. Smaller
|
|
393
|
+
custom models now let Claude Code's native auto-compact trigger against their
|
|
394
|
+
real context budget instead of falling back to Claude Code's generic 200K
|
|
395
|
+
assumption.
|
|
396
|
+
|
|
397
|
+
### 0.1.63
|
|
398
|
+
|
|
399
|
+
- **Plan Mode stop guard**: when a non-Anthropic model is already in Plan Mode
|
|
400
|
+
and stops after a short acknowledgement without a tool call, the Stop hook
|
|
401
|
+
now returns structured JSON feedback so Claude Code continues with a
|
|
402
|
+
plan-mode-safe tool instead of leaking text into the prompt box.
|
|
403
|
+
- **Guard-feedback filtering**: claude-any filters its own plan-guard marker
|
|
404
|
+
from router history for all roles, preventing Stop hook recovery messages from
|
|
405
|
+
being sent back to upstream models.
|
|
406
|
+
- **Safer retry budget**: the Stop guard retry counter now resets once a real
|
|
407
|
+
tool call is attempted, while `SubagentStop` events are kept observational.
|
|
408
|
+
|
|
388
409
|
### 0.1.62
|
|
389
410
|
|
|
390
411
|
- **Ollama context catalog**: added `claude-any ollama-catalog`, which downloads
|
package/claude-any-tool-guard.py
CHANGED
|
@@ -57,6 +57,7 @@ TOOL_HINTS = {
|
|
|
57
57
|
"Grep": "Use Grep with pattern, path, glob, type, output_mode, context, head_limit, or multiline only.",
|
|
58
58
|
"TaskUpdate": "Use TaskUpdate with taskId and status.",
|
|
59
59
|
}
|
|
60
|
+
PLAN_GUARD_MARKER = "[claude-any-plan-guard]"
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def active() -> bool:
|
|
@@ -299,6 +300,82 @@ def transcript_plan_mode_active(transcript_path: str | None) -> bool:
|
|
|
299
300
|
return active
|
|
300
301
|
|
|
301
302
|
|
|
303
|
+
def message_text(message: dict[str, Any]) -> str:
|
|
304
|
+
content = message.get("content")
|
|
305
|
+
if isinstance(content, str):
|
|
306
|
+
return content.strip()
|
|
307
|
+
if not isinstance(content, list):
|
|
308
|
+
return ""
|
|
309
|
+
parts: list[str] = []
|
|
310
|
+
for block in content:
|
|
311
|
+
if isinstance(block, str):
|
|
312
|
+
parts.append(block)
|
|
313
|
+
elif isinstance(block, dict) and block.get("type") == "text":
|
|
314
|
+
parts.append(str(block.get("text") or ""))
|
|
315
|
+
return "\n".join(part for part in parts if part).strip()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def message_has_tool_use(message: dict[str, Any]) -> bool:
|
|
319
|
+
content = message.get("content")
|
|
320
|
+
if not isinstance(content, list):
|
|
321
|
+
return False
|
|
322
|
+
return any(isinstance(block, dict) and block.get("type") == "tool_use" for block in content)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def transcript_latest_turn(transcript_path: str | None) -> dict[str, Any]:
|
|
326
|
+
if not transcript_path:
|
|
327
|
+
return {}
|
|
328
|
+
path = Path(transcript_path)
|
|
329
|
+
if not path.exists():
|
|
330
|
+
return {}
|
|
331
|
+
try:
|
|
332
|
+
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()[-160:]
|
|
333
|
+
except Exception:
|
|
334
|
+
return {}
|
|
335
|
+
|
|
336
|
+
latest_assistant: dict[str, Any] | None = None
|
|
337
|
+
latest_assistant_index = -1
|
|
338
|
+
parsed: list[dict[str, Any]] = []
|
|
339
|
+
for line in lines:
|
|
340
|
+
try:
|
|
341
|
+
data = json.loads(line)
|
|
342
|
+
except Exception:
|
|
343
|
+
continue
|
|
344
|
+
parsed.append(data)
|
|
345
|
+
message = data.get("message")
|
|
346
|
+
if isinstance(message, dict) and message.get("role") == "assistant":
|
|
347
|
+
latest_assistant = message
|
|
348
|
+
latest_assistant_index = len(parsed) - 1
|
|
349
|
+
|
|
350
|
+
if not latest_assistant:
|
|
351
|
+
return {}
|
|
352
|
+
|
|
353
|
+
latest_user_text = ""
|
|
354
|
+
for data in reversed(parsed[:latest_assistant_index]):
|
|
355
|
+
if data.get("type") != "user":
|
|
356
|
+
continue
|
|
357
|
+
message = data.get("message")
|
|
358
|
+
if not isinstance(message, dict):
|
|
359
|
+
continue
|
|
360
|
+
if message.get("isMeta") is True:
|
|
361
|
+
continue
|
|
362
|
+
text = message_text(message)
|
|
363
|
+
if not text:
|
|
364
|
+
continue
|
|
365
|
+
if text.startswith("Stop hook feedback:") or PLAN_GUARD_MARKER in text:
|
|
366
|
+
continue
|
|
367
|
+
if text.startswith("Claude Any plan guard:"):
|
|
368
|
+
continue
|
|
369
|
+
latest_user_text = text
|
|
370
|
+
break
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
"assistant_text": message_text(latest_assistant),
|
|
374
|
+
"assistant_has_tool_use": message_has_tool_use(latest_assistant),
|
|
375
|
+
"user_text": latest_user_text,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
302
379
|
def short_resume_prompt(text: str) -> bool:
|
|
303
380
|
normalized = re.sub(r"\s+", " ", text or "").strip()
|
|
304
381
|
if not normalized or len(normalized) > 32:
|
|
@@ -307,16 +384,43 @@ def short_resume_prompt(text: str) -> bool:
|
|
|
307
384
|
|
|
308
385
|
|
|
309
386
|
def non_actionable_stop_text(text: str) -> bool:
|
|
310
|
-
|
|
387
|
+
stripped = (text or "").strip()
|
|
388
|
+
normalized = re.sub(r"\s+", " ", stripped).strip()
|
|
311
389
|
if not normalized or len(normalized) > 220:
|
|
312
390
|
return False
|
|
313
|
-
if "\n" in
|
|
391
|
+
if "\n" in stripped:
|
|
314
392
|
return False
|
|
315
393
|
if re.search(r"[`{};/\\\\]|https?://", normalized):
|
|
316
394
|
return False
|
|
317
395
|
return True
|
|
318
396
|
|
|
319
397
|
|
|
398
|
+
def should_block_plan_stop(transcript_path: str | None) -> tuple[bool, str]:
|
|
399
|
+
if not transcript_plan_mode_active(transcript_path):
|
|
400
|
+
return False, ""
|
|
401
|
+
turn = transcript_latest_turn(transcript_path)
|
|
402
|
+
assistant_text = str(turn.get("assistant_text") or "")
|
|
403
|
+
user_text = str(turn.get("user_text") or "")
|
|
404
|
+
if turn.get("assistant_has_tool_use"):
|
|
405
|
+
return False, ""
|
|
406
|
+
if not non_actionable_stop_text(assistant_text):
|
|
407
|
+
return False, ""
|
|
408
|
+
if re.search(r"[??]", assistant_text):
|
|
409
|
+
return False, ""
|
|
410
|
+
if not short_resume_prompt(user_text):
|
|
411
|
+
return False, ""
|
|
412
|
+
reason = (
|
|
413
|
+
f"{PLAN_GUARD_MARKER} Claude Any plan guard: Claude Code is still in plan mode, "
|
|
414
|
+
"but the latest response ended as a short "
|
|
415
|
+
"acknowledgement without any concrete tool call. Continue now by calling the next required Claude Code "
|
|
416
|
+
"plan-mode-safe tool, such as Read, Glob, Grep, or ExitPlanMode. Use TaskUpdate only when an existing "
|
|
417
|
+
"task is being updated. If mutation is required, call ExitPlanMode with the plan first. Do not put the "
|
|
418
|
+
"next step into the user input box and do not wait for the user unless you are asking a real "
|
|
419
|
+
"clarification question."
|
|
420
|
+
)
|
|
421
|
+
return True, reason
|
|
422
|
+
|
|
423
|
+
|
|
320
424
|
def stop_block_count_path(session_id: str) -> Path:
|
|
321
425
|
return cache_dir() / f"stop-block-{session_id or 'unknown'}.json"
|
|
322
426
|
|
|
@@ -331,17 +435,41 @@ def increment_stop_block_count(session_id: str | None, text: str) -> int:
|
|
|
331
435
|
except Exception:
|
|
332
436
|
data = {}
|
|
333
437
|
count = int(data.get(key) or 0) + 1
|
|
334
|
-
|
|
438
|
+
data[key] = count
|
|
439
|
+
tmp = path.with_suffix(".tmp")
|
|
440
|
+
tmp.write_text(json.dumps(data, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
441
|
+
tmp.replace(path)
|
|
335
442
|
return count
|
|
336
443
|
|
|
337
444
|
|
|
445
|
+
def reset_stop_block_count(session_id: str | None) -> None:
|
|
446
|
+
if not session_id:
|
|
447
|
+
return
|
|
448
|
+
path = stop_block_count_path(session_id)
|
|
449
|
+
try:
|
|
450
|
+
path.unlink(missing_ok=True)
|
|
451
|
+
except Exception:
|
|
452
|
+
pass
|
|
453
|
+
|
|
454
|
+
|
|
338
455
|
def handle_stop(event: dict[str, Any]) -> int:
|
|
339
456
|
log_json_event(event)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
# observational and do continuation control in the router instead.
|
|
457
|
+
if str(event.get("hook_event_name") or "") == "SubagentStop":
|
|
458
|
+
log_event(f"SubagentStop guard observed session={event.get('session_id') or ''}")
|
|
459
|
+
return 0
|
|
344
460
|
session_id = str(event.get("session_id") or "")
|
|
461
|
+
transcript_path = str(event.get("transcript_path") or "")
|
|
462
|
+
if active():
|
|
463
|
+
should_block, reason = should_block_plan_stop(transcript_path)
|
|
464
|
+
if should_block:
|
|
465
|
+
count = increment_stop_block_count(session_id, reason)
|
|
466
|
+
if count <= 3:
|
|
467
|
+
out = {"decision": "block", "reason": reason, "suppressOutput": True}
|
|
468
|
+
log_json_event(event, out)
|
|
469
|
+
log_event(f"Stop guard blocked plan idle session={session_id} count={count} transcript={transcript_path}")
|
|
470
|
+
emit(out)
|
|
471
|
+
return 0
|
|
472
|
+
log_event(f"Stop guard allowed repeated plan idle session={session_id} count={count} transcript={transcript_path}")
|
|
345
473
|
log_event(f"Stop guard observed session={session_id}")
|
|
346
474
|
return 0
|
|
347
475
|
|
|
@@ -405,6 +533,7 @@ def handle_pre_tool(event: dict[str, Any]) -> None:
|
|
|
405
533
|
if tool.startswith("mcp__"):
|
|
406
534
|
return
|
|
407
535
|
log_json_event(event)
|
|
536
|
+
reset_stop_block_count(str(event.get("session_id") or ""))
|
|
408
537
|
raw = event.get("tool_input")
|
|
409
538
|
if not isinstance(raw, dict):
|
|
410
539
|
pre_deny(
|
package/claude_any.py
CHANGED
|
@@ -90,7 +90,7 @@ PROVIDER_LABELS = {
|
|
|
90
90
|
"self-hosted-nim": "Self Hosted NIM",
|
|
91
91
|
}
|
|
92
92
|
APP_NAME = "Claude Any"
|
|
93
|
-
VERSION = "0.1.
|
|
93
|
+
VERSION = "0.1.64"
|
|
94
94
|
CREDITS = "Credits: One Ciel LLC"
|
|
95
95
|
|
|
96
96
|
LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
|
|
@@ -106,6 +106,7 @@ _RATE_LIMIT_LOCK = threading.Lock()
|
|
|
106
106
|
_CHAT_CONDITION = threading.Condition()
|
|
107
107
|
_CHAT_NEXT_ID: int | None = None
|
|
108
108
|
ADVISOR_FEEDBACK_MARKER = "CLAUDE_ANY_ADVISOR_FEEDBACK"
|
|
109
|
+
PLAN_GUARD_MARKER = "[claude-any-plan-guard]"
|
|
109
110
|
|
|
110
111
|
# Tools Claude Code injects into every model's tool list that misfire when called
|
|
111
112
|
# by non-Anthropic models. See docs/notes from anthropics/claude-code issues
|
|
@@ -2110,19 +2111,28 @@ def plan_mode_tool_name_for_emit(body: dict[str, Any], name: str, tool_input: di
|
|
|
2110
2111
|
router_log("WARN", "dropped ExitPlanMode while plan mode is not active")
|
|
2111
2112
|
return None, tool_input
|
|
2112
2113
|
return name, tool_input
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
def
|
|
2114
|
+
|
|
2115
|
+
|
|
2116
|
+
def is_guard_feedback_text(text: str) -> bool:
|
|
2117
|
+
stripped = (text or "").strip()
|
|
2118
|
+
return (
|
|
2119
|
+
stripped.startswith("Stop hook feedback:")
|
|
2120
|
+
or stripped.startswith("Claude Any plan guard:")
|
|
2121
|
+
or PLAN_GUARD_MARKER in stripped
|
|
2122
|
+
)
|
|
2123
|
+
|
|
2124
|
+
|
|
2125
|
+
def latest_user_text(body: dict[str, Any]) -> str:
|
|
2116
2126
|
for message in reversed(body.get("messages") or []):
|
|
2117
2127
|
if not isinstance(message, dict) or message.get("role") != "user":
|
|
2118
2128
|
continue
|
|
2119
2129
|
if message.get("isMeta") is True:
|
|
2120
2130
|
continue
|
|
2121
|
-
content = message.get("content")
|
|
2122
|
-
if isinstance(content, str):
|
|
2123
|
-
if content
|
|
2124
|
-
continue
|
|
2125
|
-
return content
|
|
2131
|
+
content = message.get("content")
|
|
2132
|
+
if isinstance(content, str):
|
|
2133
|
+
if is_guard_feedback_text(content):
|
|
2134
|
+
continue
|
|
2135
|
+
return content
|
|
2126
2136
|
if not isinstance(content, list):
|
|
2127
2137
|
# Claude Code can inject user-role attachment records such as
|
|
2128
2138
|
# plan_mode_exit. They are state metadata, not new user intent.
|
|
@@ -2133,11 +2143,11 @@ def latest_user_text(body: dict[str, Any]) -> str:
|
|
|
2133
2143
|
text_blocks = [
|
|
2134
2144
|
block for block in content
|
|
2135
2145
|
if isinstance(block, str) or (isinstance(block, dict) and block.get("type") == "text")
|
|
2136
|
-
]
|
|
2137
|
-
text = anthropic_content_to_text(text_blocks)
|
|
2138
|
-
if not text or text
|
|
2139
|
-
continue
|
|
2140
|
-
return text
|
|
2146
|
+
]
|
|
2147
|
+
text = anthropic_content_to_text(text_blocks)
|
|
2148
|
+
if not text or is_guard_feedback_text(text):
|
|
2149
|
+
continue
|
|
2150
|
+
return text
|
|
2141
2151
|
return ""
|
|
2142
2152
|
|
|
2143
2153
|
|
|
@@ -3910,7 +3920,7 @@ def should_skip_upstream_message(message: dict[str, Any]) -> bool:
|
|
|
3910
3920
|
if role == "user" and message.get("isMeta") is True:
|
|
3911
3921
|
return True
|
|
3912
3922
|
text = anthropic_content_to_text(content).strip()
|
|
3913
|
-
if
|
|
3923
|
+
if is_guard_feedback_text(text):
|
|
3914
3924
|
return True
|
|
3915
3925
|
# Router diagnostics must never be fed back to the upstream model. In Claude
|
|
3916
3926
|
# Code they can also appear in the prompt input after a malformed/empty turn.
|
|
@@ -8764,6 +8774,13 @@ def claude_code_output_token_limit(provider: str, pcfg: dict[str, Any]) -> int |
|
|
|
8764
8774
|
return None
|
|
8765
8775
|
|
|
8766
8776
|
|
|
8777
|
+
def claude_code_auto_compact_window(provider: str, pcfg: dict[str, Any]) -> int | None:
|
|
8778
|
+
limit = context_limit_for_status(provider, pcfg)
|
|
8779
|
+
if limit:
|
|
8780
|
+
return limit
|
|
8781
|
+
return None
|
|
8782
|
+
|
|
8783
|
+
|
|
8767
8784
|
def claude_code_context_model_alias(provider: str, pcfg: dict[str, Any], model: str) -> str:
|
|
8768
8785
|
model = strip_claude_context_suffix(model)
|
|
8769
8786
|
limit = context_limit_for_status(provider, pcfg)
|
|
@@ -8780,6 +8797,9 @@ def apply_common_claude_env(provider: str, pcfg: dict[str, Any], env: dict[str,
|
|
|
8780
8797
|
output_tokens = claude_code_output_token_limit(provider, pcfg)
|
|
8781
8798
|
if output_tokens:
|
|
8782
8799
|
env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"] = str(output_tokens)
|
|
8800
|
+
compact_window = claude_code_auto_compact_window(provider, pcfg)
|
|
8801
|
+
if compact_window:
|
|
8802
|
+
env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] = str(compact_window)
|
|
8783
8803
|
advisor_model = str(pcfg.get("advisor_model") or "").strip()
|
|
8784
8804
|
if advisor_model:
|
|
8785
8805
|
env["CLAUDE_ANY_ADVISOR_MODEL"] = advisor_model
|
|
@@ -8870,6 +8890,7 @@ def cmd_env(_: argparse.Namespace) -> None:
|
|
|
8870
8890
|
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",
|
|
8871
8891
|
"CLAUDE_CODE_ATTRIBUTION_HEADER",
|
|
8872
8892
|
"CLAUDE_CODE_MAX_OUTPUT_TOKENS",
|
|
8893
|
+
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
|
8873
8894
|
"ANTHROPIC_MODEL",
|
|
8874
8895
|
"ANTHROPIC_CUSTOM_MODEL_OPTION",
|
|
8875
8896
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
package/docs/README.ja.md
CHANGED
|
@@ -47,7 +47,7 @@ vLLM、NVIDIA hosted、self-hosted NIM を選択し、通常の Claude Code 引
|
|
|
47
47
|
|
|
48
48
|
Credits: One Ciel LLC
|
|
49
49
|
|
|
50
|
-
現在のバージョン: `0.1.
|
|
50
|
+
現在のバージョン: `0.1.64`
|
|
51
51
|
|
|
52
52
|
## 作られた理由
|
|
53
53
|
|
|
@@ -351,6 +351,25 @@ Windows/Linux 管理、クリーンアップスクリプト、定期的なセキ
|
|
|
351
351
|
|
|
352
352
|
## 変更履歴
|
|
353
353
|
|
|
354
|
+
### 0.1.64
|
|
355
|
+
|
|
356
|
+
- **モデル context 対応の native auto-compact**: claude-any は起動時に、選択中の
|
|
357
|
+
provider/model の context window を使って `CLAUDE_CODE_AUTO_COMPACT_WINDOW`
|
|
358
|
+
を注入します。Ollama/Ollama Cloud ではディスクにキャッシュした model catalog
|
|
359
|
+
も利用するため、小さい custom model でも Claude Code の汎用 200K 仮定ではなく、
|
|
360
|
+
実際の context budget に合わせて native auto-compact が発火します。
|
|
361
|
+
|
|
362
|
+
### 0.1.63
|
|
363
|
+
|
|
364
|
+
- **Plan Mode stop guard**: non-Anthropic モデルが Plan Mode 中に短い確認文だけで
|
|
365
|
+
tool call なしに停止した場合、Stop hook が構造化 JSON フィードバックを返し、
|
|
366
|
+
Claude Code が plan-mode-safe tool で続行できるようにしました。
|
|
367
|
+
- **Guard feedback filtering**: claude-any の plan-guard marker をすべての role
|
|
368
|
+
の router history から除外し、Stop hook の復旧メッセージが upstream モデルへ
|
|
369
|
+
戻らないようにしました。
|
|
370
|
+
- **より安全な retry budget**: 実際の tool call が試行されたら Stop guard の
|
|
371
|
+
カウンターをリセットし、`SubagentStop` は観察専用のままにします。
|
|
372
|
+
|
|
354
373
|
### 0.1.62
|
|
355
374
|
|
|
356
375
|
- **Ollama context catalog**: `claude-any ollama-catalog` を追加しました。
|
package/docs/README.ko.md
CHANGED
|
@@ -47,7 +47,7 @@ NVIDIA hosted, self-hosted NIM을 선택하고, Claude Code의 일반 인자는
|
|
|
47
47
|
|
|
48
48
|
Credits: One Ciel LLC
|
|
49
49
|
|
|
50
|
-
현재 버전: `0.1.
|
|
50
|
+
현재 버전: `0.1.64`
|
|
51
51
|
|
|
52
52
|
## 왜 만들었나
|
|
53
53
|
|
|
@@ -351,6 +351,25 @@ Windows 이벤트 로그 리뷰, 바이러스/랜섬웨어 침입 시도 정리,
|
|
|
351
351
|
|
|
352
352
|
## 변경 이력
|
|
353
353
|
|
|
354
|
+
### 0.1.64
|
|
355
|
+
|
|
356
|
+
- **모델 컨텍스트 인식 native auto-compact**: claude-any가 실행 시 선택된
|
|
357
|
+
provider/model의 context window를 기준으로 `CLAUDE_CODE_AUTO_COMPACT_WINDOW`를
|
|
358
|
+
주입합니다. Ollama/Ollama Cloud는 디스크에 캐시된 model catalog도 활용하므로,
|
|
359
|
+
작은 커스텀 모델도 Claude Code의 기본 200K 가정이 아니라 실제 context budget에
|
|
360
|
+
맞춰 native auto-compact가 발동됩니다.
|
|
361
|
+
|
|
362
|
+
### 0.1.63
|
|
363
|
+
|
|
364
|
+
- **Plan Mode stop guard**: non-Anthropic 모델이 Plan Mode 안에서 짧은 확인
|
|
365
|
+
문장만 내고 tool call 없이 멈추는 경우, Stop hook이 구조화된 JSON 피드백을
|
|
366
|
+
반환해 Claude Code가 plan-mode-safe tool로 계속 진행하도록 했습니다.
|
|
367
|
+
- **Guard 피드백 필터링**: claude-any의 plan-guard marker를 모든 role의 router
|
|
368
|
+
history에서 제거하여, Stop hook 복구 메시지가 upstream 모델로 다시 전달되지
|
|
369
|
+
않게 했습니다.
|
|
370
|
+
- **더 안전한 retry budget**: 실제 tool call이 시도되면 Stop guard 카운터를
|
|
371
|
+
리셋하고, `SubagentStop` 이벤트는 관찰 전용으로 유지합니다.
|
|
372
|
+
|
|
354
373
|
### 0.1.62
|
|
355
374
|
|
|
356
375
|
- **Ollama 컨텍스트 카탈로그**: `claude-any ollama-catalog` 명령을 추가했습니다.
|
package/docs/README.zh.md
CHANGED
|
@@ -47,7 +47,7 @@ NIM,并把普通 Claude Code 参数原样传递。
|
|
|
47
47
|
|
|
48
48
|
Credits: One Ciel LLC
|
|
49
49
|
|
|
50
|
-
当前版本: `0.1.
|
|
50
|
+
当前版本: `0.1.64`
|
|
51
51
|
|
|
52
52
|
## 为什么存在
|
|
53
53
|
|
|
@@ -337,6 +337,24 @@ Hermes 格式模型或部分较旧的 Qwen tool template。
|
|
|
337
337
|
|
|
338
338
|
## 更新日志
|
|
339
339
|
|
|
340
|
+
### 0.1.64
|
|
341
|
+
|
|
342
|
+
- **按模型上下文触发 native auto-compact**:claude-any 启动时会根据当前
|
|
343
|
+
provider/model 的 context window 注入 `CLAUDE_CODE_AUTO_COMPACT_WINDOW`。
|
|
344
|
+
Ollama/Ollama Cloud 会同时使用磁盘缓存的 model catalog,因此较小的 custom
|
|
345
|
+
model 也会按真实 context budget 触发 Claude Code 原生 auto-compact,而不是
|
|
346
|
+
退回到 Claude Code 通用的 200K 假设。
|
|
347
|
+
|
|
348
|
+
### 0.1.63
|
|
349
|
+
|
|
350
|
+
- **Plan Mode stop guard**:当 non-Anthropic 模型已经处于 Plan Mode,却只输出
|
|
351
|
+
简短确认文本且没有 tool call 就停止时,Stop hook 现在会返回结构化 JSON
|
|
352
|
+
反馈,让 Claude Code 继续调用 plan-mode-safe tool。
|
|
353
|
+
- **Guard feedback filtering**:claude-any 会从所有 role 的 router history 中过滤
|
|
354
|
+
自己的 plan-guard marker,避免 Stop hook 恢复消息再次发送给 upstream 模型。
|
|
355
|
+
- **更安全的 retry budget**:一旦真正的 tool call 被尝试,Stop guard 计数器会
|
|
356
|
+
重置;`SubagentStop` 事件保持仅观察模式。
|
|
357
|
+
|
|
340
358
|
### 0.1.62
|
|
341
359
|
|
|
342
360
|
- **Ollama 上下文目录**:新增 `claude-any ollama-catalog` 命令。它会下载
|
package/docs/manual.md
CHANGED
package/package.json
CHANGED