clouds-coder 2026.5.2__tar.gz → 2026.5.28__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.5.2 → clouds_coder-2026.5.28}/Clouds_Coder.py +2021 -363
- {clouds_coder-2026.5.2/clouds_coder.egg-info → clouds_coder-2026.5.28}/PKG-INFO +35 -4
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/README.md +34 -3
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28/clouds_coder.egg-info}/PKG-INFO +35 -4
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/pyproject.toml +1 -1
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/LICENSE +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/clouds_coder.egg-info/SOURCES.txt +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/clouds_coder.egg-info/dependency_links.txt +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/clouds_coder.egg-info/entry_points.txt +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/clouds_coder.egg-info/requires.txt +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/clouds_coder.egg-info/top_level.txt +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/setup.cfg +0 -0
- {clouds_coder-2026.5.2 → clouds_coder-2026.5.28}/tests/test_smoke.py +0 -0
|
@@ -142,6 +142,10 @@ LONG_OUTPUT_LISTING_OFFLOAD_CHARS = 6_000
|
|
|
142
142
|
LONG_OUTPUT_READ_PAGE_LINES = 240
|
|
143
143
|
LONG_OUTPUT_READ_PAGE_MAX_CHARS = 16_000
|
|
144
144
|
LONG_OUTPUT_TEMP_MAX_FILES = 160
|
|
145
|
+
READ_FILE_DEFAULT_MAX_CHARS = 50_000
|
|
146
|
+
READ_FILE_HARD_MAX_CHARS = 120_000
|
|
147
|
+
READ_FILE_OVERVIEW_HEAD_LINES = 80
|
|
148
|
+
READ_FILE_SEARCH_MAX_MATCHES = 24
|
|
145
149
|
JSON_FSYNC_ENABLED = str(os.getenv("AGENT_JSON_FSYNC", "true") or "true").strip().lower() not in {"0", "false", "no", "off"}
|
|
146
150
|
RAG_LIBRARY_DIRNAME = "RAG_Library"
|
|
147
151
|
RAG_ADMIN_PORT_OFFSET = 2
|
|
@@ -447,6 +451,13 @@ COMPACT_TIER3_PCT = 0.10 # <10%: tier 3 heavy
|
|
|
447
451
|
# Absolute minimums — prevent percentage instability at low ctx_left
|
|
448
452
|
COMPACT_TIER1_ABS = 3000
|
|
449
453
|
COMPACT_TIER2_ABS = 1500
|
|
454
|
+
CONTEXT_COMPACT_INEFFECTIVE_COOLDOWN_SECONDS = max(
|
|
455
|
+
5.0,
|
|
456
|
+
min(
|
|
457
|
+
300.0,
|
|
458
|
+
float(str(os.getenv("AGENT_CONTEXT_COMPACT_INEFFECTIVE_COOLDOWN", "45") or "45")),
|
|
459
|
+
),
|
|
460
|
+
)
|
|
450
461
|
# File buffer
|
|
451
462
|
FILE_BUFFER_CONTENT_THRESHOLD = 2000 # chars: content larger than this gets offloaded
|
|
452
463
|
FILE_BUFFER_MAX_FILES = 500
|
|
@@ -6950,7 +6961,7 @@ Use this skill when the agent shows model degradation symptoms:
|
|
|
6950
6961
|
2. Record concrete evidence (exact errors / statuses / turn pattern) before changing strategy.
|
|
6951
6962
|
|
|
6952
6963
|
## Recovery Workflow (Mandatory Order)
|
|
6953
|
-
1. If context may be missing, call `context_recall` first.
|
|
6964
|
+
1. If context may be missing, call `context_recall` with `mode="summary"` first, then use `mode="search"` or `mode="window"` for focused evidence.
|
|
6954
6965
|
2. Build or repair todo plan with 3-7 items (one `in_progress`) via `TodoWrite` or `TodoWriteRescue`.
|
|
6955
6966
|
3. Enter strict execution mode:
|
|
6956
6967
|
- execute exactly ONE tool call per round,
|
|
@@ -6982,7 +6993,7 @@ Return:
|
|
|
6982
6993
|
|
|
6983
6994
|
# Fast Decision Rules
|
|
6984
6995
|
|
|
6985
|
-
1. Missing context -> `context_recall
|
|
6996
|
+
1. Missing context -> `context_recall mode="summary"`, then `mode="search"` or `mode="window"` for focused evidence.
|
|
6986
6997
|
2. No todo plan -> `TodoWriteRescue`.
|
|
6987
6998
|
3. Repeated tool failure -> one-tool strict retry with smaller chunk.
|
|
6988
6999
|
4. Still failing -> explicit blocker, stop loop.
|
|
@@ -7025,7 +7036,7 @@ Assess the error and pick the matching depth. This is the single most important
|
|
|
7025
7036
|
| Signal | Depth | Budget | Strategy |
|
|
7026
7037
|
|--------|-------|--------|----------|
|
|
7027
7038
|
| Typo, missing import, syntax error | **Shallow** | 1-2 tool calls | Pattern-match fix directly from error message |
|
|
7028
|
-
| Single clear exception with traceback | **Standard** | 3-6 tool calls | Trace call chain, read
|
|
7039
|
+
| Single clear exception with traceback | **Standard** | 3-6 tool calls | Trace call chain, read the exact crash construct, fix + verify |
|
|
7029
7040
|
| Intermittent / multi-component / no clear trace | **Deep** | 8-15 tool calls | Hypothesize → isolate → instrument → validate causal chain |
|
|
7030
7041
|
| Reproduces only under specific state / concurrency | **Forensic** | 15-25 tool calls | State reconstruction, bisect, invariant analysis |
|
|
7031
7042
|
|
|
@@ -7047,7 +7058,7 @@ Every bug has a causal chain: **trigger → propagation → manifestation**. Mos
|
|
|
7047
7058
|
|
|
7048
7059
|
### Step 3: Targeted Investigation
|
|
7049
7060
|
- Read ONLY the code that your hypothesis predicts is involved.
|
|
7050
|
-
- Use `read_file`
|
|
7061
|
+
- Use `read_file` in the smallest mode that fits the question: `window` for file:line, `symbol` for functions/classes, `search` for locating evidence, and `full` only when exact broad context is needed.
|
|
7051
7062
|
- If hypothesis is wrong, update it based on what you learned. Don't restart from scratch.
|
|
7052
7063
|
|
|
7053
7064
|
### Step 4: Fix at the Trigger, Not the Symptom
|
|
@@ -7404,7 +7415,7 @@ def ensure_generated_smart_file_navigation_skill(skills_root: Path):
|
|
|
7404
7415
|
root = generated_root / "smart-file-navigation"
|
|
7405
7416
|
skill_md = """---
|
|
7406
7417
|
name: smart-file-navigation
|
|
7407
|
-
description: Adaptive codebase exploration engine that scales reading strategy to project size and task scope — from surgical
|
|
7418
|
+
description: Adaptive codebase exploration engine that scales reading strategy to project size and task scope — from surgical symbol/window reads to systematic dependency-graph traversal, with question-driven navigation and workspace awareness.
|
|
7408
7419
|
---
|
|
7409
7420
|
|
|
7410
7421
|
# Smart File Navigation
|
|
@@ -7418,7 +7429,7 @@ Decide your reading strategy BEFORE opening any file. Wrong strategy wastes your
|
|
|
7418
7429
|
|
|
7419
7430
|
| Task Scope | Strategy | Read Budget | Key Principle |
|
|
7420
7431
|
|-----------|----------|-------------|---------------|
|
|
7421
|
-
| Fix a specific error with file:line | **Surgical** | 2-4 reads | Read
|
|
7432
|
+
| Fix a specific error with file:line | **Surgical** | 2-4 reads | Read the exact crash construct. Follow ONE call chain. |
|
|
7422
7433
|
| Implement feature in known area | **Focused** | 5-10 reads | Scan interfaces of affected modules. Read implementations you'll modify. |
|
|
7423
7434
|
| Understand unfamiliar module | **Exploratory** | 8-15 reads | Structure scan → entry points → data flow → key abstractions. |
|
|
7424
7435
|
| Full codebase assessment | **Systematic** | 15-25 reads | Top-down: build config → architecture → module boundaries → hot paths. |
|
|
@@ -7430,7 +7441,7 @@ Decide your reading strategy BEFORE opening any file. Wrong strategy wastes your
|
|
|
7430
7441
|
### The Navigation Loop
|
|
7431
7442
|
1. **State your question**: "What does function X do?" / "Where is Y defined?" / "How does data flow from A to B?"
|
|
7432
7443
|
2. **Predict the answer's location**: Based on naming conventions, directory structure, imports.
|
|
7433
|
-
3. **Read the
|
|
7444
|
+
3. **Read the right shape of context**: use `overview`, `symbol`, `search`, `window`, or `full` according to the question.
|
|
7434
7445
|
4. **Record the answer**: Note it in your reasoning. Don't re-read to "remember".
|
|
7435
7446
|
5. **Derive the next question**: Each answer either resolves your task or generates a more specific question.
|
|
7436
7447
|
|
|
@@ -7440,10 +7451,10 @@ If step 5 generates the SAME question you already answered → you're in a loop.
|
|
|
7440
7451
|
|
|
7441
7452
|
| File Size | Strategy |
|
|
7442
7453
|
|-----------|----------|
|
|
7443
|
-
| < 150 lines | Read
|
|
7444
|
-
| 150-500 lines | Read
|
|
7445
|
-
| 500-2000 lines |
|
|
7446
|
-
| 2000+ lines |
|
|
7454
|
+
| < 150 lines | Read the whole file if that answers the question |
|
|
7455
|
+
| 150-500 lines | Read the relevant symbol or window around the target |
|
|
7456
|
+
| 500-2000 lines | Use `search` or `symbol` to locate the exact region, then read a focused window |
|
|
7457
|
+
| 2000+ lines | Use `overview` first, then `search`, `symbol`, or `window` for the exact region |
|
|
7447
7458
|
|
|
7448
7459
|
## Dependency Tracing
|
|
7449
7460
|
|
|
@@ -7467,7 +7478,7 @@ When you see an import, make a TRIAGE decision:
|
|
|
7467
7478
|
## Error-Driven Navigation
|
|
7468
7479
|
|
|
7469
7480
|
When you have an error with a location:
|
|
7470
|
-
1. Read `file:line` with
|
|
7481
|
+
1. Read `file:line` with `mode="window"` and enough context to see the surrounding construct.
|
|
7471
7482
|
2. Identify the VARIABLE or EXPRESSION that caused the error.
|
|
7472
7483
|
3. Trace BACKWARD: where was that variable last assigned? Read THAT location.
|
|
7473
7484
|
4. If the assignment depends on another function's return value, read THAT function (just the return statements).
|
|
@@ -7477,11 +7488,8 @@ When you have an error with a location:
|
|
|
7477
7488
|
|
|
7478
7489
|
### Self-Monitoring Rules
|
|
7479
7490
|
- **Track what you've read**: After each read, note "file X, lines Y-Z, learned: ...".
|
|
7480
|
-
- **
|
|
7481
|
-
- **
|
|
7482
|
-
1. Write down what you know so far.
|
|
7483
|
-
2. Identify the SPECIFIC gap in your knowledge.
|
|
7484
|
-
3. Take an action based on what you know (even if imperfect).
|
|
7491
|
+
- **Avoid pointless rereads**: If you already know the answer from a range, move to the next missing question instead of reopening the same slice.
|
|
7492
|
+
- **If you keep circling the same question**, pause, summarize what you know, and take an action based on the current hypothesis.
|
|
7485
7493
|
|
|
7486
7494
|
### Recovery from Navigation Dead Ends
|
|
7487
7495
|
If you can't find what you're looking for:
|
|
@@ -8821,7 +8829,7 @@ clouds_coder:
|
|
|
8821
8829
|
- bash
|
|
8822
8830
|
description: >
|
|
8823
8831
|
Specialized guide for code library retrieval. Code-specific query patterns,
|
|
8824
|
-
language filtering, symbol-aware search, and integration with read_file for
|
|
8832
|
+
language filtering, symbol-aware search, and integration with read_file for focused context.
|
|
8825
8833
|
TRIGGER when: looking up code implementations, function signatures, API patterns, code review.
|
|
8826
8834
|
DO NOT TRIGGER for: knowledge/document retrieval (use rag-retrieval-mastery),
|
|
8827
8835
|
general research (use research-orchestrator-pro).
|
|
@@ -8886,7 +8894,7 @@ Code library returns **snippets** (320 chars max). To get full context:
|
|
|
8886
8894
|
|
|
8887
8895
|
1. **Query**: `result = query_code_library(query="parse config file", top_k=5)`
|
|
8888
8896
|
2. **Extract path**: Read `citation` field → contains file path
|
|
8889
|
-
3. **Read
|
|
8897
|
+
3. **Read the best context**: use `read_file` with `mode="symbol"` for named code, `mode="window"` for nearby lines, `mode="search"` for evidence, or `mode="full"` when broad exact context is needed
|
|
8890
8898
|
4. **Analyze**: Now you have the full function/class context
|
|
8891
8899
|
|
|
8892
8900
|
This is the core workflow: **search → locate → read → understand**.
|
|
@@ -10062,7 +10070,7 @@ _BUILTIN_SKILLS: dict[str, dict] = {
|
|
|
10062
10070
|
"# Context Management Guide\n"
|
|
10063
10071
|
"- Context has a token upper bound; keep steps compact.\n"
|
|
10064
10072
|
"- When <compact-resume> hint appears, inherit pending todos and continue immediately.\n"
|
|
10065
|
-
"- After compaction, use context_recall to
|
|
10073
|
+
"- After compaction, use context_recall mode='summary' to map archived messages, then mode='search' or mode='window' for focused evidence.\n"
|
|
10066
10074
|
"- Do not guess content that was compacted away—recall it first.\n"
|
|
10067
10075
|
"- For large tasks, break into subtasks to avoid context overflow.\n"
|
|
10068
10076
|
),
|
|
@@ -11676,6 +11684,16 @@ class MessageBus:
|
|
|
11676
11684
|
out.append(row)
|
|
11677
11685
|
return out
|
|
11678
11686
|
|
|
11687
|
+
def peek_inbox(self, name: str) -> list[dict]:
|
|
11688
|
+
path = self._path(name)
|
|
11689
|
+
if not path.exists():
|
|
11690
|
+
return []
|
|
11691
|
+
with self.lock:
|
|
11692
|
+
payload = self.crypto.read_json(path, [])
|
|
11693
|
+
if not isinstance(payload, list):
|
|
11694
|
+
payload = []
|
|
11695
|
+
return [row for row in payload if isinstance(row, dict)]
|
|
11696
|
+
|
|
11679
11697
|
def broadcast(self, sender: str, content: str, names: list[str]) -> str:
|
|
11680
11698
|
count = 0
|
|
11681
11699
|
for name in names:
|
|
@@ -11898,6 +11916,10 @@ class WorktreeManager:
|
|
|
11898
11916
|
out = payload[-n:] if isinstance(payload, list) else []
|
|
11899
11917
|
return json_dumps(out, indent=2)
|
|
11900
11918
|
|
|
11919
|
+
def events_objects(self) -> list[dict]:
|
|
11920
|
+
payload = self.crypto.read_json(self.events_path, [])
|
|
11921
|
+
return [row for row in payload if isinstance(row, dict)] if isinstance(payload, list) else []
|
|
11922
|
+
|
|
11901
11923
|
class OllamaError(RuntimeError):
|
|
11902
11924
|
def __init__(
|
|
11903
11925
|
self,
|
|
@@ -13407,8 +13429,30 @@ TOOLS = [
|
|
|
13407
13429
|
tool_def("bash", "Run a shell command.", {"command": {"type": "string"}}, ["command"]),
|
|
13408
13430
|
tool_def(
|
|
13409
13431
|
"read_file",
|
|
13410
|
-
|
|
13411
|
-
|
|
13432
|
+
(
|
|
13433
|
+
"Read files or directories with structure-aware modes. "
|
|
13434
|
+
"Examples: large.py + func_42 -> mode='symbol' target='func_42'; "
|
|
13435
|
+
"app.py line 240 -> mode='window' line=240 context=5; "
|
|
13436
|
+
"run.txt E123 -> mode='search' query='E123'. "
|
|
13437
|
+
"Use mode='auto' by default; use mode='symbol', 'search', or 'window' for focused reads, "
|
|
13438
|
+
"and mode='full' when complete content is explicitly needed."
|
|
13439
|
+
),
|
|
13440
|
+
{
|
|
13441
|
+
"path": {"type": "string"},
|
|
13442
|
+
"mode": {
|
|
13443
|
+
"type": "string",
|
|
13444
|
+
"enum": ["auto", "full", "overview", "window", "symbol", "search", "directory"],
|
|
13445
|
+
"description": "Reading strategy. Use symbol with target for a function/class; search with query for known text/errors; window with line/context for a line range. Avoid full for large logs when a query is known.",
|
|
13446
|
+
},
|
|
13447
|
+
"target": {"type": "string", "description": "Symbol name for mode='symbol', for example 'ClassName.method' or 'func_42'."},
|
|
13448
|
+
"query": {"type": "string", "description": "Search text or regex for mode='search'; can also be used when target is unknown."},
|
|
13449
|
+
"line": {"type": "integer", "description": "1-based center line for mode='window'."},
|
|
13450
|
+
"context": {"type": "integer", "description": "Number of surrounding lines for mode='window' or mode='search'."},
|
|
13451
|
+
"regex": {"type": "boolean", "description": "Treat query as a regular expression in mode='search'."},
|
|
13452
|
+
"max_chars": {"type": "integer", "description": "Maximum characters to return for broad reads; use only when wider context is needed."},
|
|
13453
|
+
"limit": {"type": "integer", "description": "Legacy line count for compatibility; prefer mode/context for new calls."},
|
|
13454
|
+
"offset": {"type": "integer", "description": "Legacy 0-based line offset for compatibility; prefer mode='window' with line/context."},
|
|
13455
|
+
},
|
|
13412
13456
|
["path"],
|
|
13413
13457
|
),
|
|
13414
13458
|
tool_def("write_file", "Write file content.", {"path": {"type": "string"}, "content": {"type": "string"}}, ["path", "content"]),
|
|
@@ -13451,15 +13495,26 @@ TOOLS = [
|
|
|
13451
13495
|
),
|
|
13452
13496
|
tool_def("scan_skills", "Force reload skills from ./skills and return summary.", {}),
|
|
13453
13497
|
tool_def("compress", "Compress conversation context.", {}),
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13498
|
+
tool_def(
|
|
13499
|
+
"context_recall",
|
|
13500
|
+
(
|
|
13501
|
+
"Recall archived compacted context with focused modes. "
|
|
13502
|
+
"Use this tool, not read_from_blackboard, when the user says archived context, compacted context, previous segment, or context segment. "
|
|
13503
|
+
"Use mode='summary' for a map, 'search' with query/tool_name/role for evidence, "
|
|
13504
|
+
"or 'window' with around_index/context for nearby messages."
|
|
13505
|
+
),
|
|
13457
13506
|
{
|
|
13507
|
+
"mode": {"type": "string", "enum": ["summary", "search", "recent", "window", "detail"]},
|
|
13458
13508
|
"segment_id": {"type": "string"},
|
|
13459
13509
|
"query": {"type": "string"},
|
|
13510
|
+
"role": {"type": "string"},
|
|
13511
|
+
"tool_name": {"type": "string"},
|
|
13512
|
+
"around_index": {"type": "integer"},
|
|
13513
|
+
"context": {"type": "integer"},
|
|
13514
|
+
"max_chars": {"type": "integer"},
|
|
13460
13515
|
"recent_segments": {"type": "integer"},
|
|
13461
13516
|
"max_messages": {"type": "integer"},
|
|
13462
|
-
"offset": {"type": "integer"},
|
|
13517
|
+
"offset": {"type": "integer", "description": "Legacy offset for compatibility; prefer query or around_index/context."},
|
|
13463
13518
|
"include_tools": {"type": "boolean"},
|
|
13464
13519
|
},
|
|
13465
13520
|
),
|
|
@@ -13511,11 +13566,37 @@ TOOLS = [
|
|
|
13511
13566
|
["media_type", "prompt"],
|
|
13512
13567
|
),
|
|
13513
13568
|
tool_def("background_run", "Run command in background.", {"command": {"type": "string"}, "timeout": {"type": "integer"}}, ["command"]),
|
|
13514
|
-
|
|
13569
|
+
tool_def(
|
|
13570
|
+
"check_background",
|
|
13571
|
+
(
|
|
13572
|
+
"Inspect background tasks with summary/search/detail/tail modes. "
|
|
13573
|
+
"If the user names an error token, command text, or output text such as E123 or pytest, use mode='search' with query. "
|
|
13574
|
+
"Use tail only for recent jobs when no query is known."
|
|
13575
|
+
),
|
|
13576
|
+
{
|
|
13577
|
+
"task_id": {"type": "string"},
|
|
13578
|
+
"mode": {"type": "string", "enum": ["summary", "search", "detail", "tail"]},
|
|
13579
|
+
"query": {"type": "string"},
|
|
13580
|
+
"status": {"type": "string"},
|
|
13581
|
+
"limit": {"type": "integer"},
|
|
13582
|
+
"max_chars": {"type": "integer"},
|
|
13583
|
+
},
|
|
13584
|
+
),
|
|
13515
13585
|
tool_def("task_create", "Create task.", {"subject": {"type": "string"}, "description": {"type": "string"}}, ["subject"]),
|
|
13516
13586
|
tool_def("task_get", "Get task.", {"task_id": {"type": "integer"}}, ["task_id"]),
|
|
13517
13587
|
tool_def("task_update", "Update task.", {"task_id": {"type": "integer"}, "status": {"type": "string"}, "add_blocked_by": {"type": "array", "items": {"type": "integer"}}, "add_blocks": {"type": "array", "items": {"type": "integer"}}}, ["task_id"]),
|
|
13518
|
-
tool_def(
|
|
13588
|
+
tool_def(
|
|
13589
|
+
"task_list",
|
|
13590
|
+
"List tasks with optional status/owner/query filters and summary/detail modes.",
|
|
13591
|
+
{
|
|
13592
|
+
"mode": {"type": "string", "enum": ["summary", "search", "detail", "recent"]},
|
|
13593
|
+
"query": {"type": "string"},
|
|
13594
|
+
"status": {"type": "string"},
|
|
13595
|
+
"owner": {"type": "string"},
|
|
13596
|
+
"limit": {"type": "integer"},
|
|
13597
|
+
"max_chars": {"type": "integer"},
|
|
13598
|
+
},
|
|
13599
|
+
),
|
|
13519
13600
|
tool_def("claim_task", "Claim task.", {"task_id": {"type": "integer"}}, ["task_id"]),
|
|
13520
13601
|
tool_def("spawn_teammate", "Spawn teammate thread.", {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, ["name", "role", "prompt"]),
|
|
13521
13602
|
tool_def("list_teammates", "List teammates.", {}),
|
|
@@ -13529,9 +13610,13 @@ TOOLS = [
|
|
|
13529
13610
|
},
|
|
13530
13611
|
["to", "intent", "content"],
|
|
13531
13612
|
),
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13613
|
+
tool_def(
|
|
13614
|
+
"read_from_blackboard",
|
|
13615
|
+
(
|
|
13616
|
+
"Read shared multi-agent blackboard state with summary/search/recent/window/detail modes. "
|
|
13617
|
+
"Use this for research_notes, execution_logs, review_feedback, code_artifacts, and status; "
|
|
13618
|
+
"use context_recall instead for archived or compacted conversation context."
|
|
13619
|
+
),
|
|
13535
13620
|
{
|
|
13536
13621
|
"section": {
|
|
13537
13622
|
"type": "string",
|
|
@@ -13546,6 +13631,13 @@ TOOLS = [
|
|
|
13546
13631
|
"status",
|
|
13547
13632
|
],
|
|
13548
13633
|
},
|
|
13634
|
+
"mode": {"type": "string", "enum": ["summary", "search", "recent", "window", "detail"]},
|
|
13635
|
+
"query": {"type": "string"},
|
|
13636
|
+
"actor": {"type": "string"},
|
|
13637
|
+
"status": {"type": "string"},
|
|
13638
|
+
"around_index": {"type": "integer"},
|
|
13639
|
+
"context": {"type": "integer"},
|
|
13640
|
+
"max_chars": {"type": "integer"},
|
|
13549
13641
|
"limit": {"type": "integer"},
|
|
13550
13642
|
},
|
|
13551
13643
|
),
|
|
@@ -13574,7 +13666,18 @@ TOOLS = [
|
|
|
13574
13666
|
["section", "content"],
|
|
13575
13667
|
),
|
|
13576
13668
|
tool_def("send_message", "Send message.", {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string"}}, ["to", "content"]),
|
|
13577
|
-
tool_def(
|
|
13669
|
+
tool_def(
|
|
13670
|
+
"read_inbox",
|
|
13671
|
+
"Read lead inbox. Use mode='peek' to inspect without draining, mode='search' to find messages, or mode='drain' to consume them.",
|
|
13672
|
+
{
|
|
13673
|
+
"mode": {"type": "string", "enum": ["peek", "drain", "search", "summary"]},
|
|
13674
|
+
"query": {"type": "string"},
|
|
13675
|
+
"from": {"type": "string"},
|
|
13676
|
+
"type": {"type": "string"},
|
|
13677
|
+
"limit": {"type": "integer"},
|
|
13678
|
+
"max_chars": {"type": "integer"},
|
|
13679
|
+
},
|
|
13680
|
+
),
|
|
13578
13681
|
tool_def("broadcast", "Broadcast to teammates.", {"content": {"type": "string"}}, ["content"]),
|
|
13579
13682
|
tool_def("shutdown_request", "Request teammate shutdown.", {"teammate": {"type": "string"}}, ["teammate"]),
|
|
13580
13683
|
tool_def("plan_approval", "Respond to plan approval request.", {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "feedback": {"type": "string"}}, ["request_id", "approve"]),
|
|
@@ -13584,7 +13687,23 @@ TOOLS = [
|
|
|
13584
13687
|
tool_def("worktree_run", "Run command in worktree.", {"name": {"type": "string"}, "command": {"type": "string"}}, ["name", "command"]),
|
|
13585
13688
|
tool_def("worktree_keep", "Mark worktree kept.", {"name": {"type": "string"}}, ["name"]),
|
|
13586
13689
|
tool_def("worktree_remove", "Remove worktree.", {"name": {"type": "string"}, "force": {"type": "boolean"}, "complete_task": {"type": "boolean"}}, ["name"]),
|
|
13587
|
-
|
|
13690
|
+
tool_def(
|
|
13691
|
+
"worktree_events",
|
|
13692
|
+
(
|
|
13693
|
+
"Read worktree lifecycle events with summary/search/detail modes. "
|
|
13694
|
+
"For a worktree name like alpha, set worktree='alpha'; do not put worktree names in event. "
|
|
13695
|
+
"For known failure text use mode='search' and query='failed' or the exact error; event is for lifecycle names like worktree.remove.failed."
|
|
13696
|
+
),
|
|
13697
|
+
{
|
|
13698
|
+
"mode": {"type": "string", "enum": ["summary", "search", "recent", "detail"]},
|
|
13699
|
+
"query": {"type": "string"},
|
|
13700
|
+
"event": {"type": "string"},
|
|
13701
|
+
"worktree": {"type": "string"},
|
|
13702
|
+
"task_id": {"type": "integer"},
|
|
13703
|
+
"limit": {"type": "integer"},
|
|
13704
|
+
"max_chars": {"type": "integer"},
|
|
13705
|
+
},
|
|
13706
|
+
),
|
|
13588
13707
|
]
|
|
13589
13708
|
|
|
13590
13709
|
TOOL_REQUIRED_ARGS: dict[str, list[str]] = {}
|
|
@@ -13926,6 +14045,15 @@ class SessionState:
|
|
|
13926
14045
|
self.context_limit_locked = bool(context_limit_locked)
|
|
13927
14046
|
self.context_token_upper_bound = self.max_context_token_limit
|
|
13928
14047
|
self.context_estimate_calibration = float(CONTEXT_ESTIMATE_SAFETY_MULTIPLIER)
|
|
14048
|
+
self.context_limit_source = "configured"
|
|
14049
|
+
self.context_last_compact_before: dict = {}
|
|
14050
|
+
self.context_last_compact_after: dict = {}
|
|
14051
|
+
self.context_last_compact_effective = True
|
|
14052
|
+
self.context_last_compact_used_reduction = 0
|
|
14053
|
+
self.context_last_compact_skip_ts = 0.0
|
|
14054
|
+
self.context_last_compact_skip_reason = ""
|
|
14055
|
+
self.context_last_next_call_estimate = 0
|
|
14056
|
+
self.context_last_next_call_label = ""
|
|
13929
14057
|
self.last_context_actual_prompt_tokens = 0
|
|
13930
14058
|
self.last_context_actual_completion_tokens = 0
|
|
13931
14059
|
self.last_context_actual_total_tokens = 0
|
|
@@ -14838,6 +14966,35 @@ class SessionState:
|
|
|
14838
14966
|
)
|
|
14839
14967
|
except Exception:
|
|
14840
14968
|
self.context_estimate_calibration = float(CONTEXT_ESTIMATE_SAFETY_MULTIPLIER)
|
|
14969
|
+
self.context_limit_source = trim(str(raw.get("context_limit_source", self.context_limit_source) or "configured"), 80)
|
|
14970
|
+
self.context_last_compact_before = (
|
|
14971
|
+
dict(raw.get("context_last_compact_before", {}) or {})
|
|
14972
|
+
if isinstance(raw.get("context_last_compact_before", {}), dict)
|
|
14973
|
+
else {}
|
|
14974
|
+
)
|
|
14975
|
+
self.context_last_compact_after = (
|
|
14976
|
+
dict(raw.get("context_last_compact_after", {}) or {})
|
|
14977
|
+
if isinstance(raw.get("context_last_compact_after", {}), dict)
|
|
14978
|
+
else {}
|
|
14979
|
+
)
|
|
14980
|
+
self.context_last_compact_effective = bool(
|
|
14981
|
+
raw.get("context_last_compact_effective", self.context_last_compact_effective)
|
|
14982
|
+
)
|
|
14983
|
+
self.context_last_compact_used_reduction = max(
|
|
14984
|
+
0, int(raw.get("context_last_compact_used_reduction", 0) or 0)
|
|
14985
|
+
)
|
|
14986
|
+
self.context_last_compact_skip_ts = max(
|
|
14987
|
+
0.0, float(raw.get("context_last_compact_skip_ts", 0.0) or 0.0)
|
|
14988
|
+
)
|
|
14989
|
+
self.context_last_compact_skip_reason = trim(
|
|
14990
|
+
str(raw.get("context_last_compact_skip_reason", "") or ""), 160
|
|
14991
|
+
)
|
|
14992
|
+
self.context_last_next_call_estimate = max(
|
|
14993
|
+
0, int(raw.get("context_last_next_call_estimate", 0) or 0)
|
|
14994
|
+
)
|
|
14995
|
+
self.context_last_next_call_label = trim(
|
|
14996
|
+
str(raw.get("context_last_next_call_label", "") or ""), 80
|
|
14997
|
+
)
|
|
14841
14998
|
self.last_context_actual_prompt_tokens = max(0, int(raw.get("last_context_actual_prompt_tokens", 0) or 0))
|
|
14842
14999
|
self.last_context_actual_completion_tokens = max(0, int(raw.get("last_context_actual_completion_tokens", 0) or 0))
|
|
14843
15000
|
self.last_context_actual_total_tokens = max(0, int(raw.get("last_context_actual_total_tokens", 0) or 0))
|
|
@@ -15192,6 +15349,15 @@ class SessionState:
|
|
|
15192
15349
|
"thinking": self.thinking,
|
|
15193
15350
|
"context_limit_locked": bool(self.context_limit_locked),
|
|
15194
15351
|
"context_estimate_calibration": float(getattr(self, "context_estimate_calibration", CONTEXT_ESTIMATE_SAFETY_MULTIPLIER) or CONTEXT_ESTIMATE_SAFETY_MULTIPLIER),
|
|
15352
|
+
"context_limit_source": str(getattr(self, "context_limit_source", "") or "configured"),
|
|
15353
|
+
"context_last_compact_before": dict(getattr(self, "context_last_compact_before", {}) or {}),
|
|
15354
|
+
"context_last_compact_after": dict(getattr(self, "context_last_compact_after", {}) or {}),
|
|
15355
|
+
"context_last_compact_effective": bool(getattr(self, "context_last_compact_effective", True)),
|
|
15356
|
+
"context_last_compact_used_reduction": int(getattr(self, "context_last_compact_used_reduction", 0) or 0),
|
|
15357
|
+
"context_last_compact_skip_ts": float(getattr(self, "context_last_compact_skip_ts", 0.0) or 0.0),
|
|
15358
|
+
"context_last_compact_skip_reason": str(getattr(self, "context_last_compact_skip_reason", "") or ""),
|
|
15359
|
+
"context_last_next_call_estimate": int(getattr(self, "context_last_next_call_estimate", 0) or 0),
|
|
15360
|
+
"context_last_next_call_label": str(getattr(self, "context_last_next_call_label", "") or ""),
|
|
15195
15361
|
"last_context_actual_prompt_tokens": int(getattr(self, "last_context_actual_prompt_tokens", 0) or 0),
|
|
15196
15362
|
"last_context_actual_completion_tokens": int(getattr(self, "last_context_actual_completion_tokens", 0) or 0),
|
|
15197
15363
|
"last_context_actual_total_tokens": int(getattr(self, "last_context_actual_total_tokens", 0) or 0),
|
|
@@ -16128,6 +16294,80 @@ class SessionState:
|
|
|
16128
16294
|
loaded = bb.get("loaded_skills", {})
|
|
16129
16295
|
return loaded if isinstance(loaded, dict) else {}
|
|
16130
16296
|
|
|
16297
|
+
def _loaded_skill_body_from_cache(self, skill_key: str, row: dict | None = None) -> str:
|
|
16298
|
+
key = str(skill_key or "").strip()
|
|
16299
|
+
if not key:
|
|
16300
|
+
return ""
|
|
16301
|
+
cache_row = self.skill_load_cache.get(key, {})
|
|
16302
|
+
if isinstance(cache_row, dict):
|
|
16303
|
+
body_z = str(cache_row.get("body_z", "") or "")
|
|
16304
|
+
if body_z:
|
|
16305
|
+
body = decompress_text_blob(body_z)
|
|
16306
|
+
if body:
|
|
16307
|
+
return body
|
|
16308
|
+
meta_row = row if isinstance(row, dict) else {}
|
|
16309
|
+
skill_path = str(meta_row.get("skill_path", "") or "").strip()
|
|
16310
|
+
if skill_path:
|
|
16311
|
+
try:
|
|
16312
|
+
path_obj = Path(skill_path)
|
|
16313
|
+
if path_obj.exists() and path_obj.is_file():
|
|
16314
|
+
return path_obj.read_text(encoding="utf-8", errors="replace")
|
|
16315
|
+
except Exception:
|
|
16316
|
+
pass
|
|
16317
|
+
return ""
|
|
16318
|
+
|
|
16319
|
+
def _loaded_skills_context_block(self, *, for_role: str = "", max_chars: int = 7000) -> str:
|
|
16320
|
+
"""Bounded active-skill instructions rehydrated on every prompt.
|
|
16321
|
+
|
|
16322
|
+
The original <loaded-skill> message may be compacted, so each model
|
|
16323
|
+
call gets a small workflow-critical copy from the skill cache/path.
|
|
16324
|
+
"""
|
|
16325
|
+
loaded = self._loaded_skill_rows()
|
|
16326
|
+
if not loaded:
|
|
16327
|
+
return ""
|
|
16328
|
+
budget = max(800, int(max_chars or 7000))
|
|
16329
|
+
parts: list[str] = [
|
|
16330
|
+
"ACTIVE SKILL WORKFLOWS (rehydrated):",
|
|
16331
|
+
"Treat these instructions as active operating procedure for matching steps. "
|
|
16332
|
+
"If they conflict with a generic plan, the skill workflow wins.",
|
|
16333
|
+
]
|
|
16334
|
+
remaining = budget
|
|
16335
|
+
for skill_key, row_obj in list(loaded.items())[:5]:
|
|
16336
|
+
row = row_obj if isinstance(row_obj, dict) else {}
|
|
16337
|
+
skill_name = str(row.get("skill_name", skill_key) or skill_key).strip() or skill_key
|
|
16338
|
+
skill_path = str(row.get("skill_path", "") or "").strip()
|
|
16339
|
+
body = self._loaded_skill_body_from_cache(str(skill_key), row)
|
|
16340
|
+
preview = trim(str(row.get("preview", "") or ""), 600)
|
|
16341
|
+
source = body or preview
|
|
16342
|
+
if not source:
|
|
16343
|
+
continue
|
|
16344
|
+
per_skill = max(600, min(2600, remaining // max(1, len(loaded))))
|
|
16345
|
+
excerpt = trim(source, per_skill)
|
|
16346
|
+
header = f"\n<active-skill name=\"{skill_name}\" key=\"{skill_key}\""
|
|
16347
|
+
if skill_path:
|
|
16348
|
+
header += f" path=\"{skill_path}\""
|
|
16349
|
+
header += ">"
|
|
16350
|
+
block = f"{header}\n{excerpt}\n</active-skill>"
|
|
16351
|
+
parts.append(block)
|
|
16352
|
+
remaining -= max(1, len(block))
|
|
16353
|
+
if remaining <= 600:
|
|
16354
|
+
break
|
|
16355
|
+
if len(parts) <= 2:
|
|
16356
|
+
return ""
|
|
16357
|
+
role_note = ""
|
|
16358
|
+
role_key = str(for_role or "").strip().lower()
|
|
16359
|
+
if role_key == "manager":
|
|
16360
|
+
role_note = (
|
|
16361
|
+
"\nManager duty: delegate with the concrete tools/scripts/files from the active skill; "
|
|
16362
|
+
"do not replace the skill workflow with a generic approach."
|
|
16363
|
+
)
|
|
16364
|
+
elif role_key:
|
|
16365
|
+
role_note = (
|
|
16366
|
+
"\nWorker duty: before acting, map the current step to the active skill workflow and "
|
|
16367
|
+
"use the specified tools/scripts/files when applicable."
|
|
16368
|
+
)
|
|
16369
|
+
return trim("\n".join(parts) + role_note + "\n", budget)
|
|
16370
|
+
|
|
16131
16371
|
def _clear_loaded_skill_contexts(self):
|
|
16132
16372
|
def _filter_rows(rows: list[dict]) -> list[dict]:
|
|
16133
16373
|
kept: list[dict] = []
|
|
@@ -16405,8 +16645,6 @@ class SessionState:
|
|
|
16405
16645
|
({"ppt", "ppt-master", "pptx", "academic-pptx", "slide-making-skill"}, "Presentation/PPT processing"),
|
|
16406
16646
|
# Research orchestrators
|
|
16407
16647
|
({"deep-research-orchestrator", "research-orchestrator-pro"}, "Research orchestration"),
|
|
16408
|
-
# Frontend design
|
|
16409
|
-
({"frontend-design", "frontend-composition-algorithm", "canvas-design", "web-artifacts-builder"}, "Frontend/web design"),
|
|
16410
16648
|
]
|
|
16411
16649
|
# Normalize: strip common prefixes/suffixes for matching
|
|
16412
16650
|
def _norm(name: str) -> set[str]:
|
|
@@ -16538,7 +16776,9 @@ class SessionState:
|
|
|
16538
16776
|
Keeps all three modes in sync — change here propagates everywhere.
|
|
16539
16777
|
"""
|
|
16540
16778
|
hint = self._loaded_skills_prompt_hint(for_role=for_role)
|
|
16541
|
-
|
|
16779
|
+
active = self._loaded_skills_context_block(for_role=for_role, max_chars=6500)
|
|
16780
|
+
active_block = f"\n{active}\n" if active else "\n"
|
|
16781
|
+
return f"{hint}{active_block}Skills:\n{self.skills.descriptions()}\n"
|
|
16542
16782
|
|
|
16543
16783
|
def _refresh_runtime_code_reference(self, text: str):
|
|
16544
16784
|
cb = getattr(self, "reference_prepare_callback", None)
|
|
@@ -16763,6 +17003,8 @@ class SessionState:
|
|
|
16763
17003
|
"Use load_skill for workspace-paths and tool-best-practices if needed. "
|
|
16764
17004
|
"Use list_skills to discover available skills for specific tasks. "
|
|
16765
17005
|
)
|
|
17006
|
+
skill_context_block = self._loaded_skills_context_block(for_role="developer", max_chars=7000)
|
|
17007
|
+
skill_context = f"{skill_context_block}\n" if skill_context_block else ""
|
|
16766
17008
|
plan_steps_block = ""
|
|
16767
17009
|
plan_ctx = self._plan_steps_context_for_manager()
|
|
16768
17010
|
if plan_ctx:
|
|
@@ -16786,9 +17028,13 @@ class SessionState:
|
|
|
16786
17028
|
f"Context limit ~{max(1, int(self.context_token_upper_bound) - max(1, int(math.ceil(int(self.context_token_upper_bound) * CONTEXT_AUTO_COMPACT_RESERVE_RATIO))))} usable tokens "
|
|
16787
17029
|
f"(5% reserved for auto compact; raw upper bound ~{self.context_token_upper_bound}). "
|
|
16788
17030
|
f"{_detect_os_shell_instruction()} "
|
|
16789
|
-
|
|
17031
|
+
"Use tools to inspect, edit, and execute. "
|
|
17032
|
+
"If you say you will create, write, build, copy, modify, or verify an artifact, the same turn must include the concrete tool call that does it; do not stop at a promise to act. "
|
|
17033
|
+
"When reading files, choose the shape that matches the question: mode='window' for file:line, mode='symbol' for named code, mode='search' for keywords/errors, mode='overview' for structure, and mode='full' only when exact broad context is required. "
|
|
17034
|
+
"When inspecting collections or memory, use focused modes too: context_recall/read_from_blackboard/task_list/check_background/read_inbox/worktree_events support mode='summary', mode='search', mode='window', and mode='detail' where applicable. Prefer query/status/actor/tool filters over repeatedly listing recent items. "
|
|
16790
17035
|
"Call finish_current_task only when the overall user task is done. "
|
|
16791
17036
|
f"{skill_hint}"
|
|
17037
|
+
f"{skill_context}"
|
|
16792
17038
|
f"{mm_hint}"
|
|
16793
17039
|
f"{plan_steps_block}"
|
|
16794
17040
|
f"{plan_todo_block}"
|
|
@@ -17047,8 +17293,199 @@ class SessionState:
|
|
|
17047
17293
|
"last_actual_age_seconds": round(float(actual_age), 3) if getattr(self, "last_context_actual_ts", 0.0) else None,
|
|
17048
17294
|
"last_estimated_prompt_tokens": int(actual_estimate_at_call),
|
|
17049
17295
|
"calibration_max": float(CONTEXT_USAGE_CALIBRATION_MAX),
|
|
17296
|
+
"limit_source": str(getattr(self, "context_limit_source", "") or "configured"),
|
|
17050
17297
|
}
|
|
17051
17298
|
|
|
17299
|
+
def _context_window_error_hint(self, exc: Exception | str) -> bool:
|
|
17300
|
+
text = str(exc or "").lower()
|
|
17301
|
+
if not text:
|
|
17302
|
+
return False
|
|
17303
|
+
markers = (
|
|
17304
|
+
"context length",
|
|
17305
|
+
"context window",
|
|
17306
|
+
"maximum context",
|
|
17307
|
+
"max context",
|
|
17308
|
+
"num_ctx",
|
|
17309
|
+
"prompt too long",
|
|
17310
|
+
"too many tokens",
|
|
17311
|
+
"token limit",
|
|
17312
|
+
"input is too long",
|
|
17313
|
+
"reduce the length",
|
|
17314
|
+
"exceeds the context",
|
|
17315
|
+
"exceeded context",
|
|
17316
|
+
"context overflow",
|
|
17317
|
+
)
|
|
17318
|
+
return any(marker in text for marker in markers)
|
|
17319
|
+
|
|
17320
|
+
def _shrink_context_upper_bound_from_actual_pressure(
|
|
17321
|
+
self,
|
|
17322
|
+
*,
|
|
17323
|
+
estimated_prompt_tokens: int,
|
|
17324
|
+
reason: str,
|
|
17325
|
+
) -> bool:
|
|
17326
|
+
if self.context_limit_locked:
|
|
17327
|
+
return False
|
|
17328
|
+
old_bound = int(self.context_token_upper_bound or self.max_context_token_limit)
|
|
17329
|
+
prompt = max(MIN_CONTEXT_TOKEN_LIMIT, int(estimated_prompt_tokens or 0))
|
|
17330
|
+
if prompt <= 0:
|
|
17331
|
+
return False
|
|
17332
|
+
# This is only used after the provider reports a context-window failure.
|
|
17333
|
+
# Leave enough room for recovery prompts, but never infer model context from output length.
|
|
17334
|
+
new_bound = max(MIN_CONTEXT_TOKEN_LIMIT, min(old_bound - 1, int(prompt * 0.96)))
|
|
17335
|
+
if new_bound >= old_bound:
|
|
17336
|
+
return False
|
|
17337
|
+
self.context_token_upper_bound = new_bound
|
|
17338
|
+
self.context_limit_source = f"provider-context-error:{trim(str(reason or 'unknown'), 48)}"
|
|
17339
|
+
self._emit(
|
|
17340
|
+
"status",
|
|
17341
|
+
{
|
|
17342
|
+
"summary": (
|
|
17343
|
+
"context upper bound adjusted from provider context error "
|
|
17344
|
+
f"{old_bound}->{new_bound} (estimated_prompt≈{prompt})"
|
|
17345
|
+
)
|
|
17346
|
+
},
|
|
17347
|
+
)
|
|
17348
|
+
return True
|
|
17349
|
+
|
|
17350
|
+
def _context_metrics_for_model_call(
|
|
17351
|
+
self,
|
|
17352
|
+
messages: list[dict],
|
|
17353
|
+
*,
|
|
17354
|
+
tools: list | None = None,
|
|
17355
|
+
system: str = "",
|
|
17356
|
+
media_inputs: list[dict] | None = None,
|
|
17357
|
+
label: str = "",
|
|
17358
|
+
record: bool = True,
|
|
17359
|
+
) -> dict:
|
|
17360
|
+
estimate = self._estimate_model_call_prompt_tokens(
|
|
17361
|
+
messages if isinstance(messages, list) else [],
|
|
17362
|
+
tools=tools,
|
|
17363
|
+
system=system,
|
|
17364
|
+
media_inputs=media_inputs,
|
|
17365
|
+
)
|
|
17366
|
+
label_text = trim(str(label or ""), 80)
|
|
17367
|
+
if record:
|
|
17368
|
+
self.context_last_next_call_estimate = int(estimate)
|
|
17369
|
+
self.context_last_next_call_label = label_text
|
|
17370
|
+
metrics = self._context_budget_metrics(token_estimate=estimate)
|
|
17371
|
+
metrics["estimate_source"] = "next-model-call"
|
|
17372
|
+
metrics["next_call_label"] = label_text
|
|
17373
|
+
return metrics
|
|
17374
|
+
|
|
17375
|
+
def _role_specific_context_is_live_call(self, role: str) -> bool:
|
|
17376
|
+
role_key = self._sanitize_agent_role(role)
|
|
17377
|
+
if not role_key:
|
|
17378
|
+
return False
|
|
17379
|
+
if self._is_multi_agent_mode():
|
|
17380
|
+
return True
|
|
17381
|
+
phase = str(self.current_phase or "").strip().lower()
|
|
17382
|
+
active = str(self.active_agent_role or "").strip().lower()
|
|
17383
|
+
return bool(
|
|
17384
|
+
phase.startswith("plan-mode:")
|
|
17385
|
+
and active == role_key
|
|
17386
|
+
and bool(self._agent_context(role_key))
|
|
17387
|
+
)
|
|
17388
|
+
|
|
17389
|
+
def _active_next_call_context_metrics(self, role: str = "", media_inputs: list[dict] | None = None) -> dict:
|
|
17390
|
+
raw_role = str(role or self.active_agent_role or "").strip().lower()
|
|
17391
|
+
if raw_role == "manager":
|
|
17392
|
+
return self._context_metrics_for_model_call(
|
|
17393
|
+
self.manager_context,
|
|
17394
|
+
tools=self._manager_route_tools(),
|
|
17395
|
+
system=self._manager_system_prompt(),
|
|
17396
|
+
media_inputs=media_inputs,
|
|
17397
|
+
label="manager next turn",
|
|
17398
|
+
)
|
|
17399
|
+
role_key = self._sanitize_agent_role(raw_role)
|
|
17400
|
+
role_ctx = self._agent_context(role_key) if role_key in AGENT_ROLES else []
|
|
17401
|
+
if role_key in AGENT_ROLES and self._role_specific_context_is_live_call(role_key):
|
|
17402
|
+
return self._context_metrics_for_model_call(
|
|
17403
|
+
role_ctx,
|
|
17404
|
+
tools=self._tools_for_agent(role_key),
|
|
17405
|
+
system=self._agent_role_system_prompt(role_key),
|
|
17406
|
+
media_inputs=media_inputs,
|
|
17407
|
+
label=f"{role_key} next turn",
|
|
17408
|
+
)
|
|
17409
|
+
return self._context_metrics_for_model_call(
|
|
17410
|
+
self.messages,
|
|
17411
|
+
tools=TOOLS,
|
|
17412
|
+
system=self._system_prompt(),
|
|
17413
|
+
media_inputs=media_inputs,
|
|
17414
|
+
label="single-agent next turn",
|
|
17415
|
+
)
|
|
17416
|
+
|
|
17417
|
+
def _agent_context_budget_metrics_snapshot(self) -> list[dict]:
|
|
17418
|
+
rows: list[dict] = []
|
|
17419
|
+
active = str(self.active_agent_role or "").strip().lower()
|
|
17420
|
+
candidates: list[str] = []
|
|
17421
|
+
multi_mode = self._is_multi_agent_mode()
|
|
17422
|
+
plan_role_active = bool(
|
|
17423
|
+
(not multi_mode)
|
|
17424
|
+
and active in AGENT_ROLES
|
|
17425
|
+
and str(self.current_phase or "").strip().lower().startswith("plan-mode:")
|
|
17426
|
+
)
|
|
17427
|
+
if multi_mode:
|
|
17428
|
+
candidates.append("manager")
|
|
17429
|
+
if multi_mode:
|
|
17430
|
+
role_order = list(self.runtime_participants or [])
|
|
17431
|
+
for role in role_order:
|
|
17432
|
+
role_key = self._sanitize_agent_role(role)
|
|
17433
|
+
if role_key and role_key not in candidates:
|
|
17434
|
+
candidates.append(role_key)
|
|
17435
|
+
elif plan_role_active:
|
|
17436
|
+
candidates.append(active)
|
|
17437
|
+
if not candidates:
|
|
17438
|
+
candidates = ["single"]
|
|
17439
|
+
for role in candidates:
|
|
17440
|
+
try:
|
|
17441
|
+
if role == "manager":
|
|
17442
|
+
messages = self.manager_context
|
|
17443
|
+
tools = self._manager_route_tools()
|
|
17444
|
+
system = self._manager_system_prompt()
|
|
17445
|
+
label = "manager next turn"
|
|
17446
|
+
msg_count = len(self.manager_context)
|
|
17447
|
+
display = backend_role_label("manager", getattr(self, "ui_language", DEFAULT_UI_LANGUAGE))
|
|
17448
|
+
elif role in AGENT_ROLES:
|
|
17449
|
+
ctx = self._agent_context(role)
|
|
17450
|
+
messages = ctx
|
|
17451
|
+
tools = self._tools_for_agent(role)
|
|
17452
|
+
system = self._agent_role_system_prompt(role)
|
|
17453
|
+
label = f"{role} next turn"
|
|
17454
|
+
msg_count = len(ctx)
|
|
17455
|
+
display = self._agent_display_name(role)
|
|
17456
|
+
else:
|
|
17457
|
+
messages = self.messages
|
|
17458
|
+
tools = TOOLS
|
|
17459
|
+
system = self._system_prompt()
|
|
17460
|
+
label = "single-agent next turn"
|
|
17461
|
+
msg_count = len(self.messages)
|
|
17462
|
+
display = "Single"
|
|
17463
|
+
metrics = self._context_metrics_for_model_call(
|
|
17464
|
+
messages,
|
|
17465
|
+
tools=tools,
|
|
17466
|
+
system=system,
|
|
17467
|
+
label=label,
|
|
17468
|
+
record=False,
|
|
17469
|
+
)
|
|
17470
|
+
rows.append(
|
|
17471
|
+
{
|
|
17472
|
+
"role": role,
|
|
17473
|
+
"label": display,
|
|
17474
|
+
"active": bool(active == role),
|
|
17475
|
+
"used": int(metrics.get("used", 0) or 0),
|
|
17476
|
+
"left": int(metrics.get("left", 0) or 0),
|
|
17477
|
+
"left_percent": round(float(metrics.get("left_percent", 0.0)), 2),
|
|
17478
|
+
"effective_limit": int(metrics.get("effective_limit", metrics.get("limit", 0)) or 0),
|
|
17479
|
+
"tier": int(self._context_compression_tier(metrics)),
|
|
17480
|
+
"message_count": int(msg_count),
|
|
17481
|
+
"next_call_label": str(metrics.get("next_call_label", "") or label),
|
|
17482
|
+
}
|
|
17483
|
+
)
|
|
17484
|
+
except Exception:
|
|
17485
|
+
continue
|
|
17486
|
+
rows.sort(key=lambda x: (float(x.get("left_percent", 100.0)), int(x.get("left", 0) or 0)))
|
|
17487
|
+
return rows
|
|
17488
|
+
|
|
17052
17489
|
def _context_compression_tier(self, metrics: dict | None = None) -> int:
|
|
17053
17490
|
"""Return compression tier 0-3 based on context budget.
|
|
17054
17491
|
Tier 0: >40% left (normal)
|
|
@@ -17080,43 +17517,137 @@ class SessionState:
|
|
|
17080
17517
|
"manager_context": [MANAGER_CTX_LIMIT_TIER0, MANAGER_CTX_LIMIT_TIER1, MANAGER_CTX_LIMIT_TIER2, MANAGER_CTX_LIMIT_TIER3][t],
|
|
17081
17518
|
}
|
|
17082
17519
|
|
|
17083
|
-
def
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
|
|
17088
|
-
|
|
17089
|
-
|
|
17090
|
-
if
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
if
|
|
17095
|
-
|
|
17096
|
-
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
17520
|
+
def _loaded_skill_stub_content(self, content: str) -> str:
|
|
17521
|
+
raw = str(content or "")
|
|
17522
|
+
if "<loaded-skill name=" not in raw:
|
|
17523
|
+
return raw
|
|
17524
|
+
match = re.search(r'<loaded-skill\s+name=["\']([^"\']+)["\']', raw)
|
|
17525
|
+
key = str(match.group(1) if match else "").strip()
|
|
17526
|
+
row = self._loaded_skill_rows().get(key, {}) if key else {}
|
|
17527
|
+
skill_name = str((row if isinstance(row, dict) else {}).get("skill_name", key) or key or "skill").strip()
|
|
17528
|
+
skill_path = str((row if isinstance(row, dict) else {}).get("skill_path", "") or "").strip()
|
|
17529
|
+
preview = trim(str((row if isinstance(row, dict) else {}).get("preview", "") or ""), 500)
|
|
17530
|
+
path_note = f"\npath: {skill_path}" if skill_path else ""
|
|
17531
|
+
preview_note = f"\npreview: {preview}" if preview else ""
|
|
17532
|
+
return (
|
|
17533
|
+
f"<loaded-skill-stub name=\"{skill_name}\" key=\"{key}\">"
|
|
17534
|
+
f"{path_note}{preview_note}\n"
|
|
17535
|
+
"Full skill instructions are rehydrated in the system prompt as ACTIVE SKILL WORKFLOWS. "
|
|
17536
|
+
"Follow that workflow for matching steps."
|
|
17537
|
+
"\n</loaded-skill-stub>"
|
|
17538
|
+
)
|
|
17539
|
+
|
|
17540
|
+
def _compact_shared_context(self, tier: int):
|
|
17541
|
+
if tier <= 0:
|
|
17542
|
+
return
|
|
17543
|
+
for msg in self.agent_messages:
|
|
17544
|
+
if not isinstance(msg, dict):
|
|
17545
|
+
continue
|
|
17546
|
+
if str(msg.get("agent_role", "") or "") != "shared":
|
|
17547
|
+
continue
|
|
17548
|
+
content = str(msg.get("content", "") or "")
|
|
17549
|
+
if "<loaded-skill name=" in content:
|
|
17550
|
+
msg["content"] = self._loaded_skill_stub_content(content)
|
|
17551
|
+
elif tier >= 3 and len(content) > 1000:
|
|
17552
|
+
msg["content"] = trim(content, 1000)
|
|
17105
17553
|
if tier >= 2:
|
|
17106
|
-
self.
|
|
17107
|
-
|
|
17108
|
-
|
|
17109
|
-
|
|
17554
|
+
self._compact_plan_context(tier)
|
|
17555
|
+
|
|
17556
|
+
def _compact_role_context(self, role: str, tier: int):
|
|
17557
|
+
"""Compact one model-call context without globally erasing peer roles."""
|
|
17558
|
+
t = min(max(int(tier or 0), 0), 3)
|
|
17559
|
+
if t <= 0:
|
|
17560
|
+
return
|
|
17561
|
+
role_raw = str(role or "").strip().lower()
|
|
17562
|
+
limits = self._tier_agent_context_limits(t)
|
|
17563
|
+
keep_recent = max(1, 3 - t)
|
|
17564
|
+
if role_raw == "manager":
|
|
17565
|
+
self._microcompact_agent_messages(self.manager_context, keep_recent=keep_recent)
|
|
17566
|
+
if len(self.manager_context) > limits["manager_context"]:
|
|
17567
|
+
self.manager_context = self.manager_context[-limits["manager_context"]:]
|
|
17568
|
+
if t >= 2:
|
|
17569
|
+
self._offload_agent_message_content(self.manager_context)
|
|
17570
|
+
if t >= 3:
|
|
17571
|
+
for msg in self.manager_context:
|
|
17572
|
+
content = str(msg.get("content", "") or "")
|
|
17573
|
+
if len(content) > 700:
|
|
17574
|
+
msg["content"] = trim(content, 700)
|
|
17575
|
+
return
|
|
17576
|
+
|
|
17577
|
+
role_key = self._sanitize_agent_role(role_raw)
|
|
17578
|
+
if not role_key:
|
|
17579
|
+
return
|
|
17580
|
+
scoped_rows = [
|
|
17581
|
+
m for m in self.agent_messages
|
|
17582
|
+
if isinstance(m, dict)
|
|
17583
|
+
and (
|
|
17584
|
+
str(m.get("agent_role", "") or "") == role_key
|
|
17585
|
+
or (str(m.get("role", "") or "") == "user" and not str(m.get("agent_role", "") or "").strip())
|
|
17586
|
+
or str(m.get("agent_role", "") or "") == "shared"
|
|
17587
|
+
)
|
|
17588
|
+
]
|
|
17589
|
+
self._microcompact_agent_messages(scoped_rows, keep_recent=keep_recent)
|
|
17590
|
+
if t >= 2:
|
|
17591
|
+
self._compact_shared_context(t)
|
|
17592
|
+
self._offload_agent_message_content(
|
|
17593
|
+
[
|
|
17594
|
+
m for m in self.agent_messages
|
|
17595
|
+
if isinstance(m, dict)
|
|
17596
|
+
and (
|
|
17597
|
+
str(m.get("agent_role", "") or "") == role_key
|
|
17598
|
+
or (str(m.get("role", "") or "") == "user" and not str(m.get("agent_role", "") or "").strip())
|
|
17599
|
+
)
|
|
17600
|
+
]
|
|
17601
|
+
)
|
|
17602
|
+
if t >= 3:
|
|
17110
17603
|
for msg in self.agent_messages:
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17604
|
+
if not isinstance(msg, dict):
|
|
17605
|
+
continue
|
|
17606
|
+
msg_role = str(msg.get("agent_role", "") or "")
|
|
17607
|
+
in_scope = msg_role == role_key or (str(msg.get("role", "") or "") == "user" and not msg_role)
|
|
17608
|
+
if not in_scope:
|
|
17609
|
+
continue
|
|
17610
|
+
content = str(msg.get("content", "") or "")
|
|
17611
|
+
if len(content) > 700:
|
|
17612
|
+
msg["content"] = trim(content, 700)
|
|
17613
|
+
role_limit = limits["context_per_role"]
|
|
17614
|
+
scoped_indices = [
|
|
17615
|
+
i for i, m in enumerate(self.agent_messages)
|
|
17616
|
+
if isinstance(m, dict)
|
|
17617
|
+
and (
|
|
17618
|
+
str(m.get("agent_role", "") or "") == role_key
|
|
17619
|
+
or (str(m.get("role", "") or "") == "user" and not str(m.get("agent_role", "") or "").strip())
|
|
17620
|
+
)
|
|
17621
|
+
]
|
|
17622
|
+
if len(scoped_indices) > role_limit:
|
|
17623
|
+
keep = set(scoped_indices[-role_limit:])
|
|
17624
|
+
self.agent_messages = [
|
|
17625
|
+
m for i, m in enumerate(self.agent_messages)
|
|
17626
|
+
if i not in scoped_indices or i in keep
|
|
17627
|
+
]
|
|
17628
|
+
self.contexts[role_key] = self._agent_context(role_key)[-400:]
|
|
17629
|
+
|
|
17630
|
+
def _compact_agent_contexts(self, tier: int):
|
|
17631
|
+
"""Compress shared state and each role without flattening all agents together."""
|
|
17632
|
+
t = min(max(int(tier or 0), 0), 3)
|
|
17633
|
+
if t <= 0:
|
|
17634
|
+
return
|
|
17635
|
+
limits = self._tier_agent_context_limits(t)
|
|
17636
|
+
self._compact_shared_context(t)
|
|
17637
|
+
self._compact_role_context("manager", t)
|
|
17638
|
+
for role in AGENT_ROLES:
|
|
17639
|
+
self._compact_role_context(role, t)
|
|
17640
|
+
am_limit = int(limits["agent_messages"])
|
|
17641
|
+
if len(self.agent_messages) > int(am_limit * 1.5):
|
|
17642
|
+
shared_rows = [
|
|
17643
|
+
m for m in self.agent_messages
|
|
17644
|
+
if isinstance(m, dict) and str(m.get("agent_role", "") or "") == "shared"
|
|
17645
|
+
][-12:]
|
|
17646
|
+
tail = self.agent_messages[-am_limit:]
|
|
17647
|
+
existing_ids = {id(m) for m in tail}
|
|
17648
|
+
preserved = [m for m in shared_rows if id(m) not in existing_ids]
|
|
17649
|
+
self.agent_messages = (preserved + tail)[-int(am_limit + len(preserved)):]
|
|
17650
|
+
self._compact_plan_context(t)
|
|
17120
17651
|
|
|
17121
17652
|
def _compact_plan_context(self, tier: int):
|
|
17122
17653
|
"""Compress plan mode context based on tier.
|
|
@@ -17182,21 +17713,41 @@ class SessionState:
|
|
|
17182
17713
|
if plan_phase not in ("executing", "awaiting_choice"):
|
|
17183
17714
|
self.runtime_plan_proposal = {}
|
|
17184
17715
|
|
|
17185
|
-
def _apply_auto_compact_if_needed(
|
|
17186
|
-
|
|
17716
|
+
def _apply_auto_compact_if_needed(
|
|
17717
|
+
self,
|
|
17718
|
+
reason: str = "auto",
|
|
17719
|
+
*,
|
|
17720
|
+
metrics: dict | None = None,
|
|
17721
|
+
role: str = "",
|
|
17722
|
+
media_inputs: list[dict] | None = None,
|
|
17723
|
+
) -> bool:
|
|
17724
|
+
metrics = metrics or self._active_next_call_context_metrics(role=role, media_inputs=media_inputs)
|
|
17187
17725
|
tier = self._context_compression_tier(metrics)
|
|
17726
|
+
role_raw = str(role or self.active_agent_role or "").strip().lower()
|
|
17727
|
+
role_key = "manager" if role_raw == "manager" else self._sanitize_agent_role(role_raw)
|
|
17728
|
+
if role_key in AGENT_ROLES and not self._role_specific_context_is_live_call(role_key):
|
|
17729
|
+
role_key = ""
|
|
17188
17730
|
# Tier 1+: apply progressively aggressive microcompact
|
|
17189
|
-
if tier >= 1:
|
|
17731
|
+
if role_key and tier >= 1:
|
|
17732
|
+
self._compact_role_context(role_key, tier)
|
|
17733
|
+
if tier >= 2:
|
|
17734
|
+
self._compact_shared_context(tier)
|
|
17735
|
+
elif role_key:
|
|
17736
|
+
if role_key == "manager":
|
|
17737
|
+
self._microcompact_agent_messages(self.manager_context, keep_recent=3)
|
|
17738
|
+
else:
|
|
17739
|
+
self._microcompact_agent_messages(self._agent_context(role_key), keep_recent=3)
|
|
17740
|
+
elif tier >= 1:
|
|
17190
17741
|
keep = max(1, 3 - tier)
|
|
17191
17742
|
self._microcompact(keep_recent=keep)
|
|
17192
17743
|
else:
|
|
17193
17744
|
self._microcompact()
|
|
17194
17745
|
# Tier 2+: compact agent contexts proactively
|
|
17195
|
-
if tier >= 2:
|
|
17746
|
+
if tier >= 2 and not role_key:
|
|
17196
17747
|
self._compact_agent_contexts(tier)
|
|
17197
17748
|
# Re-check after tier-based compression. The effective limit keeps a
|
|
17198
17749
|
# small hard reserve so auto-compact still has room to run.
|
|
17199
|
-
metrics = self.
|
|
17750
|
+
metrics = self._active_next_call_context_metrics(role=role, media_inputs=media_inputs)
|
|
17200
17751
|
used = int(metrics.get("used", 0) or 0)
|
|
17201
17752
|
effective_limit = max(1, int(metrics.get("effective_limit", metrics.get("limit", 0)) or 0))
|
|
17202
17753
|
if used < effective_limit:
|
|
@@ -17204,7 +17755,13 @@ class SessionState:
|
|
|
17204
17755
|
now_tick = now_ts()
|
|
17205
17756
|
if (now_tick - float(self.last_compact_ts or 0.0)) < 0.8:
|
|
17206
17757
|
return False
|
|
17207
|
-
|
|
17758
|
+
if (
|
|
17759
|
+
not bool(getattr(self, "context_last_compact_effective", True))
|
|
17760
|
+
and (now_tick - float(getattr(self, "context_last_compact_skip_ts", 0.0) or 0.0))
|
|
17761
|
+
< float(CONTEXT_COMPACT_INEFFECTIVE_COOLDOWN_SECONDS)
|
|
17762
|
+
):
|
|
17763
|
+
return False
|
|
17764
|
+
self._auto_compact(reason, metrics=metrics, role=role, media_inputs=media_inputs)
|
|
17208
17765
|
return True
|
|
17209
17766
|
|
|
17210
17767
|
def _estimate_output_tokens(self, text: str, thinking_text: str = "", tool_calls: list | None = None) -> int:
|
|
@@ -17219,10 +17776,9 @@ class SessionState:
|
|
|
17219
17776
|
return max(1, t_main + t_think + t_tools)
|
|
17220
17777
|
|
|
17221
17778
|
def _derive_context_limit_from_output(self, output_tokens: int) -> int:
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
)
|
|
17225
|
-
return max(MIN_CONTEXT_TOKEN_LIMIT, min(self.max_context_token_limit, estimated))
|
|
17779
|
+
# Kept for compatibility with older call sites. Output length is not a
|
|
17780
|
+
# reliable proxy for input context capacity, so never shrink from it.
|
|
17781
|
+
return max(MIN_CONTEXT_TOKEN_LIMIT, int(self.max_context_token_limit))
|
|
17226
17782
|
|
|
17227
17783
|
def _trim_truncated_tail_line(self, text: str) -> str:
|
|
17228
17784
|
src = str(text or "")
|
|
@@ -17430,9 +17986,9 @@ class SessionState:
|
|
|
17430
17986
|
if now_tick - self.last_truncation_ts >= 0.2:
|
|
17431
17987
|
self.truncation_count += 1
|
|
17432
17988
|
self.last_truncation_ts = now_tick
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17989
|
+
# Output truncation is a generation-budget signal, not proof that the
|
|
17990
|
+
# prompt context window is smaller. Keep the configured/adaptive input
|
|
17991
|
+
# context bound intact; only provider context-window errors may shrink it.
|
|
17436
17992
|
|
|
17437
17993
|
self._publish_live_truncation(
|
|
17438
17994
|
text=working,
|
|
@@ -17861,22 +18417,13 @@ class SessionState:
|
|
|
17861
18417
|
return
|
|
17862
18418
|
self.truncation_count += 1
|
|
17863
18419
|
self.last_truncation_ts = now_tick
|
|
17864
|
-
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
"status",
|
|
17871
|
-
{
|
|
17872
|
-
"summary": (
|
|
17873
|
-
f"context upper bound adjusted {old_bound}->{self.context_token_upper_bound} "
|
|
17874
|
-
f"(output_tokens≈{output_tokens})"
|
|
17875
|
-
)
|
|
17876
|
-
},
|
|
18420
|
+
if allow_compact:
|
|
18421
|
+
metrics = self._active_next_call_context_metrics()
|
|
18422
|
+
if int(metrics.get("used", 0) or 0) >= int(metrics.get("effective_limit", 0) or 0):
|
|
18423
|
+
self._apply_auto_compact_if_needed(
|
|
18424
|
+
f"truncation-rescue:{source or 'auto'}",
|
|
18425
|
+
metrics=metrics,
|
|
17877
18426
|
)
|
|
17878
|
-
if allow_compact and self._estimate_tokens() > self.context_token_upper_bound:
|
|
17879
|
-
self._auto_compact(f"truncation-rescue:{source or 'auto'}")
|
|
17880
18427
|
self._ensure_truncation_todos()
|
|
17881
18428
|
task_ids = self._create_truncation_subtasks(reason)
|
|
17882
18429
|
self._inject_truncation_rescue_hint(reason, output_tokens, task_ids)
|
|
@@ -18220,7 +18767,7 @@ class SessionState:
|
|
|
18220
18767
|
if len(model_pages) > 1 or should_temp:
|
|
18221
18768
|
model_truncated = True
|
|
18222
18769
|
preview = (
|
|
18223
|
-
self._run_read(temp_output_path,
|
|
18770
|
+
self._run_read(temp_output_path, mode="overview", max_chars=LONG_OUTPUT_MODEL_PAGE_CHARS)
|
|
18224
18771
|
if temp_output_path
|
|
18225
18772
|
else model_pages[0]
|
|
18226
18773
|
)
|
|
@@ -18232,7 +18779,11 @@ class SessionState:
|
|
|
18232
18779
|
]
|
|
18233
18780
|
if temp_output_path:
|
|
18234
18781
|
parts.append(
|
|
18235
|
-
f"
|
|
18782
|
+
f"full_output_path={temp_output_path}"
|
|
18783
|
+
)
|
|
18784
|
+
parts.append(
|
|
18785
|
+
"Use read_file on full_output_path with mode=\"search\" for a keyword/error, "
|
|
18786
|
+
"mode=\"window\" with line/context for nearby lines, or mode=\"full\" with max_chars when broad exact output is needed."
|
|
18236
18787
|
)
|
|
18237
18788
|
if buffer_ref:
|
|
18238
18789
|
parts.append(f"buffer_ref={buffer_ref}")
|
|
@@ -18549,8 +19100,29 @@ class SessionState:
|
|
|
18549
19100
|
kept.sort(key=lambda x: x[0])
|
|
18550
19101
|
return [msg for _, msg in kept]
|
|
18551
19102
|
|
|
18552
|
-
def
|
|
18553
|
-
|
|
19103
|
+
def _strip_archival_runtime_hints(self, rows: list[dict]) -> list[dict]:
|
|
19104
|
+
cleaned: list[dict] = []
|
|
19105
|
+
for row in rows or []:
|
|
19106
|
+
item = dict(row) if isinstance(row, dict) else {"role": "", "content": str(row or "")}
|
|
19107
|
+
role = str(item.get("role", "") or "")
|
|
19108
|
+
content = str(item.get("content", "") or "")
|
|
19109
|
+
low = content.strip().lower()
|
|
19110
|
+
if role == "user" and any(low.startswith(prefix) for prefix in RETRY_RUNTIME_HINT_PREFIXES):
|
|
19111
|
+
continue
|
|
19112
|
+
if "<compact-resume>" in low or "<state_handoff>" in low:
|
|
19113
|
+
item["content"] = "[previous compact-resume archived; use context_recall for details]"
|
|
19114
|
+
cleaned.append(item)
|
|
19115
|
+
return cleaned
|
|
19116
|
+
|
|
19117
|
+
def _auto_compact(
|
|
19118
|
+
self,
|
|
19119
|
+
reason: str,
|
|
19120
|
+
*,
|
|
19121
|
+
metrics: dict | None = None,
|
|
19122
|
+
role: str = "",
|
|
19123
|
+
media_inputs: list[dict] | None = None,
|
|
19124
|
+
):
|
|
19125
|
+
context_before = metrics or self._active_next_call_context_metrics(role=role, media_inputs=media_inputs)
|
|
18554
19126
|
tier = self._context_compression_tier(context_before)
|
|
18555
19127
|
transcript_dir = self.root / "transcripts"
|
|
18556
19128
|
transcript_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -18569,15 +19141,16 @@ class SessionState:
|
|
|
18569
19141
|
if len(tail) >= len(self.messages):
|
|
18570
19142
|
tail = self._select_compact_tail(max(2200, int(tail_budget * 0.55)), min_count=4, max_count=20)
|
|
18571
19143
|
archived_rows = self.messages[:-len(tail)] if tail else list(self.messages)
|
|
19144
|
+
tail = self._strip_archival_runtime_hints(tail)
|
|
18572
19145
|
seg = self._archive_context_segment(archived_rows, reason) if archived_rows else {}
|
|
18573
19146
|
summary = self._summarize_compact_rows(archived_rows)
|
|
18574
19147
|
seg_id = str(seg.get("id", "")) if isinstance(seg, dict) else ""
|
|
18575
19148
|
seg_msg_count = int(seg.get("messages", 0) or 0) if isinstance(seg, dict) else 0
|
|
18576
19149
|
seg_path = str(seg.get("path", "")) if isinstance(seg, dict) else ""
|
|
18577
19150
|
continuation = (
|
|
18578
|
-
f"If details are missing, call context_recall with segment_id='{seg_id}',
|
|
19151
|
+
f"If details are missing, call context_recall with segment_id='{seg_id}', mode='search' and a specific query, or mode='window' with around_index/context."
|
|
18579
19152
|
if seg_id
|
|
18580
|
-
else "If details are missing, call context_recall with
|
|
19153
|
+
else "If details are missing, call context_recall with mode='summary' first, then mode='search' or mode='window' for focused evidence."
|
|
18581
19154
|
)
|
|
18582
19155
|
# Create checkpoint before compaction
|
|
18583
19156
|
self._maybe_create_checkpoint()
|
|
@@ -18622,14 +19195,38 @@ class SessionState:
|
|
|
18622
19195
|
while len(tail) > 5 and self._estimate_messages_tokens(tail) > target_tokens:
|
|
18623
19196
|
tail.pop(0)
|
|
18624
19197
|
self.messages = tail
|
|
18625
|
-
# Compact
|
|
18626
|
-
|
|
19198
|
+
# Compact only the pressured role when known; shared state remains tier-aware.
|
|
19199
|
+
role_raw = str(role or "").strip().lower()
|
|
19200
|
+
role_key = "manager" if role_raw == "manager" else self._sanitize_agent_role(role_raw)
|
|
19201
|
+
if role_key in AGENT_ROLES and not self._role_specific_context_is_live_call(role_key):
|
|
19202
|
+
role_key = ""
|
|
19203
|
+
if role_key:
|
|
19204
|
+
self._compact_role_context(role_key, max(tier, 1))
|
|
19205
|
+
if tier >= 2:
|
|
19206
|
+
self._compact_shared_context(tier)
|
|
19207
|
+
else:
|
|
19208
|
+
self._compact_agent_contexts(max(tier, 1))
|
|
18627
19209
|
# Tier 2+: offload remaining large content in messages to file buffer
|
|
18628
19210
|
if tier >= 2:
|
|
18629
19211
|
for msg in self.messages:
|
|
18630
19212
|
c = msg.get("content", "")
|
|
18631
19213
|
if isinstance(c, str) and len(c) >= FILE_BUFFER_CONTENT_THRESHOLD * 2:
|
|
18632
19214
|
msg["content"] = self._offload_to_file_buffer(c, label="compact-msg")
|
|
19215
|
+
context_after = self._active_next_call_context_metrics(role=role, media_inputs=media_inputs)
|
|
19216
|
+
before_used = int(context_before.get("used", 0) or 0)
|
|
19217
|
+
after_used = int(context_after.get("used", 0) or 0)
|
|
19218
|
+
reduction = max(0, before_used - after_used)
|
|
19219
|
+
effective = bool(reduction >= max(400, int(before_used * 0.05)) or after_used < int(context_after.get("effective_limit", 0) or 0))
|
|
19220
|
+
self.context_last_compact_before = dict(context_before)
|
|
19221
|
+
self.context_last_compact_after = dict(context_after)
|
|
19222
|
+
self.context_last_compact_effective = bool(effective)
|
|
19223
|
+
self.context_last_compact_used_reduction = int(reduction)
|
|
19224
|
+
if not effective:
|
|
19225
|
+
self.context_last_compact_skip_ts = now_ts()
|
|
19226
|
+
self.context_last_compact_skip_reason = (
|
|
19227
|
+
f"compact ineffective: used {before_used}->{after_used}, "
|
|
19228
|
+
f"limit={int(context_after.get('effective_limit', 0) or 0)}"
|
|
19229
|
+
)
|
|
18633
19230
|
self.last_compact_reason = str(reason or "")
|
|
18634
19231
|
self.last_compact_ts = now_ts()
|
|
18635
19232
|
self._emit(
|
|
@@ -18644,6 +19241,13 @@ class SessionState:
|
|
|
18644
19241
|
"context_used_before": int(context_before.get("used", 0)),
|
|
18645
19242
|
"context_left_before": int(context_before.get("left", 0)),
|
|
18646
19243
|
"context_left_percent_before": round(float(context_before.get("left_percent", 0.0)), 2),
|
|
19244
|
+
"context_used_after": int(context_after.get("used", 0)),
|
|
19245
|
+
"context_left_after": int(context_after.get("left", 0)),
|
|
19246
|
+
"context_left_percent_after": round(float(context_after.get("left_percent", 0.0)), 2),
|
|
19247
|
+
"context_used_reduction": int(reduction),
|
|
19248
|
+
"effective": bool(effective),
|
|
19249
|
+
"next_call_label": str(context_after.get("next_call_label", "") or ""),
|
|
19250
|
+
"role": role_key or "",
|
|
18647
19251
|
},
|
|
18648
19252
|
)
|
|
18649
19253
|
|
|
@@ -21449,47 +22053,315 @@ body{padding:18px}
|
|
|
21449
22053
|
body = "\n".join(lines[:line_cap])
|
|
21450
22054
|
return trim(body, max_chars)
|
|
21451
22055
|
|
|
21452
|
-
def
|
|
22056
|
+
def _read_file_int_arg(self, value: object, default: int, minimum: int, maximum: int) -> int:
|
|
22057
|
+
try:
|
|
22058
|
+
iv = int(value)
|
|
22059
|
+
except Exception:
|
|
22060
|
+
iv = int(default)
|
|
22061
|
+
return max(int(minimum), min(int(maximum), iv))
|
|
22062
|
+
|
|
22063
|
+
def _read_file_max_chars(self, value: object = None, default: int = READ_FILE_DEFAULT_MAX_CHARS) -> int:
|
|
22064
|
+
if value is None or str(value).strip() == "":
|
|
22065
|
+
raw = int(default)
|
|
22066
|
+
else:
|
|
22067
|
+
raw = self._read_file_int_arg(value, int(default), 1200, READ_FILE_HARD_MAX_CHARS)
|
|
22068
|
+
return max(1200, min(int(READ_FILE_HARD_MAX_CHARS), int(raw)))
|
|
22069
|
+
|
|
22070
|
+
def _clip_read_file_output(self, text: str, max_chars: int, hint: str = "") -> str:
|
|
22071
|
+
cap = self._read_file_max_chars(max_chars)
|
|
22072
|
+
raw = str(text or "")
|
|
22073
|
+
if len(raw) <= cap:
|
|
22074
|
+
return raw
|
|
22075
|
+
default_hint = 'Use mode="search", mode="symbol", mode="window", or raise max_chars for a wider read.'
|
|
22076
|
+
suffix = (
|
|
22077
|
+
f"\n[read_file clipped chars=1-{cap} of {len(raw)} by max_chars. "
|
|
22078
|
+
f"{hint or default_hint}]"
|
|
22079
|
+
)
|
|
22080
|
+
return raw[:cap].rstrip() + suffix
|
|
22081
|
+
|
|
22082
|
+
def _tool_int_arg(self, value: object, default: int, minimum: int, maximum: int) -> int:
|
|
22083
|
+
try:
|
|
22084
|
+
iv = int(value)
|
|
22085
|
+
except Exception:
|
|
22086
|
+
iv = int(default)
|
|
22087
|
+
return max(int(minimum), min(int(maximum), iv))
|
|
22088
|
+
|
|
22089
|
+
def _tool_max_chars(self, value: object = None, default: int = 24000) -> int:
|
|
22090
|
+
return self._tool_int_arg(value, default, 1200, READ_FILE_HARD_MAX_CHARS)
|
|
22091
|
+
|
|
22092
|
+
def _row_text_for_search(self, row: object) -> str:
|
|
22093
|
+
try:
|
|
22094
|
+
return json_dumps(row, indent=0)
|
|
22095
|
+
except Exception:
|
|
22096
|
+
return str(row or "")
|
|
22097
|
+
|
|
22098
|
+
def _row_field_value(self, row: object, field: str) -> str:
|
|
22099
|
+
if not isinstance(row, dict):
|
|
22100
|
+
return ""
|
|
22101
|
+
cur: object = row
|
|
22102
|
+
for part in str(field or "").split("."):
|
|
22103
|
+
if not isinstance(cur, dict):
|
|
22104
|
+
return ""
|
|
22105
|
+
cur = cur.get(part)
|
|
22106
|
+
if isinstance(cur, (dict, list)):
|
|
22107
|
+
return self._row_text_for_search(cur)
|
|
22108
|
+
return str(cur or "")
|
|
22109
|
+
|
|
22110
|
+
def _collection_filter_rows(
|
|
22111
|
+
self,
|
|
22112
|
+
rows: list[dict],
|
|
22113
|
+
*,
|
|
22114
|
+
query: str = "",
|
|
22115
|
+
filters: dict[str, object] | None = None,
|
|
22116
|
+
) -> list[dict]:
|
|
22117
|
+
q = str(query or "").strip().lower()
|
|
22118
|
+
clean_filters = {
|
|
22119
|
+
str(k): str(v).strip().lower()
|
|
22120
|
+
for k, v in (filters or {}).items()
|
|
22121
|
+
if str(v or "").strip()
|
|
22122
|
+
}
|
|
22123
|
+
out: list[dict] = []
|
|
22124
|
+
for row in rows:
|
|
22125
|
+
if not isinstance(row, dict):
|
|
22126
|
+
continue
|
|
22127
|
+
if clean_filters:
|
|
22128
|
+
ok = True
|
|
22129
|
+
for key, wanted in clean_filters.items():
|
|
22130
|
+
actual = self._row_field_value(row, key).strip().lower()
|
|
22131
|
+
if wanted not in actual:
|
|
22132
|
+
ok = False
|
|
22133
|
+
break
|
|
22134
|
+
if not ok:
|
|
22135
|
+
continue
|
|
22136
|
+
if q and q not in self._row_text_for_search(row).lower():
|
|
22137
|
+
continue
|
|
22138
|
+
out.append(row)
|
|
22139
|
+
return out
|
|
22140
|
+
|
|
22141
|
+
def _render_collection_tool_payload(
|
|
22142
|
+
self,
|
|
22143
|
+
*,
|
|
22144
|
+
tool: str,
|
|
22145
|
+
rows: list[dict],
|
|
22146
|
+
mode: object = None,
|
|
22147
|
+
query: object = "",
|
|
22148
|
+
limit: object = None,
|
|
22149
|
+
around_index: object = None,
|
|
22150
|
+
context: object = None,
|
|
22151
|
+
max_chars: object = None,
|
|
22152
|
+
filters: dict[str, object] | None = None,
|
|
22153
|
+
summary_fields: list[str] | None = None,
|
|
22154
|
+
detail_fields: list[str] | None = None,
|
|
22155
|
+
default_limit: int = 12,
|
|
22156
|
+
) -> str:
|
|
22157
|
+
mode_text = str(mode or "").strip().lower() or ("search" if str(query or "").strip() else "summary")
|
|
22158
|
+
if mode_text not in {"summary", "search", "recent", "window", "detail", "tail", "peek", "drain"}:
|
|
22159
|
+
mode_text = "summary"
|
|
22160
|
+
cap = self._tool_max_chars(max_chars)
|
|
22161
|
+
all_rows = [row for row in rows if isinstance(row, dict)]
|
|
22162
|
+
filtered = self._collection_filter_rows(all_rows, query=str(query or ""), filters=filters)
|
|
22163
|
+
n = self._tool_int_arg(limit, default_limit, 1, 200)
|
|
22164
|
+
if mode_text in {"recent", "tail", "peek", "drain"}:
|
|
22165
|
+
selected = filtered[-n:]
|
|
22166
|
+
elif mode_text == "window":
|
|
22167
|
+
if around_index is None or str(around_index).strip() == "":
|
|
22168
|
+
selected = filtered[-n:]
|
|
22169
|
+
else:
|
|
22170
|
+
idx = self._tool_int_arg(around_index, 0, 0, max(0, len(filtered) - 1))
|
|
22171
|
+
ctx = self._tool_int_arg(context, 4, 0, 80)
|
|
22172
|
+
selected = filtered[max(0, idx - ctx): min(len(filtered), idx + ctx + 1)]
|
|
22173
|
+
elif mode_text == "detail":
|
|
22174
|
+
selected = filtered[:n] if str(query or "").strip() else filtered[-n:]
|
|
22175
|
+
else:
|
|
22176
|
+
selected = filtered[:n] if str(query or "").strip() else filtered[-n:]
|
|
22177
|
+
|
|
22178
|
+
def compact(row: dict, idx: int) -> dict:
|
|
22179
|
+
if mode_text == "detail":
|
|
22180
|
+
if detail_fields:
|
|
22181
|
+
return {k: row.get(k) for k in detail_fields if k in row}
|
|
22182
|
+
return row
|
|
22183
|
+
fields = summary_fields or list(row.keys())[:8]
|
|
22184
|
+
item = {"index": idx}
|
|
22185
|
+
for key in fields:
|
|
22186
|
+
if key in row:
|
|
22187
|
+
val = row.get(key)
|
|
22188
|
+
item[key] = trim(val if not isinstance(val, (dict, list)) else json_dumps(val), 800)
|
|
22189
|
+
return item
|
|
22190
|
+
|
|
22191
|
+
index_by_identity = {id(row): idx for idx, row in enumerate(filtered)}
|
|
22192
|
+
rendered = [
|
|
22193
|
+
compact(row, int(index_by_identity.get(id(row), 0)))
|
|
22194
|
+
for row in selected
|
|
22195
|
+
]
|
|
22196
|
+
payload = {
|
|
22197
|
+
"tool": tool,
|
|
22198
|
+
"mode": mode_text,
|
|
22199
|
+
"query": str(query or ""),
|
|
22200
|
+
"filters": {k: v for k, v in (filters or {}).items() if str(v or "").strip()},
|
|
22201
|
+
"total_rows": len(all_rows),
|
|
22202
|
+
"matched_rows": len(filtered),
|
|
22203
|
+
"returned": len(rendered),
|
|
22204
|
+
"items": rendered,
|
|
22205
|
+
"focused_reads": [
|
|
22206
|
+
f"{tool} mode='search' query='<term>'",
|
|
22207
|
+
f"{tool} mode='window' around_index=<index> context=4",
|
|
22208
|
+
f"{tool} mode='detail' query='<specific id/name/status>'",
|
|
22209
|
+
],
|
|
22210
|
+
}
|
|
22211
|
+
return trim(json_dumps(payload, indent=2), cap)
|
|
22212
|
+
|
|
22213
|
+
def _read_file_code_data(self, fp: Path, lines: list[str]) -> dict:
|
|
22214
|
+
text = "\n".join(lines)
|
|
22215
|
+
language = ""
|
|
22216
|
+
imports: list[str] = []
|
|
22217
|
+
symbols: list[dict] = []
|
|
22218
|
+
try:
|
|
22219
|
+
parser = CodeContentParser()
|
|
22220
|
+
language = parser.detect_language(fp, text=text[:120_000]) or ""
|
|
22221
|
+
imports = parser._extract_imports(text[:240_000], language) if language else []
|
|
22222
|
+
if language == "python":
|
|
22223
|
+
_, symbols = parser._python_chunks(text)
|
|
22224
|
+
else:
|
|
22225
|
+
_, symbols = parser._generic_code_chunks(text, language)
|
|
22226
|
+
except Exception:
|
|
22227
|
+
symbols = []
|
|
22228
|
+
if not symbols:
|
|
22229
|
+
symbols = self._read_file_fallback_symbols(fp, lines, language)
|
|
22230
|
+
total = len(lines)
|
|
22231
|
+
clean: list[dict] = []
|
|
22232
|
+
seen: set[tuple[str, int]] = set()
|
|
22233
|
+
for row in symbols:
|
|
22234
|
+
if not isinstance(row, dict):
|
|
22235
|
+
continue
|
|
22236
|
+
name = str(row.get("name", "") or "").strip()
|
|
22237
|
+
kind = str(row.get("kind", "") or row.get("symbol_kind", "") or "symbol").strip() or "symbol"
|
|
22238
|
+
start = self._read_file_int_arg(row.get("line_start", 1), 1, 1, max(1, total))
|
|
22239
|
+
end = self._read_file_int_arg(row.get("line_end", start), start, start, max(start, total))
|
|
22240
|
+
sig = str(row.get("signature", "") or "").strip()
|
|
22241
|
+
if not sig and 1 <= start <= total:
|
|
22242
|
+
sig = lines[start - 1].strip()
|
|
22243
|
+
key = (name.lower(), start)
|
|
22244
|
+
if key in seen:
|
|
22245
|
+
continue
|
|
22246
|
+
seen.add(key)
|
|
22247
|
+
clean.append(
|
|
22248
|
+
{
|
|
22249
|
+
"name": name or sig[:80],
|
|
22250
|
+
"kind": kind,
|
|
22251
|
+
"line_start": start,
|
|
22252
|
+
"line_end": end,
|
|
22253
|
+
"signature": trim(sig, 180),
|
|
22254
|
+
}
|
|
22255
|
+
)
|
|
22256
|
+
clean.sort(key=lambda r: (int(r.get("line_start", 0) or 0), str(r.get("name", ""))))
|
|
22257
|
+
return {"language": language or "text", "imports": imports[:64], "symbols": clean[:240]}
|
|
22258
|
+
|
|
22259
|
+
def _read_file_fallback_symbols(self, fp: Path, lines: list[str], language: str = "") -> list[dict]:
|
|
22260
|
+
symbols: list[dict] = []
|
|
22261
|
+
ext = fp.suffix.lower()
|
|
22262
|
+
patterns: list[tuple[re.Pattern[str], str]] = []
|
|
22263
|
+
if ext in {".py", ".pyi"} or language == "python":
|
|
22264
|
+
patterns = [
|
|
22265
|
+
(re.compile(r"^\s*class\s+([A-Za-z_][A-Za-z0-9_]*)\b"), "class"),
|
|
22266
|
+
(re.compile(r"^\s*async\s+def\s+([A-Za-z_][A-Za-z0-9_]*)\b"), "async_function"),
|
|
22267
|
+
(re.compile(r"^\s*def\s+([A-Za-z_][A-Za-z0-9_]*)\b"), "function"),
|
|
22268
|
+
]
|
|
22269
|
+
else:
|
|
22270
|
+
try:
|
|
22271
|
+
patterns = CodeContentParser()._decl_matchers(language or "")
|
|
22272
|
+
except Exception:
|
|
22273
|
+
patterns = []
|
|
22274
|
+
if not patterns:
|
|
22275
|
+
patterns = [
|
|
22276
|
+
(re.compile(r"^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\b"), "function"),
|
|
22277
|
+
(re.compile(r"^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b"), "class"),
|
|
22278
|
+
(re.compile(r"^\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\("), "function"),
|
|
22279
|
+
]
|
|
22280
|
+
for idx, line in enumerate(lines, 1):
|
|
22281
|
+
for pattern, kind in patterns:
|
|
22282
|
+
m = pattern.search(line)
|
|
22283
|
+
if not m:
|
|
22284
|
+
continue
|
|
22285
|
+
name = str(m.group(1) if m.groups() else "").strip()
|
|
22286
|
+
if not name:
|
|
22287
|
+
continue
|
|
22288
|
+
symbols.append(
|
|
22289
|
+
{
|
|
22290
|
+
"name": name,
|
|
22291
|
+
"kind": kind,
|
|
22292
|
+
"line_start": idx,
|
|
22293
|
+
"line_end": idx,
|
|
22294
|
+
"signature": trim(line.strip(), 180),
|
|
22295
|
+
}
|
|
22296
|
+
)
|
|
22297
|
+
break
|
|
22298
|
+
if len(symbols) >= 240:
|
|
22299
|
+
break
|
|
22300
|
+
for pos, row in enumerate(symbols):
|
|
22301
|
+
start = int(row.get("line_start", 1) or 1)
|
|
22302
|
+
next_start = int(symbols[pos + 1].get("line_start", 0) or 0) if pos + 1 < len(symbols) else 0
|
|
22303
|
+
row["line_end"] = max(start, (next_start - 1) if next_start > start else min(len(lines), start + 120))
|
|
22304
|
+
return symbols
|
|
22305
|
+
|
|
22306
|
+
def _render_text_overview(
|
|
22307
|
+
self,
|
|
22308
|
+
fp: Path,
|
|
22309
|
+
rel: str,
|
|
22310
|
+
lines: list[str],
|
|
22311
|
+
*,
|
|
22312
|
+
max_chars: int | None = None,
|
|
22313
|
+
) -> str:
|
|
21453
22314
|
total_lines = len(lines)
|
|
21454
22315
|
try:
|
|
21455
22316
|
size = int(fp.stat().st_size)
|
|
21456
22317
|
except Exception:
|
|
21457
22318
|
size = 0
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
if stripped.startswith(("class ", "def ", "async def ")):
|
|
21465
|
-
symbols.append(f"{idx}: {trim(stripped, 180)}")
|
|
21466
|
-
if len(symbols) >= 80:
|
|
21467
|
-
break
|
|
21468
|
-
elif ext in {".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"}:
|
|
21469
|
-
pattern = re.compile(r"^\s*(?:export\s+)?(?:async\s+)?(?:function|class)\s+([A-Za-z_$][\w$]*)|^\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(")
|
|
21470
|
-
for idx, line in enumerate(lines, 1):
|
|
21471
|
-
if pattern.search(line):
|
|
21472
|
-
symbols.append(f"{idx}: {trim(line.strip(), 180)}")
|
|
21473
|
-
if len(symbols) >= 80:
|
|
21474
|
-
break
|
|
21475
|
-
next_limit = LONG_OUTPUT_READ_PAGE_LINES
|
|
22319
|
+
cap = self._read_file_max_chars(max_chars, default=READ_FILE_DEFAULT_MAX_CHARS)
|
|
22320
|
+
code_data = self._read_file_code_data(fp, lines)
|
|
22321
|
+
language = str(code_data.get("language", "") or "text")
|
|
22322
|
+
symbols = code_data.get("symbols", []) if isinstance(code_data.get("symbols"), list) else []
|
|
22323
|
+
imports = code_data.get("imports", []) if isinstance(code_data.get("imports"), list) else []
|
|
22324
|
+
is_code = bool(symbols) or (language and language != "text")
|
|
21476
22325
|
out = [
|
|
22326
|
+
f"[read_file overview path={rel} bytes={size} lines={total_lines} language={language}]",
|
|
21477
22327
|
(
|
|
21478
|
-
|
|
21479
|
-
|
|
22328
|
+
"Choose a focused read that matches the question. "
|
|
22329
|
+
"Prefer symbol/search/window for investigation; use full only when exact broad context is needed."
|
|
21480
22330
|
),
|
|
21481
|
-
"This file is too large to inject fully into model context. Use paged read_file calls or RAG/code-library search for focused retrieval.",
|
|
21482
|
-
f"First page: read_file path=\"{rel}\" offset=0 limit={next_limit}",
|
|
21483
22331
|
]
|
|
22332
|
+
if imports:
|
|
22333
|
+
out.append("\nImports:")
|
|
22334
|
+
out.append(", ".join(str(x) for x in imports[:24]))
|
|
21484
22335
|
if symbols:
|
|
21485
22336
|
out.append("\nSymbols:")
|
|
21486
|
-
|
|
22337
|
+
for row in symbols[:120]:
|
|
22338
|
+
start = int(row.get("line_start", 0) or 0)
|
|
22339
|
+
end = int(row.get("line_end", start) or start)
|
|
22340
|
+
name = str(row.get("name", "") or "").strip()
|
|
22341
|
+
kind = str(row.get("kind", "symbol") or "symbol")
|
|
22342
|
+
sig = str(row.get("signature", "") or "").strip()
|
|
22343
|
+
suffix = f" — {sig}" if sig and sig != name else ""
|
|
22344
|
+
out.append(f"L{start}-{end} {kind} {name}{suffix}")
|
|
22345
|
+
if len(symbols) > 120:
|
|
22346
|
+
out.append(f"... {len(symbols) - 120} more symbols omitted; use mode=\"search\" or mode=\"symbol\".")
|
|
22347
|
+
head = "\n".join(lines[:READ_FILE_OVERVIEW_HEAD_LINES]).strip("\n")
|
|
21487
22348
|
if head:
|
|
21488
22349
|
out.append("\nHead preview:")
|
|
21489
22350
|
out.append(head)
|
|
21490
|
-
|
|
21491
|
-
|
|
21492
|
-
|
|
22351
|
+
out.append("\nFocused reads:")
|
|
22352
|
+
if symbols:
|
|
22353
|
+
first_symbol = str(symbols[0].get("name", "") or "").strip()
|
|
22354
|
+
if first_symbol:
|
|
22355
|
+
out.append(f"- read_file path=\"{rel}\" mode=\"symbol\" target=\"{first_symbol}\"")
|
|
22356
|
+
out.append(f"- read_file path=\"{rel}\" mode=\"search\" query=\"<term>\" context=6")
|
|
22357
|
+
out.append(f"- read_file path=\"{rel}\" mode=\"window\" line=<line> context=80")
|
|
22358
|
+
out.append(f"- read_file path=\"{rel}\" mode=\"full\" max_chars={min(cap, READ_FILE_DEFAULT_MAX_CHARS)}")
|
|
22359
|
+
if not is_code:
|
|
22360
|
+
out.append("- For long logs or command output, start with mode=\"search\" for the error, warning, filename, or keyword.")
|
|
22361
|
+
return self._clip_read_file_output("\n".join(out), cap)
|
|
22362
|
+
|
|
22363
|
+
def _large_text_file_overview(self, fp: Path, rel: str, lines: list[str]) -> str:
|
|
22364
|
+
return self._render_text_overview(fp, rel, lines)
|
|
21493
22365
|
|
|
21494
22366
|
def add_upload(self, filename: str, raw: bytes, mime: str = "") -> dict:
|
|
21495
22367
|
safe_name = self._safe_upload_name(filename)
|
|
@@ -21978,6 +22850,29 @@ body{padding:18px}
|
|
|
21978
22850
|
]
|
|
21979
22851
|
return any(x in t for x in continue_markers)
|
|
21980
22852
|
|
|
22853
|
+
def _looks_like_action_promise_without_tool(self, text: str) -> bool:
|
|
22854
|
+
raw = strip_thinking_content(str(text or "")).strip()
|
|
22855
|
+
if not raw:
|
|
22856
|
+
return False
|
|
22857
|
+
low = raw.lower()
|
|
22858
|
+
if self._looks_like_conclusive_reply(raw) or self._looks_like_user_decision_needed(raw):
|
|
22859
|
+
return False
|
|
22860
|
+
promise_markers = (
|
|
22861
|
+
"现在创建", "现在开始", "开始构建", "开始编写", "开始生成", "我将创建", "我会创建",
|
|
22862
|
+
"我将编写", "我会编写", "接下来创建", "接下来构建", "让我创建", "让我开始",
|
|
22863
|
+
"现在建立", "開始建立", "開始編寫", "我將建立", "我會建立", "接下來建立",
|
|
22864
|
+
"now create", "now build", "now write", "i will create", "i will build", "i will write",
|
|
22865
|
+
"i'll create", "i'll build", "i'll write", "let me create", "let me build", "let me write",
|
|
22866
|
+
"next i will create", "next i will build",
|
|
22867
|
+
)
|
|
22868
|
+
if not any(marker in low for marker in promise_markers):
|
|
22869
|
+
return False
|
|
22870
|
+
artifact_markers = (
|
|
22871
|
+
"html", "报告", "報告", "交互", "interactive", "file", "文件", "script", "代码", "程式",
|
|
22872
|
+
".html", ".js", ".css", "write_file", "edit_file", "bash", "生成", "创建", "建立", "构建", "編寫",
|
|
22873
|
+
)
|
|
22874
|
+
return any(marker in low for marker in artifact_markers)
|
|
22875
|
+
|
|
21981
22876
|
def _looks_like_user_decision_needed(self, text: str) -> bool:
|
|
21982
22877
|
t = (text or "").strip().lower()
|
|
21983
22878
|
if not t:
|
|
@@ -22288,6 +23183,27 @@ body{padding:18px}
|
|
|
22288
23183
|
]
|
|
22289
23184
|
):
|
|
22290
23185
|
return "TASK_COMPLETED"
|
|
23186
|
+
if any(
|
|
23187
|
+
x in low
|
|
23188
|
+
for x in [
|
|
23189
|
+
"action_required",
|
|
23190
|
+
"action required",
|
|
23191
|
+
"tool_required",
|
|
23192
|
+
"tool required",
|
|
23193
|
+
"execute_tool",
|
|
23194
|
+
"execute tool",
|
|
23195
|
+
"needs tool",
|
|
23196
|
+
"needs_action",
|
|
23197
|
+
"needs action",
|
|
23198
|
+
"需要工具",
|
|
23199
|
+
"需要执行",
|
|
23200
|
+
"需要執行",
|
|
23201
|
+
"需要動作",
|
|
23202
|
+
"工具が必要",
|
|
23203
|
+
"実行が必要",
|
|
23204
|
+
]
|
|
23205
|
+
):
|
|
23206
|
+
return "ACTION_REQUIRED"
|
|
22291
23207
|
if any(
|
|
22292
23208
|
x in low
|
|
22293
23209
|
for x in [
|
|
@@ -22335,12 +23251,18 @@ body{padding:18px}
|
|
|
22335
23251
|
"VALID_PLAN": "VALID_PLANNING",
|
|
22336
23252
|
"PLANNING": "VALID_PLANNING",
|
|
22337
23253
|
"PLAN": "VALID_PLANNING",
|
|
23254
|
+
"ACTION": "ACTION_REQUIRED",
|
|
23255
|
+
"REQUIRES_ACTION": "ACTION_REQUIRED",
|
|
23256
|
+
"NEEDS_ACTION": "ACTION_REQUIRED",
|
|
23257
|
+
"TOOL_REQUIRED": "ACTION_REQUIRED",
|
|
23258
|
+
"NEEDS_TOOL": "ACTION_REQUIRED",
|
|
23259
|
+
"EXECUTE_TOOL": "ACTION_REQUIRED",
|
|
22338
23260
|
"EMPTY": "EMPTY_RAMBLING",
|
|
22339
23261
|
"RAMBLING": "EMPTY_RAMBLING",
|
|
22340
23262
|
"IDLE": "EMPTY_RAMBLING",
|
|
22341
23263
|
}
|
|
22342
23264
|
status = aliases.get(key, key)
|
|
22343
|
-
if status in {"TASK_COMPLETED", "VALID_PLANNING", "EMPTY_RAMBLING"}:
|
|
23265
|
+
if status in {"TASK_COMPLETED", "ACTION_REQUIRED", "VALID_PLANNING", "EMPTY_RAMBLING"}:
|
|
22344
23266
|
return status
|
|
22345
23267
|
inferred = self._infer_arbiter_status_from_text(fallback_text)
|
|
22346
23268
|
return inferred or ""
|
|
@@ -22399,25 +23321,32 @@ body{padding:18px}
|
|
|
22399
23321
|
|
|
22400
23322
|
def _call_arbiter_llm(self, assistant_text: str, thinking_text: str = "") -> dict:
|
|
22401
23323
|
clean = strip_thinking_content(str(assistant_text or "")).strip()
|
|
23324
|
+
thinking_clean = str(thinking_text or "").strip()
|
|
22402
23325
|
if not self.arbiter_enabled:
|
|
22403
23326
|
return {"status": "DISABLED", "reasoning": "arbiter disabled", "raw": ""}
|
|
22404
|
-
|
|
23327
|
+
probe_len = max(len(clean), len(thinking_clean))
|
|
23328
|
+
if (
|
|
23329
|
+
probe_len < int(ARBITER_TRIGGER_MIN_CONTENT_CHARS)
|
|
23330
|
+
and not self._looks_like_action_promise_without_tool(f"{clean}\n{thinking_clean}")
|
|
23331
|
+
):
|
|
22405
23332
|
return {"status": "SKIP_SHORT", "reasoning": "content too short", "raw": ""}
|
|
22406
23333
|
snapshot = self._arbiter_context_snapshot(clean, thinking_text)
|
|
22407
23334
|
arbiter_system = (
|
|
22408
23335
|
"You are a task-state arbiter. "
|
|
22409
23336
|
"Classify worker output into exactly one status: "
|
|
22410
|
-
"TASK_COMPLETED, VALID_PLANNING, or EMPTY_RAMBLING. "
|
|
23337
|
+
"TASK_COMPLETED, ACTION_REQUIRED, VALID_PLANNING, or EMPTY_RAMBLING. "
|
|
22411
23338
|
"Return strict JSON only."
|
|
22412
23339
|
)
|
|
22413
23340
|
arbiter_user = (
|
|
22414
23341
|
"Read the snapshot and classify the worker state.\n"
|
|
22415
23342
|
"Status definitions:\n"
|
|
22416
23343
|
"- TASK_COMPLETED: worker already completed user's target and gave final deliverable/summary.\n"
|
|
22417
|
-
"-
|
|
23344
|
+
"- ACTION_REQUIRED: worker has decided or promised to create/write/modify/run/verify something, or thinking contains a concrete next tool action, but no tool call was emitted.\n"
|
|
23345
|
+
"- VALID_PLANNING: worker output is useful high-level analysis or design that still needs more reasoning before a concrete tool action.\n"
|
|
22418
23346
|
"- EMPTY_RAMBLING: worker is stalling, repeating, or hallucinating with no actionable progress.\n"
|
|
23347
|
+
"Prefer ACTION_REQUIRED over VALID_PLANNING when the next step is already a concrete artifact/action.\n"
|
|
22419
23348
|
"Output JSON only:\n"
|
|
22420
|
-
"{\"status\":\"TASK_COMPLETED|VALID_PLANNING|EMPTY_RAMBLING\",\"reasoning\":\"<=40 words\"}\n\n"
|
|
23349
|
+
"{\"status\":\"TASK_COMPLETED|ACTION_REQUIRED|VALID_PLANNING|EMPTY_RAMBLING\",\"reasoning\":\"<=40 words\"}\n\n"
|
|
22421
23350
|
f"Snapshot:\n{json_dumps(snapshot, indent=2)}"
|
|
22422
23351
|
)
|
|
22423
23352
|
box: dict[str, object] = {}
|
|
@@ -22447,18 +23376,28 @@ body{padding:18px}
|
|
|
22447
23376
|
return {"status": "ARBITER_ERROR", "reasoning": trim(str(err), 220), "raw": ""}
|
|
22448
23377
|
rsp = box.get("rsp") if isinstance(box.get("rsp"), dict) else {}
|
|
22449
23378
|
raw_content = trim(str((rsp or {}).get("content", "") or ""), 2000)
|
|
23379
|
+
raw_thinking = trim(str((rsp or {}).get("thinking", "") or ""), 1200)
|
|
23380
|
+
status_source = raw_content or raw_thinking
|
|
22450
23381
|
payload = extract_json_object_from_text(raw_content, {})
|
|
22451
|
-
|
|
23382
|
+
if not payload and raw_thinking:
|
|
23383
|
+
payload = extract_json_object_from_text(raw_thinking, {})
|
|
23384
|
+
status = self._normalize_arbiter_status(str(payload.get("status", "") or ""), status_source)
|
|
22452
23385
|
if not status:
|
|
22453
|
-
status = self._infer_arbiter_status_from_text(
|
|
23386
|
+
status = self._infer_arbiter_status_from_text(status_source)
|
|
23387
|
+
if (
|
|
23388
|
+
(not status or status == "UNKNOWN")
|
|
23389
|
+
and self._looks_like_action_promise_without_tool(f"{clean}\n{thinking_clean}\n{status_source}")
|
|
23390
|
+
):
|
|
23391
|
+
status = "ACTION_REQUIRED"
|
|
22454
23392
|
reasoning = trim(
|
|
22455
23393
|
str(payload.get("reasoning", payload.get("reason", "")) or ""),
|
|
22456
23394
|
280,
|
|
22457
|
-
) or trim(
|
|
23395
|
+
) or trim(status_source, 280)
|
|
22458
23396
|
return {
|
|
22459
23397
|
"status": status or "UNKNOWN",
|
|
22460
23398
|
"reasoning": reasoning,
|
|
22461
23399
|
"raw": raw_content,
|
|
23400
|
+
"raw_thinking": raw_thinking,
|
|
22462
23401
|
"model": str(self.arbiter_model or self.ollama.model or "").strip(),
|
|
22463
23402
|
}
|
|
22464
23403
|
|
|
@@ -22479,6 +23418,26 @@ body{padding:18px}
|
|
|
22479
23418
|
}
|
|
22480
23419
|
)
|
|
22481
23420
|
|
|
23421
|
+
def _inject_arbiter_action_required_hint(self, decision: dict, assistant_text: str = ""):
|
|
23422
|
+
reasoning = trim(str(decision.get("reasoning", "") or "").strip(), 180)
|
|
23423
|
+
latest = trim(strip_thinking_content(str(assistant_text or "")).strip(), 420)
|
|
23424
|
+
self._prune_runtime_retry_hints()
|
|
23425
|
+
self.messages.append(
|
|
23426
|
+
{
|
|
23427
|
+
"role": "user",
|
|
23428
|
+
"content": (
|
|
23429
|
+
"<arbiter-continue>"
|
|
23430
|
+
"系统仲裁判定:当前回复已经到了需要执行工具的阶段,但没有发出工具调用。"
|
|
23431
|
+
"下一轮必须只调用一个具体工具来推进,例如 write_file/edit_file/bash/read_file/check_background;"
|
|
23432
|
+
"如果确实已经完成,则调用 finish_task 并提供文件路径和验证证据。不要再输出计划性说明。"
|
|
23433
|
+
f"{(' 判定依据: ' + reasoning + '。') if reasoning else ''}"
|
|
23434
|
+
f"{(' 上一条文本: ' + latest) if latest else ''}"
|
|
23435
|
+
"</arbiter-continue>"
|
|
23436
|
+
),
|
|
23437
|
+
"ts": now_ts(),
|
|
23438
|
+
}
|
|
23439
|
+
)
|
|
23440
|
+
|
|
22482
23441
|
def _mark_all_done_silently(self, reason: str = "") -> dict:
|
|
22483
23442
|
summary = trim(str(reason or "").strip(), 160) or "arbiter: task completed"
|
|
22484
23443
|
# Protect plan_step todos — they must only be completed by _advance_plan_step
|
|
@@ -23481,6 +24440,15 @@ body{padding:18px}
|
|
|
23481
24440
|
system=system,
|
|
23482
24441
|
media_inputs=media_inputs,
|
|
23483
24442
|
)
|
|
24443
|
+
label_low = str(context_label or "").strip().lower()
|
|
24444
|
+
context_role_hint = ""
|
|
24445
|
+
if "manager" in label_low:
|
|
24446
|
+
context_role_hint = "manager"
|
|
24447
|
+
else:
|
|
24448
|
+
for _role in AGENT_ROLES:
|
|
24449
|
+
if _role in label_low:
|
|
24450
|
+
context_role_hint = _role
|
|
24451
|
+
break
|
|
23484
24452
|
|
|
23485
24453
|
def _emit_http_retry(meta: dict):
|
|
23486
24454
|
try:
|
|
@@ -23541,6 +24509,28 @@ body{padding:18px}
|
|
|
23541
24509
|
last_exc = exc
|
|
23542
24510
|
if self.cancel_requested or "interrupted by user" in str(exc).lower():
|
|
23543
24511
|
raise
|
|
24512
|
+
if self._context_window_error_hint(exc):
|
|
24513
|
+
if self._shrink_context_upper_bound_from_actual_pressure(
|
|
24514
|
+
estimated_prompt_tokens=estimated_prompt_tokens,
|
|
24515
|
+
reason=f"{context_label}:{trim(str(exc), 80)}",
|
|
24516
|
+
):
|
|
24517
|
+
self._apply_auto_compact_if_needed(
|
|
24518
|
+
f"provider-context-error:{context_label}",
|
|
24519
|
+
metrics=self._context_budget_metrics(token_estimate=estimated_prompt_tokens),
|
|
24520
|
+
role=context_role_hint,
|
|
24521
|
+
)
|
|
24522
|
+
if attempt <= retry_budget:
|
|
24523
|
+
self._emit(
|
|
24524
|
+
"status",
|
|
24525
|
+
{
|
|
24526
|
+
"summary": (
|
|
24527
|
+
f"{context_label} context-window retry after compact "
|
|
24528
|
+
f"({attempt}/{retry_budget})"
|
|
24529
|
+
)
|
|
24530
|
+
},
|
|
24531
|
+
)
|
|
24532
|
+
time.sleep(min(1.0, 0.25 * attempt))
|
|
24533
|
+
continue
|
|
23544
24534
|
# Detect media format incompatibility and auto-fallback
|
|
23545
24535
|
exc_str = str(exc).lower()
|
|
23546
24536
|
_media_fmt_err = media_inputs and any(
|
|
@@ -24309,7 +25299,16 @@ body{padding:18px}
|
|
|
24309
25299
|
scored.sort(key=lambda row: (-row[0], len(row[1]), row[1]))
|
|
24310
25300
|
return [path for _, path in scored[: max(1, int(limit or 1))]]
|
|
24311
25301
|
|
|
24312
|
-
def _render_directory_read(
|
|
25302
|
+
def _render_directory_read(
|
|
25303
|
+
self,
|
|
25304
|
+
fp: Path,
|
|
25305
|
+
rel: str,
|
|
25306
|
+
limit: int | None = None,
|
|
25307
|
+
offset: int | None = None,
|
|
25308
|
+
*,
|
|
25309
|
+
query: object = "",
|
|
25310
|
+
max_chars: object = None,
|
|
25311
|
+
) -> str:
|
|
24313
25312
|
entries = sorted(
|
|
24314
25313
|
list(fp.iterdir()),
|
|
24315
25314
|
key=lambda p: (0 if p.is_dir() else 1, p.name.lower()),
|
|
@@ -24317,16 +25316,26 @@ body{padding:18px}
|
|
|
24317
25316
|
total = len(entries)
|
|
24318
25317
|
if total == 0:
|
|
24319
25318
|
return f"[read_file directory path={rel} entries=0]\n(empty directory)"
|
|
24320
|
-
|
|
24321
|
-
|
|
24322
|
-
|
|
25319
|
+
query_text = str(query or "").strip().lower()
|
|
25320
|
+
if query_text:
|
|
25321
|
+
filtered = [p for p in entries if query_text in p.name.lower() or query_text in p.as_posix().lower()]
|
|
25322
|
+
else:
|
|
25323
|
+
filtered = entries
|
|
25324
|
+
filtered_total = len(filtered)
|
|
25325
|
+
offset_val = self._read_file_int_arg(offset, 0, 0, max(0, filtered_total))
|
|
25326
|
+
requested_limit = self._read_file_int_arg(limit, 200 if query_text else 120, 1, 500)
|
|
25327
|
+
if offset_val >= filtered_total:
|
|
24323
25328
|
return (
|
|
24324
|
-
f"[read_file directory path={rel} entries=0 of {total} offset={offset_val}]\n"
|
|
25329
|
+
f"[read_file directory path={rel} entries=0 of {filtered_total} total={total} offset={offset_val}]\n"
|
|
24325
25330
|
"[end_of_directory]"
|
|
24326
25331
|
)
|
|
24327
|
-
page =
|
|
25332
|
+
page = filtered[offset_val: offset_val + requested_limit]
|
|
24328
25333
|
lines = [
|
|
24329
|
-
|
|
25334
|
+
(
|
|
25335
|
+
f"[read_file directory path={rel} entries={offset_val + 1}-{offset_val + len(page)} "
|
|
25336
|
+
f"of {filtered_total} total={total} mode=directory"
|
|
25337
|
+
f"{f' query={query_text!r}' if query_text else ''}]"
|
|
25338
|
+
)
|
|
24330
25339
|
]
|
|
24331
25340
|
for child in page:
|
|
24332
25341
|
kind = "dir" if child.is_dir() else "file"
|
|
@@ -24335,13 +25344,15 @@ body{padding:18px}
|
|
|
24335
25344
|
except Exception:
|
|
24336
25345
|
size_text = ""
|
|
24337
25346
|
lines.append(f"{kind} {child.name}{size_text}")
|
|
24338
|
-
|
|
24339
|
-
if
|
|
24340
|
-
lines.append(
|
|
24341
|
-
|
|
24342
|
-
|
|
24343
|
-
|
|
24344
|
-
|
|
25347
|
+
remaining = max(0, filtered_total - (offset_val + len(page)))
|
|
25348
|
+
if remaining > 0:
|
|
25349
|
+
lines.append(
|
|
25350
|
+
f"[directory has {remaining} more matching entries. Use query to narrow results, "
|
|
25351
|
+
"or read a specific subdirectory/file.]"
|
|
25352
|
+
)
|
|
25353
|
+
if not query_text and total > requested_limit:
|
|
25354
|
+
lines.append(f"[tip read_file path=\"{rel}\" mode=\"directory\" query=\"<name-fragment>\" narrows large directories]")
|
|
25355
|
+
return self._clip_read_file_output("\n".join(lines), self._read_file_max_chars(max_chars))
|
|
24345
25356
|
|
|
24346
25357
|
def _read_text_with_fallback(self, fp: Path) -> str:
|
|
24347
25358
|
tried: list[str] = []
|
|
@@ -24373,7 +25384,176 @@ body{padding:18px}
|
|
|
24373
25384
|
)
|
|
24374
25385
|
return "\n".join(lines)
|
|
24375
25386
|
|
|
24376
|
-
def
|
|
25387
|
+
def _render_full_text_read(self, rel: str, lines: list[str], *, max_chars: object = None) -> str:
|
|
25388
|
+
full_text = "\n".join(lines)
|
|
25389
|
+
cap = self._read_file_max_chars(max_chars)
|
|
25390
|
+
if len(full_text) <= cap:
|
|
25391
|
+
return full_text
|
|
25392
|
+
header = (
|
|
25393
|
+
f"[read_file full path={rel} chars=1-{cap} of {len(full_text)} max_chars={cap}]\n"
|
|
25394
|
+
)
|
|
25395
|
+
return header + self._clip_read_file_output(
|
|
25396
|
+
full_text,
|
|
25397
|
+
cap,
|
|
25398
|
+
"For exact focused context, use mode=\"search\", mode=\"symbol\", or mode=\"window\"; "
|
|
25399
|
+
"for a wider full read, set a larger max_chars value.",
|
|
25400
|
+
)
|
|
25401
|
+
|
|
25402
|
+
def _render_window_text_read(
|
|
25403
|
+
self,
|
|
25404
|
+
rel: str,
|
|
25405
|
+
lines: list[str],
|
|
25406
|
+
*,
|
|
25407
|
+
limit: int | None = None,
|
|
25408
|
+
offset: int | None = None,
|
|
25409
|
+
line: object = None,
|
|
25410
|
+
context: object = None,
|
|
25411
|
+
max_chars: object = None,
|
|
25412
|
+
) -> str:
|
|
25413
|
+
total = len(lines)
|
|
25414
|
+
if total <= 0:
|
|
25415
|
+
return f"[read_file window path={rel} lines=0]\n[end_of_file]"
|
|
25416
|
+
ctx = self._read_file_int_arg(context, 60, 0, 2000)
|
|
25417
|
+
default_limit = ctx * 2 + 1 if line not in (None, "") else LONG_OUTPUT_READ_PAGE_LINES
|
|
25418
|
+
requested_limit = self._read_file_int_arg(limit, default_limit, 1, 4000)
|
|
25419
|
+
line_val = self._read_file_int_arg(line, 0, 0, max(1, total)) if line not in (None, "") else 0
|
|
25420
|
+
if line_val > 0:
|
|
25421
|
+
start = max(0, line_val - ctx - 1)
|
|
25422
|
+
else:
|
|
25423
|
+
start = self._read_file_int_arg(offset, 0, 0, max(0, total))
|
|
25424
|
+
end = min(total, start + requested_limit)
|
|
25425
|
+
if start >= total:
|
|
25426
|
+
return f"[read_file window path={rel} lines=0 of {total} start={start + 1}]\n[end_of_file]"
|
|
25427
|
+
body = "\n".join(lines[start:end])
|
|
25428
|
+
header = f"[read_file window path={rel} lines={start + 1}-{end} of {total}]\n"
|
|
25429
|
+
return header + self._clip_read_file_output(body, self._read_file_max_chars(max_chars))
|
|
25430
|
+
|
|
25431
|
+
def _render_search_text_read(
|
|
25432
|
+
self,
|
|
25433
|
+
rel: str,
|
|
25434
|
+
lines: list[str],
|
|
25435
|
+
*,
|
|
25436
|
+
query: object = "",
|
|
25437
|
+
regex: object = False,
|
|
25438
|
+
context: object = None,
|
|
25439
|
+
max_chars: object = None,
|
|
25440
|
+
) -> str:
|
|
25441
|
+
needle = str(query or "").strip()
|
|
25442
|
+
if not needle:
|
|
25443
|
+
return (
|
|
25444
|
+
f"[read_file search path={rel}]\n"
|
|
25445
|
+
"Error: query is required for mode=\"search\". Use mode=\"overview\" to inspect available structure."
|
|
25446
|
+
)
|
|
25447
|
+
ctx = self._read_file_int_arg(context, 6, 0, 80)
|
|
25448
|
+
total = len(lines)
|
|
25449
|
+
matches: list[int] = []
|
|
25450
|
+
if bool(regex):
|
|
25451
|
+
try:
|
|
25452
|
+
pattern = re.compile(needle)
|
|
25453
|
+
except re.error as exc:
|
|
25454
|
+
return f"Error: invalid regex for read_file search: {exc}"
|
|
25455
|
+
for idx, row in enumerate(lines):
|
|
25456
|
+
if pattern.search(row):
|
|
25457
|
+
matches.append(idx)
|
|
25458
|
+
if len(matches) >= READ_FILE_SEARCH_MAX_MATCHES:
|
|
25459
|
+
break
|
|
25460
|
+
else:
|
|
25461
|
+
low = needle.lower()
|
|
25462
|
+
for idx, row in enumerate(lines):
|
|
25463
|
+
if low in row.lower():
|
|
25464
|
+
matches.append(idx)
|
|
25465
|
+
if len(matches) >= READ_FILE_SEARCH_MAX_MATCHES:
|
|
25466
|
+
break
|
|
25467
|
+
if not matches:
|
|
25468
|
+
return f"[read_file search path={rel} query={needle!r} matches=0]\n(no matches)"
|
|
25469
|
+
ranges: list[tuple[int, int]] = []
|
|
25470
|
+
for idx in matches:
|
|
25471
|
+
start = max(0, idx - ctx)
|
|
25472
|
+
end = min(total, idx + ctx + 1)
|
|
25473
|
+
if ranges and start <= ranges[-1][1]:
|
|
25474
|
+
ranges[-1] = (ranges[-1][0], max(ranges[-1][1], end))
|
|
25475
|
+
else:
|
|
25476
|
+
ranges.append((start, end))
|
|
25477
|
+
out = [
|
|
25478
|
+
(
|
|
25479
|
+
f"[read_file search path={rel} query={needle!r} matches_returned={len(matches)} "
|
|
25480
|
+
f"windows={len(ranges)} total_lines={total}]"
|
|
25481
|
+
)
|
|
25482
|
+
]
|
|
25483
|
+
for start, end in ranges:
|
|
25484
|
+
out.append(f"\n@@ lines {start + 1}-{end} @@")
|
|
25485
|
+
for line_no in range(start, end):
|
|
25486
|
+
out.append(f"{line_no + 1:>6}: {lines[line_no]}")
|
|
25487
|
+
return self._clip_read_file_output("\n".join(out), self._read_file_max_chars(max_chars))
|
|
25488
|
+
|
|
25489
|
+
def _render_symbol_text_read(
|
|
25490
|
+
self,
|
|
25491
|
+
fp: Path,
|
|
25492
|
+
rel: str,
|
|
25493
|
+
lines: list[str],
|
|
25494
|
+
*,
|
|
25495
|
+
target: object = "",
|
|
25496
|
+
context: object = None,
|
|
25497
|
+
max_chars: object = None,
|
|
25498
|
+
) -> str:
|
|
25499
|
+
symbol_name = str(target or "").strip()
|
|
25500
|
+
if not symbol_name:
|
|
25501
|
+
return (
|
|
25502
|
+
f"[read_file symbol path={rel}]\n"
|
|
25503
|
+
"Error: target is required for mode=\"symbol\".\n"
|
|
25504
|
+
+ self._render_text_overview(fp, rel, lines, max_chars=max_chars)
|
|
25505
|
+
)
|
|
25506
|
+
data = self._read_file_code_data(fp, lines)
|
|
25507
|
+
symbols = data.get("symbols", []) if isinstance(data.get("symbols"), list) else []
|
|
25508
|
+
wanted = symbol_name.lower()
|
|
25509
|
+
|
|
25510
|
+
def score(row: dict) -> int:
|
|
25511
|
+
name = str(row.get("name", "") or "").strip().lower()
|
|
25512
|
+
sig = str(row.get("signature", "") or "").strip().lower()
|
|
25513
|
+
if name == wanted:
|
|
25514
|
+
return 100
|
|
25515
|
+
if name.endswith("." + wanted):
|
|
25516
|
+
return 90
|
|
25517
|
+
if wanted in name:
|
|
25518
|
+
return 70
|
|
25519
|
+
if wanted in sig:
|
|
25520
|
+
return 50
|
|
25521
|
+
return 0
|
|
25522
|
+
|
|
25523
|
+
ranked = sorted(((score(row), row) for row in symbols), key=lambda x: (-x[0], int(x[1].get("line_start", 0) or 0)))
|
|
25524
|
+
match = next((row for sc, row in ranked if sc > 0), None)
|
|
25525
|
+
if not match:
|
|
25526
|
+
available = ", ".join(str(row.get("name", "")) for row in symbols[:40] if str(row.get("name", "")).strip())
|
|
25527
|
+
return (
|
|
25528
|
+
f"[read_file symbol path={rel} target={symbol_name!r} matches=0]\n"
|
|
25529
|
+
f"Available symbols: {available or '(none detected)'}\n"
|
|
25530
|
+
f"Try read_file path=\"{rel}\" mode=\"search\" query=\"{symbol_name}\""
|
|
25531
|
+
)
|
|
25532
|
+
total = len(lines)
|
|
25533
|
+
ctx = self._read_file_int_arg(context, 0, 0, 1000)
|
|
25534
|
+
start = max(1, int(match.get("line_start", 1) or 1) - ctx)
|
|
25535
|
+
end = min(total, int(match.get("line_end", start) or start) + ctx)
|
|
25536
|
+
body = "\n".join(lines[start - 1:end])
|
|
25537
|
+
header = (
|
|
25538
|
+
f"[read_file symbol path={rel} target={symbol_name!r} "
|
|
25539
|
+
f"matched={match.get('kind', 'symbol')} {match.get('name', '')} lines={start}-{end} of {total}]\n"
|
|
25540
|
+
)
|
|
25541
|
+
return header + self._clip_read_file_output(body, self._read_file_max_chars(max_chars))
|
|
25542
|
+
|
|
25543
|
+
def _run_read(
|
|
25544
|
+
self,
|
|
25545
|
+
path: str,
|
|
25546
|
+
limit: int | None = None,
|
|
25547
|
+
offset: int | None = None,
|
|
25548
|
+
*,
|
|
25549
|
+
mode: object = None,
|
|
25550
|
+
target: object = "",
|
|
25551
|
+
query: object = "",
|
|
25552
|
+
line: object = None,
|
|
25553
|
+
context: object = None,
|
|
25554
|
+
regex: object = False,
|
|
25555
|
+
max_chars: object = None,
|
|
25556
|
+
) -> str:
|
|
24377
25557
|
try:
|
|
24378
25558
|
rel = self._normalize_tool_path_text(path)
|
|
24379
25559
|
fp = self._fuzzy_resolve_path(self._session_path(rel))
|
|
@@ -24381,7 +25561,7 @@ body{padding:18px}
|
|
|
24381
25561
|
if not fp.exists():
|
|
24382
25562
|
return self._render_missing_read_hint(rel)
|
|
24383
25563
|
if fp.is_dir():
|
|
24384
|
-
return self._render_directory_read(fp, rel, limit=limit, offset=offset)
|
|
25564
|
+
return self._render_directory_read(fp, rel, limit=limit, offset=offset, query=query, max_chars=max_chars)
|
|
24385
25565
|
# Multimodal: detect image/audio/video files and handle natively
|
|
24386
25566
|
ext = fp.suffix.lower() if fp.suffix else ""
|
|
24387
25567
|
if ext in IMAGE_EXTS:
|
|
@@ -24398,54 +25578,65 @@ body{padding:18px}
|
|
|
24398
25578
|
file_size = int(fp.stat().st_size)
|
|
24399
25579
|
except Exception:
|
|
24400
25580
|
file_size = 0
|
|
25581
|
+
mode_text = str(mode or "auto").strip().lower() or "auto"
|
|
25582
|
+
if mode_text not in {"auto", "full", "overview", "window", "symbol", "search", "directory"}:
|
|
25583
|
+
mode_text = "auto"
|
|
25584
|
+
if mode_text == "auto":
|
|
25585
|
+
if str(target or "").strip():
|
|
25586
|
+
mode_text = "symbol"
|
|
25587
|
+
elif str(query or "").strip():
|
|
25588
|
+
mode_text = "search"
|
|
25589
|
+
elif line not in (None, ""):
|
|
25590
|
+
mode_text = "window"
|
|
25591
|
+
if mode_text == "directory":
|
|
25592
|
+
return f"Error: path is a file, not a directory: {rel}"
|
|
25593
|
+
if mode_text == "overview":
|
|
25594
|
+
return self._render_text_overview(fp, rel, lines, max_chars=max_chars)
|
|
25595
|
+
if mode_text == "full":
|
|
25596
|
+
return self._render_full_text_read(rel, lines, max_chars=max_chars)
|
|
25597
|
+
if mode_text == "search":
|
|
25598
|
+
return self._render_search_text_read(
|
|
25599
|
+
rel,
|
|
25600
|
+
lines,
|
|
25601
|
+
query=query or target,
|
|
25602
|
+
regex=regex,
|
|
25603
|
+
context=context,
|
|
25604
|
+
max_chars=max_chars,
|
|
25605
|
+
)
|
|
25606
|
+
if mode_text == "symbol":
|
|
25607
|
+
return self._render_symbol_text_read(
|
|
25608
|
+
fp,
|
|
25609
|
+
rel,
|
|
25610
|
+
lines,
|
|
25611
|
+
target=target or query,
|
|
25612
|
+
context=context,
|
|
25613
|
+
max_chars=max_chars,
|
|
25614
|
+
)
|
|
25615
|
+
if (
|
|
25616
|
+
mode_text == "window"
|
|
25617
|
+
or limit is not None
|
|
25618
|
+
or self._read_file_int_arg(offset, 0, 0, 1_000_000) > 0
|
|
25619
|
+
or line not in (None, "")
|
|
25620
|
+
):
|
|
25621
|
+
return self._render_window_text_read(
|
|
25622
|
+
rel,
|
|
25623
|
+
lines,
|
|
25624
|
+
limit=limit,
|
|
25625
|
+
offset=offset,
|
|
25626
|
+
line=line,
|
|
25627
|
+
context=context,
|
|
25628
|
+
max_chars=max_chars,
|
|
25629
|
+
)
|
|
24401
25630
|
if (
|
|
24402
25631
|
limit is None
|
|
24403
|
-
and
|
|
25632
|
+
and self._read_file_int_arg(offset, 0, 0, 1_000_000) <= 0
|
|
24404
25633
|
and (file_size >= LARGE_FILE_AUTO_PAGE_BYTES or total_lines >= LARGE_FILE_AUTO_PAGE_LINES)
|
|
24405
25634
|
):
|
|
24406
|
-
return self.
|
|
24407
|
-
offset_val = max(0, int(offset or 0))
|
|
24408
|
-
requested_limit = max(1, int(limit or LONG_OUTPUT_READ_PAGE_LINES))
|
|
24409
|
-
if offset_val >= total_lines:
|
|
24410
|
-
return (
|
|
24411
|
-
f"[read_file page path={rel} lines=0 of {total_lines} offset={offset_val}]\n"
|
|
24412
|
-
"[end_of_file]"
|
|
24413
|
-
)
|
|
25635
|
+
return self._render_text_overview(fp, rel, lines, max_chars=max_chars)
|
|
24414
25636
|
full_text = "\n".join(lines)
|
|
24415
|
-
|
|
24416
|
-
if not auto_paginate and limit is None and offset_val == 0 and len(full_text) <= MAX_TOOL_OUTPUT:
|
|
25637
|
+
if len(full_text) <= self._read_file_max_chars(max_chars):
|
|
24417
25638
|
return full_text
|
|
24418
|
-
|
|
24419
|
-
chars = 0
|
|
24420
|
-
idx = offset_val
|
|
24421
|
-
while idx < total_lines and len(page_parts) < requested_limit:
|
|
24422
|
-
line = lines[idx]
|
|
24423
|
-
piece = line if not page_parts else "\n" + line
|
|
24424
|
-
if page_parts and (chars + len(piece)) > LONG_OUTPUT_READ_PAGE_MAX_CHARS:
|
|
24425
|
-
break
|
|
24426
|
-
if (not page_parts) and len(line) > LONG_OUTPUT_READ_PAGE_MAX_CHARS:
|
|
24427
|
-
page_parts.append(line[:LONG_OUTPUT_READ_PAGE_MAX_CHARS])
|
|
24428
|
-
idx += 1
|
|
24429
|
-
break
|
|
24430
|
-
page_parts.append(piece if page_parts else line)
|
|
24431
|
-
chars += len(piece)
|
|
24432
|
-
idx += 1
|
|
24433
|
-
body = "".join(page_parts)
|
|
24434
|
-
page_no = (offset_val // requested_limit) + 1
|
|
24435
|
-
total_pages = max(1, (total_lines + requested_limit - 1) // requested_limit)
|
|
24436
|
-
out = [
|
|
24437
|
-
(
|
|
24438
|
-
f"[read_file page path={rel} lines={offset_val + 1}-{idx} of {total_lines} "
|
|
24439
|
-
f"page={page_no}/{total_pages} offset={offset_val} limit={requested_limit}]"
|
|
24440
|
-
),
|
|
24441
|
-
body,
|
|
24442
|
-
]
|
|
24443
|
-
if idx < total_lines:
|
|
24444
|
-
out.append(f"[next_page read_file path=\"{rel}\" offset={idx} limit={requested_limit}]")
|
|
24445
|
-
if offset_val > 0:
|
|
24446
|
-
prev_offset = max(0, offset_val - requested_limit)
|
|
24447
|
-
out.append(f"[prev_page read_file path=\"{rel}\" offset={prev_offset} limit={requested_limit}]")
|
|
24448
|
-
return "\n".join(part for part in out if part != "")
|
|
25639
|
+
return self._render_text_overview(fp, rel, lines, max_chars=max_chars)
|
|
24449
25640
|
except Exception as exc:
|
|
24450
25641
|
return f"Error: {type(exc).__name__}: {exc}"
|
|
24451
25642
|
|
|
@@ -24514,6 +25705,22 @@ body{padding:18px}
|
|
|
24514
25705
|
f"File exists at {fp}. Use bash tools to process it if needed."
|
|
24515
25706
|
)
|
|
24516
25707
|
|
|
25708
|
+
def _tool_result_context_content(
|
|
25709
|
+
self,
|
|
25710
|
+
name: str,
|
|
25711
|
+
args: dict | None,
|
|
25712
|
+
output: object,
|
|
25713
|
+
default_limit: int = MAX_TOOL_OUTPUT,
|
|
25714
|
+
) -> str:
|
|
25715
|
+
text = str(output or "")
|
|
25716
|
+
tool_name = canonicalize_tool_name(name)
|
|
25717
|
+
if tool_name == "read_file":
|
|
25718
|
+
req = (args or {}).get("max_chars") if isinstance(args, dict) else None
|
|
25719
|
+
requested = self._read_file_max_chars(req, default=READ_FILE_DEFAULT_MAX_CHARS)
|
|
25720
|
+
cap = max(int(default_limit or 0), min(int(READ_FILE_HARD_MAX_CHARS), requested))
|
|
25721
|
+
return trim(text, cap)
|
|
25722
|
+
return trim(text, int(default_limit or MAX_TOOL_OUTPUT))
|
|
25723
|
+
|
|
24517
25724
|
def _is_html_file_rel(self, path: str) -> bool:
|
|
24518
25725
|
low = str(path or "").strip().lower()
|
|
24519
25726
|
return low.endswith(".html") or low.endswith(".htm")
|
|
@@ -24771,8 +25978,29 @@ body{padding:18px}
|
|
|
24771
25978
|
tool_def("bash", "Run command.", {"command": {"type": "string"}}, ["command"]),
|
|
24772
25979
|
tool_def(
|
|
24773
25980
|
"read_file",
|
|
24774
|
-
|
|
24775
|
-
|
|
25981
|
+
(
|
|
25982
|
+
"Read files or directories with structure-aware modes. "
|
|
25983
|
+
"Examples: large.py + func_42 -> mode='symbol' target='func_42'; "
|
|
25984
|
+
"app.py line 240 -> mode='window' line=240 context=5; "
|
|
25985
|
+
"run.txt E123 -> mode='search' query='E123'. "
|
|
25986
|
+
"Use mode='symbol', 'search', or 'window' for focused reads; use mode='full' only when needed."
|
|
25987
|
+
),
|
|
25988
|
+
{
|
|
25989
|
+
"path": {"type": "string"},
|
|
25990
|
+
"mode": {
|
|
25991
|
+
"type": "string",
|
|
25992
|
+
"enum": ["auto", "full", "overview", "window", "symbol", "search", "directory"],
|
|
25993
|
+
"description": "Reading strategy. Use symbol with target for a function/class; search with query for known text/errors; window with line/context for a line range. Avoid full for large logs when a query is known.",
|
|
25994
|
+
},
|
|
25995
|
+
"target": {"type": "string", "description": "Symbol name for mode='symbol', for example 'ClassName.method' or 'func_42'."},
|
|
25996
|
+
"query": {"type": "string", "description": "Search text or regex for mode='search'; can also be used when target is unknown."},
|
|
25997
|
+
"line": {"type": "integer", "description": "1-based center line for mode='window'."},
|
|
25998
|
+
"context": {"type": "integer", "description": "Number of surrounding lines for mode='window' or mode='search'."},
|
|
25999
|
+
"regex": {"type": "boolean", "description": "Treat query as a regular expression in mode='search'."},
|
|
26000
|
+
"max_chars": {"type": "integer", "description": "Maximum characters to return for broad reads; use only when wider context is needed."},
|
|
26001
|
+
"limit": {"type": "integer", "description": "Legacy line count for compatibility; prefer mode/context for new calls."},
|
|
26002
|
+
"offset": {"type": "integer", "description": "Legacy 0-based line offset for compatibility; prefer mode='window' with line/context."},
|
|
26003
|
+
},
|
|
24776
26004
|
["path"],
|
|
24777
26005
|
),
|
|
24778
26006
|
]
|
|
@@ -24848,14 +26076,30 @@ body{padding:18px}
|
|
|
24848
26076
|
if name == "bash":
|
|
24849
26077
|
out = self._run_bash(args.get("command", ""))
|
|
24850
26078
|
elif name == "read_file":
|
|
24851
|
-
out = self._run_read(
|
|
26079
|
+
out = self._run_read(
|
|
26080
|
+
args.get("path", ""),
|
|
26081
|
+
args.get("limit"),
|
|
26082
|
+
args.get("offset"),
|
|
26083
|
+
mode=args.get("mode"),
|
|
26084
|
+
target=args.get("target"),
|
|
26085
|
+
query=args.get("query"),
|
|
26086
|
+
line=args.get("line"),
|
|
26087
|
+
context=args.get("context"),
|
|
26088
|
+
regex=args.get("regex"),
|
|
26089
|
+
max_chars=args.get("max_chars"),
|
|
26090
|
+
)
|
|
24852
26091
|
elif name == "write_file":
|
|
24853
26092
|
out = self._run_write(args.get("path", ""), args.get("content", ""))
|
|
24854
26093
|
elif name == "edit_file":
|
|
24855
26094
|
out = self._run_edit(args.get("path", ""), args.get("old_text", ""), args.get("new_text", ""))
|
|
24856
26095
|
else:
|
|
24857
26096
|
out = f"Unknown tool: {name}"
|
|
24858
|
-
msgs.append({
|
|
26097
|
+
msgs.append({
|
|
26098
|
+
"role": "tool",
|
|
26099
|
+
"tool_call_id": tc["id"],
|
|
26100
|
+
"name": name,
|
|
26101
|
+
"content": self._tool_result_context_content(name, args if isinstance(args, dict) else {}, out),
|
|
26102
|
+
})
|
|
24859
26103
|
return last_text or "(subagent done)"
|
|
24860
26104
|
|
|
24861
26105
|
def _spawn_teammate(self, name: str, role: str, prompt: str) -> str:
|
|
@@ -31359,6 +32603,9 @@ body{padding:18px}
|
|
|
31359
32603
|
html_hint = self._html_frontend_boost_instruction()
|
|
31360
32604
|
# Loaded skills constraint for manager
|
|
31361
32605
|
skills_constraint = self._loaded_skills_prompt_hint(for_role="manager")
|
|
32606
|
+
skills_context = self._loaded_skills_context_block(for_role="manager", max_chars=5200)
|
|
32607
|
+
if skills_context:
|
|
32608
|
+
skills_constraint += skills_context
|
|
31362
32609
|
bb_skills = board.get("loaded_skills", {})
|
|
31363
32610
|
if isinstance(bb_skills, dict) and bb_skills:
|
|
31364
32611
|
skill_names = list(bb_skills.keys())[:5]
|
|
@@ -32628,6 +33875,11 @@ body{padding:18px}
|
|
|
32628
33875
|
)
|
|
32629
33876
|
self._append_manager_context({"role": "user", "content": prompt, "ts": now_ts()})
|
|
32630
33877
|
self._microcompact_agent_messages(self.manager_context)
|
|
33878
|
+
self._apply_auto_compact_if_needed(
|
|
33879
|
+
"auto:manager",
|
|
33880
|
+
role="manager",
|
|
33881
|
+
media_inputs=media_inputs_round,
|
|
33882
|
+
)
|
|
32631
33883
|
with self.lock:
|
|
32632
33884
|
self.current_phase = "manager:model-call"
|
|
32633
33885
|
self.current_tool_name = ""
|
|
@@ -33719,9 +34971,23 @@ body{padding:18px}
|
|
|
33719
34971
|
return self._sanitize_agent_role(m.group(1)), self._sanitize_agent_role(m.group(2))
|
|
33720
34972
|
|
|
33721
34973
|
def _effective_execution_mode(self) -> str:
|
|
33722
|
-
|
|
34974
|
+
runtime_raw = str(self.runtime_execution_mode or "").strip()
|
|
34975
|
+
runtime_mode = normalize_execution_mode(runtime_raw, default="") if runtime_raw else ""
|
|
33723
34976
|
if runtime_mode in {EXECUTION_MODE_SINGLE, EXECUTION_MODE_SEQUENTIAL, EXECUTION_MODE_SYNC}:
|
|
33724
34977
|
return runtime_mode
|
|
34978
|
+
try:
|
|
34979
|
+
bb = self.blackboard if isinstance(self.blackboard, dict) else {}
|
|
34980
|
+
profile = bb.get("task_profile", {}) if isinstance(bb.get("task_profile", {}), dict) else {}
|
|
34981
|
+
judgement = bb.get("manager_judgement", {}) if isinstance(bb.get("manager_judgement", {}), dict) else {}
|
|
34982
|
+
for raw in (
|
|
34983
|
+
profile.get("execution_mode", ""),
|
|
34984
|
+
judgement.get("execution_mode", ""),
|
|
34985
|
+
):
|
|
34986
|
+
mode = normalize_execution_mode(raw, default="")
|
|
34987
|
+
if mode in {EXECUTION_MODE_SINGLE, EXECUTION_MODE_SEQUENTIAL, EXECUTION_MODE_SYNC}:
|
|
34988
|
+
return mode
|
|
34989
|
+
except Exception:
|
|
34990
|
+
pass
|
|
33725
34991
|
return normalize_execution_mode(self.execution_mode, default=EXECUTION_MODE_SYNC)
|
|
33726
34992
|
|
|
33727
34993
|
def _is_multi_agent_mode(self) -> bool:
|
|
@@ -33999,6 +35265,8 @@ body{padding:18px}
|
|
|
33999
35265
|
"Do not leave '/js_lib/...', '/assets/js_lib/...', or other virtual aliases in final exported HTML. "
|
|
34000
35266
|
"Use blackboard for shared state, ask_colleague for inter-agent communication. "
|
|
34001
35267
|
"Keep outputs concise and action-oriented. "
|
|
35268
|
+
"When reading files, choose the shape that matches the question: mode='window' for file:line, mode='symbol' for named code, mode='search' for keywords/errors, mode='overview' for structure, and mode='full' only when exact broad context is required. "
|
|
35269
|
+
"When inspecting collections or memory, use focused modes too: context_recall/read_from_blackboard/task_list/check_background/read_inbox/worktree_events support mode='summary', mode='search', mode='window', and mode='detail' where applicable. Prefer query/status/actor/tool filters over repeatedly listing recent items. "
|
|
34002
35270
|
f"{code_note + ' ' if code_note else ''}"
|
|
34003
35271
|
f"{engineering_note + ' ' if engineering_note else ''}"
|
|
34004
35272
|
f"{html_note + ' ' if html_note else ''}"
|
|
@@ -34052,8 +35320,8 @@ body{padding:18px}
|
|
|
34052
35320
|
)
|
|
34053
35321
|
return base + (
|
|
34054
35322
|
"Role: implement code changes, execute tools, record progress to blackboard. "
|
|
34055
|
-
"SKILL PRIORITY (critical): When ACTIVE SKILLS are listed above,
|
|
34056
|
-
"<loaded-skill> messages in your context
|
|
35323
|
+
"SKILL PRIORITY (critical): When ACTIVE SKILLS are listed above, read the "
|
|
35324
|
+
"ACTIVE SKILL WORKFLOWS or <loaded-skill> messages in your context before starting any step. "
|
|
34057
35325
|
"The skill's workflow, tools, and file structure OVERRIDE the plan's implementation "
|
|
34058
35326
|
"approach — if the plan says 'use python-pptx' but the skill says 'use PptxGenJS', "
|
|
34059
35327
|
"use PptxGenJS. The skill defines HOW to implement; the plan defines WHAT to do. "
|
|
@@ -34068,13 +35336,13 @@ body{padding:18px}
|
|
|
34068
35336
|
"Do not silently batch multiple subtasks and do not delay todo updates until the end of the step. "
|
|
34069
35337
|
"This manual update is critical because skill re-evaluation is triggered by actual todo progress. "
|
|
34070
35338
|
"EDIT METHODOLOGY (follow strictly): "
|
|
34071
|
-
"1) Read the EXACT target location using read_file before any edit — never edit from memory. "
|
|
35339
|
+
"1) Read the EXACT target location using read_file before any edit — prefer mode='symbol', mode='search', or mode='window' to get the right context directly; never edit from memory. "
|
|
34072
35340
|
"2) Copy EXACT text into old_text (preserve all whitespace/indentation/line breaks). "
|
|
34073
35341
|
"3) Keep old_text as SHORT as possible while still unique (1-3 lines ideal). "
|
|
34074
35342
|
"4) If edit_file fails 'text not found': IMMEDIATELY re-read the file, compare whitespace, retry with exact content. "
|
|
34075
35343
|
"5) If edit_file fails 2+ times on same file: switch to write_file to rewrite entire file. "
|
|
34076
35344
|
"6) After every successful edit, run build/test to verify. "
|
|
34077
|
-
"
|
|
35345
|
+
"If read_file is not answering the question, change the read shape (overview/symbol/search/window/full), form a sharper hypothesis, then attempt a concrete edit_file, write_file, path reconciliation, or verification call. "
|
|
34078
35346
|
"PROBLEM-SOLVING (critical): "
|
|
34079
35347
|
"When you discover missing files, broken imports, or incomplete source code: "
|
|
34080
35348
|
"A) Think deeply about what the missing content should contain based on ALL available context "
|
|
@@ -34082,7 +35350,7 @@ body{padding:18px}
|
|
|
34082
35350
|
"B) CREATE the missing files yourself using write_file — do not wait or re-read. "
|
|
34083
35351
|
"C) If compilation fails due to missing dependencies, write stub implementations. "
|
|
34084
35352
|
"D) If read_file or bash says a path is missing, empty, or mismatched, reconcile the path against uploads, recent files, and close matches before trying again. "
|
|
34085
|
-
"E)
|
|
35353
|
+
"E) Avoid repeated identical reads; when you need more evidence, ask a more specific read_file question instead of reopening the same context. "
|
|
34086
35354
|
"F) Do not declare success until at least one fix-and-verify cycle is complete and the evidence is observable. "
|
|
34087
35355
|
"G) If truly blocked, explain WHY to the user and propose alternatives. "
|
|
34088
35356
|
)
|
|
@@ -34317,23 +35585,9 @@ body{padding:18px}
|
|
|
34317
35585
|
)
|
|
34318
35586
|
|
|
34319
35587
|
def _tighten_context_for_segmented_retry(self, reason: str, output_tokens: int):
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
reduced = int(max(MIN_CONTEXT_TOKEN_LIMIT, min(old_bound - 1200, old_bound * 0.78)))
|
|
34324
|
-
derived = self._derive_context_limit_from_output(max(1, output_tokens))
|
|
34325
|
-
new_bound = min(old_bound, reduced, derived)
|
|
34326
|
-
if new_bound < old_bound:
|
|
34327
|
-
self.context_token_upper_bound = new_bound
|
|
34328
|
-
self._emit(
|
|
34329
|
-
"status",
|
|
34330
|
-
{
|
|
34331
|
-
"summary": (
|
|
34332
|
-
"context upper bound reduced for segmented retry "
|
|
34333
|
-
f"{old_bound}->{new_bound} ({trim(reason, 90)})"
|
|
34334
|
-
)
|
|
34335
|
-
},
|
|
34336
|
-
)
|
|
35588
|
+
# Segmented retry is an execution-shaping signal. It should not shrink
|
|
35589
|
+
# the input context window unless a provider reports a real context error.
|
|
35590
|
+
self.context_limit_source = str(getattr(self, "context_limit_source", "") or "configured")
|
|
34337
35591
|
|
|
34338
35592
|
def _inject_segmented_retry_hint(self, name: str, reason: str):
|
|
34339
35593
|
goal = self._latest_user_goal_text()
|
|
@@ -34485,7 +35739,7 @@ body{padding:18px}
|
|
|
34485
35739
|
actions.append("split into smaller subtasks and regenerate full tool JSON")
|
|
34486
35740
|
if compact_hints > 0:
|
|
34487
35741
|
causes.append("context may be compacted")
|
|
34488
|
-
actions.append("call context_recall
|
|
35742
|
+
actions.append("call context_recall mode='summary', then mode='search' or mode='window' for focused details")
|
|
34489
35743
|
if tool_errors > 0:
|
|
34490
35744
|
causes.append("recent tool execution errors")
|
|
34491
35745
|
actions.append("repair arguments for one failing tool and retry only that tool")
|
|
@@ -34527,7 +35781,7 @@ body{padding:18px}
|
|
|
34527
35781
|
seg_id = str(seg.get("id", "")).strip()
|
|
34528
35782
|
if seg_id:
|
|
34529
35783
|
recall_hint = (
|
|
34530
|
-
f"Call context_recall
|
|
35784
|
+
f"Call context_recall with segment_id='{seg_id}' and mode='summary' to map the segment, then mode='search' for the missing topic or mode='window' with around_index/context for nearby messages. "
|
|
34531
35785
|
)
|
|
34532
35786
|
self._prune_runtime_retry_hints()
|
|
34533
35787
|
self.messages.append(
|
|
@@ -34556,6 +35810,30 @@ body{padding:18px}
|
|
|
34556
35810
|
},
|
|
34557
35811
|
)
|
|
34558
35812
|
|
|
35813
|
+
def _inject_action_promise_recovery_hint(self, assistant_text: str):
|
|
35814
|
+
goal = trim(str(self._latest_user_goal_text() or ""), 500)
|
|
35815
|
+
latest = trim(strip_thinking_content(str(assistant_text or "")).strip(), 500)
|
|
35816
|
+
self._prune_runtime_retry_hints()
|
|
35817
|
+
self.messages.append(
|
|
35818
|
+
{
|
|
35819
|
+
"role": "user",
|
|
35820
|
+
"content": (
|
|
35821
|
+
"<no-tool-recovery>"
|
|
35822
|
+
"你刚才说明要开始创建/编写/构建产物,但没有调用任何工具,因此产物并未创建。"
|
|
35823
|
+
"如果任务已完成,请调用 finish_task 并给出文件路径和验证证据;"
|
|
35824
|
+
"否则下一轮必须只执行一个具体工具调用来推进,例如 write_file 创建 HTML、"
|
|
35825
|
+
"edit_file 修改文件、或 bash 运行必要的复制/验证命令。不要再输出计划性说明。"
|
|
35826
|
+
f"目标: {goal}. 上一条文本: {latest}"
|
|
35827
|
+
"</no-tool-recovery>"
|
|
35828
|
+
),
|
|
35829
|
+
"ts": now_ts(),
|
|
35830
|
+
}
|
|
35831
|
+
)
|
|
35832
|
+
self._emit(
|
|
35833
|
+
"status",
|
|
35834
|
+
{"summary": "no-tool action promise detected; forcing one concrete tool turn"},
|
|
35835
|
+
)
|
|
35836
|
+
|
|
34559
35837
|
def _inject_thinking_empty_recovery_hint(self, streak: int = 1, budget_forced: bool = False):
|
|
34560
35838
|
self._prune_runtime_retry_hints()
|
|
34561
35839
|
budget_note = (
|
|
@@ -34693,7 +35971,7 @@ body{padding:18px}
|
|
|
34693
35971
|
if "<auto-context-recall>" in content:
|
|
34694
35972
|
return False
|
|
34695
35973
|
try:
|
|
34696
|
-
recalled = self._context_recall({"recent_segments": 1, "max_messages": 24, "
|
|
35974
|
+
recalled = self._context_recall({"recent_segments": 1, "max_messages": 24, "mode": "summary"})
|
|
34697
35975
|
except Exception:
|
|
34698
35976
|
return False
|
|
34699
35977
|
text = str(recalled or "").strip()
|
|
@@ -34791,13 +36069,13 @@ body{padding:18px}
|
|
|
34791
36069
|
def _context_recall(self, args: dict) -> str:
|
|
34792
36070
|
segment_id = str(args.get("segment_id", "") or "").strip()
|
|
34793
36071
|
query = str(args.get("query", "") or "").strip()
|
|
34794
|
-
|
|
34795
|
-
|
|
34796
|
-
|
|
36072
|
+
mode = str(args.get("mode", "") or "").strip().lower()
|
|
36073
|
+
role_filter = str(args.get("role", "") or "").strip()
|
|
36074
|
+
tool_filter = str(args.get("tool_name", "") or "").strip()
|
|
36075
|
+
recent_segments = self._tool_int_arg(args.get("recent_segments", 2), 2, 1, 20)
|
|
36076
|
+
max_messages = self._tool_int_arg(args.get("max_messages", 30), 30, 1, 120)
|
|
36077
|
+
offset = self._tool_int_arg(args.get("offset", 0), 0, 0, 100000)
|
|
34797
36078
|
include_tools = bool(args.get("include_tools", True))
|
|
34798
|
-
recent_segments = max(1, min(20, recent_segments))
|
|
34799
|
-
max_messages = max(1, min(120, max_messages))
|
|
34800
|
-
offset = max(0, offset)
|
|
34801
36079
|
|
|
34802
36080
|
segments: list[dict] = []
|
|
34803
36081
|
if segment_id:
|
|
@@ -34819,16 +36097,21 @@ body{padding:18px}
|
|
|
34819
36097
|
role = str(row.get("role", ""))
|
|
34820
36098
|
if not include_tools and role == "tool":
|
|
34821
36099
|
continue
|
|
36100
|
+
if role_filter and role_filter.lower() not in role.lower():
|
|
36101
|
+
continue
|
|
36102
|
+
name = str(row.get("name", "") or "")
|
|
36103
|
+
if tool_filter and tool_filter.lower() not in name.lower():
|
|
36104
|
+
continue
|
|
34822
36105
|
content = str(row.get("content", ""))
|
|
34823
36106
|
if query_low:
|
|
34824
|
-
pool = f"{role}\n{
|
|
36107
|
+
pool = f"{role}\n{name}\n{content}".lower()
|
|
34825
36108
|
if query_low not in pool:
|
|
34826
36109
|
continue
|
|
34827
36110
|
out_row = {
|
|
34828
36111
|
"segment_id": seg_id,
|
|
34829
36112
|
"index": idx,
|
|
34830
36113
|
"role": role,
|
|
34831
|
-
"name":
|
|
36114
|
+
"name": name,
|
|
34832
36115
|
"tool_call_id": row.get("tool_call_id", ""),
|
|
34833
36116
|
"ts": float(row.get("ts", 0.0) or 0.0),
|
|
34834
36117
|
"content": trim(content, 6000),
|
|
@@ -34837,8 +36120,6 @@ body{padding:18px}
|
|
|
34837
36120
|
out_row["thinking"] = trim(row.get("thinking", ""), 2500)
|
|
34838
36121
|
matches.append(out_row)
|
|
34839
36122
|
|
|
34840
|
-
total = len(matches)
|
|
34841
|
-
page = matches[offset : offset + max_messages]
|
|
34842
36123
|
selected_segments = [
|
|
34843
36124
|
{
|
|
34844
36125
|
"id": str(seg.get("id", "")),
|
|
@@ -34849,17 +36130,256 @@ body{padding:18px}
|
|
|
34849
36130
|
}
|
|
34850
36131
|
for seg in segments
|
|
34851
36132
|
]
|
|
34852
|
-
|
|
34853
|
-
|
|
34854
|
-
|
|
34855
|
-
|
|
34856
|
-
|
|
34857
|
-
|
|
34858
|
-
|
|
34859
|
-
|
|
34860
|
-
|
|
36133
|
+
if not mode and (args.get("offset") is not None):
|
|
36134
|
+
page = matches[offset : offset + max_messages]
|
|
36135
|
+
payload = {
|
|
36136
|
+
"mode": "legacy",
|
|
36137
|
+
"query": query,
|
|
36138
|
+
"segment_id": segment_id or "",
|
|
36139
|
+
"segments_considered": selected_segments,
|
|
36140
|
+
"total_matches": len(matches),
|
|
36141
|
+
"offset": offset,
|
|
36142
|
+
"returned": len(page),
|
|
36143
|
+
"matches": page,
|
|
36144
|
+
"focused_reads": [
|
|
36145
|
+
"context_recall mode='search' query='<term>'",
|
|
36146
|
+
"context_recall mode='window' around_index=<index> context=4",
|
|
36147
|
+
"context_recall mode='detail' query='<exact evidence>'",
|
|
36148
|
+
],
|
|
36149
|
+
}
|
|
36150
|
+
return trim(json_dumps(payload, indent=2), self._tool_max_chars(args.get("max_chars"), 24000))
|
|
36151
|
+
rendered = self._render_collection_tool_payload(
|
|
36152
|
+
tool="context_recall",
|
|
36153
|
+
rows=matches,
|
|
36154
|
+
mode=mode or ("search" if query else "summary"),
|
|
36155
|
+
query="",
|
|
36156
|
+
limit=max_messages,
|
|
36157
|
+
around_index=args.get("around_index"),
|
|
36158
|
+
context=args.get("context"),
|
|
36159
|
+
max_chars=args.get("max_chars"),
|
|
36160
|
+
filters={},
|
|
36161
|
+
summary_fields=["segment_id", "index", "role", "name", "content"],
|
|
36162
|
+
detail_fields=["segment_id", "index", "role", "name", "tool_call_id", "ts", "content", "thinking"],
|
|
36163
|
+
default_limit=12,
|
|
36164
|
+
)
|
|
36165
|
+
try:
|
|
36166
|
+
payload = parse_json_object(rendered, {})
|
|
36167
|
+
if isinstance(payload, dict):
|
|
36168
|
+
payload["query"] = query
|
|
36169
|
+
payload["role"] = role_filter
|
|
36170
|
+
payload["tool_name"] = tool_filter
|
|
36171
|
+
payload["segments_considered"] = selected_segments
|
|
36172
|
+
return trim(json_dumps(payload, indent=2), self._tool_max_chars(args.get("max_chars"), 24000))
|
|
36173
|
+
except Exception:
|
|
36174
|
+
pass
|
|
36175
|
+
return rendered
|
|
36176
|
+
|
|
36177
|
+
def _read_blackboard_enhanced(self, args: dict) -> str:
|
|
36178
|
+
section = str(args.get("section", "all") or "all").strip().lower()
|
|
36179
|
+
mode = str(args.get("mode", "") or "").strip().lower()
|
|
36180
|
+
query = str(args.get("query", "") or "").strip()
|
|
36181
|
+
limit = self._tool_int_arg(args.get("limit", 6), 6, 1, 80)
|
|
36182
|
+
board = self._ensure_blackboard()
|
|
36183
|
+
if section in {"", "all"} and not mode and not query and not args.get("actor") and not args.get("around_index"):
|
|
36184
|
+
return self._blackboard_read_state_markdown(max_items=min(20, limit))
|
|
36185
|
+
if section == "original_goal":
|
|
36186
|
+
return trim(str(board.get("original_goal", "") or "").strip(), self._tool_max_chars(args.get("max_chars"), 4000)) or "(empty)"
|
|
36187
|
+
if section == "status":
|
|
36188
|
+
wd = board.get("watchdog", {}) if isinstance(board.get("watchdog"), dict) else {}
|
|
36189
|
+
dq = board.get("decomposition_queue", {}) if isinstance(board.get("decomposition_queue"), dict) else {}
|
|
36190
|
+
return trim(json_dumps(
|
|
36191
|
+
{
|
|
36192
|
+
"status": board.get("status", "INITIALIZING"),
|
|
36193
|
+
"active_agent": board.get("active_agent", ""),
|
|
36194
|
+
"manager_cycles": int(board.get("manager_cycles", 0) or 0),
|
|
36195
|
+
"manager_summary_attempts": int(board.get("manager_summary_attempts", 0) or 0),
|
|
36196
|
+
"approval": board.get("approval", {}),
|
|
36197
|
+
"last_delegate": board.get("last_delegate", {}),
|
|
36198
|
+
"watchdog": {
|
|
36199
|
+
"intent_no_tool_streak": int(wd.get("intent_no_tool_streak", 0) or 0),
|
|
36200
|
+
"repeat_no_tool_streak": int(wd.get("repeat_no_tool_streak", 0) or 0),
|
|
36201
|
+
"state_unchanged_streak": int(wd.get("state_unchanged_streak", 0) or 0),
|
|
36202
|
+
"trigger_count": int(wd.get("trigger_count", 0) or 0),
|
|
36203
|
+
"last_trigger_reason": trim(str(wd.get("last_trigger_reason", "") or "").strip(), 160),
|
|
36204
|
+
},
|
|
36205
|
+
"decomposition_queue": {
|
|
36206
|
+
"active": bool(dq.get("active", False)),
|
|
36207
|
+
"trigger_reason": trim(str(dq.get("trigger_reason", "") or "").strip(), 160),
|
|
36208
|
+
"cursor": int(dq.get("cursor", 0) or 0),
|
|
36209
|
+
"total": len(dq.get("steps", []) or []),
|
|
36210
|
+
"last_error": trim(str(dq.get("last_error", "") or "").strip(), 220),
|
|
36211
|
+
},
|
|
36212
|
+
},
|
|
36213
|
+
indent=2,
|
|
36214
|
+
), self._tool_max_chars(args.get("max_chars"), 12000))
|
|
36215
|
+
sections = ["research_notes", "execution_logs", "review_feedback", "conversation_history"]
|
|
36216
|
+
rows: list[dict] = []
|
|
36217
|
+
if section == "code_artifacts":
|
|
36218
|
+
artifacts = board.get("code_artifacts", {})
|
|
36219
|
+
if not isinstance(artifacts, dict):
|
|
36220
|
+
artifacts = {}
|
|
36221
|
+
for key, value in artifacts.items():
|
|
36222
|
+
row = dict(value) if isinstance(value, dict) else {"value": value}
|
|
36223
|
+
row["key"] = key
|
|
36224
|
+
rows.append(row)
|
|
36225
|
+
elif section in sections:
|
|
36226
|
+
raw_rows = board.get(section, [])
|
|
36227
|
+
rows = [row for row in raw_rows if isinstance(row, dict)] if isinstance(raw_rows, list) else []
|
|
36228
|
+
elif section in {"", "all"}:
|
|
36229
|
+
for sec in sections:
|
|
36230
|
+
raw_rows = board.get(sec, [])
|
|
36231
|
+
if isinstance(raw_rows, list):
|
|
36232
|
+
for row in raw_rows:
|
|
36233
|
+
if isinstance(row, dict):
|
|
36234
|
+
item = dict(row)
|
|
36235
|
+
item["section"] = sec
|
|
36236
|
+
rows.append(item)
|
|
36237
|
+
artifacts = board.get("code_artifacts", {})
|
|
36238
|
+
if isinstance(artifacts, dict):
|
|
36239
|
+
for key, value in artifacts.items():
|
|
36240
|
+
item = dict(value) if isinstance(value, dict) else {"value": value}
|
|
36241
|
+
item["section"] = "code_artifacts"
|
|
36242
|
+
item["key"] = key
|
|
36243
|
+
rows.append(item)
|
|
36244
|
+
else:
|
|
36245
|
+
return f"Error: unsupported blackboard section '{section}'"
|
|
36246
|
+
return self._render_collection_tool_payload(
|
|
36247
|
+
tool="read_from_blackboard",
|
|
36248
|
+
rows=rows,
|
|
36249
|
+
mode=mode or ("search" if query else "summary"),
|
|
36250
|
+
query=query,
|
|
36251
|
+
limit=limit,
|
|
36252
|
+
around_index=args.get("around_index"),
|
|
36253
|
+
context=args.get("context"),
|
|
36254
|
+
max_chars=args.get("max_chars"),
|
|
36255
|
+
filters={"actor": args.get("actor"), "status": args.get("status")},
|
|
36256
|
+
summary_fields=["section", "key", "actor", "status", "content", "summary", "path"],
|
|
36257
|
+
detail_fields=[],
|
|
36258
|
+
default_limit=limit,
|
|
36259
|
+
)
|
|
36260
|
+
|
|
36261
|
+
def _task_list_enhanced(self, args: dict) -> str:
|
|
36262
|
+
rows = []
|
|
36263
|
+
for task in self.tasks.list_objects():
|
|
36264
|
+
if isinstance(task, dict):
|
|
36265
|
+
rows.append(dict(task))
|
|
36266
|
+
if not rows:
|
|
36267
|
+
return "No tasks."
|
|
36268
|
+
mode = str(args.get("mode", "") or "").strip().lower()
|
|
36269
|
+
query = str(args.get("query", "") or "").strip()
|
|
36270
|
+
limit = self._tool_int_arg(args.get("limit", 30), 30, 1, 200)
|
|
36271
|
+
if not mode and not query and not args.get("status") and not args.get("owner"):
|
|
36272
|
+
return self.tasks.list_all()
|
|
36273
|
+
return self._render_collection_tool_payload(
|
|
36274
|
+
tool="task_list",
|
|
36275
|
+
rows=rows,
|
|
36276
|
+
mode=mode or ("search" if query else "summary"),
|
|
36277
|
+
query=query,
|
|
36278
|
+
limit=limit,
|
|
36279
|
+
max_chars=args.get("max_chars"),
|
|
36280
|
+
filters={"status": args.get("status"), "owner": args.get("owner")},
|
|
36281
|
+
summary_fields=["id", "status", "owner", "subject", "blockedBy", "worktree", "updated_at"],
|
|
36282
|
+
detail_fields=[],
|
|
36283
|
+
default_limit=limit,
|
|
36284
|
+
)
|
|
36285
|
+
|
|
36286
|
+
def _check_background_enhanced(self, args: dict) -> str:
|
|
36287
|
+
task_id = str(args.get("task_id", "") or "").strip()
|
|
36288
|
+
mode = str(args.get("mode", "") or "").strip().lower()
|
|
36289
|
+
query = str(args.get("query", "") or "").strip()
|
|
36290
|
+
if task_id and not mode and not query:
|
|
36291
|
+
return self.bg.check(task_id)
|
|
36292
|
+
rows = self.bg.list_objects()
|
|
36293
|
+
if not rows:
|
|
36294
|
+
return "No bg tasks."
|
|
36295
|
+
if not task_id and not mode and not query and not args.get("status"):
|
|
36296
|
+
return self.bg.check(None)
|
|
36297
|
+
known_ids = {str(row.get("id", "") or "").strip() for row in rows if isinstance(row, dict)}
|
|
36298
|
+
if task_id and task_id not in known_ids and (mode or query or args.get("status")):
|
|
36299
|
+
if not query:
|
|
36300
|
+
query = task_id
|
|
36301
|
+
task_id = ""
|
|
36302
|
+
if query and mode in {"tail", "recent"}:
|
|
36303
|
+
mode = "search"
|
|
36304
|
+
filters = {"status": args.get("status")}
|
|
36305
|
+
if task_id:
|
|
36306
|
+
filters["id"] = task_id
|
|
36307
|
+
return self._render_collection_tool_payload(
|
|
36308
|
+
tool="check_background",
|
|
36309
|
+
rows=rows,
|
|
36310
|
+
mode=mode or ("search" if query else "summary"),
|
|
36311
|
+
query=query,
|
|
36312
|
+
limit=args.get("limit", 20),
|
|
36313
|
+
max_chars=args.get("max_chars"),
|
|
36314
|
+
filters=filters,
|
|
36315
|
+
summary_fields=["id", "status", "command", "started_at", "finished_at"],
|
|
36316
|
+
detail_fields=[],
|
|
36317
|
+
default_limit=20,
|
|
36318
|
+
)
|
|
36319
|
+
|
|
36320
|
+
def _read_inbox_enhanced(self, args: dict) -> str:
|
|
36321
|
+
mode = str(args.get("mode", "") or "").strip().lower()
|
|
36322
|
+
query = str(args.get("query", "") or "").strip()
|
|
36323
|
+
# Backward compatibility: no arguments means consume the inbox as before.
|
|
36324
|
+
if not args or (not mode and not query and not args.get("from") and not args.get("type")):
|
|
36325
|
+
return json_dumps(self.bus.read_inbox("lead"), indent=2)
|
|
36326
|
+
rows = self.bus.peek_inbox("lead")
|
|
36327
|
+
if mode == "drain":
|
|
36328
|
+
rows = self.bus.read_inbox("lead")
|
|
36329
|
+
if not rows:
|
|
36330
|
+
return "[]"
|
|
36331
|
+
return self._render_collection_tool_payload(
|
|
36332
|
+
tool="read_inbox",
|
|
36333
|
+
rows=rows,
|
|
36334
|
+
mode=mode or ("search" if query else "peek"),
|
|
36335
|
+
query=query,
|
|
36336
|
+
limit=args.get("limit", 20),
|
|
36337
|
+
max_chars=args.get("max_chars"),
|
|
36338
|
+
filters={"from": args.get("from"), "type": args.get("type")},
|
|
36339
|
+
summary_fields=["timestamp", "from", "type", "content"],
|
|
36340
|
+
detail_fields=[],
|
|
36341
|
+
default_limit=20,
|
|
36342
|
+
)
|
|
36343
|
+
|
|
36344
|
+
def _worktree_events_enhanced(self, args: dict) -> str:
|
|
36345
|
+
rows = self.worktrees.events_objects()
|
|
36346
|
+
if not rows:
|
|
36347
|
+
return "[]"
|
|
36348
|
+
mode = str(args.get("mode", "") or "").strip().lower()
|
|
36349
|
+
query = str(args.get("query", "") or "").strip()
|
|
36350
|
+
event_arg = str(args.get("event", "") or "").strip()
|
|
36351
|
+
worktree_arg = str(args.get("worktree", "") or "").strip()
|
|
36352
|
+
limit = self._tool_int_arg(args.get("limit", 20), 20, 1, 200)
|
|
36353
|
+
known_events = {
|
|
36354
|
+
str(row.get("event", "") or "").strip().lower()
|
|
36355
|
+
for row in rows
|
|
36356
|
+
if isinstance(row, dict) and str(row.get("event", "") or "").strip()
|
|
34861
36357
|
}
|
|
34862
|
-
|
|
36358
|
+
if event_arg and event_arg.lower() not in known_events and "." not in event_arg:
|
|
36359
|
+
if not worktree_arg:
|
|
36360
|
+
worktree_arg = event_arg
|
|
36361
|
+
elif not query:
|
|
36362
|
+
query = event_arg
|
|
36363
|
+
event_arg = ""
|
|
36364
|
+
if not mode and not query and not event_arg and not worktree_arg and not args.get("task_id"):
|
|
36365
|
+
return self.worktrees.events_recent(limit)
|
|
36366
|
+
filters = {
|
|
36367
|
+
"event": event_arg,
|
|
36368
|
+
"worktree.name": worktree_arg,
|
|
36369
|
+
"task.id": args.get("task_id"),
|
|
36370
|
+
}
|
|
36371
|
+
return self._render_collection_tool_payload(
|
|
36372
|
+
tool="worktree_events",
|
|
36373
|
+
rows=rows,
|
|
36374
|
+
mode=mode or ("search" if query else "summary"),
|
|
36375
|
+
query=query,
|
|
36376
|
+
limit=limit,
|
|
36377
|
+
max_chars=args.get("max_chars"),
|
|
36378
|
+
filters=filters,
|
|
36379
|
+
summary_fields=["ts", "event", "task", "worktree", "error"],
|
|
36380
|
+
detail_fields=[],
|
|
36381
|
+
default_limit=limit,
|
|
36382
|
+
)
|
|
34863
36383
|
|
|
34864
36384
|
def _attempt_malformed_tool_repair(self, name: str, raw_args: object) -> tuple[bool, str]:
|
|
34865
36385
|
# Safe auto-repair only for todo tools; file/code tools require regenerate.
|
|
@@ -34980,10 +36500,24 @@ body{padding:18px}
|
|
|
34980
36500
|
rel = self._session_rel(fp)
|
|
34981
36501
|
except Exception as exc:
|
|
34982
36502
|
return f"Error: {type(exc).__name__}: {exc}"
|
|
34983
|
-
out = self._run_read(
|
|
34984
|
-
|
|
34985
|
-
|
|
36503
|
+
out = self._run_read(
|
|
36504
|
+
rel,
|
|
36505
|
+
args.get("limit"),
|
|
36506
|
+
args.get("offset"),
|
|
36507
|
+
mode=args.get("mode"),
|
|
36508
|
+
target=args.get("target"),
|
|
36509
|
+
query=args.get("query"),
|
|
36510
|
+
line=args.get("line"),
|
|
36511
|
+
context=args.get("context"),
|
|
36512
|
+
regex=args.get("regex"),
|
|
36513
|
+
max_chars=args.get("max_chars"),
|
|
36514
|
+
)
|
|
36515
|
+
limit_val = self._read_file_int_arg(args.get("limit", 0), 0, 0, 1_000_000) if args.get("limit") is not None else 0
|
|
36516
|
+
offset_val = self._read_file_int_arg(args.get("offset", 0), 0, 0, 1_000_000) if args.get("offset") is not None else 0
|
|
36517
|
+
mode_val = str(args.get("mode", "") or "").strip()
|
|
34986
36518
|
summary = f"read file: {rel}"
|
|
36519
|
+
if mode_val:
|
|
36520
|
+
summary += f" mode={mode_val}"
|
|
34987
36521
|
if offset_val > 0 or limit_val > 0:
|
|
34988
36522
|
summary += (
|
|
34989
36523
|
f" offset={offset_val}"
|
|
@@ -34993,12 +36527,13 @@ body{padding:18px}
|
|
|
34993
36527
|
"file_read",
|
|
34994
36528
|
{
|
|
34995
36529
|
"path": rel,
|
|
36530
|
+
"mode": mode_val or "auto",
|
|
34996
36531
|
"offset": offset_val,
|
|
34997
36532
|
"limit": limit_val,
|
|
34998
36533
|
"summary": summary,
|
|
34999
36534
|
"large_file_guard": bool(
|
|
35000
36535
|
limit_val <= 0
|
|
35001
|
-
and str(out).startswith("[
|
|
36536
|
+
and str(out).startswith("[read_file overview")
|
|
35002
36537
|
),
|
|
35003
36538
|
},
|
|
35004
36539
|
)
|
|
@@ -35205,8 +36740,9 @@ body{padding:18px}
|
|
|
35205
36740
|
):
|
|
35206
36741
|
return (
|
|
35207
36742
|
"Error: reviewer finalization requires blackboard evidence read. "
|
|
35208
|
-
"Call read_from_blackboard
|
|
35209
|
-
"review_feedback, status),
|
|
36743
|
+
"Call read_from_blackboard with mode='summary' or mode='search' "
|
|
36744
|
+
"(sections: code_artifacts, execution_logs, review_feedback, status), "
|
|
36745
|
+
"then call finish_task with structured summary."
|
|
35210
36746
|
)
|
|
35211
36747
|
if not self._final_summary_sufficient(summary, strict=True):
|
|
35212
36748
|
return (
|
|
@@ -35215,7 +36751,8 @@ body{padding:18px}
|
|
|
35215
36751
|
"(1) changes/files touched, "
|
|
35216
36752
|
"(2) validation evidence (tests/commands/results), "
|
|
35217
36753
|
"(3) residual risks or next steps. "
|
|
35218
|
-
"If evidence is missing, read_from_blackboard
|
|
36754
|
+
"If evidence is missing, use read_from_blackboard mode='summary' or mode='search', "
|
|
36755
|
+
"or ask Explorer for final_summary_request."
|
|
35219
36756
|
)
|
|
35220
36757
|
if name == "finish_task":
|
|
35221
36758
|
todo_mark = self.todo.complete_all_open(summary)
|
|
@@ -35351,7 +36888,7 @@ body{padding:18px}
|
|
|
35351
36888
|
)
|
|
35352
36889
|
return str(payload.get("output", out_filtered or "(no output)"))
|
|
35353
36890
|
if name == "check_background":
|
|
35354
|
-
return self.
|
|
36891
|
+
return self._check_background_enhanced(args)
|
|
35355
36892
|
if name == "task_create":
|
|
35356
36893
|
return self.tasks.create(args["subject"], args.get("description", ""))
|
|
35357
36894
|
if name == "task_get":
|
|
@@ -35359,7 +36896,7 @@ body{padding:18px}
|
|
|
35359
36896
|
if name == "task_update":
|
|
35360
36897
|
return self.tasks.update(int(args["task_id"]), args.get("status"), args.get("add_blocked_by"), args.get("add_blocks"))
|
|
35361
36898
|
if name == "task_list":
|
|
35362
|
-
return self.
|
|
36899
|
+
return self._task_list_enhanced(args)
|
|
35363
36900
|
if name == "claim_task":
|
|
35364
36901
|
return self.tasks.claim(int(args["task_id"]), "lead")
|
|
35365
36902
|
if name == "spawn_teammate":
|
|
@@ -35402,57 +36939,7 @@ body{padding:18px}
|
|
|
35402
36939
|
f"intent={env.get('intent')}, id={env.get('id')})"
|
|
35403
36940
|
)
|
|
35404
36941
|
if name == "read_from_blackboard":
|
|
35405
|
-
|
|
35406
|
-
limit = max(1, min(20, int(args.get("limit", 6) or 6)))
|
|
35407
|
-
board = self._ensure_blackboard()
|
|
35408
|
-
if section in {"", "all"}:
|
|
35409
|
-
return self._blackboard_read_state_markdown(max_items=limit)
|
|
35410
|
-
if section == "original_goal":
|
|
35411
|
-
return trim(str(board.get("original_goal", "") or "").strip(), 4000) or "(empty)"
|
|
35412
|
-
if section == "status":
|
|
35413
|
-
wd = board.get("watchdog", {}) if isinstance(board.get("watchdog"), dict) else {}
|
|
35414
|
-
dq = board.get("decomposition_queue", {}) if isinstance(board.get("decomposition_queue"), dict) else {}
|
|
35415
|
-
return json_dumps(
|
|
35416
|
-
{
|
|
35417
|
-
"status": board.get("status", "INITIALIZING"),
|
|
35418
|
-
"active_agent": board.get("active_agent", ""),
|
|
35419
|
-
"manager_cycles": int(board.get("manager_cycles", 0) or 0),
|
|
35420
|
-
"manager_summary_attempts": int(board.get("manager_summary_attempts", 0) or 0),
|
|
35421
|
-
"approval": board.get("approval", {}),
|
|
35422
|
-
"last_delegate": board.get("last_delegate", {}),
|
|
35423
|
-
"watchdog": {
|
|
35424
|
-
"intent_no_tool_streak": int(wd.get("intent_no_tool_streak", 0) or 0),
|
|
35425
|
-
"repeat_no_tool_streak": int(wd.get("repeat_no_tool_streak", 0) or 0),
|
|
35426
|
-
"state_unchanged_streak": int(wd.get("state_unchanged_streak", 0) or 0),
|
|
35427
|
-
"trigger_count": int(wd.get("trigger_count", 0) or 0),
|
|
35428
|
-
"last_trigger_reason": trim(str(wd.get("last_trigger_reason", "") or "").strip(), 160),
|
|
35429
|
-
},
|
|
35430
|
-
"decomposition_queue": {
|
|
35431
|
-
"active": bool(dq.get("active", False)),
|
|
35432
|
-
"trigger_reason": trim(str(dq.get("trigger_reason", "") or "").strip(), 160),
|
|
35433
|
-
"cursor": int(dq.get("cursor", 0) or 0),
|
|
35434
|
-
"total": len(dq.get("steps", []) or []),
|
|
35435
|
-
"last_error": trim(str(dq.get("last_error", "") or "").strip(), 220),
|
|
35436
|
-
},
|
|
35437
|
-
},
|
|
35438
|
-
indent=2,
|
|
35439
|
-
)
|
|
35440
|
-
if section == "code_artifacts":
|
|
35441
|
-
artifacts = board.get("code_artifacts", {})
|
|
35442
|
-
if not isinstance(artifacts, dict):
|
|
35443
|
-
artifacts = {}
|
|
35444
|
-
rows = sorted(
|
|
35445
|
-
list(artifacts.items()),
|
|
35446
|
-
key=lambda item: float((item[1] or {}).get("updated_at", 0.0) if isinstance(item[1], dict) else 0.0),
|
|
35447
|
-
reverse=True,
|
|
35448
|
-
)
|
|
35449
|
-
return json_dumps({k: v for k, v in rows[:limit]}, indent=2)
|
|
35450
|
-
if section in {"research_notes", "execution_logs", "review_feedback", "conversation_history"}:
|
|
35451
|
-
rows = board.get(section, [])
|
|
35452
|
-
if not isinstance(rows, list):
|
|
35453
|
-
rows = []
|
|
35454
|
-
return json_dumps(rows[-limit:], indent=2)
|
|
35455
|
-
return f"Error: unsupported blackboard section '{section}'"
|
|
36942
|
+
return self._read_blackboard_enhanced(args)
|
|
35456
36943
|
if name == "write_to_blackboard":
|
|
35457
36944
|
section = str(args.get("section", "") or "").strip().lower()
|
|
35458
36945
|
content = trim(str(args.get("content", "") or "").strip(), BLACKBOARD_MAX_TEXT)
|
|
@@ -35482,7 +36969,7 @@ body{padding:18px}
|
|
|
35482
36969
|
if name == "send_message":
|
|
35483
36970
|
return self.bus.send("lead", args["to"], args["content"], args.get("msg_type", "message"))
|
|
35484
36971
|
if name == "read_inbox":
|
|
35485
|
-
return
|
|
36972
|
+
return self._read_inbox_enhanced(args)
|
|
35486
36973
|
if name == "broadcast":
|
|
35487
36974
|
return self.bus.broadcast("lead", args["content"], list(self.teammates.keys()))
|
|
35488
36975
|
if name == "shutdown_request":
|
|
@@ -35541,7 +37028,7 @@ body{padding:18px}
|
|
|
35541
37028
|
if name == "worktree_remove":
|
|
35542
37029
|
return self.worktrees.remove(args["name"], bool(args.get("force", False)), bool(args.get("complete_task", False)))
|
|
35543
37030
|
if name == "worktree_events":
|
|
35544
|
-
return self.
|
|
37031
|
+
return self._worktree_events_enhanced(args)
|
|
35545
37032
|
return f"Unknown tool: {name}"
|
|
35546
37033
|
|
|
35547
37034
|
def _live_input_delay_locked(self) -> tuple[int, str]:
|
|
@@ -36166,6 +37653,12 @@ body{padding:18px}
|
|
|
36166
37653
|
if not ctx:
|
|
36167
37654
|
return {"status": "skip", "reason": "empty-context", "role": role_key}
|
|
36168
37655
|
self._microcompact_agent_messages(ctx)
|
|
37656
|
+
self._apply_auto_compact_if_needed(
|
|
37657
|
+
f"auto:agent:{role_key}",
|
|
37658
|
+
role=role_key,
|
|
37659
|
+
media_inputs=media_inputs_round,
|
|
37660
|
+
)
|
|
37661
|
+
ctx = self._agent_context(role_key)
|
|
36169
37662
|
with self.lock:
|
|
36170
37663
|
self.current_phase = f"agent:{role_key}:model-call"
|
|
36171
37664
|
self.current_tool_name = ""
|
|
@@ -36290,7 +37783,7 @@ body{padding:18px}
|
|
|
36290
37783
|
"role": "tool",
|
|
36291
37784
|
"tool_call_id": tc["id"],
|
|
36292
37785
|
"name": name,
|
|
36293
|
-
"content":
|
|
37786
|
+
"content": self._tool_result_context_content(name, args if isinstance(args, dict) else {}, output),
|
|
36294
37787
|
"ts": now_ts(),
|
|
36295
37788
|
"agent_role": role_key,
|
|
36296
37789
|
},
|
|
@@ -36545,7 +38038,7 @@ body{padding:18px}
|
|
|
36545
38038
|
self._emit("status", {"summary": "sync loop break: stall escalated to plan mode"})
|
|
36546
38039
|
break
|
|
36547
38040
|
self._inject_pending_user_inputs()
|
|
36548
|
-
self._apply_auto_compact_if_needed("auto:multi-sync")
|
|
38041
|
+
self._apply_auto_compact_if_needed("auto:multi-sync", role="manager")
|
|
36549
38042
|
# Periodic checkpoint in multi-agent sync loop
|
|
36550
38043
|
if rounds_used % CHECKPOINT_INTERVAL_ROUNDS == 0:
|
|
36551
38044
|
self._maybe_create_checkpoint()
|
|
@@ -36898,7 +38391,7 @@ body{padding:18px}
|
|
|
36898
38391
|
if self.cancel_requested:
|
|
36899
38392
|
self._emit("status", {"summary": "run interrupted"})
|
|
36900
38393
|
break
|
|
36901
|
-
self._apply_auto_compact_if_needed("auto:multi-seq")
|
|
38394
|
+
self._apply_auto_compact_if_needed("auto:multi-seq", role=current_role)
|
|
36902
38395
|
with self.lock:
|
|
36903
38396
|
self.agent_round_index = int(self.agent_round_index) + 1
|
|
36904
38397
|
latest_user_ts = self._latest_user_message_ts()
|
|
@@ -37118,11 +38611,13 @@ body{padding:18px}
|
|
|
37118
38611
|
if preview:
|
|
37119
38612
|
skill_previews.append(f"- **{skey}**: {preview}")
|
|
37120
38613
|
if skill_previews:
|
|
38614
|
+
active_skill_workflows = self._loaded_skills_context_block(for_role="explorer", max_chars=5000)
|
|
37121
38615
|
skills_section = (
|
|
37122
38616
|
"\n## Loaded Skills\n"
|
|
37123
38617
|
+ "\n".join(skill_previews)
|
|
38618
|
+
+ (f"\n\n{active_skill_workflows}\n" if active_skill_workflows else "")
|
|
37124
38619
|
+ "\n\nCRITICAL: For each loaded skill, you MUST:\n"
|
|
37125
|
-
"1. Read the skill
|
|
38620
|
+
"1. Read the active-skill workflow block or <loaded-skill> content available in your context\n"
|
|
37126
38621
|
"2. Identify the skill's concrete workflow steps (e.g., what scripts to run, what files to read/write)\n"
|
|
37127
38622
|
"3. List the specific tool calls and file paths each skill requires\n"
|
|
37128
38623
|
"4. Include these details in your plan_findings so the synthesis phase can produce actionable steps\n"
|
|
@@ -37163,13 +38658,15 @@ body{padding:18px}
|
|
|
37163
38658
|
self._append_agent_context_message("explorer", {
|
|
37164
38659
|
"role": "system",
|
|
37165
38660
|
"content": (
|
|
37166
|
-
|
|
37167
|
-
|
|
37168
|
-
|
|
37169
|
-
|
|
37170
|
-
|
|
37171
|
-
|
|
37172
|
-
|
|
38661
|
+
"You are Explorer in plan-mode (read-only research). "
|
|
38662
|
+
"Analyze the codebase to understand the task scope. "
|
|
38663
|
+
"Do NOT modify any files. Use read_file, bash (read-only commands), "
|
|
38664
|
+
"list_skills, load_skill, and blackboard tools only. "
|
|
38665
|
+
"When reading files, choose the shape that matches the question: mode='window' for file:line, mode='symbol' for named code, mode='search' for keywords/errors, mode='overview' for structure, and mode='full' only when exact broad context is required. "
|
|
38666
|
+
"When reading blackboard or archived context, use mode='summary' first, then mode='search' or mode='window' for focused evidence. "
|
|
38667
|
+
f"{skills_block}"
|
|
38668
|
+
"IMPORTANT: If the task requires specialized output (PPTX, reports, deep research, code review), "
|
|
38669
|
+
"call list_skills first to discover relevant skills, then note in plan_findings which skills to use. "
|
|
37173
38670
|
f"{os_note} "
|
|
37174
38671
|
f"{model_language_instruction(self.ui_language)}"
|
|
37175
38672
|
),
|
|
@@ -37200,6 +38697,8 @@ body{padding:18px}
|
|
|
37200
38697
|
self.current_phase = f"plan-mode:explorer:round-{round_idx}"
|
|
37201
38698
|
self.current_tool_name = ""
|
|
37202
38699
|
self.active_agent_role = "explorer"
|
|
38700
|
+
self._apply_auto_compact_if_needed("auto:plan-explorer", role="explorer")
|
|
38701
|
+
ctx = self._agent_context("explorer")
|
|
37203
38702
|
# Build skills awareness block (same as sync/single mode)
|
|
37204
38703
|
skills_block = self._skills_awareness_block(for_role="explorer")
|
|
37205
38704
|
response = self._chat_with_same_model_retry(
|
|
@@ -37209,6 +38708,8 @@ body{padding:18px}
|
|
|
37209
38708
|
"You are Explorer in plan-mode research. Read-only analysis. "
|
|
37210
38709
|
"Do NOT create, write, or edit files. "
|
|
37211
38710
|
f"Workspace: \"{self.files_root}\" ($SESSION_ROOT). "
|
|
38711
|
+
"When reading files, choose the shape that matches the question: mode='window' for file:line, mode='symbol' for named code, mode='search' for keywords/errors, mode='overview' for structure, and mode='full' only when exact broad context is required. "
|
|
38712
|
+
"When reading blackboard or archived context, use mode='summary' first, then mode='search' or mode='window' for focused evidence. "
|
|
37212
38713
|
f"{skills_block}"
|
|
37213
38714
|
f"{_detect_os_shell_instruction()} "
|
|
37214
38715
|
f"{model_language_instruction(self.ui_language)}"
|
|
@@ -37292,7 +38793,12 @@ body{padding:18px}
|
|
|
37292
38793
|
self._append_agent_context_message("explorer", {
|
|
37293
38794
|
"role": "tool",
|
|
37294
38795
|
"tool_call_id": tc["id"],
|
|
37295
|
-
"content":
|
|
38796
|
+
"content": self._tool_result_context_content(
|
|
38797
|
+
fn_name,
|
|
38798
|
+
fn_args if isinstance(fn_args, dict) else {},
|
|
38799
|
+
result_content,
|
|
38800
|
+
8000,
|
|
38801
|
+
),
|
|
37296
38802
|
"ts": now_ts(),
|
|
37297
38803
|
"agent_role": "explorer",
|
|
37298
38804
|
}, mirror_to_global=False)
|
|
@@ -37798,9 +39304,11 @@ body{padding:18px}
|
|
|
37798
39304
|
if preview:
|
|
37799
39305
|
skill_previews.append(f"- {skey}: {preview}")
|
|
37800
39306
|
if skill_previews:
|
|
39307
|
+
active_skill_workflows = self._loaded_skills_context_block(for_role="developer", max_chars=6500)
|
|
37801
39308
|
skills_section = (
|
|
37802
39309
|
"\n## Available Skills\n"
|
|
37803
39310
|
+ "\n".join(skill_previews)
|
|
39311
|
+
+ (f"\n\n{active_skill_workflows}\n" if active_skill_workflows else "")
|
|
37804
39312
|
+ "\n\nWhen skills are loaded, each step MUST specify concrete actions:\n"
|
|
37805
39313
|
"- Which tool to call (bash, read_file, write_file, etc.)\n"
|
|
37806
39314
|
"- Which specific file paths to use (e.g., 'Read uploaded/IEDM_.parsed.md')\n"
|
|
@@ -37887,14 +39395,32 @@ body{padding:18px}
|
|
|
37887
39395
|
), "ts": now_ts()},
|
|
37888
39396
|
{"role": "user", "content": synthesis_prompt, "ts": now_ts()},
|
|
37889
39397
|
]
|
|
39398
|
+
synthesis_tools = self._plan_mode_synthesis_tools()
|
|
39399
|
+
synthesis_system = (
|
|
39400
|
+
"Generate a structured plan proposal. "
|
|
39401
|
+
"You MUST call submit_plan_proposal exactly once. "
|
|
39402
|
+
"Do not answer with plain text."
|
|
39403
|
+
)
|
|
39404
|
+
synthesis_metrics = self._context_metrics_for_model_call(
|
|
39405
|
+
synthesis_ctx,
|
|
39406
|
+
tools=synthesis_tools,
|
|
39407
|
+
system=synthesis_system,
|
|
39408
|
+
label="plan-mode synthesis",
|
|
39409
|
+
)
|
|
39410
|
+
synthesis_tier = self._context_compression_tier(synthesis_metrics)
|
|
39411
|
+
if synthesis_tier >= 1:
|
|
39412
|
+
self._compact_plan_context(synthesis_tier)
|
|
39413
|
+
self._compact_role_context("explorer", synthesis_tier)
|
|
39414
|
+
if synthesis_tier >= 2:
|
|
39415
|
+
compact_findings = trim(findings_text, 2400 if synthesis_tier == 2 else 1200)
|
|
39416
|
+
synthesis_ctx[1]["content"] = synthesis_ctx[1]["content"].replace(
|
|
39417
|
+
f"## Research Findings\n{trim(findings_text, 6000)}",
|
|
39418
|
+
f"## Research Findings\n{compact_findings}",
|
|
39419
|
+
)
|
|
37890
39420
|
response = self._chat_with_same_model_retry(
|
|
37891
39421
|
synthesis_ctx,
|
|
37892
|
-
tools=
|
|
37893
|
-
system=
|
|
37894
|
-
"Generate a structured plan proposal. "
|
|
37895
|
-
"You MUST call submit_plan_proposal exactly once. "
|
|
37896
|
-
"Do not answer with plain text."
|
|
37897
|
-
),
|
|
39422
|
+
tools=synthesis_tools,
|
|
39423
|
+
system=synthesis_system,
|
|
37898
39424
|
max_tokens=PLAN_MODE_MANAGER_SYNTHESIS_MAX_TOKENS,
|
|
37899
39425
|
think=False,
|
|
37900
39426
|
stream_thinking=False,
|
|
@@ -37917,7 +39443,7 @@ body{padding:18px}
|
|
|
37917
39443
|
"ts": now_ts(),
|
|
37918
39444
|
}
|
|
37919
39445
|
],
|
|
37920
|
-
tools=
|
|
39446
|
+
tools=synthesis_tools,
|
|
37921
39447
|
system="You MUST call submit_plan_proposal exactly once. Do not answer with plain text.",
|
|
37922
39448
|
max_tokens=PLAN_MODE_MANAGER_SYNTHESIS_MAX_TOKENS,
|
|
37923
39449
|
think=False,
|
|
@@ -38692,6 +40218,56 @@ body{padding:18px}
|
|
|
38692
40218
|
target = base_level + int(shift)
|
|
38693
40219
|
return max(min(TASK_LEVEL_CHOICES), min(max(TASK_LEVEL_CHOICES), int(target)))
|
|
38694
40220
|
|
|
40221
|
+
def _prune_planner_context_after_plan_approval(self, choice_id: str, chosen: dict, grouped_steps: list):
|
|
40222
|
+
planner_rows = [
|
|
40223
|
+
m for m in self.messages
|
|
40224
|
+
if isinstance(m, dict) and str(m.get("agent_role", "") or "") == "planner"
|
|
40225
|
+
]
|
|
40226
|
+
if not planner_rows:
|
|
40227
|
+
return
|
|
40228
|
+
seg = self._archive_context_segment(planner_rows, "plan-approved")
|
|
40229
|
+
seg_id = str(seg.get("id", "") if isinstance(seg, dict) else "")
|
|
40230
|
+
title = trim(str((chosen or {}).get("title", "") or choice_id), 220)
|
|
40231
|
+
summary = trim(str((chosen or {}).get("summary", "") or ""), 900)
|
|
40232
|
+
steps_text = "\n".join(
|
|
40233
|
+
f"- {trim(str(step or ''), 260)}"
|
|
40234
|
+
for step in list(grouped_steps or [])[:12]
|
|
40235
|
+
if str(step or "").strip()
|
|
40236
|
+
)
|
|
40237
|
+
handoff = (
|
|
40238
|
+
"<plan-approved-handoff>\n"
|
|
40239
|
+
f"chosen: {choice_id} {title}\n"
|
|
40240
|
+
f"summary: {summary}\n"
|
|
40241
|
+
f"steps:\n{steps_text or '- See blackboard plan.steps and .clouds_coder/plan.md'}\n"
|
|
40242
|
+
f"archived_planner_context: {seg_id or 'none'}\n"
|
|
40243
|
+
"Execution source of truth: blackboard plan.steps, project_todos, and .clouds_coder/plan.md. "
|
|
40244
|
+
"Old plan-mode research/proposal bubbles were compacted after approval; use context_recall only for missing evidence.\n"
|
|
40245
|
+
"</plan-approved-handoff>"
|
|
40246
|
+
)
|
|
40247
|
+
self.messages = [
|
|
40248
|
+
m for m in self.messages
|
|
40249
|
+
if not (isinstance(m, dict) and str(m.get("agent_role", "") or "") == "planner")
|
|
40250
|
+
][-380:]
|
|
40251
|
+
self.messages.append(
|
|
40252
|
+
{
|
|
40253
|
+
"role": "assistant",
|
|
40254
|
+
"content": trim(handoff, 5000),
|
|
40255
|
+
"ts": now_ts(),
|
|
40256
|
+
"agent_role": "planner",
|
|
40257
|
+
"_compact_planner_handoff": True,
|
|
40258
|
+
}
|
|
40259
|
+
)
|
|
40260
|
+
self.runtime_plan_proposal = {
|
|
40261
|
+
"context": "approved plan compacted",
|
|
40262
|
+
"options": [dict(chosen or {})],
|
|
40263
|
+
"recommended": choice_id,
|
|
40264
|
+
}
|
|
40265
|
+
try:
|
|
40266
|
+
self._compact_role_context("explorer", 2)
|
|
40267
|
+
self._compact_shared_context(1)
|
|
40268
|
+
except Exception:
|
|
40269
|
+
pass
|
|
40270
|
+
|
|
38695
40271
|
def _inject_plan_into_context(self, choice_id: str):
|
|
38696
40272
|
chosen = next(
|
|
38697
40273
|
(o for o in self.runtime_plan_proposal.get("options", [])
|
|
@@ -38843,6 +40419,10 @@ body{padding:18px}
|
|
|
38843
40419
|
self._preload_skills_from_plan_steps(grouped_steps)
|
|
38844
40420
|
except Exception:
|
|
38845
40421
|
pass
|
|
40422
|
+
try:
|
|
40423
|
+
self._prune_planner_context_after_plan_approval(choice_id, chosen, grouped_steps)
|
|
40424
|
+
except Exception:
|
|
40425
|
+
pass
|
|
38846
40426
|
|
|
38847
40427
|
def _preload_skills_from_plan_steps(self, steps: list):
|
|
38848
40428
|
"""Scan plan step text for skill names and auto-load any that aren't already loaded."""
|
|
@@ -39054,7 +40634,7 @@ body{padding:18px}
|
|
|
39054
40634
|
},
|
|
39055
40635
|
)
|
|
39056
40636
|
break
|
|
39057
|
-
self._apply_auto_compact_if_needed("auto")
|
|
40637
|
+
self._apply_auto_compact_if_needed("auto", role=single_role)
|
|
39058
40638
|
# Periodic checkpoint in single-agent loop
|
|
39059
40639
|
_sa_round = int(getattr(self, "agent_round_index", 0) or 0)
|
|
39060
40640
|
if _sa_round > 0 and _sa_round % CHECKPOINT_INTERVAL_ROUNDS == 0:
|
|
@@ -39343,7 +40923,12 @@ body{padding:18px}
|
|
|
39343
40923
|
)
|
|
39344
40924
|
continue
|
|
39345
40925
|
clean_decision_probe = strip_thinking_content(decision_probe).strip()
|
|
39346
|
-
|
|
40926
|
+
arbiter_probe_len = max(len(clean_decision_probe), len(str(thinking_text or "").strip()))
|
|
40927
|
+
arbiter_should_run = bool(self.arbiter_enabled) and (
|
|
40928
|
+
arbiter_probe_len >= int(ARBITER_TRIGGER_MIN_CONTENT_CHARS)
|
|
40929
|
+
or self._looks_like_action_promise_without_tool(done_probe)
|
|
40930
|
+
)
|
|
40931
|
+
if arbiter_should_run:
|
|
39347
40932
|
arbiter_decision = self._call_arbiter_llm(clean_decision_probe, thinking_text)
|
|
39348
40933
|
arbiter_status = str(arbiter_decision.get("status", "") or "").strip().upper()
|
|
39349
40934
|
if arbiter_status == "TASK_COMPLETED":
|
|
@@ -39362,6 +40947,35 @@ body{padding:18px}
|
|
|
39362
40947
|
},
|
|
39363
40948
|
)
|
|
39364
40949
|
break
|
|
40950
|
+
if arbiter_status == "ACTION_REQUIRED":
|
|
40951
|
+
arbiter_planning_rounds = 0
|
|
40952
|
+
no_tool_rounds = 0
|
|
40953
|
+
fault_counter = 0
|
|
40954
|
+
last_fault_reason = ""
|
|
40955
|
+
force_single_tool_rounds = max(force_single_tool_rounds, 2)
|
|
40956
|
+
self._inject_arbiter_action_required_hint(arbiter_decision, done_probe)
|
|
40957
|
+
if auto_continue_budget > 0:
|
|
40958
|
+
auto_continue_budget -= 1
|
|
40959
|
+
self._emit(
|
|
40960
|
+
"status",
|
|
40961
|
+
{
|
|
40962
|
+
"summary": (
|
|
40963
|
+
"arbiter decision=ACTION_REQUIRED; "
|
|
40964
|
+
"auto-continue to concrete tool execution "
|
|
40965
|
+
f"(remaining={auto_continue_budget})"
|
|
40966
|
+
)
|
|
40967
|
+
},
|
|
40968
|
+
)
|
|
40969
|
+
continue
|
|
40970
|
+
self._emit(
|
|
40971
|
+
"status",
|
|
40972
|
+
{
|
|
40973
|
+
"summary": (
|
|
40974
|
+
"arbiter decision=ACTION_REQUIRED but auto-continue budget exhausted; "
|
|
40975
|
+
"falling back to no-tool handling"
|
|
40976
|
+
)
|
|
40977
|
+
},
|
|
40978
|
+
)
|
|
39365
40979
|
if arbiter_status == "VALID_PLANNING":
|
|
39366
40980
|
arbiter_planning_rounds += 1
|
|
39367
40981
|
if arbiter_planning_rounds <= int(ARBITER_VALID_PLANNING_STREAK_LIMIT):
|
|
@@ -39438,6 +41052,7 @@ body{padding:18px}
|
|
|
39438
41052
|
substantial_reply = self._looks_like_substantial_informative_reply(done_probe)
|
|
39439
41053
|
done_like = self._looks_like_conclusive_reply(done_probe)
|
|
39440
41054
|
todo_blocking = self._todo_should_block_auto_continue(done_probe)
|
|
41055
|
+
action_promise_pending = self._looks_like_action_promise_without_tool(done_probe)
|
|
39441
41056
|
endpoint = self._detect_endpoint_intent(done_probe, tool_calls)
|
|
39442
41057
|
if bool(endpoint.get("matched", False)):
|
|
39443
41058
|
arbiter_planning_rounds = 0
|
|
@@ -39493,7 +41108,7 @@ body{padding:18px}
|
|
|
39493
41108
|
break
|
|
39494
41109
|
no_tool_rounds += 1
|
|
39495
41110
|
diagnosis = self._diagnose_no_tool_idle(decision_probe, no_tool_rounds)
|
|
39496
|
-
pending_like = bool(diagnosis.get("work_pending", False)) or todo_blocking
|
|
41111
|
+
pending_like = bool(diagnosis.get("work_pending", False)) or todo_blocking or action_promise_pending
|
|
39497
41112
|
if no_tool_rounds >= 2 and pending_like:
|
|
39498
41113
|
fault_counter += 1
|
|
39499
41114
|
last_fault_reason = f"no-tool-idle(streak={no_tool_rounds})"
|
|
@@ -39515,7 +41130,11 @@ body{padding:18px}
|
|
|
39515
41130
|
if (not done_like) and no_tool_rounds >= 1 and pending_like:
|
|
39516
41131
|
if auto_continue_budget > 0:
|
|
39517
41132
|
auto_continue_budget -= 1
|
|
39518
|
-
if
|
|
41133
|
+
if action_promise_pending:
|
|
41134
|
+
force_single_tool_rounds = max(force_single_tool_rounds, 2)
|
|
41135
|
+
self._inject_action_promise_recovery_hint(done_probe)
|
|
41136
|
+
summary = "no-tool action promise recovered"
|
|
41137
|
+
elif no_tool_rounds >= 2:
|
|
39519
41138
|
force_single_tool_rounds = max(force_single_tool_rounds, 2)
|
|
39520
41139
|
self._inject_no_tool_recovery_hint(diagnosis)
|
|
39521
41140
|
summary = "no-tool recovery mode engaged"
|
|
@@ -39535,7 +41154,7 @@ body{padding:18px}
|
|
|
39535
41154
|
if auto_continue_budget > 8 and not self._is_long_running_engineering_context():
|
|
39536
41155
|
auto_continue_budget = min(auto_continue_budget, 8)
|
|
39537
41156
|
can_continue = auto_continue_budget > 0 and (
|
|
39538
|
-
todo_blocking or self._looks_like_incomplete_reply(text)
|
|
41157
|
+
todo_blocking or action_promise_pending or self._looks_like_incomplete_reply(text)
|
|
39539
41158
|
)
|
|
39540
41159
|
if can_continue:
|
|
39541
41160
|
if self.cancel_requested:
|
|
@@ -39832,7 +41451,13 @@ body{padding:18px}
|
|
|
39832
41451
|
manual_compact = True
|
|
39833
41452
|
if dispatched_name in {"finish_task", "finish_current_task", "mark_done"} and not str(output).startswith("Error:"):
|
|
39834
41453
|
stop_due_to_finish_task = True
|
|
39835
|
-
self.messages.append({
|
|
41454
|
+
self.messages.append({
|
|
41455
|
+
"role": "tool",
|
|
41456
|
+
"tool_call_id": tc["id"],
|
|
41457
|
+
"name": name,
|
|
41458
|
+
"content": self._tool_result_context_content(name, args if isinstance(args, dict) else {}, output),
|
|
41459
|
+
"ts": now_ts(),
|
|
41460
|
+
})
|
|
39836
41461
|
single_round_tool_results.append(
|
|
39837
41462
|
{
|
|
39838
41463
|
"name": dispatched_name or name,
|
|
@@ -40014,12 +41639,10 @@ body{padding:18px}
|
|
|
40014
41639
|
"content": (
|
|
40015
41640
|
"<read-loop-intervention>"
|
|
40016
41641
|
f"{_read_loop_reason} "
|
|
40017
|
-
"
|
|
40018
|
-
"
|
|
40019
|
-
"
|
|
40020
|
-
"
|
|
40021
|
-
"4) Think deeply about the user's goal and the project structure to find the solution. "
|
|
40022
|
-
"Do NOT run ls, cat, find, or head on the same paths again."
|
|
41642
|
+
"Change strategy now: state the exact unanswered question, then use a more focused tool call "
|
|
41643
|
+
"(read_file mode='overview', 'symbol', 'search', or 'window'), reconcile the path, "
|
|
41644
|
+
"or take a concrete edit/verification action based on the current evidence. "
|
|
41645
|
+
"Think about the user's goal and project structure before opening more broad listings."
|
|
40023
41646
|
"</read-loop-intervention>"
|
|
40024
41647
|
),
|
|
40025
41648
|
"ts": now_ts(),
|
|
@@ -40593,6 +42216,7 @@ body{padding:18px}
|
|
|
40593
42216
|
row["data"] = data
|
|
40594
42217
|
operations_view.append(row)
|
|
40595
42218
|
ctx = self._context_budget_metrics()
|
|
42219
|
+
agent_contexts_view = self._agent_context_budget_metrics_snapshot()
|
|
40596
42220
|
model_catalog = self.model_catalog() if include_model_catalog else None
|
|
40597
42221
|
blackboard = self._normalize_blackboard(self.blackboard)
|
|
40598
42222
|
blackboard_view = (
|
|
@@ -40651,6 +42275,7 @@ body{padding:18px}
|
|
|
40651
42275
|
"agent_round_index": int(self.agent_round_index),
|
|
40652
42276
|
"agent_phase": str(self.current_phase or "idle"),
|
|
40653
42277
|
"agent_active_tool": str(self.current_tool_name or ""),
|
|
42278
|
+
"agent_contexts": agent_contexts_view,
|
|
40654
42279
|
"blackboard": blackboard_view,
|
|
40655
42280
|
"queued_user_inputs_count": len(self.pending_user_inputs),
|
|
40656
42281
|
"scheduler_queued_inputs_count": sum(
|
|
@@ -40663,6 +42288,12 @@ body{padding:18px}
|
|
|
40663
42288
|
"context_reserve_percent": float(ctx.get("reserve_percent", 0.0)),
|
|
40664
42289
|
"context_token_limit_config": int(self.max_context_token_limit),
|
|
40665
42290
|
"context_token_limit_locked": bool(self.context_limit_locked),
|
|
42291
|
+
"context_limit_source": str(getattr(self, "context_limit_source", "") or "configured"),
|
|
42292
|
+
"context_next_call_estimate": int(getattr(self, "context_last_next_call_estimate", 0) or 0),
|
|
42293
|
+
"context_next_call_label": str(getattr(self, "context_last_next_call_label", "") or ""),
|
|
42294
|
+
"context_last_compact_effective": bool(getattr(self, "context_last_compact_effective", True)),
|
|
42295
|
+
"context_last_compact_used_reduction": int(getattr(self, "context_last_compact_used_reduction", 0) or 0),
|
|
42296
|
+
"context_last_compact_skip_reason": str(getattr(self, "context_last_compact_skip_reason", "") or ""),
|
|
40666
42297
|
"context_estimator": str(ctx.get("estimator", "")),
|
|
40667
42298
|
"context_estimate_safety_multiplier": float(ctx.get("safety_multiplier", CONTEXT_ESTIMATE_SAFETY_MULTIPLIER)),
|
|
40668
42299
|
"context_estimate_base_safety_multiplier": float(ctx.get("base_safety_multiplier", CONTEXT_ESTIMATE_SAFETY_MULTIPLIER)),
|
|
@@ -42313,8 +43944,9 @@ body[data-ui-style="trad"] .msg-event-cell{background:#fff}
|
|
|
42313
43944
|
.msg-run-dot{width:8px;height:8px;border-radius:999px;background:#13b8a6;box-shadow:0 0 0 0 rgba(19,184,166,.38);animation:msgRunPulse 1.6s ease-out infinite}
|
|
42314
43945
|
@keyframes msgRunPulse{0%{box-shadow:0 0 0 0 rgba(19,184,166,.36)}70%{box-shadow:0 0 0 9px rgba(19,184,166,0)}100%{box-shadow:0 0 0 0 rgba(19,184,166,0)}}
|
|
42315
43946
|
@media (prefers-reduced-motion:reduce){.msg-run-dot{animation:none}}
|
|
42316
|
-
.msg-code-shell{margin:0;max-height:210px;overflow:auto;padding:8px;border:1px solid #dfe6ef;border-radius:8px;background:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78rem;line-height:1.35;overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
42317
|
-
.msg-diff-shell{max-height:210px;overflow:auto;padding:8px;border:1px solid #dfe6ef;border-radius:8px;background:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78rem;line-height:1.35;overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
43947
|
+
.msg-code-shell{margin:0;max-height:210px;overflow:auto;padding:8px;border:1px solid #dfe6ef;border-radius:8px;background:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78rem;line-height:1.35;white-space:pre;tab-size:4;word-break:normal;overflow-wrap:normal;overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
43948
|
+
.msg-diff-shell{max-height:210px;overflow:auto;padding:8px;border:1px solid #dfe6ef;border-radius:8px;background:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78rem;line-height:1.35;white-space:pre;tab-size:4;word-break:normal;overflow-wrap:normal;overscroll-behavior:contain;scrollbar-gutter:stable}
|
|
43949
|
+
.msg-diff-shell .diff-row{display:block;min-width:max-content;white-space:pre;tab-size:4}
|
|
42318
43950
|
.composer{border-top:1px solid var(--line);padding-top:10px;margin-top:10px}
|
|
42319
43951
|
.composer-shell{position:relative;border:1px solid var(--control-line);border-radius:16px;background:linear-gradient(180deg,var(--control-panel),var(--control-panel-soft));box-shadow:inset 0 1px 0 rgba(255,255,255,.7),0 10px 22px rgba(15,23,42,.05);overflow:hidden;transition:border-color .18s ease,box-shadow .18s ease,transform .18s ease}
|
|
42320
43952
|
.composer-shell:focus-within{border-color:var(--focus-border);box-shadow:0 0 0 4px var(--focus-ring),inset 0 1px 0 rgba(255,255,255,.78),0 16px 34px rgba(15,23,42,.08)}
|
|
@@ -42344,7 +43976,11 @@ body[data-ui-style="trad"] .msg-event-cell{background:#fff}
|
|
|
42344
43976
|
.ctx-live{margin-left:auto;display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid #d6deea;border-radius:999px;background:#f8fbff;min-width:250px}
|
|
42345
43977
|
.ctx-live-dot{width:8px;height:8px;border-radius:50%;background:#13b8a6;box-shadow:0 0 0 rgba(19,184,166,.45)}
|
|
42346
43978
|
.ctx-live-bar{position:relative;display:inline-block;width:84px;height:6px;border-radius:999px;background:#e5edf8;overflow:hidden}
|
|
42347
|
-
.ctx-live-fill{display:block;height:100%;width:0%;background:linear-gradient(90deg,#13b8a6,#1f6feb);transition:width .24s ease,background .24s ease}
|
|
43979
|
+
.ctx-live-fill{position:relative;display:block;height:100%;width:0%;background:linear-gradient(90deg,#13b8a6,#1f6feb);transition:width .24s ease,background .24s ease}
|
|
43980
|
+
.ctx-live-fill.segmented{width:100%;background:#e5edf8;transition:background .24s ease}
|
|
43981
|
+
.ctx-live-fill.multi{width:100%;height:100%;background:transparent;transition:none}
|
|
43982
|
+
.ctx-live-agent-layer{position:absolute;left:0;right:0;top:50%;height:4px;border-radius:999px;transform:translateY(-50%);background:transparent;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ctx-agent-color,#4a5568) 42%, transparent);overflow:hidden;transition:height .24s ease}
|
|
43983
|
+
.ctx-live-agent-fill{position:absolute;left:0;top:0;bottom:0;width:0%;border-radius:999px;background:linear-gradient(90deg,color-mix(in srgb,var(--ctx-agent-color,#4a5568) 82%, #ffffff 18%),var(--ctx-agent-color,#4a5568));box-shadow:0 0 0 1px rgba(255,255,255,.22) inset;transition:width .24s ease}
|
|
42348
43984
|
@media (max-width:760px){
|
|
42349
43985
|
.composer-footer{flex-direction:column;align-items:stretch}
|
|
42350
43986
|
.composer-file-btn{justify-content:center}
|
|
@@ -42352,8 +43988,12 @@ body[data-ui-style="trad"] .msg-event-cell{background:#fff}
|
|
|
42352
43988
|
}
|
|
42353
43989
|
.ctx-live.warn .ctx-live-dot{background:#e1a400}
|
|
42354
43990
|
.ctx-live.warn .ctx-live-fill{background:linear-gradient(90deg,#ffcc66,#e1a400)}
|
|
43991
|
+
.ctx-live.warn .ctx-live-fill.segmented{background:#e5edf8}
|
|
43992
|
+
.ctx-live.warn .ctx-live-fill.multi{background:transparent}
|
|
42355
43993
|
.ctx-live.danger .ctx-live-dot{background:#cf3b3b}
|
|
42356
43994
|
.ctx-live.danger .ctx-live-fill{background:linear-gradient(90deg,#ff8a8a,#cf3b3b)}
|
|
43995
|
+
.ctx-live.danger .ctx-live-fill.segmented{background:#e5edf8}
|
|
43996
|
+
.ctx-live.danger .ctx-live-fill.multi{background:transparent}
|
|
42357
43997
|
.ctx-live.danger{border-color:#f1c5c5;background:#fff4f4}
|
|
42358
43998
|
.error-box{margin-top:8px;padding:8px 10px;border:1px solid #f2b4b4;background:#fff1f1;color:#8f1d1d;border-radius:8px}
|
|
42359
43999
|
.hidden{display:none}
|
|
@@ -42366,6 +44006,18 @@ body[data-ui-style="trad"] .msg-event-cell{background:#fff}
|
|
|
42366
44006
|
.runtime-pill-label{flex:0 0 auto;font-weight:700;color:#667b94;white-space:nowrap}
|
|
42367
44007
|
.runtime-pill-value{min-width:0;color:#17283d;white-space:normal;overflow-wrap:anywhere;word-break:break-word}
|
|
42368
44008
|
.runtime-pill-value.mono{font-size:.76rem}
|
|
44009
|
+
.agent-ctx-group{display:flex;flex-wrap:wrap;gap:6px;width:100%;margin-top:2px}
|
|
44010
|
+
.agent-ctx-chip{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:5px 8px;border:1px solid #dbe5f2;border-radius:999px;background:#fff;font-size:.75rem;color:#24364b}
|
|
44011
|
+
.agent-ctx-chip.active{box-shadow:0 0 0 2px rgba(47,111,237,.12)}
|
|
44012
|
+
.agent-ctx-chip.warn{border-color:#f1c97a;background:#fff9ea}
|
|
44013
|
+
.agent-ctx-chip.danger{border-color:#ef9a9a;background:#fff1f1}
|
|
44014
|
+
.agent-ctx-chip .agent-dot{width:7px;height:7px;border-radius:50%;background:#718096;flex:0 0 auto}
|
|
44015
|
+
.agent-ctx-chip.role-explorer .agent-dot{background:#ff4d4f}
|
|
44016
|
+
.agent-ctx-chip.role-developer .agent-dot{background:#20c997}
|
|
44017
|
+
.agent-ctx-chip.role-reviewer .agent-dot{background:#f59f00}
|
|
44018
|
+
.agent-ctx-chip.role-manager .agent-dot{background:#9b5cff}
|
|
44019
|
+
.agent-ctx-chip.role-single .agent-dot{background:#64748b}
|
|
44020
|
+
.agent-ctx-chip .agent-ctx-value{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:#17283d;white-space:nowrap}
|
|
42369
44021
|
.render-bridge{margin:0 0 10px;border:1px solid #d9e4f1;border-radius:10px;background:#fbfdff;overflow:hidden}
|
|
42370
44022
|
.render-meta{padding:6px 8px;border-bottom:1px solid #e6edf7;color:#51627a;font-size:.76rem;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
42371
44023
|
.render-canvas{display:block;width:100%;height:220px;background:#ffffff}
|
|
@@ -42434,6 +44086,7 @@ h3{font-size:.96rem;margin:10px 0 6px}
|
|
|
42434
44086
|
.diff-item{margin-bottom:8px;padding:6px;border:1px solid #e7edf5;border-radius:8px;background:#fff;min-width:0}
|
|
42435
44087
|
.diff-head{font-weight:600;margin-bottom:4px;overflow-wrap:anywhere;word-break:break-word}
|
|
42436
44088
|
.diff-body{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.78rem;white-space:pre;overflow:auto;max-height:220px;background:#f8fafc;border-radius:6px;padding:6px}
|
|
44089
|
+
.diff-body .diff-row{display:block;min-width:max-content;white-space:pre;tab-size:4}
|
|
42437
44090
|
.diff-line-add{background:#eaffea;color:#0f6a1b}
|
|
42438
44091
|
.diff-line-del{background:#ffeaea;color:#8a1d1d}
|
|
42439
44092
|
.diff-line-hunk{background:#edf4ff;color:#1f4b8f}
|
|
@@ -42686,7 +44339,7 @@ const I18N={
|
|
|
42686
44339
|
};
|
|
42687
44340
|
Object.assign(I18N['en'],{
|
|
42688
44341
|
sec_todos:'Todos',sec_tasks:'Tasks',sec_activity:'Activity',sec_commands:'Commands',sec_diffs:'File Diffs',sec_catalog:'Catalog',
|
|
42689
|
-
role_explorer:'Explorer',role_developer:'Developer',role_reviewer:'Reviewer',role_manager:'Manager',role_planner:'Planner',role_agent:'Agent',
|
|
44342
|
+
role_explorer:'Explorer',role_developer:'Developer',role_reviewer:'Reviewer',role_manager:'Manager',role_planner:'Planner',role_agent:'Agent',role_single:'Context',
|
|
42690
44343
|
callout_warning:'Warning',callout_notice:'Notice',callout_instruction:'Instruction',callout_tip:'Tip',callout_reminder:'Reminder',
|
|
42691
44344
|
event_manager_delegate_title:'Manager Delegate',event_objective:'Objective',event_instruction:'Instruction',event_intent:'intent',
|
|
42692
44345
|
event_tool_calls_title:'Tool Calls',event_tool_calls_note:'Model scheduled these tools for the current turn.',event_tool_calls_empty:'No structured tool metadata was attached to this turn.',
|
|
@@ -42700,7 +44353,7 @@ Object.assign(I18N['en'],{
|
|
|
42700
44353
|
event_scheduler_queued_title:'Queued Task',event_scheduler_queued_note:'This message is saved and waiting for an execution slot.',event_scheduler_queue_position:'queue position',event_scheduler_reason:'reason',event_scheduler_queued_hint:'queued',
|
|
42701
44354
|
event_auto_continue:'Auto Continue',event_arbiter_continue:'Arbiter Continue',event_continuation_briefing:'Continuation Briefing',event_reminder:'Reminder',event_todo_rescue:'Todo Rescue',event_tool_retry:'Tool Retry',event_segmented_retry:'Segmented Retry',event_forced_converge:'Forced Converge',event_no_tool_recovery:'No-Tool Recovery',event_context_recall:'Context Recall',event_failure_recovery:'Failure Recovery',event_truncate_rescue:'Truncation Rescue',event_thinking_recovery:'Thinking Recovery',event_fault_prefill:'Fault Prefill',event_edit_recovery:'Edit Recovery',
|
|
42702
44355
|
state_on:'on',state_off:'off',
|
|
42703
|
-
rt_session:'session',rt_model:'model',rt_thinking:'thinking',rt_thinking_stream:'thinking_stream',rt_mode:'mode',rt_active_agent:'active_agent',rt_blackboard:'bb',rt_task:'task',rt_complexity:'complexity',rt_judgement:'judgement',rt_budget:'budget',rt_remaining:'remaining',rt_blackboard_cycles:'bb_cycles',rt_round_limit:'round_limit',rt_round:'round',rt_phase:'phase',rt_queued_inputs:'queued_inputs',rt_run_timeout:'run_timeout',rt_ctx_used:'ctx_used',rt_ctx_limit:'ctx_limit',rt_ctx_mode:'ctx_mode',rt_manual_lock:'manual-lock',rt_adaptive:'adaptive',rt_ctx_left:'ctx_left',rt_truncation:'truncation',rt_trunc_retry:'trunc_retry',rt_trunc_tokens:'trunc_tokens~',rt_archive:'archive',rt_last_compact:'last_compact',rt_ollama:'ollama',rt_files:'files',rt_ui_mode:'ui_mode',rt_state:'state',
|
|
44356
|
+
rt_session:'session',rt_model:'model',rt_thinking:'thinking',rt_thinking_stream:'thinking_stream',rt_mode:'mode',rt_active_agent:'active_agent',rt_blackboard:'bb',rt_task:'task',rt_complexity:'complexity',rt_judgement:'judgement',rt_budget:'budget',rt_remaining:'remaining',rt_blackboard_cycles:'bb_cycles',rt_round_limit:'round_limit',rt_round:'round',rt_phase:'phase',rt_queued_inputs:'queued_inputs',rt_run_timeout:'run_timeout',rt_ctx_used:'ctx_used',rt_ctx_limit:'ctx_limit',rt_ctx_mode:'ctx_mode',rt_manual_lock:'manual-lock',rt_adaptive:'adaptive',rt_ctx_left:'ctx_left',rt_ctx_left_for:'{label} left',rt_ctx_live_title:'Remaining context budget by active call',rt_truncation:'truncation',rt_trunc_retry:'trunc_retry',rt_trunc_tokens:'trunc_tokens~',rt_archive:'archive',rt_last_compact:'last_compact',rt_ollama:'ollama',rt_files:'files',rt_ui_mode:'ui_mode',rt_state:'state',
|
|
42704
44357
|
preview_download:'Download',preview_source:'Source',preview_rendered:'Preview',
|
|
42705
44358
|
fe_nodes:'nodes={n}',fe_loading:'loading...',fe_tree_truncated:'tree truncated at {n} nodes',fe_items:'{n} item(s)',
|
|
42706
44359
|
cmd_ui_preview_truncated:'UI preview truncated',cmd_model_context_truncated:'Model context truncated',cmd_temp_read_file_ready:'Temp read_file ready',cmd_buffered_copy:'Buffered copy',cmd_prev:'Prev',cmd_next:'Next',cmd_preview:'preview',cmd_of:'of',cmd_read_file_path:'read_file path',cmd_buffer_ref:'buffer_ref',cmd_chars:'chars',cmd_lines:'lines',cmd_strategy:'strategy',cmd_full_output:'full_output',cmd_exit:'exit',cmd_default_name:'command'
|
|
@@ -42708,7 +44361,7 @@ Object.assign(I18N['en'],{
|
|
|
42708
44361
|
Object.assign(I18N['zh-CN'],{
|
|
42709
44362
|
sec_todos:'待办',sec_tasks:'任务',sec_activity:'活动',sec_commands:'命令',sec_diffs:'文件差异',sec_catalog:'目录',
|
|
42710
44363
|
no_todos:'暂无待办',no_tasks:'暂无任务',no_catalog:'暂无目录',
|
|
42711
|
-
role_explorer:'探索者',role_developer:'开发者',role_reviewer:'审查者',role_manager:'管理者',role_planner:'规划者',role_agent:'Agent',
|
|
44364
|
+
role_explorer:'探索者',role_developer:'开发者',role_reviewer:'审查者',role_manager:'管理者',role_planner:'规划者',role_agent:'Agent',role_single:'上下文',
|
|
42712
44365
|
callout_warning:'警告',callout_notice:'提示',callout_instruction:'指令',callout_tip:'建议',callout_reminder:'提醒',
|
|
42713
44366
|
event_manager_delegate_title:'管理者委派',event_objective:'目标',event_instruction:'指令',event_intent:'意图',
|
|
42714
44367
|
event_tool_calls_title:'工具调用',event_tool_calls_note:'模型已为当前轮安排以下工具调用。',event_tool_calls_empty:'当前轮没有附带结构化工具元数据。',
|
|
@@ -42722,7 +44375,7 @@ Object.assign(I18N['zh-CN'],{
|
|
|
42722
44375
|
event_scheduler_queued_title:'任务已排队',event_scheduler_queued_note:'这条消息已保存,正在等待后台执行名额。',event_scheduler_queue_position:'队列位置',event_scheduler_reason:'原因',event_scheduler_queued_hint:'已排队',
|
|
42723
44376
|
event_auto_continue:'自动继续',event_arbiter_continue:'裁决继续',event_continuation_briefing:'续跑简报',event_reminder:'提醒',event_todo_rescue:'待办救援',event_tool_retry:'工具重试',event_segmented_retry:'分段重试',event_forced_converge:'强制收敛',event_no_tool_recovery:'无工具恢复',event_context_recall:'上下文召回',event_failure_recovery:'故障恢复',event_truncate_rescue:'截断救援',event_thinking_recovery:'思考恢复',event_fault_prefill:'故障预填',event_edit_recovery:'编辑恢复',
|
|
42724
44377
|
state_on:'开',state_off:'关',
|
|
42725
|
-
rt_session:'会话',rt_model:'模型',rt_thinking:'思考',rt_thinking_stream:'思考流',rt_mode:'模式',rt_active_agent:'活跃代理',rt_blackboard:'黑板',rt_task:'任务',rt_complexity:'复杂度',rt_judgement:'裁决',rt_budget:'预算',rt_remaining:'剩余',rt_blackboard_cycles:'黑板轮次',rt_round_limit:'轮次上限',rt_round:'轮次',rt_phase:'阶段',rt_queued_inputs:'排队输入',rt_run_timeout:'运行超时',rt_ctx_used:'上下文已用',rt_ctx_limit:'上下文上限',rt_ctx_mode:'上下文模式',rt_manual_lock:'手动锁定',rt_adaptive:'自适应',rt_ctx_left:'上下文剩余',rt_truncation:'截断数',rt_trunc_retry:'截断重试',rt_trunc_tokens:'截断Token~',rt_archive:'归档',rt_last_compact:'最近压缩',rt_ollama:'Ollama',rt_files:'文件根目录',rt_ui_mode:'界面模式',rt_state:'状态',
|
|
44378
|
+
rt_session:'会话',rt_model:'模型',rt_thinking:'思考',rt_thinking_stream:'思考流',rt_mode:'模式',rt_active_agent:'活跃代理',rt_blackboard:'黑板',rt_task:'任务',rt_complexity:'复杂度',rt_judgement:'裁决',rt_budget:'预算',rt_remaining:'剩余',rt_blackboard_cycles:'黑板轮次',rt_round_limit:'轮次上限',rt_round:'轮次',rt_phase:'阶段',rt_queued_inputs:'排队输入',rt_run_timeout:'运行超时',rt_ctx_used:'上下文已用',rt_ctx_limit:'上下文上限',rt_ctx_mode:'上下文模式',rt_manual_lock:'手动锁定',rt_adaptive:'自适应',rt_ctx_left:'上下文剩余',rt_ctx_left_for:'{label}剩余',rt_ctx_live_title:'按真实调用显示上下文剩余',rt_truncation:'截断数',rt_trunc_retry:'截断重试',rt_trunc_tokens:'截断Token~',rt_archive:'归档',rt_last_compact:'最近压缩',rt_ollama:'Ollama',rt_files:'文件根目录',rt_ui_mode:'界面模式',rt_state:'状态',
|
|
42726
44379
|
preview_download:'下载',preview_source:'源码',preview_rendered:'预览',
|
|
42727
44380
|
fe_nodes:'节点={n}',fe_loading:'加载中...',fe_tree_truncated:'目录树在 {n} 个节点处被截断',fe_items:'{n} 项',
|
|
42728
44381
|
cmd_ui_preview_truncated:'UI 预览截断',cmd_model_context_truncated:'模型上下文截断',cmd_temp_read_file_ready:'临时 read_file 已就绪',cmd_buffered_copy:'缓冲副本',cmd_prev:'上一页',cmd_next:'下一页',cmd_preview:'预览',cmd_of:'共',cmd_read_file_path:'read_file 路径',cmd_buffer_ref:'缓冲引用',cmd_chars:'字符',cmd_lines:'行',cmd_strategy:'策略',cmd_full_output:'完整输出',cmd_exit:'退出码',cmd_default_name:'命令'
|
|
@@ -42733,7 +44386,7 @@ Object.assign(I18N['zh-TW'],{
|
|
|
42733
44386
|
sec_todos:'待辦',sec_tasks:'任務',sec_activity:'活動',sec_commands:'命令',sec_diffs:'檔案差異',sec_catalog:'目錄',
|
|
42734
44387
|
no_todos:'尚無待辦',no_tasks:'尚無任務',no_catalog:'尚無目錄',
|
|
42735
44388
|
level_3_collab:'L3 協作',
|
|
42736
|
-
role_explorer:'探索者',role_developer:'開發者',role_reviewer:'審查者',role_manager:'管理者',role_planner:'規劃者',role_agent:'Agent',
|
|
44389
|
+
role_explorer:'探索者',role_developer:'開發者',role_reviewer:'審查者',role_manager:'管理者',role_planner:'規劃者',role_agent:'Agent',role_single:'上下文',
|
|
42737
44390
|
callout_warning:'警告',callout_notice:'提示',callout_instruction:'指令',callout_tip:'建議',callout_reminder:'提醒',
|
|
42738
44391
|
event_manager_delegate_title:'管理者委派',event_objective:'目標',event_instruction:'指令',event_intent:'意圖',
|
|
42739
44392
|
event_tool_calls_title:'工具呼叫',event_tool_calls_note:'模型已為目前輪安排以下工具呼叫。',event_tool_calls_empty:'目前輪沒有附帶結構化工具中繼資料。',
|
|
@@ -42747,7 +44400,7 @@ Object.assign(I18N['zh-TW'],{
|
|
|
42747
44400
|
event_scheduler_queued_title:'任務已排隊',event_scheduler_queued_note:'這則訊息已保存,正在等待背景執行名額。',event_scheduler_queue_position:'佇列位置',event_scheduler_reason:'原因',event_scheduler_queued_hint:'已排隊',
|
|
42748
44401
|
event_auto_continue:'自動繼續',event_arbiter_continue:'裁決繼續',event_continuation_briefing:'續跑簡報',event_reminder:'提醒',event_todo_rescue:'待辦救援',event_tool_retry:'工具重試',event_segmented_retry:'分段重試',event_forced_converge:'強制收斂',event_no_tool_recovery:'無工具恢復',event_context_recall:'上下文召回',event_failure_recovery:'故障恢復',event_truncate_rescue:'截斷救援',event_thinking_recovery:'思考恢復',event_fault_prefill:'故障預填',event_edit_recovery:'編輯恢復',
|
|
42749
44402
|
state_on:'開',state_off:'關',
|
|
42750
|
-
rt_session:'會話',rt_model:'模型',rt_thinking:'思考',rt_thinking_stream:'思考流',rt_mode:'模式',rt_active_agent:'活躍代理',rt_blackboard:'黑板',rt_task:'任務',rt_complexity:'複雜度',rt_judgement:'裁決',rt_budget:'預算',rt_remaining:'剩餘',rt_blackboard_cycles:'黑板輪次',rt_round_limit:'輪次上限',rt_round:'輪次',rt_phase:'階段',rt_queued_inputs:'排隊輸入',rt_run_timeout:'執行逾時',rt_ctx_used:'上下文已用',rt_ctx_limit:'上下文上限',rt_ctx_mode:'上下文模式',rt_manual_lock:'手動鎖定',rt_adaptive:'自適應',rt_ctx_left:'上下文剩餘',rt_truncation:'截斷數',rt_trunc_retry:'截斷重試',rt_trunc_tokens:'截斷Token~',rt_archive:'封存',rt_last_compact:'最近壓縮',rt_ollama:'Ollama',rt_files:'檔案根目錄',rt_ui_mode:'介面模式',rt_state:'狀態',
|
|
44403
|
+
rt_session:'會話',rt_model:'模型',rt_thinking:'思考',rt_thinking_stream:'思考流',rt_mode:'模式',rt_active_agent:'活躍代理',rt_blackboard:'黑板',rt_task:'任務',rt_complexity:'複雜度',rt_judgement:'裁決',rt_budget:'預算',rt_remaining:'剩餘',rt_blackboard_cycles:'黑板輪次',rt_round_limit:'輪次上限',rt_round:'輪次',rt_phase:'階段',rt_queued_inputs:'排隊輸入',rt_run_timeout:'執行逾時',rt_ctx_used:'上下文已用',rt_ctx_limit:'上下文上限',rt_ctx_mode:'上下文模式',rt_manual_lock:'手動鎖定',rt_adaptive:'自適應',rt_ctx_left:'上下文剩餘',rt_ctx_left_for:'{label}剩餘',rt_ctx_live_title:'依真實呼叫顯示上下文剩餘',rt_truncation:'截斷數',rt_trunc_retry:'截斷重試',rt_trunc_tokens:'截斷Token~',rt_archive:'封存',rt_last_compact:'最近壓縮',rt_ollama:'Ollama',rt_files:'檔案根目錄',rt_ui_mode:'介面模式',rt_state:'狀態',
|
|
42751
44404
|
preview_download:'下載',preview_source:'原始碼',preview_rendered:'預覽',
|
|
42752
44405
|
fe_nodes:'節點={n}',fe_loading:'載入中...',fe_tree_truncated:'目錄樹在 {n} 個節點處被截斷',fe_items:'{n} 項',
|
|
42753
44406
|
cmd_ui_preview_truncated:'UI 預覽截斷',cmd_model_context_truncated:'模型上下文截斷',cmd_temp_read_file_ready:'暫存 read_file 已就緒',cmd_buffered_copy:'緩衝副本',cmd_prev:'上一頁',cmd_next:'下一頁',cmd_preview:'預覽',cmd_of:'共',cmd_read_file_path:'read_file 路徑',cmd_buffer_ref:'緩衝引用',cmd_chars:'字元',cmd_lines:'行',cmd_strategy:'策略',cmd_full_output:'完整輸出',cmd_exit:'退出碼',cmd_default_name:'命令'
|
|
@@ -42756,7 +44409,7 @@ Object.assign(I18N['ja'],{
|
|
|
42756
44409
|
sec_todos:'Todo',sec_tasks:'タスク',sec_activity:'アクティビティ',sec_commands:'コマンド',sec_diffs:'ファイル差分',sec_catalog:'カタログ',
|
|
42757
44410
|
thinking:'思考',thinking_stream:'思考(ストリーム)',copy_code:'コードをコピー',copy_done:'コピーしました',
|
|
42758
44411
|
no_todos:'Todo はありません',no_tasks:'タスクはありません',no_catalog:'カタログなし',
|
|
42759
|
-
role_explorer:'探索担当',role_developer:'開発担当',role_reviewer:'レビュー担当',role_manager:'マネージャー',role_planner:'プランナー',role_agent:'Agent',
|
|
44412
|
+
role_explorer:'探索担当',role_developer:'開発担当',role_reviewer:'レビュー担当',role_manager:'マネージャー',role_planner:'プランナー',role_agent:'Agent',role_single:'コンテキスト',
|
|
42760
44413
|
callout_warning:'警告',callout_notice:'通知',callout_instruction:'指示',callout_tip:'ヒント',callout_reminder:'リマインダー',
|
|
42761
44414
|
event_manager_delegate_title:'マネージャー委任',event_objective:'目的',event_instruction:'指示',event_intent:'意図',
|
|
42762
44415
|
event_tool_calls_title:'ツール呼び出し',event_tool_calls_note:'モデルはこのターンで次のツール呼び出しを予定しました。',event_tool_calls_empty:'このターンには構造化されたツールメタデータがありません。',
|
|
@@ -42770,7 +44423,7 @@ Object.assign(I18N['ja'],{
|
|
|
42770
44423
|
event_scheduler_queued_title:'キュー済みタスク',event_scheduler_queued_note:'このメッセージは保存され、実行枠を待っています。',event_scheduler_queue_position:'キュー位置',event_scheduler_reason:'理由',event_scheduler_queued_hint:'キュー済み',
|
|
42771
44424
|
event_auto_continue:'自動継続',event_arbiter_continue:'判定継続',event_continuation_briefing:'継続ブリーフ',event_reminder:'リマインダー',event_todo_rescue:'Todo 救援',event_tool_retry:'ツール再試行',event_segmented_retry:'分割再試行',event_forced_converge:'強制収束',event_no_tool_recovery:'ツールなし復旧',event_context_recall:'コンテキスト再呼び出し',event_failure_recovery:'障害復旧',event_truncate_rescue:'切り詰め救援',event_thinking_recovery:'思考復旧',event_fault_prefill:'障害プリフィル',event_edit_recovery:'編集復旧',
|
|
42772
44425
|
state_on:'オン',state_off:'オフ',
|
|
42773
|
-
rt_session:'セッション',rt_model:'モデル',rt_thinking:'思考',rt_thinking_stream:'思考ストリーム',rt_mode:'モード',rt_active_agent:'アクティブAgent',rt_blackboard:'黒板',rt_task:'タスク',rt_complexity:'複雑度',rt_judgement:'判定',rt_budget:'予算',rt_remaining:'残り',rt_blackboard_cycles:'黒板サイクル',rt_round_limit:'ラウンド上限',rt_round:'ラウンド',rt_phase:'フェーズ',rt_queued_inputs:'待機入力',rt_run_timeout:'実行タイムアウト',rt_ctx_used:'コンテキスト使用量',rt_ctx_limit:'コンテキスト上限',rt_ctx_mode:'コンテキストモード',rt_manual_lock:'手動固定',rt_adaptive:'適応',rt_ctx_left:'残りコンテキスト',rt_truncation:'切り詰め数',rt_trunc_retry:'切り詰め再試行',rt_trunc_tokens:'切り詰めToken~',rt_archive:'アーカイブ',rt_last_compact:'直近 compact',rt_ollama:'Ollama',rt_files:'ファイルルート',rt_ui_mode:'UIモード',rt_state:'状態',
|
|
44426
|
+
rt_session:'セッション',rt_model:'モデル',rt_thinking:'思考',rt_thinking_stream:'思考ストリーム',rt_mode:'モード',rt_active_agent:'アクティブAgent',rt_blackboard:'黒板',rt_task:'タスク',rt_complexity:'複雑度',rt_judgement:'判定',rt_budget:'予算',rt_remaining:'残り',rt_blackboard_cycles:'黒板サイクル',rt_round_limit:'ラウンド上限',rt_round:'ラウンド',rt_phase:'フェーズ',rt_queued_inputs:'待機入力',rt_run_timeout:'実行タイムアウト',rt_ctx_used:'コンテキスト使用量',rt_ctx_limit:'コンテキスト上限',rt_ctx_mode:'コンテキストモード',rt_manual_lock:'手動固定',rt_adaptive:'適応',rt_ctx_left:'残りコンテキスト',rt_ctx_left_for:'{label}残り',rt_ctx_live_title:'実際の呼び出し別の残りコンテキスト',rt_truncation:'切り詰め数',rt_trunc_retry:'切り詰め再試行',rt_trunc_tokens:'切り詰めToken~',rt_archive:'アーカイブ',rt_last_compact:'直近 compact',rt_ollama:'Ollama',rt_files:'ファイルルート',rt_ui_mode:'UIモード',rt_state:'状態',
|
|
42774
44427
|
preview_download:'ダウンロード',preview_source:'ソース',preview_rendered:'プレビュー',
|
|
42775
44428
|
fe_nodes:'ノード={n}',fe_loading:'読み込み中...',fe_tree_truncated:'ツリーは {n} ノードで切り詰められました',fe_items:'{n} 件',
|
|
42776
44429
|
cmd_ui_preview_truncated:'UI プレビュー切り詰め',cmd_model_context_truncated:'モデルコンテキスト切り詰め',cmd_temp_read_file_ready:'一時 read_file 準備完了',cmd_buffered_copy:'バッファコピー',cmd_prev:'前へ',cmd_next:'次へ',cmd_preview:'プレビュー',cmd_of:'全',cmd_read_file_path:'read_file パス',cmd_buffer_ref:'buffer_ref',cmd_chars:'文字',cmd_lines:'行',cmd_strategy:'戦略',cmd_full_output:'完全出力',cmd_exit:'終了コード',cmd_default_name:'コマンド'
|
|
@@ -42781,7 +44434,7 @@ function applyUiStyle(){const style=normalizeUiStyle(S.config?.ui_style||'neo');
|
|
|
42781
44434
|
function t(key,vars){const lang=currentLang();const pack=I18N[lang]||I18N['en'];const fallback=I18N['en'];let txt=String((pack&&pack[key])??(fallback&&fallback[key])??key);if(vars&&typeof vars==='object'){for(const [k,v] of Object.entries(vars)){txt=txt.replaceAll('{'+k+'}',String(v??''))}}return txt}
|
|
42782
44435
|
function setText(id,key){const el=E(id);if(el)el.textContent=t(key)}
|
|
42783
44436
|
function setPlaceholder(id,key){const el=E(id);if(el)el.placeholder=t(key)}
|
|
42784
|
-
function applyMainI18n(){document.documentElement.lang=currentLang();const h1=document.querySelector('header h1');if(h1)h1.textContent=t('app_title');const hp=document.querySelectorAll('header p');if(hp&&hp[0])hp[0].textContent=t('app_subtitle');if(hp&&hp[1])hp[1].textContent=t('powered_by');setText('applyModelBtn','apply_model');setText('llmConfigBtn','upload_llm_config');setText('llmModalTitle','llm_fill_config');setText('llmProviderLabel','llm_provider');setText('llmConfigConfirm','llm_confirm');setText('llmConfigImport','llm_import_config');setText('newSessionBtn','btn_new_session');setText('renameSessionBtn','btn_rename');setText('deleteSessionBtn','btn_delete');setText('sendBtn','btn_send');setText('interruptBtn','btn_interrupt');setText('toolsMenuBtn','btn_tools');setText('compactAction','btn_compact_action');setText('refreshAction','btn_refresh_action');setText('previewReloadBtn','btn_refresh');setText('previewCopyBtn','copy_code');setText('downloadSessionBtn','btn_export_session');setText('clearStaleTodosBtn','btn_clear_stale_todos');setText('refreshFilesBtn','btn_refresh');setPlaceholder('prompt','prompt_placeholder');const up=E('uploadDrop');if(up)up.textContent=t('upload_drop');const pfht=E('promptFileHintText');if(pfht)pfht.textContent=t('upload_file_hint');const pfpk=E('promptFilePick');if(pfpk)pfpk.textContent=t('upload_pick_file');const pdol=E('promptDropOverlay');if(pdol)pdol.textContent=t('upload_drop_release');const panels=document.querySelectorAll('.panel-title');if(panels&&panels[0])panels[0].textContent=t('panel_sessions');if(panels&&panels[1])panels[1].textContent=t('panel_conversation');if(panels&&panels[2])panels[2].textContent=t('panel_runtime');const hs=document.querySelectorAll('#runtimeScroll h3');const keys=['sec_todos','sec_tasks','sec_activity','sec_commands','sec_diffs','sec_files','sec_catalog'];for(let i=0;i<hs.length&&i<keys.length;i++){hs[i].textContent=t(keys[i])}const _lvl2=S.snap?.user_task_level||0;updateLevelBtn(_lvl2);renderPreviewTabs()}
|
|
44437
|
+
function applyMainI18n(){document.documentElement.lang=currentLang();const h1=document.querySelector('header h1');if(h1)h1.textContent=t('app_title');const hp=document.querySelectorAll('header p');if(hp&&hp[0])hp[0].textContent=t('app_subtitle');if(hp&&hp[1])hp[1].textContent=t('powered_by');setText('applyModelBtn','apply_model');setText('llmConfigBtn','upload_llm_config');setText('llmModalTitle','llm_fill_config');setText('llmProviderLabel','llm_provider');setText('llmConfigConfirm','llm_confirm');setText('llmConfigImport','llm_import_config');setText('newSessionBtn','btn_new_session');setText('renameSessionBtn','btn_rename');setText('deleteSessionBtn','btn_delete');setText('sendBtn','btn_send');setText('interruptBtn','btn_interrupt');setText('toolsMenuBtn','btn_tools');setText('compactAction','btn_compact_action');setText('refreshAction','btn_refresh_action');setText('previewReloadBtn','btn_refresh');setText('previewCopyBtn','copy_code');setText('downloadSessionBtn','btn_export_session');setText('clearStaleTodosBtn','btn_clear_stale_todos');setText('refreshFilesBtn','btn_refresh');setPlaceholder('prompt','prompt_placeholder');const up=E('uploadDrop');if(up)up.textContent=t('upload_drop');const pfht=E('promptFileHintText');if(pfht)pfht.textContent=t('upload_file_hint');const pfpk=E('promptFilePick');if(pfpk)pfpk.textContent=t('upload_pick_file');const pdol=E('promptDropOverlay');if(pdol)pdol.textContent=t('upload_drop_release');const ctxLive=E('ctxLive');if(ctxLive)ctxLive.setAttribute('title',t('rt_ctx_live_title'));const panels=document.querySelectorAll('.panel-title');if(panels&&panels[0])panels[0].textContent=t('panel_sessions');if(panels&&panels[1])panels[1].textContent=t('panel_conversation');if(panels&&panels[2])panels[2].textContent=t('panel_runtime');const hs=document.querySelectorAll('#runtimeScroll h3');const keys=['sec_todos','sec_tasks','sec_activity','sec_commands','sec_diffs','sec_files','sec_catalog'];for(let i=0;i<hs.length&&i<keys.length;i++){hs[i].textContent=t(keys[i])}const _lvl2=S.snap?.user_task_level||0;updateLevelBtn(_lvl2);renderPreviewTabs()}
|
|
42785
44438
|
function renderLanguageControls(){const sel=E('langSelect');if(!sel)return;const langs=Array.isArray(S.config?.supported_languages)?S.config.supported_languages:[];if(!langs.length){sel.innerHTML='';return}const cur=String(S.config?.language||currentLang());sel.innerHTML='';for(const row of langs){const code=String(row?.code||'').trim();if(!code)continue;const op=document.createElement('option');op.value=code;op.textContent=String(row?.label||code);sel.appendChild(op)}if(cur)sel.value=cur}
|
|
42786
44439
|
async function setLanguage(lang){const code=String(lang||'').trim();if(!code)return;await api('/api/config/language',{method:'POST',body:JSON.stringify({language:code})});S.config=S.config||{};S.config.language=code;if(S.snap)S.snap.ui_language=code;if(S.mdWorker){try{S.mdWorker.terminate()}catch(_){}S.mdWorker=null}applyMainI18n();renderLanguageControls();renderStats();renderSessions();renderBoards();renderUploadList();scheduleRenderChat('language');renderSkillsEntryLink()}
|
|
42787
44440
|
function globalApiTimeoutMs(){const vals=[S.snap?.max_run_seconds,S.config?.request_timeout_default,S.config?.run_timeout];for(const raw of vals){const n=Number(raw);if(Number.isFinite(n)&&n>0)return Math.max(1000,Math.min(86400000,Math.round(n*1000)))}return 45000}
|
|
@@ -42838,8 +44491,13 @@ function setPanelHtml(id,html){
|
|
|
42838
44491
|
}
|
|
42839
44492
|
}
|
|
42840
44493
|
function formatContextLeft(snap){const left=Number(snap?.context_left_tokens);const pct=Number(snap?.context_left_percent);if(!Number.isFinite(left)||!Number.isFinite(pct))return '-';return `${left} (${pct.toFixed(1)}%)`}
|
|
44494
|
+
function contextLiveRows(snap){const mode=String(snap?.execution_mode||'').trim().toLowerCase();const showAgents=(mode==='sync'||mode==='sequential');const rows=showAgents&&Array.isArray(snap?.agent_contexts)?snap.agent_contexts.slice():[];const valid=rows.filter(r=>Number.isFinite(Number(r?.left))&&Number.isFinite(Number(r?.left_percent))).sort((a,b)=>(Number(a.left_percent)-Number(b.left_percent))||(Number(a.left)-Number(b.left)));if(valid.length>1)return valid;const left=Number(snap?.context_left_tokens);const pct=Number(snap?.context_left_percent);if(Number.isFinite(left)&&Number.isFinite(pct))return[{role:'single',label:t('role_single'),left,left_percent:pct,tier:0,active:true}];if(valid.length===1)return valid;return[]}
|
|
44495
|
+
function agentCtxColor(role){const r=String(role||'').trim().toLowerCase();if(r==='explorer')return'#ff4d4f';if(r==='developer')return'#20c997';if(r==='reviewer')return'#f59f00';if(r==='manager')return'#9b5cff';return'#64748b'}
|
|
44496
|
+
function agentContextLabel(row){const role=String(row?.role||'single').trim().toLowerCase();const roleKey=_chatVirtAgentRoleKey(role);if(roleKey)return _chatVirtAgentRoleLabel(roleKey);const raw=String(row?.label||'').trim();if(!raw||raw.toLowerCase()==='single')return t('role_single');return raw}
|
|
44497
|
+
function contextLiveNestedHtml(rows){const valid=(Array.isArray(rows)?rows:[]).filter(r=>Number.isFinite(Number(r?.left_percent))).slice(0,6);if(valid.length<=1)return'';const maxH=6;return valid.map((row,i)=>{const pct=Math.max(0,Math.min(100,Number(row?.left_percent)));const left=Number(row?.left);const tier=Number(row?.tier||0);const role=String(row?.role||'single').trim().toLowerCase();const roleKey=_chatVirtAgentRoleKey(role)||role||'single';const label=agentContextLabel(row);const color=agentCtxColor(roleKey);const h=Math.max(1.5,maxH-(i*1.2));const z=valid.length-i;const title=`${t('rt_ctx_left_for',{label})}=${Number.isFinite(left)?left:'-'} (${pct.toFixed(1)}%) · T${tier}`;return `<span class=\"ctx-live-agent-layer role-${esc(roleKey)}\" style=\"--ctx-agent-color:${esc(color)};height:${h.toFixed(1)}px;z-index:${z}\" title=\"${esc(title)}\"><span class=\"ctx-live-agent-fill\" style=\"width:${pct.toFixed(2)}%\"></span></span>`}).join('')}
|
|
44498
|
+
function agentContextChipsHtml(snap){const mode=String(snap?.execution_mode||'').trim().toLowerCase();if(mode!=='sync'&&mode!=='sequential')return'';const rows=Array.isArray(snap?.agent_contexts)?snap.agent_contexts:[];if(rows.length<=1)return'';return `<span class=\"agent-ctx-group\">${rows.slice(0,6).map(row=>{const role=String(row?.role||'single').trim().toLowerCase();const roleKey=_chatVirtAgentRoleKey(role)||role||'single';const label=agentContextLabel(row);const left=Number(row?.left);const pct=Number(row?.left_percent);const tier=Number(row?.tier||0);const safePct=Number.isFinite(pct)?Math.max(0,Math.min(100,pct)):0;const value=Number.isFinite(left)&&Number.isFinite(pct)?`${left} · ${safePct.toFixed(1)}% · T${tier}`:'-';const tone=safePct<=15?' danger':(safePct<=35?' warn':'');const active=row?.active?' active':'';return `<span class=\"agent-ctx-chip role-${esc(roleKey)}${tone}${active}\" title=\"${esc(String(row?.next_call_label||''))}\"><span class=\"agent-dot\"></span><span>${esc(label)}</span><span class=\"agent-ctx-value\">${esc(value)}</span></span>`}).join('')}</span>`}
|
|
42841
44499
|
function scheduleCompactRefreshBurst(count=COMPACT_AUTO_REFRESH_COUNT){if(!S.activeId)return;const n=Math.max(1,Math.min(10,Number(count)||COMPACT_AUTO_REFRESH_COUNT));const delay=Math.max(90,Math.min(1400,90+((n-1)*COMPACT_AUTO_REFRESH_INTERVAL_MS)));scheduleSnapshot({forceFull:false,delayMs:delay,allowWhenFrozen:true})}
|
|
42842
|
-
function renderCtxLive(snap){const box=E('ctxLive');const textEl=E('ctxLiveText');const fill=E('ctxLiveFill');if(!box||!textEl||!fill)return;const left=Number(
|
|
44500
|
+
function renderCtxLive(snap){const box=E('ctxLive');const textEl=E('ctxLiveText');const fill=E('ctxLiveFill');if(!box||!textEl||!fill)return;const rows=contextLiveRows(snap);const tight=rows[0]||null;const left=Number(tight?.left);const pct=Number(tight?.left_percent);if(!tight||!Number.isFinite(left)||!Number.isFinite(pct)){textEl.textContent=`${t('rt_ctx_left')}=-`;fill.innerHTML='';fill.style.width='0%';fill.style.background='';fill.classList.remove('segmented','multi');box.classList.remove('warn','danger','multi');return}const safePct=Math.max(0,Math.min(100,pct));if(rows.length>1){const label=agentContextLabel(tight);textEl.textContent=`${t('rt_ctx_left_for',{label})}=${left} (${safePct.toFixed(1)}%)`;box.classList.add('multi');fill.classList.remove('segmented');fill.classList.add('multi');fill.style.width='100%';fill.style.background='';fill.innerHTML=contextLiveNestedHtml(rows);const titleRows=rows.slice(0,6).map(row=>{const rowPct=Math.max(0,Math.min(100,Number(row?.left_percent)));const rowLeft=Number(row?.left);const rowLabel=agentContextLabel(row);return `${t('rt_ctx_left_for',{label:rowLabel})}=${Number.isFinite(rowLeft)?rowLeft:'-'} (${Number.isFinite(rowPct)?rowPct.toFixed(1):'-'}%)`}).join(' | ');if(titleRows)fill.setAttribute('title',titleRows)}else{textEl.textContent=`${t('rt_ctx_left')}=${left} (${safePct.toFixed(1)}%)`;box.classList.remove('multi');fill.innerHTML='';fill.classList.remove('segmented','multi');fill.style.width=`${safePct}%`;fill.style.background='';fill.removeAttribute('title')}box.classList.toggle('warn',safePct<=35&&safePct>15);box.classList.toggle('danger',safePct<=15)}
|
|
42843
44501
|
function showCompactToast(text){let el=document.querySelector('.compact-toast');if(!el){el=document.createElement('div');el.className='compact-toast';document.body.appendChild(el)}el.textContent=text;el.classList.add('show');if(el._t)clearTimeout(el._t);el._t=setTimeout(()=>el.classList.remove('show'),2800)}
|
|
42844
44502
|
function parseCompactReason(data){const direct=String(data?.reason||'').trim();if(direct)return direct;const s=String(data?.summary||'');const m=s.match(/context compacted \\(([^)]*)\\)/);return m?String(m[1]||'').trim():''}
|
|
42845
44503
|
function isRenderRuntimeEventType(evtType){return RENDER_EVT_TYPES.has(String(evtType||''))}
|
|
@@ -43176,7 +44834,7 @@ function _deltaStartWatchdog(){
|
|
|
43176
44834
|
function renderSkillsEntryLink(){const link=E('downloadBtn');if(!link)return;const host=location.hostname||'127.0.0.1';const enabled=Boolean(S.config?.skills_ui_enabled);const fromConfig=String(S.config?.skills_ui_url||'').trim();const skillsPort=Number(S.config?.skills_port||0);let href='#';if(enabled){if(fromConfig){href=fromConfig}else if(Number.isFinite(skillsPort)&&skillsPort>0){const currentPort=Number(location.port||0);if(!(currentPort&&skillsPort===currentPort)){href=`${location.protocol}//${host}:${skillsPort}`}}}const offline=(href==='#');link.href=href;link.classList.toggle('disabled',offline);link.textContent=offline?t('skills_offline'):t('open_skills')}
|
|
43177
44835
|
function tailSig(rows,count,mapper){const arr=Array.isArray(rows)?rows:[];if(!arr.length)return'';return arr.slice(Math.max(0,arr.length-count)).map(mapper).join('|')}
|
|
43178
44836
|
function feedSignature(snap){const feed=Array.isArray(snap?.conversation_feed)?snap.conversation_feed:(Array.isArray(snap?.messages)?snap.messages:[]);const sig=tailSig(feed,8,row=>`${Number(row?.ts||0)}:${String(row?.role||'')}:${String(row?.agent_role||'')}:${String(row?.type||'')}:${String(row?.text||'').length}:${String(row?.thinking||'').length}:${String(row?.text||'').slice(-12)}:${String(row?.thinking||'').slice(-12)}`);const live=String(snap?.live_thinking||'');const runActive=snap?.live_run_notice_active?1:0;const runLabel=String(snap?.live_run_notice_label||'');const runStart=Number(snap?.live_run_notice_started_at||0);const truncText=String(snap?.live_truncation_text||'');const truncKind=String(snap?.live_truncation_kind||'');const truncTool=String(snap?.live_truncation_tool||'');const truncAttempts=Number(snap?.live_truncation_attempts||0);const truncTokens=Number(snap?.live_truncation_tokens||0);const truncActive=snap?.live_truncation_active?1:0;return `${feed.length}|${sig}|lt=${live.length}:${live.slice(-12)}|rn=${runActive}:${runStart}:${runLabel.slice(-12)}|tr=${truncActive}:${truncAttempts}:${truncTokens}:${truncKind.slice(-12)}:${truncTool.slice(-12)}:${truncText.length}`}
|
|
43179
|
-
function boardsSignature(snap){return [snap?.running?1:0,snap?.agent_phase||'',Number(snap?.agent_round_index||0),Number(snap?.queued_user_inputs_count||0),Number(snap?.truncation_count||0),Number(snap?.live_truncation_attempts||0),Number(snap?.live_truncation_tokens||0),snap?.live_truncation_active?1:0,Number(snap?.context_tokens_estimate||0),Number(snap?.context_left_tokens||0),Number(snap?.context_left_percent||0),Number(snap?.render_bridge?.seq||0),(snap?.todos||[]).length,(snap?.tasks||[]).length,(snap?.activity||[]).length,(snap?.operations||[]).length,(snap?.uploads||[]).length].join('|')}
|
|
44837
|
+
function boardsSignature(snap){const agentCtx=(Array.isArray(snap?.agent_contexts)?snap.agent_contexts:[]).map(r=>`${r.role}:${r.left}:${r.left_percent}:${r.tier}:${r.active?1:0}`).join(',');return [snap?.running?1:0,snap?.agent_phase||'',Number(snap?.agent_round_index||0),Number(snap?.queued_user_inputs_count||0),Number(snap?.truncation_count||0),Number(snap?.live_truncation_attempts||0),Number(snap?.live_truncation_tokens||0),snap?.live_truncation_active?1:0,Number(snap?.context_tokens_estimate||0),Number(snap?.context_left_tokens||0),Number(snap?.context_left_percent||0),agentCtx,Number(snap?.render_bridge?.seq||0),(snap?.todos||[]).length,(snap?.tasks||[]).length,(snap?.activity||[]).length,(snap?.operations||[]).length,(snap?.uploads||[]).length].join('|')}
|
|
43180
44838
|
function sessionsSignature(list){const rows=Array.isArray(list)?list:[];const sig=tailSig(rows,6,row=>`${String(row?.id||'')}:${row?.running?1:0}:${Number(row?.message_count||0)}:${Number(row?.updated_at||0)}`);const aid=String(S.activeId||'').trim();let activeSig='-';if(aid){const activeRow=rows.find(row=>String(row?.id||'')===aid);if(activeRow){activeSig=`${aid}:${activeRow?.running?1:0}:${Number(activeRow?.message_count||0)}:${Number(activeRow?.updated_at||0)}`}else{activeSig=`missing:${aid}`}}return `${rows.length}|active=${activeSig}|${sig}`}
|
|
43181
44839
|
function _statInfinite(n){const v=Number(n);return(Number.isFinite(v)&&v>0)?String(v):'∞'}
|
|
43182
44840
|
function applyRuntimeConfigStats(cfg){if(!cfg||typeof cfg!=='object')return;S.config=S.config||{};if(cfg.scheduler&&typeof cfg.scheduler==='object')S.config.scheduler=cfg.scheduler;if(cfg.session_creation_limit&&typeof cfg.session_creation_limit==='object')S.config.session_creation_limit=cfg.session_creation_limit;if(Object.prototype.hasOwnProperty.call(cfg,'daily_session_limit'))S.config.daily_session_limit=cfg.daily_session_limit;if(Object.prototype.hasOwnProperty.call(cfg,'download_js_lib_enabled'))S.config.download_js_lib_enabled=!!cfg.download_js_lib_enabled;if(Object.prototype.hasOwnProperty.call(cfg,'request_timeout_default'))S.config.request_timeout_default=cfg.request_timeout_default;if(Object.prototype.hasOwnProperty.call(cfg,'run_timeout'))S.config.run_timeout=cfg.run_timeout;if(Object.prototype.hasOwnProperty.call(cfg,'shell_command_timeout_seconds'))S.config.shell_command_timeout_seconds=cfg.shell_command_timeout_seconds;if(Object.prototype.hasOwnProperty.call(cfg,'model')&&String(cfg.model||'').trim())S.config.model=cfg.model}
|
|
@@ -43213,7 +44871,7 @@ function renderSessions(){
|
|
|
43213
44871
|
}
|
|
43214
44872
|
function _syncActiveSessionSummaryFromSnapshot(){const sid=String(S.activeId||'').trim();const snap=S.snap;if(!sid||!snap)return false;const rows=Array.isArray(S.sessions)?S.sessions.slice():[];let idx=rows.findIndex(row=>String(row?.id||'')===sid);const running=!!snap?.running;let updatedAt=Number(snap?.updated_at||0);if(!Number.isFinite(updatedAt)||updatedAt<=0){updatedAt=(Date.now()/1000)}let msgCount=Number(snap?.message_count);if(!Number.isFinite(msgCount)||msgCount<0){const arr=Array.isArray(snap?.messages)?snap.messages:[];let cnt=0;for(const row of arr){if(String(row?.role||'').trim()==='tool')continue;cnt+=1}msgCount=cnt}msgCount=Math.max(0,Math.floor(Number(msgCount)||0));const title=String(snap?.title||'').trim();if(idx<0){rows.push({id:sid,title:title||sid,running:running,updated_at:updatedAt,message_count:msgCount});idx=rows.length-1}else{const cur=rows[idx]||{};const next={...cur};let changed=false;if(!!cur.running!==running){next.running=running;changed=true}if(Number(cur.message_count||0)!==msgCount){next.message_count=msgCount;changed=true}if(Number(cur.updated_at||0)!==updatedAt){next.updated_at=updatedAt;changed=true}if(title&&String(cur.title||'')!==title){next.title=title;changed=true}if(!changed)return false;rows[idx]=next}rows.sort((a,b)=>Number(b?.updated_at||0)-Number(a?.updated_at||0));S.sessions=rows;return true}
|
|
43215
44873
|
function diffLineClass(line){const t=String(line||'').trimStart();if(t.startsWith('+')||/^\\d+\\s+\\+\\s/.test(t))return 'diff-line-add';if(t.startsWith('-')||/^\\d+\\s+-\\s/.test(t))return 'diff-line-del';if(t.startsWith('@@')||t==='⋮'||t.startsWith('⋮ '))return 'diff-line-hunk';return ''}
|
|
43216
|
-
function diffHtml(diff){return String(diff||'').split('\\n').map(line=>`<div class=\"${diffLineClass(line)}\">${esc(line)}</div>`).join('')}
|
|
44874
|
+
function diffHtml(diff){return String(diff||'').split('\\n').map(line=>`<div class=\"diff-row ${diffLineClass(line)}\">${esc(line)}</div>`).join('')}
|
|
43217
44875
|
function _scrollContainerToNodeCenter(container,target){
|
|
43218
44876
|
if(!container||!target)return;
|
|
43219
44877
|
const maxTop=Math.max(0,Number(container.scrollHeight||0)-Number(container.clientHeight||0));
|
|
@@ -44592,8 +46250,8 @@ function _chatVirtReleaseNode(node){
|
|
|
44592
46250
|
CHAT_VIRT.poolSize=Number(CHAT_VIRT.poolSize||0)+1;
|
|
44593
46251
|
}
|
|
44594
46252
|
function _chatVirtReleaseRendered(root){if(!root)return;for(const node of root.querySelectorAll('.msg[data-vk]')){_chatVirtReleaseNode(node)}}
|
|
44595
|
-
function _chatVirtAgentRoleKey(raw){const role=String(raw||'').trim().toLowerCase();return(role==='explorer'||role==='developer'||role==='reviewer'||role==='manager'||role==='planner')?role:''}
|
|
44596
|
-
function _chatVirtAgentRoleLabel(role){if(role==='explorer')return t('role_explorer');if(role==='developer')return t('role_developer');if(role==='reviewer')return t('role_reviewer');if(role==='manager')return t('role_manager');if(role==='planner')return t('role_planner');return t('role_agent')}
|
|
46253
|
+
function _chatVirtAgentRoleKey(raw){const role=String(raw||'').trim().toLowerCase();return(role==='explorer'||role==='developer'||role==='reviewer'||role==='manager'||role==='planner'||role==='single')?role:''}
|
|
46254
|
+
function _chatVirtAgentRoleLabel(role){if(role==='explorer')return t('role_explorer');if(role==='developer')return t('role_developer');if(role==='reviewer')return t('role_reviewer');if(role==='manager')return t('role_manager');if(role==='planner')return t('role_planner');if(role==='single')return t('role_single');return t('role_agent')}
|
|
44597
46255
|
function _stripLeadingAgentTitle(raw,agentRole){
|
|
44598
46256
|
let txt=String(raw||'').replace(/^\\uFEFF/,'').trimStart();
|
|
44599
46257
|
const role=_chatVirtAgentRoleKey(agentRole);
|
|
@@ -45422,7 +47080,7 @@ function _cmdPageCount(op){const d=(op&&typeof op==='object'&&op.data&&typeof op
|
|
|
45422
47080
|
function _cmdCurrentPage(op){if(!S.commandPageState||typeof S.commandPageState!=='object')S.commandPageState={};const key=_cmdStateKey(op);const total=_cmdPageCount(op);let page=Number(S.commandPageState[key]||1);if(!Number.isFinite(page)||page<1)page=1;if(page>total)page=total;S.commandPageState[key]=page;return page}
|
|
45423
47081
|
function _cmdPageText(op,page){const d=(op&&typeof op==='object'&&op.data&&typeof op.data==='object')?op.data:{};const pages=Array.isArray(d.ui_output_pages)?d.ui_output_pages:[];if(!pages.length)return String(d.output||'');const idx=Math.max(0,Math.min(pages.length-1,Number(page||1)-1));return String(pages[idx]||'')}
|
|
45424
47082
|
function _runtimePillHtml(label,value,opts={}){const wide=opts&&opts.wide?' runtime-pill-wide':'';const tone=opts&&opts.tone?` ${opts.tone}`:'';const mono=opts&&opts.mono?' mono':'';return `<span class=\"runtime-pill${wide}${tone}\"><span class=\"runtime-pill-label\">${esc(label)}</span><span class=\"runtime-pill-value${mono}\">${esc(String(value??'-'))}</span></span>`}
|
|
45425
|
-
function renderBoards(){const uiState=S.staticMode?(S.frozen?'static':'live'):'live';const boolWord=v=>t(v?'state_on':'state_off');const activeRole=String(S.snap?.agent_active_role||'').trim();const activeRoleLabel=activeRole?_chatVirtAgentRoleLabel(activeRole):'-';const runtimeItems=[{label:t('rt_session'),value:S.snap?.id||'-',mono:true},{label:t('rt_model'),value:S.snap?.model||'-',mono:true},{label:t('rt_thinking'),value:boolWord(S.snap?.thinking)},{label:t('rt_thinking_stream'),value:boolWord(S.snap?.thinking_stream)},{label:t('rt_mode'),value:S.snap?.execution_mode||S.config?.execution_mode||'sync'},{label:t('rt_active_agent'),value:activeRoleLabel},{label:t('rt_blackboard'),value:S.snap?.blackboard?.status||'-'},{label:t('rt_task'),value:S.snap?.blackboard?.task_profile?.task_type||'-'},{label:t('rt_complexity'),value:S.snap?.blackboard?.task_profile?.complexity||'-'},{label:t('rt_judgement'),value:S.snap?.blackboard?.manager_judgement?.progress||'-'},{label:t('rt_budget'),value:S.snap?.blackboard?.task_profile?.round_budget??'-'},{label:t('rt_remaining'),value:S.snap?.blackboard?.manager_judgement?.remaining_rounds??'-'},{label:t('rt_blackboard_cycles'),value:S.snap?.blackboard?.manager_cycles??'-'},{label:t('rt_round_limit'),value:S.snap?.max_agent_rounds||'-'},{label:t('rt_round'),value:S.snap?.agent_round_index??'-'},{label:t('rt_phase'),value:S.snap?.agent_phase||t('idle')},{label:t('rt_queued_inputs'),value:S.snap?.queued_user_inputs_count??0},{label:t('rt_run_timeout'),value:`${S.snap?.max_run_seconds??'-'}s`},{label:t('rt_ctx_used'),value:S.snap?.context_tokens_estimate??'-'},{label:t('rt_ctx_limit'),value:S.snap?.context_effective_token_limit||S.snap?.context_token_upper_bound||'-'},{label:t('rt_ctx_mode'),value:t(S.snap?.context_token_limit_locked?'rt_manual_lock':'rt_adaptive')},{label:t('rt_ctx_left'),value:formatContextLeft(S.snap)},{label:t('rt_truncation'),value:S.snap?.truncation_count||0},{label:t('rt_trunc_retry'),value:S.snap?.live_truncation_attempts||0},{label:t('rt_trunc_tokens'),value:S.snap?.live_truncation_tokens||0},{label:t('rt_archive'),value:S.snap?.compact_segments_count||0},{label:t('rt_last_compact'),value:S.snap?.last_compact_reason||'-'},{label:t('rt_ollama'),value:S.snap?.ollama_base_url||'-',mono:true,wide:true},{label:t('rt_files'),value:S.snap?.session_files_root||'-',mono:true,wide:true},{label:t('rt_ui_mode'),value:uiState},{label:t('rt_state'),value:S.snap?.running?t('running'):t('idle'),tone:S.snap?.running?'state-running':'state-idle'}];E('status').innerHTML=runtimeItems.map(item=>_runtimePillHtml(item.label,item.value,item)).join('');
|
|
47083
|
+
function renderBoards(){const uiState=S.staticMode?(S.frozen?'static':'live'):'live';const boolWord=v=>t(v?'state_on':'state_off');const activeRole=String(S.snap?.agent_active_role||'').trim();const activeRoleLabel=activeRole?_chatVirtAgentRoleLabel(activeRole):'-';const runtimeItems=[{label:t('rt_session'),value:S.snap?.id||'-',mono:true},{label:t('rt_model'),value:S.snap?.model||'-',mono:true},{label:t('rt_thinking'),value:boolWord(S.snap?.thinking)},{label:t('rt_thinking_stream'),value:boolWord(S.snap?.thinking_stream)},{label:t('rt_mode'),value:S.snap?.execution_mode||S.config?.execution_mode||'sync'},{label:t('rt_active_agent'),value:activeRoleLabel},{label:t('rt_blackboard'),value:S.snap?.blackboard?.status||'-'},{label:t('rt_task'),value:S.snap?.blackboard?.task_profile?.task_type||'-'},{label:t('rt_complexity'),value:S.snap?.blackboard?.task_profile?.complexity||'-'},{label:t('rt_judgement'),value:S.snap?.blackboard?.manager_judgement?.progress||'-'},{label:t('rt_budget'),value:S.snap?.blackboard?.task_profile?.round_budget??'-'},{label:t('rt_remaining'),value:S.snap?.blackboard?.manager_judgement?.remaining_rounds??'-'},{label:t('rt_blackboard_cycles'),value:S.snap?.blackboard?.manager_cycles??'-'},{label:t('rt_round_limit'),value:S.snap?.max_agent_rounds||'-'},{label:t('rt_round'),value:S.snap?.agent_round_index??'-'},{label:t('rt_phase'),value:S.snap?.agent_phase||t('idle')},{label:t('rt_queued_inputs'),value:S.snap?.queued_user_inputs_count??0},{label:t('rt_run_timeout'),value:`${S.snap?.max_run_seconds??'-'}s`},{label:t('rt_ctx_used'),value:S.snap?.context_tokens_estimate??'-'},{label:t('rt_ctx_limit'),value:S.snap?.context_effective_token_limit||S.snap?.context_token_upper_bound||'-'},{label:t('rt_ctx_mode'),value:t(S.snap?.context_token_limit_locked?'rt_manual_lock':'rt_adaptive')},{label:t('rt_ctx_left'),value:formatContextLeft(S.snap)},{label:t('rt_truncation'),value:S.snap?.truncation_count||0},{label:t('rt_trunc_retry'),value:S.snap?.live_truncation_attempts||0},{label:t('rt_trunc_tokens'),value:S.snap?.live_truncation_tokens||0},{label:t('rt_archive'),value:S.snap?.compact_segments_count||0},{label:t('rt_last_compact'),value:S.snap?.last_compact_reason||'-'},{label:t('rt_ollama'),value:S.snap?.ollama_base_url||'-',mono:true,wide:true},{label:t('rt_files'),value:S.snap?.session_files_root||'-',mono:true,wide:true},{label:t('rt_ui_mode'),value:uiState},{label:t('rt_state'),value:S.snap?.running?t('running'):t('idle'),tone:S.snap?.running?'state-running':'state-idle'}];E('status').innerHTML=runtimeItems.map(item=>_runtimePillHtml(item.label,item.value,item)).join('')+agentContextChipsHtml(S.snap);
|
|
45426
47084
|
renderCtxLive(S.snap);
|
|
45427
47085
|
const _pmBtn=E('planModeBtn');if(_pmBtn){const _pm=S.snap?.plan_mode_preference||'auto';_pmBtn.textContent='Plan: '+_pm.charAt(0).toUpperCase()+_pm.slice(1)}
|
|
45428
47086
|
const _lvl=S.snap?.user_task_level||0;updateLevelBtn(_lvl)
|
|
@@ -45801,7 +47459,7 @@ window.addEventListener('DOMContentLoaded',async()=>{for(const id of ['chat','se
|
|
|
45801
47459
|
APP_TS = """type SessionSummary={id:string;title:string;running:boolean;updated_at:number;message_count:number};
|
|
45802
47460
|
type Msg={role:string;text:string;thinking?:string;agent_role?:string};
|
|
45803
47461
|
type UploadMeta={id:string;filename:string;workspace_path:string;kind:string;size:number;uploaded_at:number;preview?:string};
|
|
45804
|
-
type Snapshot={id:string;title:string;running:boolean;message_count?:number;model:string;ollama_base_url:string;thinking:boolean;thinking_stream?:boolean;live_thinking?:string;live_truncation_text?:string;live_truncation_kind?:string;live_truncation_tool?:string;live_truncation_active?:boolean;live_truncation_attempts?:number;live_truncation_tokens?:number;live_run_notice_active?:boolean;live_run_notice_label?:string;live_run_notice_started_at?:number;live_run_notice_elapsed?:number;execution_mode?:string;agent_active_role?:string;max_agent_rounds?:number;max_run_seconds?:number;agent_round_index?:number;agent_phase?:string;agent_active_tool?:string;queued_user_inputs_count?:number;context_token_upper_bound?:number;context_token_limit_config?:number;context_token_limit_locked?:boolean;context_tokens_estimate?:number;context_left_tokens?:number;context_left_percent?:number;context_used_percent?:number;truncation_count?:number;compact_segments_count?:number;last_compact_reason?:string;last_compact_ts?:number;last_compact_segment_id?:string;event_seq?:number;render_bridge?:{seq:number;received?:number;last_ts?:number;last_kind?:string;latest?:Record<string,unknown>};blackboard?:{status?:string;original_goal?:string;manager_cycles?:number;active_agent?:string;approval?:Record<string,unknown>;last_delegate?:Record<string,unknown>};messages:Msg[];uploads?:UploadMeta[];llm_model_catalog?:ModelCatalog|null};
|
|
47462
|
+
type Snapshot={id:string;title:string;running:boolean;message_count?:number;model:string;ollama_base_url:string;thinking:boolean;thinking_stream?:boolean;live_thinking?:string;live_truncation_text?:string;live_truncation_kind?:string;live_truncation_tool?:string;live_truncation_active?:boolean;live_truncation_attempts?:number;live_truncation_tokens?:number;live_run_notice_active?:boolean;live_run_notice_label?:string;live_run_notice_started_at?:number;live_run_notice_elapsed?:number;execution_mode?:string;agent_active_role?:string;agent_contexts?:Array<{role:string;label?:string;active?:boolean;used?:number;left?:number;left_percent?:number;effective_limit?:number;tier?:number;message_count?:number;next_call_label?:string}>;max_agent_rounds?:number;max_run_seconds?:number;agent_round_index?:number;agent_phase?:string;agent_active_tool?:string;queued_user_inputs_count?:number;context_token_upper_bound?:number;context_token_limit_config?:number;context_token_limit_locked?:boolean;context_tokens_estimate?:number;context_left_tokens?:number;context_left_percent?:number;context_used_percent?:number;truncation_count?:number;compact_segments_count?:number;last_compact_reason?:string;last_compact_ts?:number;last_compact_segment_id?:string;event_seq?:number;render_bridge?:{seq:number;received?:number;last_ts?:number;last_kind?:string;latest?:Record<string,unknown>};blackboard?:{status?:string;original_goal?:string;manager_cycles?:number;active_agent?:string;approval?:Record<string,unknown>;last_delegate?:Record<string,unknown>};messages:Msg[];uploads?:UploadMeta[];llm_model_catalog?:ModelCatalog|null};
|
|
45805
47463
|
type SkillMeta={name:string;qualified_name?:string;description:string;provider_id?:string;protocol?:string;meta:Record<string,string>};
|
|
45806
47464
|
type SkillProvider={provider_id:string;protocol:string;protocol_version:string;skill_count:number;description:string};
|
|
45807
47465
|
type SkillProtocol={protocol:string;version:string;active_providers:number;active_skills:number;description:string};
|