clouds-coder 2026.3.29__tar.gz → 2026.3.30__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.3.29 → clouds_coder-2026.3.30}/Clouds_Coder.py +1580 -340
- {clouds_coder-2026.3.29/clouds_coder.egg-info → clouds_coder-2026.3.30}/PKG-INFO +1 -1
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30/clouds_coder.egg-info}/PKG-INFO +1 -1
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/pyproject.toml +1 -1
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/LICENSE +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/README.md +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/clouds_coder.egg-info/SOURCES.txt +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/clouds_coder.egg-info/dependency_links.txt +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/clouds_coder.egg-info/entry_points.txt +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/clouds_coder.egg-info/requires.txt +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/clouds_coder.egg-info/top_level.txt +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/setup.cfg +0 -0
- {clouds_coder-2026.3.29 → clouds_coder-2026.3.30}/tests/test_smoke.py +0 -0
|
@@ -376,10 +376,10 @@ TASK_LEVEL_POLICIES: dict[int, dict] = {
|
|
|
376
376
|
},
|
|
377
377
|
3: {
|
|
378
378
|
"name": "light_collaboration",
|
|
379
|
-
"execution_mode":
|
|
380
|
-
"participants": ["
|
|
379
|
+
"execution_mode": EXECUTION_MODE_SINGLE,
|
|
380
|
+
"participants": ["developer"],
|
|
381
381
|
"assigned_expert": "developer",
|
|
382
|
-
"round_budget":
|
|
382
|
+
"round_budget": 16,
|
|
383
383
|
"requires_user_confirmation": False,
|
|
384
384
|
"complexity": "simple",
|
|
385
385
|
},
|
|
@@ -450,7 +450,7 @@ SKILL_BODY_PREVIEW_CHARS = 4_000
|
|
|
450
450
|
SKILLS_VIRTUAL_PREFIX = "/skills"
|
|
451
451
|
SKILLS_EXTERNAL_MOUNT = "__external__"
|
|
452
452
|
PLAN_MODE_ENABLED_LEVELS = {3, 4, 5}
|
|
453
|
-
PLAN_MODE_FORCED_LEVELS = {4, 5}
|
|
453
|
+
PLAN_MODE_FORCED_LEVELS = {3, 4, 5}
|
|
454
454
|
PLAN_MODE_USER_CHOICES = ("auto", "on", "off")
|
|
455
455
|
# Task phase definitions for stage-aware delegation
|
|
456
456
|
TASK_PHASES = ("research", "design", "implement", "test", "review", "deploy")
|
|
@@ -1288,6 +1288,475 @@ def model_language_instruction(lang: str) -> str:
|
|
|
1288
1288
|
)
|
|
1289
1289
|
|
|
1290
1290
|
|
|
1291
|
+
BACKEND_I18N = {
|
|
1292
|
+
"en": {
|
|
1293
|
+
"role_explorer": "Explorer",
|
|
1294
|
+
"role_developer": "Developer",
|
|
1295
|
+
"role_reviewer": "Reviewer",
|
|
1296
|
+
"role_manager": "Manager",
|
|
1297
|
+
"role_planner": "Planner",
|
|
1298
|
+
"role_agent": "Agent",
|
|
1299
|
+
"todo_no_changes": "No todo changes.",
|
|
1300
|
+
"todo_no_todos": "No todos.",
|
|
1301
|
+
"todo_working": "Working on: {content}",
|
|
1302
|
+
"todo_completed": "Completed: {content}",
|
|
1303
|
+
"todo_pending": "Pending: {content}",
|
|
1304
|
+
"todo_working_owner": "Working on ({owner}): {content}",
|
|
1305
|
+
"todo_completed_owner": "Completed ({owner}): {content}",
|
|
1306
|
+
"todo_pending_owner": "Pending ({owner}): {content}",
|
|
1307
|
+
"todo_footer": "({done}/{total} completed)",
|
|
1308
|
+
},
|
|
1309
|
+
"zh-CN": {
|
|
1310
|
+
"role_explorer": "探索者",
|
|
1311
|
+
"role_developer": "开发者",
|
|
1312
|
+
"role_reviewer": "审查者",
|
|
1313
|
+
"role_manager": "管理者",
|
|
1314
|
+
"role_planner": "规划者",
|
|
1315
|
+
"role_agent": "Agent",
|
|
1316
|
+
"todo_no_changes": "待办无变化。",
|
|
1317
|
+
"todo_no_todos": "暂无待办。",
|
|
1318
|
+
"todo_working": "进行中:{content}",
|
|
1319
|
+
"todo_completed": "已完成:{content}",
|
|
1320
|
+
"todo_pending": "待处理:{content}",
|
|
1321
|
+
"todo_working_owner": "进行中({owner}):{content}",
|
|
1322
|
+
"todo_completed_owner": "已完成({owner}):{content}",
|
|
1323
|
+
"todo_pending_owner": "待处理({owner}):{content}",
|
|
1324
|
+
"todo_footer": "(已完成 {done}/{total})",
|
|
1325
|
+
},
|
|
1326
|
+
"zh-TW": {
|
|
1327
|
+
"role_explorer": "探索者",
|
|
1328
|
+
"role_developer": "開發者",
|
|
1329
|
+
"role_reviewer": "審查者",
|
|
1330
|
+
"role_manager": "管理者",
|
|
1331
|
+
"role_planner": "規劃者",
|
|
1332
|
+
"role_agent": "Agent",
|
|
1333
|
+
"todo_no_changes": "待辦沒有變化。",
|
|
1334
|
+
"todo_no_todos": "尚無待辦。",
|
|
1335
|
+
"todo_working": "進行中:{content}",
|
|
1336
|
+
"todo_completed": "已完成:{content}",
|
|
1337
|
+
"todo_pending": "待處理:{content}",
|
|
1338
|
+
"todo_working_owner": "進行中({owner}):{content}",
|
|
1339
|
+
"todo_completed_owner": "已完成({owner}):{content}",
|
|
1340
|
+
"todo_pending_owner": "待處理({owner}):{content}",
|
|
1341
|
+
"todo_footer": "(已完成 {done}/{total})",
|
|
1342
|
+
},
|
|
1343
|
+
"ja": {
|
|
1344
|
+
"role_explorer": "探索担当",
|
|
1345
|
+
"role_developer": "開発担当",
|
|
1346
|
+
"role_reviewer": "レビュー担当",
|
|
1347
|
+
"role_manager": "マネージャー",
|
|
1348
|
+
"role_planner": "プランナー",
|
|
1349
|
+
"role_agent": "Agent",
|
|
1350
|
+
"todo_no_changes": "Todo に変更はありません。",
|
|
1351
|
+
"todo_no_todos": "Todo はありません。",
|
|
1352
|
+
"todo_working": "進行中: {content}",
|
|
1353
|
+
"todo_completed": "完了: {content}",
|
|
1354
|
+
"todo_pending": "未着手: {content}",
|
|
1355
|
+
"todo_working_owner": "進行中 ({owner}): {content}",
|
|
1356
|
+
"todo_completed_owner": "完了 ({owner}): {content}",
|
|
1357
|
+
"todo_pending_owner": "未着手 ({owner}): {content}",
|
|
1358
|
+
"todo_footer": "({done}/{total} 完了)",
|
|
1359
|
+
},
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
BACKEND_I18N["en"].update(
|
|
1363
|
+
{
|
|
1364
|
+
"todo_node_suffix": " | node: {topic}",
|
|
1365
|
+
"node_desc_manager_active": "Plan route and coordinate current node handoff ({target} active)",
|
|
1366
|
+
"node_desc_manager": "Plan route and coordinate current node handoff",
|
|
1367
|
+
"node_desc_explorer_active": "Gather constraints and evidence for current node",
|
|
1368
|
+
"node_desc_explorer": "Provide research support and risk notes for current node",
|
|
1369
|
+
"node_desc_developer_active": "Implement concrete outputs and file or tool changes for current node",
|
|
1370
|
+
"node_desc_developer": "Prepare and deliver implementation updates for current node",
|
|
1371
|
+
"node_desc_reviewer_active": "Validate current node and provide pass or fix judgement",
|
|
1372
|
+
"node_desc_reviewer": "Review outputs and keep the quality gate updated for current node",
|
|
1373
|
+
"node_desc_generic": "Handle current node work",
|
|
1374
|
+
"project_answer_objective": "Answer: {objective}",
|
|
1375
|
+
"project_answer_default": "Answer the user's question",
|
|
1376
|
+
"project_analyze_requirements": "Analyze requirements and project structure",
|
|
1377
|
+
"project_implement_objective": "Implement: {objective}",
|
|
1378
|
+
"project_implement_default": "Implement the coding task",
|
|
1379
|
+
"project_compile_check": "Compile / syntax check",
|
|
1380
|
+
"project_min_test": "Minimal functional test",
|
|
1381
|
+
"project_research_objective": "Research: {objective}",
|
|
1382
|
+
"project_research_default": "Run the research task",
|
|
1383
|
+
"project_research_summary": "Organize research findings",
|
|
1384
|
+
"project_execute_objective": "Execute: {objective}",
|
|
1385
|
+
"project_execute_default": "Execute the task",
|
|
1386
|
+
"evidence_structure_analyzed": "structure analyzed",
|
|
1387
|
+
"evidence_files_produced": "{count} file(s) produced",
|
|
1388
|
+
"evidence_compile_passed": "compile check passed",
|
|
1389
|
+
"evidence_test_passed": "tests passed",
|
|
1390
|
+
"evidence_review_passed": "review passed",
|
|
1391
|
+
"step_completed_evidence": "step completed",
|
|
1392
|
+
"plan_step_summary": "📋 Plan step {step}/{total}: {content}",
|
|
1393
|
+
"plan_step_label": "Step {step}/{total}",
|
|
1394
|
+
"plan_step_hint": "[plan-step-advance] Previous step completed. Now at {step_label}: {step_text}\nRead updated plan: read_file {plan_path}\nCall TodoWrite to set subtasks for THIS step ONLY.\nEach subtask MUST include parent_step_id='{parent_step_id}'. Create 3-5 items, one marked in_progress, others pending.\nDo NOT create subtasks for other plan steps.",
|
|
1395
|
+
"stall_execution_blocked_title": "## Execution Blocked\n",
|
|
1396
|
+
"stall_stop_reason": "**Stop reason:** {reason}",
|
|
1397
|
+
"stall_error_details": "**Error details:** {detail}",
|
|
1398
|
+
"stall_recent_error": "**Recent error:**",
|
|
1399
|
+
"stall_repeated_tools": "**Repeated tool calls:** {tools}",
|
|
1400
|
+
"stall_suggested_actions": "**Suggested actions:**",
|
|
1401
|
+
"stall_action_1": "1. Check whether the environment satisfies the task requirements (files exist, dependencies installed)",
|
|
1402
|
+
"stall_action_2": "2. Manually run the failed command and confirm the error output",
|
|
1403
|
+
"stall_action_3": "3. Provide more specific guidance or revise the task description, then retry",
|
|
1404
|
+
"stall_continue_prompt": "Please provide further instructions and I will continue from the new information.",
|
|
1405
|
+
"stall_analysis_title": "### Stall Analysis\n",
|
|
1406
|
+
"stall_goal": "**Goal:** {goal}",
|
|
1407
|
+
"stall_severity": "**Severity score:** {score}",
|
|
1408
|
+
"stall_events": "**Stall event sequence:**",
|
|
1409
|
+
"stall_event_line": "- [{source}] +{points} -> total {cumulative}",
|
|
1410
|
+
"stall_error_context": "**Error context:**",
|
|
1411
|
+
"stall_repeated_tools_label": "**Repeated tools:** {tools}",
|
|
1412
|
+
"stall_last_fault_reason": "**Last fault reason:** {reason}",
|
|
1413
|
+
"stall_open_todos": "**Open todos:**",
|
|
1414
|
+
"plan_file_proposals_title": "# Execution Plan Proposals\n",
|
|
1415
|
+
"plan_file_background": "## Background\n{context}\n",
|
|
1416
|
+
"plan_file_option": "## Option {id}: {title}",
|
|
1417
|
+
"plan_file_recommended": " [RECOMMENDED]",
|
|
1418
|
+
"plan_file_steps": "### Steps",
|
|
1419
|
+
"plan_file_pros": "**Pros:** {text}",
|
|
1420
|
+
"plan_file_cons": "**Cons:** {text}",
|
|
1421
|
+
"plan_file_risk": "**Risk:** {text}",
|
|
1422
|
+
"plan_file_awaiting_choice": "> Awaiting user choice.",
|
|
1423
|
+
"active_plan_title": "# Active Plan: {title}\n",
|
|
1424
|
+
"active_plan_status": "> Status: EXECUTING | Step {current}/{total}",
|
|
1425
|
+
"active_plan_chosen": "> Chosen: Option {choice}",
|
|
1426
|
+
"active_plan_updated": "> Updated: {updated}\n",
|
|
1427
|
+
"active_plan_summary": "## Summary\n{summary}\n",
|
|
1428
|
+
"active_plan_steps": "## Steps\n",
|
|
1429
|
+
"active_plan_step_done": "- [x] Step {idx}: {header}",
|
|
1430
|
+
"active_plan_step_current": "- [>] Step {idx}: {header} <-- CURRENT",
|
|
1431
|
+
"active_plan_step_pending": "- [ ] Step {idx}: {header}",
|
|
1432
|
+
"active_plan_completed_by": "Completed by: {actor}",
|
|
1433
|
+
"active_plan_evidence": "Evidence: {evidence}",
|
|
1434
|
+
"plan_bubble_title": "## 📋 Execution Plans\n",
|
|
1435
|
+
"plan_bubble_background": "**Background:** {context}\n",
|
|
1436
|
+
"plan_bubble_option": "### Option {id}: {title}",
|
|
1437
|
+
"plan_bubble_recommended": " ⭐ Recommended",
|
|
1438
|
+
"plan_bubble_steps": "Steps: {count}",
|
|
1439
|
+
"plan_bubble_risk": "Risk: {risk}",
|
|
1440
|
+
"plan_bubble_full_ref": "Full plan: `{path}`",
|
|
1441
|
+
"plan_bubble_reply": 'Reply with a choice (e.g. "Option A", "A", "choose 1"), or provide revisions.',
|
|
1442
|
+
"plan_read_instruction": "[plan-file] The approved execution plan is at `{path}`.\nUse: read_file {path} to review full steps and live status.\nThe plan file is the authoritative source for step ordering and completion status.\nExecute steps IN ORDER. Do NOT skip ahead. Mark the current step done before advancing.\nIf a step references a skill or workflow, call load_skill before proceeding.",
|
|
1443
|
+
"plan_read_todo_note": "\nTODO PLANNING: At the START of your work, call TodoWrite to list ALL subtasks you plan to complete for {step_label} (status=pending, parent_step_id='{parent_step_id}'). Create 3-5 subtasks for THIS step ONLY — do NOT list subtasks for other plan steps. As you complete each subtask, update it to status=completed. When ALL subtasks are done, call finish_current_task to signal step completion.\n",
|
|
1444
|
+
"plan_proposal_title": "## 📋 Execution Plans\n",
|
|
1445
|
+
"plan_proposal_background": "### Background Analysis\n{context}\n",
|
|
1446
|
+
"plan_proposal_option": "### Option {id}: {title}",
|
|
1447
|
+
"plan_proposal_recommended": " ⭐ Recommended",
|
|
1448
|
+
"plan_proposal_steps": "**Steps:**",
|
|
1449
|
+
"plan_proposal_pros": "**Pros:** {text}",
|
|
1450
|
+
"plan_proposal_cons": "**Cons:** {text}",
|
|
1451
|
+
"plan_proposal_risk": "**Risk:** {text}",
|
|
1452
|
+
"plan_proposal_reply": 'Reply with a choice (e.g. "Option A", "A", "choose 1"), or provide revisions.',
|
|
1453
|
+
"status_project_todos_synced": "project todos synced ({reason})",
|
|
1454
|
+
}
|
|
1455
|
+
)
|
|
1456
|
+
BACKEND_I18N["zh-CN"].update(
|
|
1457
|
+
{
|
|
1458
|
+
"todo_node_suffix": " | 当前节点:{topic}",
|
|
1459
|
+
"node_desc_manager_active": "规划路由并协调当前节点交接({target} 正在处理)",
|
|
1460
|
+
"node_desc_manager": "规划路由并协调当前节点交接",
|
|
1461
|
+
"node_desc_explorer_active": "为当前节点收集约束与证据",
|
|
1462
|
+
"node_desc_explorer": "为当前节点提供调研支持与风险备注",
|
|
1463
|
+
"node_desc_developer_active": "为当前节点实施具体产出以及文件/工具改动",
|
|
1464
|
+
"node_desc_developer": "为当前节点准备并交付实现更新",
|
|
1465
|
+
"node_desc_reviewer_active": "校验当前节点并给出通过/修复判断",
|
|
1466
|
+
"node_desc_reviewer": "审查当前节点产出并维护质量闸门",
|
|
1467
|
+
"node_desc_generic": "处理当前节点工作",
|
|
1468
|
+
"project_answer_objective": "回答:{objective}",
|
|
1469
|
+
"project_answer_default": "回答用户问题",
|
|
1470
|
+
"project_analyze_requirements": "分析需求和项目结构",
|
|
1471
|
+
"project_implement_objective": "实现:{objective}",
|
|
1472
|
+
"project_implement_default": "实现编码任务",
|
|
1473
|
+
"project_compile_check": "编译 / 语法检查",
|
|
1474
|
+
"project_min_test": "最小功能测试",
|
|
1475
|
+
"project_research_objective": "调研:{objective}",
|
|
1476
|
+
"project_research_default": "执行调研任务",
|
|
1477
|
+
"project_research_summary": "整理调研结果",
|
|
1478
|
+
"project_execute_objective": "执行:{objective}",
|
|
1479
|
+
"project_execute_default": "执行任务",
|
|
1480
|
+
"evidence_structure_analyzed": "结构已分析",
|
|
1481
|
+
"evidence_files_produced": "已产出 {count} 个文件",
|
|
1482
|
+
"evidence_compile_passed": "编译通过",
|
|
1483
|
+
"evidence_test_passed": "测试通过",
|
|
1484
|
+
"evidence_review_passed": "审查通过",
|
|
1485
|
+
"step_completed_evidence": "步骤已完成",
|
|
1486
|
+
"plan_step_summary": "📋 计划步骤 {step}/{total}:{content}",
|
|
1487
|
+
"plan_step_label": "步骤 {step}/{total}",
|
|
1488
|
+
"plan_step_hint": "[plan-step-advance] 上一步已完成。当前来到{step_label}:{step_text}\n读取更新后的计划:read_file {plan_path}\n现在调用 TodoWrite,只为当前步骤拆分子任务。\n每个子任务都必须包含 parent_step_id='{parent_step_id}'。创建 3-5 项,其中 1 项为 in_progress,其余为 pending。\n不要为其他计划步骤创建子任务。",
|
|
1489
|
+
"stall_execution_blocked_title": "## 执行遇阻\n",
|
|
1490
|
+
"stall_stop_reason": "**停止原因:** {reason}",
|
|
1491
|
+
"stall_error_details": "**错误详情:** {detail}",
|
|
1492
|
+
"stall_recent_error": "**最近错误:**",
|
|
1493
|
+
"stall_repeated_tools": "**重复工具调用:** {tools}",
|
|
1494
|
+
"stall_suggested_actions": "**建议操作:**",
|
|
1495
|
+
"stall_action_1": "1. 检查环境是否满足任务要求(文件是否存在、依赖是否安装)",
|
|
1496
|
+
"stall_action_2": "2. 手动执行失败的命令,确认错误输出",
|
|
1497
|
+
"stall_action_3": "3. 提供更具体的指导或调整任务描述后重试",
|
|
1498
|
+
"stall_continue_prompt": "请提供进一步指示,我会基于新的信息继续执行。",
|
|
1499
|
+
"stall_analysis_title": "### 卡死分析\n",
|
|
1500
|
+
"stall_goal": "**目标:** {goal}",
|
|
1501
|
+
"stall_severity": "**严重度分数:** {score}",
|
|
1502
|
+
"stall_events": "**卡死事件序列:**",
|
|
1503
|
+
"stall_event_line": "- [{source}] +{points} -> 累计 {cumulative}",
|
|
1504
|
+
"stall_error_context": "**错误上下文:**",
|
|
1505
|
+
"stall_repeated_tools_label": "**重复工具:** {tools}",
|
|
1506
|
+
"stall_last_fault_reason": "**最后故障原因:** {reason}",
|
|
1507
|
+
"stall_open_todos": "**未完成任务:**",
|
|
1508
|
+
"plan_file_proposals_title": "# 执行方案提案\n",
|
|
1509
|
+
"plan_file_background": "## 背景\n{context}\n",
|
|
1510
|
+
"plan_file_option": "## 方案 {id}:{title}",
|
|
1511
|
+
"plan_file_recommended": " [推荐]",
|
|
1512
|
+
"plan_file_steps": "### 步骤",
|
|
1513
|
+
"plan_file_pros": "**优势:** {text}",
|
|
1514
|
+
"plan_file_cons": "**劣势:** {text}",
|
|
1515
|
+
"plan_file_risk": "**风险:** {text}",
|
|
1516
|
+
"plan_file_awaiting_choice": "> 等待用户选择。",
|
|
1517
|
+
"active_plan_title": "# 当前执行方案:{title}\n",
|
|
1518
|
+
"active_plan_status": "> 状态:执行中 | 步骤 {current}/{total}",
|
|
1519
|
+
"active_plan_chosen": "> 已选择:方案 {choice}",
|
|
1520
|
+
"active_plan_updated": "> 更新时间:{updated}\n",
|
|
1521
|
+
"active_plan_summary": "## 摘要\n{summary}\n",
|
|
1522
|
+
"active_plan_steps": "## 步骤\n",
|
|
1523
|
+
"active_plan_step_done": "- [x] 步骤 {idx}:{header}",
|
|
1524
|
+
"active_plan_step_current": "- [>] 步骤 {idx}:{header} <-- 当前",
|
|
1525
|
+
"active_plan_step_pending": "- [ ] 步骤 {idx}:{header}",
|
|
1526
|
+
"active_plan_completed_by": "执行者:{actor}",
|
|
1527
|
+
"active_plan_evidence": "证据:{evidence}",
|
|
1528
|
+
"plan_bubble_title": "## 📋 执行方案\n",
|
|
1529
|
+
"plan_bubble_background": "**背景:** {context}\n",
|
|
1530
|
+
"plan_bubble_option": "### 方案 {id}:{title}",
|
|
1531
|
+
"plan_bubble_recommended": " ⭐推荐",
|
|
1532
|
+
"plan_bubble_steps": "步骤数:{count}",
|
|
1533
|
+
"plan_bubble_risk": "风险:{risk}",
|
|
1534
|
+
"plan_bubble_full_ref": "完整方案详见:`{path}`",
|
|
1535
|
+
"plan_bubble_reply": "请回复选择(如“方案A”“A”“选1”),或输入修改意见。",
|
|
1536
|
+
"plan_read_instruction": "[plan-file] 已批准的执行计划位于 `{path}`。\n使用:read_file {path} 查看完整步骤与实时状态。\n计划文件是步骤顺序与完成状态的唯一权威来源。\n请按顺序执行步骤,不要跳步。完成当前步骤后再推进下一步。\n如果某一步引用了 skill 或 workflow,继续前先调用 load_skill。",
|
|
1537
|
+
"plan_read_todo_note": "\nTODO 更新:一开始就调用 TodoWrite,只为当前步骤({step_label})设置子任务。\n每个子任务都必须包含 parent_step_id='{parent_step_id}'。\n创建 3-5 个只属于当前步骤的子任务,并在完成后及时标记完成。\n不要为其他计划步骤创建子任务。\n",
|
|
1538
|
+
"plan_proposal_title": "## 📋 执行方案\n",
|
|
1539
|
+
"plan_proposal_background": "### 背景分析\n{context}\n",
|
|
1540
|
+
"plan_proposal_option": "### 方案 {id}:{title}",
|
|
1541
|
+
"plan_proposal_recommended": " ⭐推荐",
|
|
1542
|
+
"plan_proposal_steps": "**步骤:**",
|
|
1543
|
+
"plan_proposal_pros": "**优势:** {text}",
|
|
1544
|
+
"plan_proposal_cons": "**劣势:** {text}",
|
|
1545
|
+
"plan_proposal_risk": "**风险:** {text}",
|
|
1546
|
+
"plan_proposal_reply": "请回复选择(如“方案A”“A”“选1”),或输入修改意见。",
|
|
1547
|
+
"status_project_todos_synced": "项目待办已同步({reason})",
|
|
1548
|
+
}
|
|
1549
|
+
)
|
|
1550
|
+
BACKEND_I18N["zh-TW"].update(
|
|
1551
|
+
{
|
|
1552
|
+
"todo_node_suffix": " | 目前節點:{topic}",
|
|
1553
|
+
"node_desc_manager_active": "規劃路由並協調目前節點交接({target} 正在處理)",
|
|
1554
|
+
"node_desc_manager": "規劃路由並協調目前節點交接",
|
|
1555
|
+
"node_desc_explorer_active": "為目前節點蒐集限制與證據",
|
|
1556
|
+
"node_desc_explorer": "為目前節點提供研究支援與風險備註",
|
|
1557
|
+
"node_desc_developer_active": "為目前節點落實具體產出以及檔案/工具變更",
|
|
1558
|
+
"node_desc_developer": "為目前節點準備並交付實作更新",
|
|
1559
|
+
"node_desc_reviewer_active": "驗證目前節點並給出通過/修復判斷",
|
|
1560
|
+
"node_desc_reviewer": "審查目前節點產出並維護品質閘門",
|
|
1561
|
+
"node_desc_generic": "處理目前節點工作",
|
|
1562
|
+
"project_answer_objective": "回答:{objective}",
|
|
1563
|
+
"project_answer_default": "回答使用者問題",
|
|
1564
|
+
"project_analyze_requirements": "分析需求與專案結構",
|
|
1565
|
+
"project_implement_objective": "實作:{objective}",
|
|
1566
|
+
"project_implement_default": "實作程式任務",
|
|
1567
|
+
"project_compile_check": "編譯 / 語法檢查",
|
|
1568
|
+
"project_min_test": "最小功能測試",
|
|
1569
|
+
"project_research_objective": "調研:{objective}",
|
|
1570
|
+
"project_research_default": "執行調研任務",
|
|
1571
|
+
"project_research_summary": "整理調研結果",
|
|
1572
|
+
"project_execute_objective": "執行:{objective}",
|
|
1573
|
+
"project_execute_default": "執行任務",
|
|
1574
|
+
"evidence_structure_analyzed": "結構已分析",
|
|
1575
|
+
"evidence_files_produced": "已產出 {count} 個檔案",
|
|
1576
|
+
"evidence_compile_passed": "編譯通過",
|
|
1577
|
+
"evidence_test_passed": "測試通過",
|
|
1578
|
+
"evidence_review_passed": "審查通過",
|
|
1579
|
+
"step_completed_evidence": "步驟已完成",
|
|
1580
|
+
"plan_step_summary": "📋 計畫步驟 {step}/{total}:{content}",
|
|
1581
|
+
"plan_step_label": "步驟 {step}/{total}",
|
|
1582
|
+
"plan_step_hint": "[plan-step-advance] 上一步已完成。現在來到{step_label}:{step_text}\n讀取更新後的計畫:read_file {plan_path}\n現在呼叫 TodoWrite,只為目前步驟拆分子任務。\n每個子任務都必須包含 parent_step_id='{parent_step_id}'。建立 3-5 項,其中 1 項為 in_progress,其餘為 pending。\n不要為其他計畫步驟建立子任務。",
|
|
1583
|
+
"stall_execution_blocked_title": "## 執行受阻\n",
|
|
1584
|
+
"stall_stop_reason": "**停止原因:** {reason}",
|
|
1585
|
+
"stall_error_details": "**錯誤詳情:** {detail}",
|
|
1586
|
+
"stall_recent_error": "**最近錯誤:**",
|
|
1587
|
+
"stall_repeated_tools": "**重複工具呼叫:** {tools}",
|
|
1588
|
+
"stall_suggested_actions": "**建議操作:**",
|
|
1589
|
+
"stall_action_1": "1. 檢查環境是否符合任務要求(檔案是否存在、依賴是否安裝)",
|
|
1590
|
+
"stall_action_2": "2. 手動執行失敗命令,確認錯誤輸出",
|
|
1591
|
+
"stall_action_3": "3. 提供更具體的指示或調整任務描述後再重試",
|
|
1592
|
+
"stall_continue_prompt": "請提供進一步指示,我會依照新的資訊繼續執行。",
|
|
1593
|
+
"stall_analysis_title": "### 卡住分析\n",
|
|
1594
|
+
"stall_goal": "**目標:** {goal}",
|
|
1595
|
+
"stall_severity": "**嚴重度分數:** {score}",
|
|
1596
|
+
"stall_events": "**卡住事件序列:**",
|
|
1597
|
+
"stall_event_line": "- [{source}] +{points} -> 累計 {cumulative}",
|
|
1598
|
+
"stall_error_context": "**錯誤上下文:**",
|
|
1599
|
+
"stall_repeated_tools_label": "**重複工具:** {tools}",
|
|
1600
|
+
"stall_last_fault_reason": "**最後故障原因:** {reason}",
|
|
1601
|
+
"stall_open_todos": "**未完成任務:**",
|
|
1602
|
+
"plan_file_proposals_title": "# 執行方案提案\n",
|
|
1603
|
+
"plan_file_background": "## 背景\n{context}\n",
|
|
1604
|
+
"plan_file_option": "## 方案 {id}:{title}",
|
|
1605
|
+
"plan_file_recommended": " [推薦]",
|
|
1606
|
+
"plan_file_steps": "### 步驟",
|
|
1607
|
+
"plan_file_pros": "**優勢:** {text}",
|
|
1608
|
+
"plan_file_cons": "**劣勢:** {text}",
|
|
1609
|
+
"plan_file_risk": "**風險:** {text}",
|
|
1610
|
+
"plan_file_awaiting_choice": "> 等待使用者選擇。",
|
|
1611
|
+
"active_plan_title": "# 目前執行方案:{title}\n",
|
|
1612
|
+
"active_plan_status": "> 狀態:執行中 | 步驟 {current}/{total}",
|
|
1613
|
+
"active_plan_chosen": "> 已選擇:方案 {choice}",
|
|
1614
|
+
"active_plan_updated": "> 更新時間:{updated}\n",
|
|
1615
|
+
"active_plan_summary": "## 摘要\n{summary}\n",
|
|
1616
|
+
"active_plan_steps": "## 步驟\n",
|
|
1617
|
+
"active_plan_step_done": "- [x] 步驟 {idx}:{header}",
|
|
1618
|
+
"active_plan_step_current": "- [>] 步驟 {idx}:{header} <-- 目前",
|
|
1619
|
+
"active_plan_step_pending": "- [ ] 步驟 {idx}:{header}",
|
|
1620
|
+
"active_plan_completed_by": "執行者:{actor}",
|
|
1621
|
+
"active_plan_evidence": "證據:{evidence}",
|
|
1622
|
+
"plan_bubble_title": "## 📋 執行方案\n",
|
|
1623
|
+
"plan_bubble_background": "**背景:** {context}\n",
|
|
1624
|
+
"plan_bubble_option": "### 方案 {id}:{title}",
|
|
1625
|
+
"plan_bubble_recommended": " ⭐推薦",
|
|
1626
|
+
"plan_bubble_steps": "步驟數:{count}",
|
|
1627
|
+
"plan_bubble_risk": "風險:{risk}",
|
|
1628
|
+
"plan_bubble_full_ref": "完整方案詳見:`{path}`",
|
|
1629
|
+
"plan_bubble_reply": "請回覆選擇(如「方案A」「A」「選1」),或輸入修改意見。",
|
|
1630
|
+
"plan_read_instruction": "[plan-file] 已核准的執行計畫位於 `{path}`。\n使用:read_file {path} 查看完整步驟與即時狀態。\n計畫檔是步驟順序與完成狀態的唯一權威來源。\n請依序執行步驟,不要跳步。完成目前步驟後再推進下一步。\n如果某一步引用了 skill 或 workflow,繼續前先呼叫 load_skill。",
|
|
1631
|
+
"plan_read_todo_note": "\nTODO 更新:一開始就呼叫 TodoWrite,只為目前步驟({step_label})設定子任務。\n每個子任務都必須包含 parent_step_id='{parent_step_id}'。\n建立 3-5 個只屬於目前步驟的子任務,並在完成後即時標記完成。\n不要為其他計畫步驟建立子任務。\n",
|
|
1632
|
+
"plan_proposal_title": "## 📋 執行方案\n",
|
|
1633
|
+
"plan_proposal_background": "### 背景分析\n{context}\n",
|
|
1634
|
+
"plan_proposal_option": "### 方案 {id}:{title}",
|
|
1635
|
+
"plan_proposal_recommended": " ⭐推薦",
|
|
1636
|
+
"plan_proposal_steps": "**步驟:**",
|
|
1637
|
+
"plan_proposal_pros": "**優勢:** {text}",
|
|
1638
|
+
"plan_proposal_cons": "**劣勢:** {text}",
|
|
1639
|
+
"plan_proposal_risk": "**風險:** {text}",
|
|
1640
|
+
"plan_proposal_reply": "請回覆選擇(如「方案A」「A」「選1」),或輸入修改意見。",
|
|
1641
|
+
"status_project_todos_synced": "專案待辦已同步({reason})",
|
|
1642
|
+
}
|
|
1643
|
+
)
|
|
1644
|
+
BACKEND_I18N["ja"].update(
|
|
1645
|
+
{
|
|
1646
|
+
"todo_node_suffix": " | 現在のノード: {topic}",
|
|
1647
|
+
"node_desc_manager_active": "現在のノードの引き継ぎを計画し調整する ({target} が担当中)",
|
|
1648
|
+
"node_desc_manager": "現在のノードの引き継ぎを計画し調整する",
|
|
1649
|
+
"node_desc_explorer_active": "現在のノードに必要な制約と証拠を集める",
|
|
1650
|
+
"node_desc_explorer": "現在のノードに調査支援とリスクメモを提供する",
|
|
1651
|
+
"node_desc_developer_active": "現在のノードで具体的な成果物とファイル/ツール変更を実装する",
|
|
1652
|
+
"node_desc_developer": "現在のノード向けの実装更新を準備して反映する",
|
|
1653
|
+
"node_desc_reviewer_active": "現在のノードを検証し、通過か修正かを判断する",
|
|
1654
|
+
"node_desc_reviewer": "出力をレビューし、現在のノードの品質ゲートを維持する",
|
|
1655
|
+
"node_desc_generic": "現在のノード作業を処理する",
|
|
1656
|
+
"project_answer_objective": "回答: {objective}",
|
|
1657
|
+
"project_answer_default": "ユーザーの質問に回答する",
|
|
1658
|
+
"project_analyze_requirements": "要件とプロジェクト構造を分析する",
|
|
1659
|
+
"project_implement_objective": "実装: {objective}",
|
|
1660
|
+
"project_implement_default": "コーディング作業を実装する",
|
|
1661
|
+
"project_compile_check": "コンパイル / 構文チェック",
|
|
1662
|
+
"project_min_test": "最小機能テスト",
|
|
1663
|
+
"project_research_objective": "調査: {objective}",
|
|
1664
|
+
"project_research_default": "調査タスクを実行する",
|
|
1665
|
+
"project_research_summary": "調査結果を整理する",
|
|
1666
|
+
"project_execute_objective": "実行: {objective}",
|
|
1667
|
+
"project_execute_default": "タスクを実行する",
|
|
1668
|
+
"evidence_structure_analyzed": "構造分析済み",
|
|
1669
|
+
"evidence_files_produced": "{count} 個のファイルを生成済み",
|
|
1670
|
+
"evidence_compile_passed": "コンパイル確認済み",
|
|
1671
|
+
"evidence_test_passed": "テスト通過",
|
|
1672
|
+
"evidence_review_passed": "レビュー通過",
|
|
1673
|
+
"step_completed_evidence": "ステップ完了",
|
|
1674
|
+
"plan_step_summary": "📋 計画ステップ {step}/{total}: {content}",
|
|
1675
|
+
"plan_step_label": "ステップ {step}/{total}",
|
|
1676
|
+
"plan_step_hint": "[plan-step-advance] 前のステップが完了しました。現在は{step_label}: {step_text}\n更新済みプランを読む: read_file {plan_path}\n今すぐ TodoWrite を呼び出し、現在のステップだけのサブタスクを設定してください。\n各サブタスクには parent_step_id='{parent_step_id}' を必ず含めてください。3-5 件作成し、1 件を in_progress、残りを pending にしてください。\n他の計画ステップのサブタスクは作成しないでください。",
|
|
1677
|
+
"stall_execution_blocked_title": "## 実行が停止しました\n",
|
|
1678
|
+
"stall_stop_reason": "**停止理由:** {reason}",
|
|
1679
|
+
"stall_error_details": "**エラー詳細:** {detail}",
|
|
1680
|
+
"stall_recent_error": "**直近のエラー:**",
|
|
1681
|
+
"stall_repeated_tools": "**重複したツール呼び出し:** {tools}",
|
|
1682
|
+
"stall_suggested_actions": "**推奨アクション:**",
|
|
1683
|
+
"stall_action_1": "1. 環境がタスク要件を満たしているか確認する(ファイルの存在、依存関係の導入など)",
|
|
1684
|
+
"stall_action_2": "2. 失敗したコマンドを手動で実行し、エラー出力を確認する",
|
|
1685
|
+
"stall_action_3": "3. より具体的な指示を与えるか、タスク記述を調整してから再試行する",
|
|
1686
|
+
"stall_continue_prompt": "追加の指示をいただければ、新しい情報に基づいて続行します。",
|
|
1687
|
+
"stall_analysis_title": "### 停滞分析\n",
|
|
1688
|
+
"stall_goal": "**目標:** {goal}",
|
|
1689
|
+
"stall_severity": "**重大度スコア:** {score}",
|
|
1690
|
+
"stall_events": "**停滞イベント列:**",
|
|
1691
|
+
"stall_event_line": "- [{source}] +{points} -> 累計 {cumulative}",
|
|
1692
|
+
"stall_error_context": "**エラー文脈:**",
|
|
1693
|
+
"stall_repeated_tools_label": "**重複ツール:** {tools}",
|
|
1694
|
+
"stall_last_fault_reason": "**直近の故障理由:** {reason}",
|
|
1695
|
+
"stall_open_todos": "**未完了 Todo:**",
|
|
1696
|
+
"plan_file_proposals_title": "# 実行プラン候補\n",
|
|
1697
|
+
"plan_file_background": "## 背景\n{context}\n",
|
|
1698
|
+
"plan_file_option": "## 案 {id}: {title}",
|
|
1699
|
+
"plan_file_recommended": " [推奨]",
|
|
1700
|
+
"plan_file_steps": "### 手順",
|
|
1701
|
+
"plan_file_pros": "**利点:** {text}",
|
|
1702
|
+
"plan_file_cons": "**欠点:** {text}",
|
|
1703
|
+
"plan_file_risk": "**リスク:** {text}",
|
|
1704
|
+
"plan_file_awaiting_choice": "> ユーザー選択待ち。",
|
|
1705
|
+
"active_plan_title": "# 現在の実行プラン: {title}\n",
|
|
1706
|
+
"active_plan_status": "> 状態: 実行中 | ステップ {current}/{total}",
|
|
1707
|
+
"active_plan_chosen": "> 選択済み: 案 {choice}",
|
|
1708
|
+
"active_plan_updated": "> 更新時刻: {updated}\n",
|
|
1709
|
+
"active_plan_summary": "## 要約\n{summary}\n",
|
|
1710
|
+
"active_plan_steps": "## 手順\n",
|
|
1711
|
+
"active_plan_step_done": "- [x] ステップ {idx}: {header}",
|
|
1712
|
+
"active_plan_step_current": "- [>] ステップ {idx}: {header} <-- 現在",
|
|
1713
|
+
"active_plan_step_pending": "- [ ] ステップ {idx}: {header}",
|
|
1714
|
+
"active_plan_completed_by": "完了者: {actor}",
|
|
1715
|
+
"active_plan_evidence": "証拠: {evidence}",
|
|
1716
|
+
"plan_bubble_title": "## 📋 実行プラン\n",
|
|
1717
|
+
"plan_bubble_background": "**背景:** {context}\n",
|
|
1718
|
+
"plan_bubble_option": "### 案 {id}: {title}",
|
|
1719
|
+
"plan_bubble_recommended": " ⭐推奨",
|
|
1720
|
+
"plan_bubble_steps": "手順数: {count}",
|
|
1721
|
+
"plan_bubble_risk": "リスク: {risk}",
|
|
1722
|
+
"plan_bubble_full_ref": "完全なプラン: `{path}`",
|
|
1723
|
+
"plan_bubble_reply": "選択肢(例: 「案A」「A」「1を選ぶ」)を返信するか、修正要望を入力してください。",
|
|
1724
|
+
"plan_read_instruction": "[plan-file] 承認済みの実行計画は `{path}` にあります。\nread_file {path} を使って完全な手順と進行状況を確認してください。\n計画ファイルは手順順序と完了状態の唯一の正式ソースです。\n必ず順番に実行し、飛ばさないでください。現在のステップを完了してから次へ進んでください。\nステップが skill や workflow を参照している場合は、続行前に load_skill を呼び出してください。",
|
|
1725
|
+
"plan_read_todo_note": "\nTODO 更新: 最初に TodoWrite を呼び出し、現在のステップ({step_label})だけのサブタスクを設定してください。\n各サブタスクには parent_step_id='{parent_step_id}' を必ず含めてください。\n現在のステップだけを分解した 3-5 件のサブタスクを作り、完了ごとに完了状態へ更新してください。\n他の計画ステップのサブタスクは作成しないでください。\n",
|
|
1726
|
+
"plan_proposal_title": "## 📋 実行プラン\n",
|
|
1727
|
+
"plan_proposal_background": "### 背景分析\n{context}\n",
|
|
1728
|
+
"plan_proposal_option": "### 案 {id}: {title}",
|
|
1729
|
+
"plan_proposal_recommended": " ⭐推奨",
|
|
1730
|
+
"plan_proposal_steps": "**手順:**",
|
|
1731
|
+
"plan_proposal_pros": "**利点:** {text}",
|
|
1732
|
+
"plan_proposal_cons": "**欠点:** {text}",
|
|
1733
|
+
"plan_proposal_risk": "**リスク:** {text}",
|
|
1734
|
+
"plan_proposal_reply": "選択肢(例: 「案A」「A」「1を選ぶ」)を返信するか、修正要望を入力してください。",
|
|
1735
|
+
"status_project_todos_synced": "プロジェクト Todo を同期しました({reason})",
|
|
1736
|
+
}
|
|
1737
|
+
)
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
def backend_i18n_text(language: str, key: str, **kwargs) -> str:
|
|
1741
|
+
code = normalize_ui_language(language)
|
|
1742
|
+
pack = BACKEND_I18N.get(code, BACKEND_I18N["en"])
|
|
1743
|
+
fallback = BACKEND_I18N["en"]
|
|
1744
|
+
template = str(pack.get(key, fallback.get(key, key)))
|
|
1745
|
+
if kwargs:
|
|
1746
|
+
try:
|
|
1747
|
+
return template.format(**{k: ("" if v is None else v) for k, v in kwargs.items()})
|
|
1748
|
+
except Exception:
|
|
1749
|
+
return template
|
|
1750
|
+
return template
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
def backend_role_label(role: str, language: str) -> str:
|
|
1754
|
+
role_key = str(role or "").strip().lower()
|
|
1755
|
+
if role_key in {"explorer", "developer", "reviewer", "manager", "planner"}:
|
|
1756
|
+
return backend_i18n_text(language, f"role_{role_key}")
|
|
1757
|
+
return backend_i18n_text(language, "role_agent")
|
|
1758
|
+
|
|
1759
|
+
|
|
1291
1760
|
def _detect_os_shell_instruction() -> str:
|
|
1292
1761
|
"""Return a shell environment note for the agent system prompt based on the host OS."""
|
|
1293
1762
|
import platform as _platform
|
|
@@ -3112,7 +3581,7 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
3112
3581
|
media_endpoints=build_profile_media_endpoints("siliconflow"),
|
|
3113
3582
|
)
|
|
3114
3583
|
|
|
3115
|
-
# ── vLLM (local)
|
|
3584
|
+
# ── vLLM (local) ──────────────────────────────────────────────
|
|
3116
3585
|
vllm_url = str(config.get("vllm_url", "")).strip()
|
|
3117
3586
|
vllm_model = str(config.get("vllm_model", "")).strip()
|
|
3118
3587
|
vllm_key = str(config.get("vllm_key", "")).strip()
|
|
@@ -4251,10 +4720,61 @@ class EventHub:
|
|
|
4251
4720
|
pass
|
|
4252
4721
|
|
|
4253
4722
|
class TodoManager:
|
|
4254
|
-
def __init__(self):
|
|
4723
|
+
def __init__(self, language: str = DEFAULT_UI_LANGUAGE):
|
|
4724
|
+
self.language = normalize_ui_language(language)
|
|
4255
4725
|
self.items: list[dict] = []
|
|
4256
4726
|
self.lock = threading.Lock()
|
|
4257
4727
|
|
|
4728
|
+
def _text(self, key: str, **kwargs) -> str:
|
|
4729
|
+
return backend_i18n_text(self.language, key, **kwargs)
|
|
4730
|
+
|
|
4731
|
+
def _owner_label(self, owner: str) -> str:
|
|
4732
|
+
return backend_role_label(owner, self.language) if str(owner or "").strip() else ""
|
|
4733
|
+
|
|
4734
|
+
def no_changes_text(self) -> str:
|
|
4735
|
+
return self._text("todo_no_changes")
|
|
4736
|
+
|
|
4737
|
+
def set_language(self, language: str, *, relabel: bool = True) -> str:
|
|
4738
|
+
with self.lock:
|
|
4739
|
+
self.language = normalize_ui_language(language)
|
|
4740
|
+
if relabel and self.items:
|
|
4741
|
+
refreshed = []
|
|
4742
|
+
for item in self.items:
|
|
4743
|
+
row = dict(item)
|
|
4744
|
+
row["activeForm"] = self._default_active_form(
|
|
4745
|
+
row.get("status", "pending"),
|
|
4746
|
+
row.get("content", ""),
|
|
4747
|
+
owner=row.get("owner", ""),
|
|
4748
|
+
)
|
|
4749
|
+
refreshed.append(row)
|
|
4750
|
+
self.items = refreshed
|
|
4751
|
+
return self.language
|
|
4752
|
+
|
|
4753
|
+
def _default_active_form(
|
|
4754
|
+
self,
|
|
4755
|
+
status: str,
|
|
4756
|
+
content: str,
|
|
4757
|
+
*,
|
|
4758
|
+
owner: str = "",
|
|
4759
|
+
note: str = "",
|
|
4760
|
+
) -> str:
|
|
4761
|
+
state = str(status or "pending").strip().lower()
|
|
4762
|
+
base = normalize_work_text(str(content or "").strip(), state) or str(content or "").strip()
|
|
4763
|
+
suffix = f" ({trim(str(note or '').strip(), 180)})" if note else ""
|
|
4764
|
+
final_content = f"{base}{suffix}".strip()
|
|
4765
|
+
owner_label = self._owner_label(owner)
|
|
4766
|
+
if owner_label:
|
|
4767
|
+
if state == "in_progress":
|
|
4768
|
+
return self._text("todo_working_owner", owner=owner_label, content=final_content)
|
|
4769
|
+
if state == "completed":
|
|
4770
|
+
return self._text("todo_completed_owner", owner=owner_label, content=final_content)
|
|
4771
|
+
return self._text("todo_pending_owner", owner=owner_label, content=final_content)
|
|
4772
|
+
if state == "in_progress":
|
|
4773
|
+
return self._text("todo_working", content=final_content)
|
|
4774
|
+
if state == "completed":
|
|
4775
|
+
return self._text("todo_completed", content=final_content)
|
|
4776
|
+
return self._text("todo_pending", content=final_content)
|
|
4777
|
+
|
|
4258
4778
|
def update(self, items: list[dict]) -> str:
|
|
4259
4779
|
if not isinstance(items, list):
|
|
4260
4780
|
raise ValueError("items must be array")
|
|
@@ -4313,12 +4833,7 @@ class TodoManager:
|
|
|
4313
4833
|
else:
|
|
4314
4834
|
worker_in_progress_seen = True
|
|
4315
4835
|
if not active_form:
|
|
4316
|
-
|
|
4317
|
-
active_form = f"Working on: {content}"
|
|
4318
|
-
elif status == "completed":
|
|
4319
|
-
active_form = f"Completed: {content}"
|
|
4320
|
-
else:
|
|
4321
|
-
active_form = f"Pending: {content}"
|
|
4836
|
+
active_form = self._default_active_form(status, content, owner=owner)
|
|
4322
4837
|
row = {"content": content, "status": status, "activeForm": active_form}
|
|
4323
4838
|
if owner:
|
|
4324
4839
|
row["owner"] = owner
|
|
@@ -4335,11 +4850,15 @@ class TodoManager:
|
|
|
4335
4850
|
for row in validated:
|
|
4336
4851
|
if row["status"] == "pending":
|
|
4337
4852
|
row["status"] = "in_progress"
|
|
4338
|
-
row["activeForm"] = row.get("activeForm") or
|
|
4853
|
+
row["activeForm"] = row.get("activeForm") or self._default_active_form(
|
|
4854
|
+
"in_progress",
|
|
4855
|
+
row["content"],
|
|
4856
|
+
owner=row.get("owner", ""),
|
|
4857
|
+
)
|
|
4339
4858
|
break
|
|
4340
4859
|
with self.lock:
|
|
4341
4860
|
if self.items == validated:
|
|
4342
|
-
return
|
|
4861
|
+
return self.no_changes_text()
|
|
4343
4862
|
self.items = validated
|
|
4344
4863
|
return self.render()
|
|
4345
4864
|
|
|
@@ -4351,7 +4870,7 @@ class TodoManager:
|
|
|
4351
4870
|
with self.lock:
|
|
4352
4871
|
items = list(self.items)
|
|
4353
4872
|
if not items:
|
|
4354
|
-
return
|
|
4873
|
+
return self._text("todo_no_todos")
|
|
4355
4874
|
lines = []
|
|
4356
4875
|
for item in items:
|
|
4357
4876
|
marker = {"completed": "[x]", "in_progress": "[>]", "pending": "[ ]"}.get(
|
|
@@ -4362,7 +4881,7 @@ class TodoManager:
|
|
|
4362
4881
|
)
|
|
4363
4882
|
lines.append(f"{marker} {item['content']}{suffix}")
|
|
4364
4883
|
done = sum(1 for x in items if x["status"] == "completed")
|
|
4365
|
-
lines.append(f"\n(
|
|
4884
|
+
lines.append(f"\n{self._text('todo_footer', done=done, total=len(items))}")
|
|
4366
4885
|
return "\n".join(lines)
|
|
4367
4886
|
|
|
4368
4887
|
def snapshot(self) -> list[dict]:
|
|
@@ -4386,8 +4905,12 @@ class TodoManager:
|
|
|
4386
4905
|
base = normalize_work_text(str(rows[idx].get("content", "")).strip()) or str(
|
|
4387
4906
|
rows[idx].get("content", "")
|
|
4388
4907
|
).strip()
|
|
4389
|
-
|
|
4390
|
-
|
|
4908
|
+
rows[idx]["activeForm"] = self._default_active_form(
|
|
4909
|
+
"completed",
|
|
4910
|
+
base,
|
|
4911
|
+
owner=rows[idx].get("owner", ""),
|
|
4912
|
+
note=note,
|
|
4913
|
+
)
|
|
4391
4914
|
changed += 1
|
|
4392
4915
|
else:
|
|
4393
4916
|
for idx, row in enumerate(rows):
|
|
@@ -4396,8 +4919,12 @@ class TodoManager:
|
|
|
4396
4919
|
base = normalize_work_text(str(rows[idx].get("content", "")).strip()) or str(
|
|
4397
4920
|
rows[idx].get("content", "")
|
|
4398
4921
|
).strip()
|
|
4399
|
-
|
|
4400
|
-
|
|
4922
|
+
rows[idx]["activeForm"] = self._default_active_form(
|
|
4923
|
+
"completed",
|
|
4924
|
+
base,
|
|
4925
|
+
owner=rows[idx].get("owner", ""),
|
|
4926
|
+
note=note,
|
|
4927
|
+
)
|
|
4401
4928
|
changed = 1
|
|
4402
4929
|
break
|
|
4403
4930
|
if changed > 0:
|
|
@@ -4420,8 +4947,12 @@ class TodoManager:
|
|
|
4420
4947
|
base = normalize_work_text(str(rows[idx].get("content", "")).strip()) or str(
|
|
4421
4948
|
rows[idx].get("content", "")
|
|
4422
4949
|
).strip()
|
|
4423
|
-
|
|
4424
|
-
|
|
4950
|
+
rows[idx]["activeForm"] = self._default_active_form(
|
|
4951
|
+
"completed",
|
|
4952
|
+
base,
|
|
4953
|
+
owner=rows[idx].get("owner", ""),
|
|
4954
|
+
note=note,
|
|
4955
|
+
)
|
|
4425
4956
|
changed += 1
|
|
4426
4957
|
if changed > 0:
|
|
4427
4958
|
self.items = rows
|
|
@@ -11265,7 +11796,7 @@ class SessionState:
|
|
|
11265
11796
|
self.active_profile_id = ""
|
|
11266
11797
|
self.multimodal_capability_cache: dict[str, dict] = {}
|
|
11267
11798
|
self.failed_selections: list[str] = []
|
|
11268
|
-
self.todo = TodoManager()
|
|
11799
|
+
self.todo = TodoManager(self.ui_language)
|
|
11269
11800
|
self.single_advance_prompt_enhance = False
|
|
11270
11801
|
self.skills = SkillStore(skills_root)
|
|
11271
11802
|
self.skill_load_cache: dict[str, dict] = {}
|
|
@@ -11311,6 +11842,7 @@ class SessionState:
|
|
|
11311
11842
|
self.runtime_task_complexity = ""
|
|
11312
11843
|
self.runtime_complexity_floor = ""
|
|
11313
11844
|
self.runtime_task_level_floor = 0
|
|
11845
|
+
self.runtime_task_level_ceiling = 0 # 0 = no ceiling; set from plan risk on approval
|
|
11314
11846
|
self.runtime_scale_preference = "balanced"
|
|
11315
11847
|
self.runtime_direct_objective = ""
|
|
11316
11848
|
self.runtime_reclassify_goal = ""
|
|
@@ -12402,6 +12934,9 @@ class SessionState:
|
|
|
12402
12934
|
self.runtime_task_level_floor = int(
|
|
12403
12935
|
raw.get("runtime_task_level_floor", self.runtime_task_level_floor) or 0
|
|
12404
12936
|
)
|
|
12937
|
+
self.runtime_task_level_ceiling = int(
|
|
12938
|
+
raw.get("runtime_task_level_ceiling", self.runtime_task_level_ceiling) or 0
|
|
12939
|
+
)
|
|
12405
12940
|
self.runtime_goal_reset_pending = bool(
|
|
12406
12941
|
raw.get("runtime_goal_reset_pending", self.runtime_goal_reset_pending)
|
|
12407
12942
|
)
|
|
@@ -12494,6 +13029,7 @@ class SessionState:
|
|
|
12494
13029
|
row["model"] = fmodel.strip()
|
|
12495
13030
|
row["selection"] = f"{self.active_profile_id}::{row.get('model','')}"
|
|
12496
13031
|
self.model_profiles[self.active_profile_id] = row
|
|
13032
|
+
self._set_ui_language(self.ui_language, relabel_todos=True)
|
|
12497
13033
|
self._apply_active_profile()
|
|
12498
13034
|
self._prune_skill_load_cache()
|
|
12499
13035
|
with self.lock:
|
|
@@ -12566,6 +13102,7 @@ class SessionState:
|
|
|
12566
13102
|
"runtime_reclassify_required": bool(self.runtime_reclassify_required),
|
|
12567
13103
|
"runtime_complexity_floor": str(self.runtime_complexity_floor or ""),
|
|
12568
13104
|
"runtime_task_level_floor": int(self.runtime_task_level_floor or 0),
|
|
13105
|
+
"runtime_task_level_ceiling": int(self.runtime_task_level_ceiling or 0),
|
|
12569
13106
|
"runtime_goal_reset_pending": bool(self.runtime_goal_reset_pending),
|
|
12570
13107
|
"runtime_plan_mode_needed": bool(self.runtime_plan_mode_needed),
|
|
12571
13108
|
"runtime_plan_approved": bool(self.runtime_plan_approved),
|
|
@@ -14598,17 +15135,14 @@ class SessionState:
|
|
|
14598
15135
|
{
|
|
14599
15136
|
"content": "Split task into small subtasks",
|
|
14600
15137
|
"status": "in_progress",
|
|
14601
|
-
"activeForm": "Working on: Split task into small subtasks",
|
|
14602
15138
|
},
|
|
14603
15139
|
{
|
|
14604
15140
|
"content": "Execute one subtask and persist intermediate result",
|
|
14605
15141
|
"status": "pending",
|
|
14606
|
-
"activeForm": "Pending: Execute one subtask and persist intermediate result",
|
|
14607
15142
|
},
|
|
14608
15143
|
{
|
|
14609
15144
|
"content": "Validate and continue remaining subtasks",
|
|
14610
15145
|
"status": "pending",
|
|
14611
|
-
"activeForm": "Pending: Validate and continue remaining subtasks",
|
|
14612
15146
|
},
|
|
14613
15147
|
]
|
|
14614
15148
|
)
|
|
@@ -16768,30 +17302,46 @@ class SessionState:
|
|
|
16768
17302
|
return False
|
|
16769
17303
|
done_markers = [
|
|
16770
17304
|
"任务完成",
|
|
17305
|
+
"任務完成",
|
|
16771
17306
|
"已完成",
|
|
16772
17307
|
"全部完成",
|
|
17308
|
+
"全部完成了",
|
|
16773
17309
|
"处理完成",
|
|
17310
|
+
"處理完成",
|
|
16774
17311
|
"修复完成",
|
|
17312
|
+
"修復完成",
|
|
16775
17313
|
"解释如上",
|
|
17314
|
+
"說明如上",
|
|
16776
17315
|
"上述代码已为您编写完毕",
|
|
16777
17316
|
"done",
|
|
16778
17317
|
"completed",
|
|
16779
17318
|
"finished",
|
|
16780
17319
|
"all set",
|
|
17320
|
+
"完了しました",
|
|
17321
|
+
"対応完了",
|
|
17322
|
+
"修正完了",
|
|
17323
|
+
"以上です",
|
|
17324
|
+
"作成しました",
|
|
16781
17325
|
# 明确表示拒绝/无法完成也应视为终结
|
|
16782
17326
|
"抱歉",
|
|
16783
17327
|
"sorry",
|
|
16784
17328
|
"无法",
|
|
16785
17329
|
"cannot",
|
|
16786
17330
|
"unable",
|
|
17331
|
+
"無法",
|
|
17332
|
+
"すみません",
|
|
17333
|
+
"できません",
|
|
16787
17334
|
]
|
|
16788
17335
|
if any(x in t for x in done_markers):
|
|
16789
17336
|
return False
|
|
16790
17337
|
continue_markers = [
|
|
16791
17338
|
"让我",
|
|
17339
|
+
"讓我",
|
|
16792
17340
|
"我将",
|
|
16793
17341
|
"继续",
|
|
17342
|
+
"繼續",
|
|
16794
17343
|
"接下来",
|
|
17344
|
+
"接下來",
|
|
16795
17345
|
"重新分析",
|
|
16796
17346
|
"修复代码",
|
|
16797
17347
|
"i will",
|
|
@@ -16799,6 +17349,12 @@ class SessionState:
|
|
|
16799
17349
|
"let me",
|
|
16800
17350
|
"next",
|
|
16801
17351
|
"continue",
|
|
17352
|
+
"続けます",
|
|
17353
|
+
"これから",
|
|
17354
|
+
"次に",
|
|
17355
|
+
"再分析",
|
|
17356
|
+
"修正します",
|
|
17357
|
+
"進めます",
|
|
16802
17358
|
]
|
|
16803
17359
|
return any(x in t for x in continue_markers)
|
|
16804
17360
|
|
|
@@ -16819,9 +17375,19 @@ class SessionState:
|
|
|
16819
17375
|
"要不要",
|
|
16820
17376
|
"是否",
|
|
16821
17377
|
"可选项",
|
|
17378
|
+
"請選擇",
|
|
17379
|
+
"請告訴我",
|
|
17380
|
+
"你希望",
|
|
17381
|
+
"要不要",
|
|
17382
|
+
"是否要",
|
|
17383
|
+
"どの案",
|
|
17384
|
+
"どれを選ぶ",
|
|
17385
|
+
"選んでください",
|
|
17386
|
+
"希望しますか",
|
|
17387
|
+
"必要ですか",
|
|
16822
17388
|
]
|
|
16823
17389
|
has_question = ("?" in t) or ("?" in text)
|
|
16824
|
-
has_option_list = any(token in t for token in ["1.", "2.", "3.", "option", "选项"])
|
|
17390
|
+
has_option_list = any(token in t for token in ["1.", "2.", "3.", "option", "选项", "選項", "方案", "案"])
|
|
16825
17391
|
return has_question and (has_option_list or any(x in t for x in ask_markers))
|
|
16826
17392
|
|
|
16827
17393
|
def _looks_like_conclusive_reply(self, text: str) -> bool:
|
|
@@ -16832,25 +17398,39 @@ class SessionState:
|
|
|
16832
17398
|
negative = [
|
|
16833
17399
|
"未完成",
|
|
16834
17400
|
"还没完成",
|
|
17401
|
+
"還沒完成",
|
|
16835
17402
|
"继续",
|
|
17403
|
+
"繼續",
|
|
16836
17404
|
"next step",
|
|
16837
17405
|
"todo",
|
|
16838
17406
|
"pending",
|
|
16839
17407
|
"in_progress",
|
|
16840
17408
|
"需要继续",
|
|
16841
17409
|
"待处理",
|
|
17410
|
+
"待處理",
|
|
17411
|
+
"未完了",
|
|
17412
|
+
"まだ完了していない",
|
|
17413
|
+
"続行",
|
|
17414
|
+
"次のステップ",
|
|
17415
|
+
"保留",
|
|
17416
|
+
"進行中",
|
|
16842
17417
|
]
|
|
16843
17418
|
if any(x in t for x in negative):
|
|
16844
17419
|
return False
|
|
16845
17420
|
done_markers = [
|
|
16846
17421
|
"任务完成",
|
|
17422
|
+
"任務完成",
|
|
16847
17423
|
"处理完成",
|
|
17424
|
+
"處理完成",
|
|
16848
17425
|
"修复完成",
|
|
17426
|
+
"修復完成",
|
|
16849
17427
|
"以上就是",
|
|
16850
17428
|
"解释如上",
|
|
17429
|
+
"說明如上",
|
|
16851
17430
|
"上述代码已为您编写完毕",
|
|
16852
17431
|
"已为您创建",
|
|
16853
17432
|
"已为你创建",
|
|
17433
|
+
"已為您建立",
|
|
16854
17434
|
"all set",
|
|
16855
17435
|
"done",
|
|
16856
17436
|
"completed",
|
|
@@ -16865,6 +17445,12 @@ class SessionState:
|
|
|
16865
17445
|
"that's all",
|
|
16866
17446
|
"that is all",
|
|
16867
17447
|
"as requested",
|
|
17448
|
+
"完了しました",
|
|
17449
|
+
"対応完了",
|
|
17450
|
+
"修正完了",
|
|
17451
|
+
"以上です",
|
|
17452
|
+
"作成しました",
|
|
17453
|
+
"準備できました",
|
|
16868
17454
|
# 明确表示无法完成的标记
|
|
16869
17455
|
"抱歉,我无法",
|
|
16870
17456
|
"无法直接获取",
|
|
@@ -16876,6 +17462,11 @@ class SessionState:
|
|
|
16876
17462
|
"建议你通过",
|
|
16877
17463
|
"i cannot",
|
|
16878
17464
|
"i'm unable",
|
|
17465
|
+
"抱歉,我無法",
|
|
17466
|
+
"無法直接取得",
|
|
17467
|
+
"無法完成",
|
|
17468
|
+
"できません",
|
|
17469
|
+
"不可能です",
|
|
16879
17470
|
]
|
|
16880
17471
|
return any(x in t for x in done_markers)
|
|
16881
17472
|
|
|
@@ -16920,6 +17511,19 @@ class SessionState:
|
|
|
16920
17511
|
"总结",
|
|
16921
17512
|
"说明如下",
|
|
16922
17513
|
"结果如下",
|
|
17514
|
+
"已完成",
|
|
17515
|
+
"修復了",
|
|
17516
|
+
"修改了",
|
|
17517
|
+
"實作了",
|
|
17518
|
+
"總結",
|
|
17519
|
+
"說明如下",
|
|
17520
|
+
"結果如下",
|
|
17521
|
+
"実装しました",
|
|
17522
|
+
"修正しました",
|
|
17523
|
+
"更新しました",
|
|
17524
|
+
"作成しました",
|
|
17525
|
+
"まとめ",
|
|
17526
|
+
"結果は以下",
|
|
16923
17527
|
]
|
|
16924
17528
|
if any(x in low for x in informative_markers):
|
|
16925
17529
|
return True
|
|
@@ -16943,7 +17547,9 @@ class SessionState:
|
|
|
16943
17547
|
return False
|
|
16944
17548
|
markers = [
|
|
16945
17549
|
"是什么",
|
|
17550
|
+
"是什麼",
|
|
16946
17551
|
"为什么",
|
|
17552
|
+
"為什麼",
|
|
16947
17553
|
"怎么",
|
|
16948
17554
|
"如何",
|
|
16949
17555
|
"explain",
|
|
@@ -16951,6 +17557,13 @@ class SessionState:
|
|
|
16951
17557
|
"why",
|
|
16952
17558
|
"how to",
|
|
16953
17559
|
"difference",
|
|
17560
|
+
"差異",
|
|
17561
|
+
"とは",
|
|
17562
|
+
"なぜ",
|
|
17563
|
+
"どうやって",
|
|
17564
|
+
"どうすれば",
|
|
17565
|
+
"違い",
|
|
17566
|
+
"何ですか",
|
|
16954
17567
|
]
|
|
16955
17568
|
return any(x in t for x in markers)
|
|
16956
17569
|
|
|
@@ -17009,6 +17622,13 @@ class SessionState:
|
|
|
17009
17622
|
"ready to use",
|
|
17010
17623
|
"as requested",
|
|
17011
17624
|
"that is all",
|
|
17625
|
+
"希望這能幫到你",
|
|
17626
|
+
"說明如上",
|
|
17627
|
+
"程式碼已修改完成",
|
|
17628
|
+
"請查看上述程式碼",
|
|
17629
|
+
"完了しました",
|
|
17630
|
+
"以上です",
|
|
17631
|
+
"ご確認ください",
|
|
17012
17632
|
]
|
|
17013
17633
|
if any(x in tail for x in endpoint_markers):
|
|
17014
17634
|
score += 2
|
|
@@ -17031,11 +17651,58 @@ class SessionState:
|
|
|
17031
17651
|
low = str(text or "").strip().lower()
|
|
17032
17652
|
if not low:
|
|
17033
17653
|
return ""
|
|
17034
|
-
if any(
|
|
17654
|
+
if any(
|
|
17655
|
+
x in low
|
|
17656
|
+
for x in [
|
|
17657
|
+
"task_completed",
|
|
17658
|
+
"task completed",
|
|
17659
|
+
"已完成",
|
|
17660
|
+
"完成",
|
|
17661
|
+
"已完成了",
|
|
17662
|
+
"done",
|
|
17663
|
+
"completed",
|
|
17664
|
+
"finished",
|
|
17665
|
+
"任務完成",
|
|
17666
|
+
"完了",
|
|
17667
|
+
"完了しました",
|
|
17668
|
+
]
|
|
17669
|
+
):
|
|
17035
17670
|
return "TASK_COMPLETED"
|
|
17036
|
-
if any(
|
|
17671
|
+
if any(
|
|
17672
|
+
x in low
|
|
17673
|
+
for x in [
|
|
17674
|
+
"valid_planning",
|
|
17675
|
+
"valid planning",
|
|
17676
|
+
"plan",
|
|
17677
|
+
"planning",
|
|
17678
|
+
"next step",
|
|
17679
|
+
"analysis",
|
|
17680
|
+
"计划",
|
|
17681
|
+
"計畫",
|
|
17682
|
+
"规划",
|
|
17683
|
+
"規劃",
|
|
17684
|
+
"下一步",
|
|
17685
|
+
"分析",
|
|
17686
|
+
"計画",
|
|
17687
|
+
"次のステップ",
|
|
17688
|
+
]
|
|
17689
|
+
):
|
|
17037
17690
|
return "VALID_PLANNING"
|
|
17038
|
-
if any(
|
|
17691
|
+
if any(
|
|
17692
|
+
x in low
|
|
17693
|
+
for x in [
|
|
17694
|
+
"empty_rambling",
|
|
17695
|
+
"empty rambling",
|
|
17696
|
+
"rambling",
|
|
17697
|
+
"idle",
|
|
17698
|
+
"stalled",
|
|
17699
|
+
"hallucination",
|
|
17700
|
+
"空想",
|
|
17701
|
+
"停滞",
|
|
17702
|
+
"停滯",
|
|
17703
|
+
"だらだら",
|
|
17704
|
+
]
|
|
17705
|
+
):
|
|
17039
17706
|
return "EMPTY_RAMBLING"
|
|
17040
17707
|
return ""
|
|
17041
17708
|
|
|
@@ -17234,6 +17901,163 @@ class SessionState:
|
|
|
17234
17901
|
)
|
|
17235
17902
|
return result
|
|
17236
17903
|
|
|
17904
|
+
def _generate_run_completion_summary(self):
|
|
17905
|
+
"""Generate a brief summary bubble when a run completes, so user isn't left without feedback."""
|
|
17906
|
+
# Guard: plan proposal is waiting for user selection — run ended but task not started yet
|
|
17907
|
+
if self.runtime_plan_mode_needed and not self.runtime_plan_approved:
|
|
17908
|
+
return
|
|
17909
|
+
try:
|
|
17910
|
+
bb = self._ensure_blackboard()
|
|
17911
|
+
board_md = self._blackboard_read_state_markdown(max_items=10)
|
|
17912
|
+
# Collect completed plan steps
|
|
17913
|
+
plan_steps = [t for t in bb.get("project_todos", []) if t.get("category") == "plan_step"]
|
|
17914
|
+
completed = [t for t in plan_steps if t.get("status") == "completed"]
|
|
17915
|
+
pending = [t for t in plan_steps if t.get("status") != "completed"]
|
|
17916
|
+
# Collect recent file operations from step_files
|
|
17917
|
+
step_files = bb.get("step_files", {})
|
|
17918
|
+
file_list = []
|
|
17919
|
+
for _sf_key, entries in (step_files.items() if isinstance(step_files, dict) else []):
|
|
17920
|
+
if isinstance(entries, list):
|
|
17921
|
+
for e in entries[-5:]:
|
|
17922
|
+
fp = str(e.get("path", "") if isinstance(e, dict) else e or "").strip()
|
|
17923
|
+
if fp:
|
|
17924
|
+
file_list.append(fp)
|
|
17925
|
+
file_list = list(dict.fromkeys(file_list))[-10:] # Dedupe, last 10
|
|
17926
|
+
# Build context for LLM summary
|
|
17927
|
+
goal = trim(str(bb.get("original_goal", "") or self.runtime_reclassify_goal or ""), 600)
|
|
17928
|
+
lang_hint = model_language_instruction(self.ui_language)
|
|
17929
|
+
parts = [f"GOAL: {goal}"] if goal else []
|
|
17930
|
+
if completed:
|
|
17931
|
+
parts.append(f"COMPLETED STEPS ({len(completed)}/{len(plan_steps)}):")
|
|
17932
|
+
for t in completed:
|
|
17933
|
+
idx = int(t.get("plan_step_index", 0) or 0) + 1
|
|
17934
|
+
parts.append(f" ✅ {idx}. {trim(str(t.get('content', '')), 100)}")
|
|
17935
|
+
if pending:
|
|
17936
|
+
parts.append(f"REMAINING ({len(pending)}):")
|
|
17937
|
+
for t in pending[:3]:
|
|
17938
|
+
idx = int(t.get("plan_step_index", 0) or 0) + 1
|
|
17939
|
+
parts.append(f" ⬜ {idx}. {trim(str(t.get('content', '')), 100)}")
|
|
17940
|
+
if file_list:
|
|
17941
|
+
parts.append(f"FILES CREATED/MODIFIED: {', '.join(file_list[:8])}")
|
|
17942
|
+
if board_md:
|
|
17943
|
+
parts.append(f"BLACKBOARD STATE:\n{trim(board_md, 800)}")
|
|
17944
|
+
context = "\n".join(parts)
|
|
17945
|
+
# --- Supplement context from messages history when plan steps are unavailable ---
|
|
17946
|
+
# (pure sync mode: no plan steps, so file_list/ops may be empty above)
|
|
17947
|
+
files_from_msgs: list[str] = []
|
|
17948
|
+
ops_from_msgs: list[str] = []
|
|
17949
|
+
agent_conclusion = ""
|
|
17950
|
+
for _m in self.messages[-60:]:
|
|
17951
|
+
if not isinstance(_m, dict):
|
|
17952
|
+
continue
|
|
17953
|
+
_role = _m.get("role", "")
|
|
17954
|
+
# file_patch type → extract file location
|
|
17955
|
+
if _m.get("type") == "file_patch":
|
|
17956
|
+
_loc = str(_m.get("location", "") or _m.get("path", "") or "").strip()
|
|
17957
|
+
if _loc and _loc not in files_from_msgs:
|
|
17958
|
+
files_from_msgs.append(_loc)
|
|
17959
|
+
# command output → capture brief preview
|
|
17960
|
+
if _m.get("type") == "command" and _role == "system":
|
|
17961
|
+
_cmd = trim(str(_m.get("content", "") or ""), 120)
|
|
17962
|
+
if _cmd:
|
|
17963
|
+
ops_from_msgs.append(_cmd)
|
|
17964
|
+
# last non-manager/non-planner assistant reply → agent self-description
|
|
17965
|
+
if _role == "assistant" and str(_m.get("agent_role", "") or "") not in ("manager", "planner"):
|
|
17966
|
+
_txt = str(_m.get("content", "") or "").strip()
|
|
17967
|
+
if _txt:
|
|
17968
|
+
agent_conclusion = trim(_txt, 400)
|
|
17969
|
+
# Merge: use message-derived file list only when step_files gave nothing
|
|
17970
|
+
if not file_list and files_from_msgs:
|
|
17971
|
+
file_list = files_from_msgs[:8]
|
|
17972
|
+
# Append agent conclusion and bash ops to context if not already present
|
|
17973
|
+
if agent_conclusion and "AGENT LAST OUTPUT" not in context:
|
|
17974
|
+
context += f"\nAGENT LAST OUTPUT:\n{agent_conclusion}"
|
|
17975
|
+
if ops_from_msgs and "BASH OPS" not in context:
|
|
17976
|
+
context += f"\nBASH OPS (last {min(3, len(ops_from_msgs))}):\n" + "\n".join(ops_from_msgs[-3:])
|
|
17977
|
+
# ---
|
|
17978
|
+
prompt = (
|
|
17979
|
+
f"{lang_hint}\n"
|
|
17980
|
+
"Generate a BRIEF completion summary for the user (3-8 sentences).\n"
|
|
17981
|
+
"Include: what was accomplished, key files created, and any remaining work.\n"
|
|
17982
|
+
"Use markdown formatting. Be concise and informative.\n"
|
|
17983
|
+
f"Do NOT include code blocks unless essential.\n\n"
|
|
17984
|
+
f"CONTEXT:\n{context}"
|
|
17985
|
+
)
|
|
17986
|
+
rsp = self.ollama.chat(
|
|
17987
|
+
[{"role": "user", "content": prompt}],
|
|
17988
|
+
max_tokens=800,
|
|
17989
|
+
temperature=0.3,
|
|
17990
|
+
)
|
|
17991
|
+
summary_text = str(rsp.get("content", "") or "").strip()
|
|
17992
|
+
if not summary_text or len(summary_text) < 10:
|
|
17993
|
+
summary_text = self._generate_static_completion_summary(
|
|
17994
|
+
completed, pending, file_list, goal, agent_text=agent_conclusion
|
|
17995
|
+
)
|
|
17996
|
+
self.messages.append({
|
|
17997
|
+
"role": "assistant",
|
|
17998
|
+
"content": summary_text,
|
|
17999
|
+
"ts": now_ts(),
|
|
18000
|
+
"agent_role": "explorer",
|
|
18001
|
+
})
|
|
18002
|
+
self._emit("message", {
|
|
18003
|
+
"role": "assistant",
|
|
18004
|
+
"text": trim(summary_text, int(ASSISTANT_MESSAGE_EVENT_MAX_CHARS)),
|
|
18005
|
+
"summary": "run completion summary",
|
|
18006
|
+
"agent_role": "explorer",
|
|
18007
|
+
})
|
|
18008
|
+
except Exception:
|
|
18009
|
+
try:
|
|
18010
|
+
bb = self._ensure_blackboard()
|
|
18011
|
+
plan_steps = [t for t in bb.get("project_todos", []) if t.get("category") == "plan_step"]
|
|
18012
|
+
completed = [t for t in plan_steps if t.get("status") == "completed"]
|
|
18013
|
+
pending = [t for t in plan_steps if t.get("status") != "completed"]
|
|
18014
|
+
goal = trim(str(bb.get("original_goal", "") or ""), 200)
|
|
18015
|
+
summary_text = self._generate_static_completion_summary(completed, pending, [], goal)
|
|
18016
|
+
self.messages.append({
|
|
18017
|
+
"role": "assistant",
|
|
18018
|
+
"content": summary_text,
|
|
18019
|
+
"ts": now_ts(),
|
|
18020
|
+
"agent_role": "explorer",
|
|
18021
|
+
})
|
|
18022
|
+
self._emit("message", {
|
|
18023
|
+
"role": "assistant",
|
|
18024
|
+
"text": trim(summary_text, int(ASSISTANT_MESSAGE_EVENT_MAX_CHARS)),
|
|
18025
|
+
"summary": "run completion summary (static)",
|
|
18026
|
+
"agent_role": "explorer",
|
|
18027
|
+
})
|
|
18028
|
+
except Exception:
|
|
18029
|
+
pass
|
|
18030
|
+
|
|
18031
|
+
def _generate_static_completion_summary(
|
|
18032
|
+
self,
|
|
18033
|
+
completed: list[dict],
|
|
18034
|
+
pending: list[dict],
|
|
18035
|
+
file_list: list[str],
|
|
18036
|
+
goal: str,
|
|
18037
|
+
agent_text: str = "",
|
|
18038
|
+
) -> str:
|
|
18039
|
+
"""Static fallback summary when LLM call fails."""
|
|
18040
|
+
lines = ["## ✅ 任务执行完成\n"]
|
|
18041
|
+
if goal:
|
|
18042
|
+
lines.append(f"**目标:** {trim(goal, 200)}\n")
|
|
18043
|
+
total = len(completed) + len(pending)
|
|
18044
|
+
if completed:
|
|
18045
|
+
lines.append(f"**进度:** {len(completed)}/{total} 步骤已完成")
|
|
18046
|
+
for t in completed[-5:]:
|
|
18047
|
+
idx = int(t.get("plan_step_index", 0) or 0) + 1
|
|
18048
|
+
lines.append(f"- ✅ {idx}. {trim(str(t.get('content', '')), 80)}")
|
|
18049
|
+
if pending:
|
|
18050
|
+
lines.append(f"\n**待完成:** {len(pending)} 步骤")
|
|
18051
|
+
for t in pending[:3]:
|
|
18052
|
+
idx = int(t.get("plan_step_index", 0) or 0) + 1
|
|
18053
|
+
lines.append(f"- ⬜ {idx}. {trim(str(t.get('content', '')), 80)}")
|
|
18054
|
+
# When no plan steps exist (pure sync mode), show agent conclusion text instead
|
|
18055
|
+
if not completed and agent_text:
|
|
18056
|
+
lines.append(f"\n**执行摘要:** {trim(agent_text, 300)}")
|
|
18057
|
+
if file_list:
|
|
18058
|
+
lines.append(f"\n**涉及文件:** {', '.join(file_list[:6])}")
|
|
18059
|
+
return "\n".join(lines)
|
|
18060
|
+
|
|
17237
18061
|
def _should_soft_pause_no_action(self, assistant_text: str, tool_calls: list | None = None) -> bool:
|
|
17238
18062
|
if tool_calls:
|
|
17239
18063
|
return False
|
|
@@ -17253,7 +18077,7 @@ class SessionState:
|
|
|
17253
18077
|
def _todo_active_brief(self) -> str:
|
|
17254
18078
|
rows = self.todo.snapshot()
|
|
17255
18079
|
if not rows:
|
|
17256
|
-
return
|
|
18080
|
+
return self._ui_text("todo_no_todos")
|
|
17257
18081
|
active = []
|
|
17258
18082
|
open_count = 0
|
|
17259
18083
|
done_count = 0
|
|
@@ -20540,13 +21364,23 @@ class SessionState:
|
|
|
20540
21364
|
board["project_todos"] = []
|
|
20541
21365
|
else:
|
|
20542
21366
|
clean_todos = []
|
|
21367
|
+
import re as _re_norm
|
|
21368
|
+
_mid_re_norm = _re_norm.compile(r"(?<=\S)\s+(\d+\.\d+\s)")
|
|
20543
21369
|
for pt in bb_src_todos[:40]:
|
|
20544
21370
|
if not isinstance(pt, dict):
|
|
20545
21371
|
continue
|
|
21372
|
+
raw_content = trim(str(pt.get("content", "") or ""), 1500)
|
|
21373
|
+
raw_full = trim(str(pt.get("full_content", "") or ""), 1500)
|
|
21374
|
+
# Migration: if full_content is empty but content has sub-steps, auto-split
|
|
21375
|
+
if not raw_full and raw_content and pt.get("category") == "plan_step":
|
|
21376
|
+
normalized = _mid_re_norm.sub(r"\n\1", raw_content)
|
|
21377
|
+
if "\n" in normalized:
|
|
21378
|
+
raw_full = normalized
|
|
21379
|
+
raw_content = normalized.split("\n")[0].strip()
|
|
20546
21380
|
clean_todos.append({
|
|
20547
21381
|
"id": trim(str(pt.get("id", "") or ""), 20),
|
|
20548
|
-
"content": trim(
|
|
20549
|
-
"full_content": trim(
|
|
21382
|
+
"content": trim(raw_content, 400),
|
|
21383
|
+
"full_content": trim(raw_full, 1500),
|
|
20550
21384
|
"status": str(pt.get("status", "pending") or "pending") if str(pt.get("status", "pending") or "pending") in ("pending", "in_progress", "completed") else "pending",
|
|
20551
21385
|
"category": trim(str(pt.get("category", "") or ""), 40),
|
|
20552
21386
|
"plan_step_index": int(pt.get("plan_step_index", -1)) if pt.get("plan_step_index") is not None else -1,
|
|
@@ -21118,16 +21952,7 @@ class SessionState:
|
|
|
21118
21952
|
self._blackboard_touch()
|
|
21119
21953
|
|
|
21120
21954
|
def _todo_owner_display_name(self, owner: str) -> str:
|
|
21121
|
-
|
|
21122
|
-
if role == "manager":
|
|
21123
|
-
return "Manager"
|
|
21124
|
-
if role == "explorer":
|
|
21125
|
-
return "Explorer"
|
|
21126
|
-
if role == "developer":
|
|
21127
|
-
return "Developer"
|
|
21128
|
-
if role == "reviewer":
|
|
21129
|
-
return "Reviewer"
|
|
21130
|
-
return "Agent"
|
|
21955
|
+
return self._agent_display_name(owner)
|
|
21131
21956
|
|
|
21132
21957
|
def _todo_stage_focus_owner(self, board: dict | None = None) -> str:
|
|
21133
21958
|
bb = board if isinstance(board, dict) else self._ensure_blackboard()
|
|
@@ -21305,21 +22130,24 @@ class SessionState:
|
|
|
21305
22130
|
def _owner_desc(owner: str) -> str:
|
|
21306
22131
|
if owner == "manager":
|
|
21307
22132
|
if delegate_target:
|
|
21308
|
-
return
|
|
21309
|
-
|
|
22133
|
+
return self._ui_text(
|
|
22134
|
+
"node_desc_manager_active",
|
|
22135
|
+
target=self._agent_display_name(delegate_target),
|
|
22136
|
+
)
|
|
22137
|
+
return self._ui_text("node_desc_manager")
|
|
21310
22138
|
if owner == "explorer":
|
|
21311
22139
|
if delegate_target == "explorer":
|
|
21312
|
-
return "
|
|
21313
|
-
return "
|
|
22140
|
+
return self._ui_text("node_desc_explorer_active")
|
|
22141
|
+
return self._ui_text("node_desc_explorer")
|
|
21314
22142
|
if owner == "developer":
|
|
21315
22143
|
if delegate_target == "developer":
|
|
21316
|
-
return "
|
|
21317
|
-
return "
|
|
22144
|
+
return self._ui_text("node_desc_developer_active")
|
|
22145
|
+
return self._ui_text("node_desc_developer")
|
|
21318
22146
|
if owner == "reviewer":
|
|
21319
22147
|
if delegate_target == "reviewer":
|
|
21320
|
-
return "
|
|
21321
|
-
return "
|
|
21322
|
-
return "
|
|
22148
|
+
return self._ui_text("node_desc_reviewer_active")
|
|
22149
|
+
return self._ui_text("node_desc_reviewer")
|
|
22150
|
+
return self._ui_text("node_desc_generic")
|
|
21323
22151
|
|
|
21324
22152
|
finish_ok, _ = self._can_auto_finish_from_approval(bb)
|
|
21325
22153
|
rows: list[dict] = []
|
|
@@ -21369,13 +22197,17 @@ class SessionState:
|
|
|
21369
22197
|
status_value = str(row.get("status", "pending") or "pending")
|
|
21370
22198
|
topic_suffix = ""
|
|
21371
22199
|
if node_topic and status_value == "in_progress":
|
|
21372
|
-
topic_suffix =
|
|
22200
|
+
topic_suffix = self._ui_text("todo_node_suffix", topic=trim(node_topic, 140))
|
|
21373
22201
|
if status_value == "in_progress":
|
|
21374
|
-
row["activeForm"] =
|
|
22202
|
+
row["activeForm"] = self._ui_text(
|
|
22203
|
+
"todo_working_owner",
|
|
22204
|
+
owner=label,
|
|
22205
|
+
content=f"{text}{topic_suffix}",
|
|
22206
|
+
)
|
|
21375
22207
|
elif status_value == "completed":
|
|
21376
|
-
row["activeForm"] =
|
|
22208
|
+
row["activeForm"] = self._ui_text("todo_completed_owner", owner=label, content=text)
|
|
21377
22209
|
else:
|
|
21378
|
-
row["activeForm"] =
|
|
22210
|
+
row["activeForm"] = self._ui_text("todo_pending_owner", owner=label, content=text)
|
|
21379
22211
|
return rows
|
|
21380
22212
|
|
|
21381
22213
|
# ── Project-based todo generation & status tracking ──────────────
|
|
@@ -21387,23 +22219,47 @@ class SessionState:
|
|
|
21387
22219
|
objective = trim(str(profile.get("direct_objective", "") or ""), 200)
|
|
21388
22220
|
|
|
21389
22221
|
if task_type == "simple_qa":
|
|
21390
|
-
return [
|
|
22222
|
+
return [
|
|
22223
|
+
{
|
|
22224
|
+
"content": self._ui_text("project_answer_objective", objective=objective)
|
|
22225
|
+
if objective
|
|
22226
|
+
else self._ui_text("project_answer_default"),
|
|
22227
|
+
"category": "implement",
|
|
22228
|
+
}
|
|
22229
|
+
]
|
|
21391
22230
|
|
|
21392
22231
|
if task_type in ("simple_code", "engineering"):
|
|
21393
22232
|
return [
|
|
21394
|
-
{"content": "
|
|
21395
|
-
{
|
|
21396
|
-
|
|
21397
|
-
|
|
22233
|
+
{"content": self._ui_text("project_analyze_requirements"), "category": "setup"},
|
|
22234
|
+
{
|
|
22235
|
+
"content": self._ui_text("project_implement_objective", objective=objective)
|
|
22236
|
+
if objective
|
|
22237
|
+
else self._ui_text("project_implement_default"),
|
|
22238
|
+
"category": "implement",
|
|
22239
|
+
},
|
|
22240
|
+
{"content": self._ui_text("project_compile_check"), "category": "compile_test"},
|
|
22241
|
+
{"content": self._ui_text("project_min_test"), "category": "min_test"},
|
|
21398
22242
|
]
|
|
21399
22243
|
|
|
21400
22244
|
if task_type == "research":
|
|
21401
22245
|
return [
|
|
21402
|
-
{
|
|
21403
|
-
|
|
22246
|
+
{
|
|
22247
|
+
"content": self._ui_text("project_research_objective", objective=objective)
|
|
22248
|
+
if objective
|
|
22249
|
+
else self._ui_text("project_research_default"),
|
|
22250
|
+
"category": "implement",
|
|
22251
|
+
},
|
|
22252
|
+
{"content": self._ui_text("project_research_summary"), "category": "review"},
|
|
21404
22253
|
]
|
|
21405
22254
|
|
|
21406
|
-
return [
|
|
22255
|
+
return [
|
|
22256
|
+
{
|
|
22257
|
+
"content": self._ui_text("project_execute_objective", objective=objective)
|
|
22258
|
+
if objective
|
|
22259
|
+
else self._ui_text("project_execute_default"),
|
|
22260
|
+
"category": "implement",
|
|
22261
|
+
}
|
|
22262
|
+
]
|
|
21407
22263
|
|
|
21408
22264
|
def _init_project_todos(self, board: dict | None = None):
|
|
21409
22265
|
bb = board if isinstance(board, dict) else self._ensure_blackboard()
|
|
@@ -21501,16 +22357,36 @@ class SessionState:
|
|
|
21501
22357
|
continue
|
|
21502
22358
|
cat = todo.get("category", "")
|
|
21503
22359
|
if cat == "setup" and (research_count > 0 or code_count > 0):
|
|
21504
|
-
todo.update(
|
|
22360
|
+
todo.update(
|
|
22361
|
+
status="completed",
|
|
22362
|
+
completed_at=float(now_ts()),
|
|
22363
|
+
evidence=self._ui_text("evidence_structure_analyzed"),
|
|
22364
|
+
)
|
|
21505
22365
|
elif cat == "implement" and code_count > 0:
|
|
21506
|
-
todo.update(
|
|
21507
|
-
|
|
22366
|
+
todo.update(
|
|
22367
|
+
status="completed",
|
|
22368
|
+
completed_at=float(now_ts()),
|
|
22369
|
+
completed_by="developer",
|
|
22370
|
+
evidence=self._ui_text("evidence_files_produced", count=code_count),
|
|
22371
|
+
)
|
|
21508
22372
|
elif cat == "compile_test" and self._has_compile_pass_evidence(bb):
|
|
21509
|
-
todo.update(
|
|
22373
|
+
todo.update(
|
|
22374
|
+
status="completed",
|
|
22375
|
+
completed_at=float(now_ts()),
|
|
22376
|
+
evidence=self._ui_text("evidence_compile_passed"),
|
|
22377
|
+
)
|
|
21510
22378
|
elif cat == "min_test" and self._has_test_pass_evidence(bb):
|
|
21511
|
-
todo.update(
|
|
22379
|
+
todo.update(
|
|
22380
|
+
status="completed",
|
|
22381
|
+
completed_at=float(now_ts()),
|
|
22382
|
+
evidence=self._ui_text("evidence_test_passed"),
|
|
22383
|
+
)
|
|
21512
22384
|
elif cat == "review" and feedback_pass:
|
|
21513
|
-
todo.update(
|
|
22385
|
+
todo.update(
|
|
22386
|
+
status="completed",
|
|
22387
|
+
completed_at=float(now_ts()),
|
|
22388
|
+
evidence=self._ui_text("evidence_review_passed"),
|
|
22389
|
+
)
|
|
21514
22390
|
elif cat == "plan_step":
|
|
21515
22391
|
# Plan steps 不自动完成,由 _advance_plan_step 显式推进
|
|
21516
22392
|
# 但如果当前步骤之前的所有步骤都完成了,标记当前步骤为 in_progress
|
|
@@ -21601,7 +22477,7 @@ class SessionState:
|
|
|
21601
22477
|
current["status"] = "completed"
|
|
21602
22478
|
current["completed_at"] = float(now_ts())
|
|
21603
22479
|
current["completed_by"] = actor
|
|
21604
|
-
current["evidence"] = trim(str(evidence or "").strip(), 200) or "
|
|
22480
|
+
current["evidence"] = trim(str(evidence or "").strip(), 200) or self._ui_text("step_completed_evidence")
|
|
21605
22481
|
# 推进 cursor,激活下一步
|
|
21606
22482
|
cursor = int(bb.get("plan_step_cursor", 0) or 0)
|
|
21607
22483
|
bb["plan_step_cursor"] = cursor + 1
|
|
@@ -21615,7 +22491,12 @@ class SessionState:
|
|
|
21615
22491
|
step_idx = int(next_step.get("plan_step_index", 0) or 0) + 1
|
|
21616
22492
|
total = int(bb.get("plan_step_total", len(todos)) or len(todos))
|
|
21617
22493
|
self._emit("status", {
|
|
21618
|
-
"summary":
|
|
22494
|
+
"summary": self._ui_text(
|
|
22495
|
+
"plan_step_summary",
|
|
22496
|
+
step=step_idx,
|
|
22497
|
+
total=total,
|
|
22498
|
+
content=trim(str(next_step.get("content", "") or ""), 120),
|
|
22499
|
+
)
|
|
21619
22500
|
})
|
|
21620
22501
|
self.blackboard = bb
|
|
21621
22502
|
self._blackboard_touch()
|
|
@@ -21648,14 +22529,13 @@ class SessionState:
|
|
|
21648
22529
|
_ns_total = int(bb.get("plan_step_total", 0) or 0)
|
|
21649
22530
|
_ns_text = trim(str(next_step.get("content", "") or ""), 200)
|
|
21650
22531
|
_ns_id = str(next_step.get("id", "") or "")
|
|
21651
|
-
_ns_label =
|
|
21652
|
-
_hint = (
|
|
21653
|
-
|
|
21654
|
-
|
|
21655
|
-
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
f"Do NOT create subtasks for other plan steps."
|
|
22532
|
+
_ns_label = self._ui_text("plan_step_label", step=_ns_idx, total=_ns_total)
|
|
22533
|
+
_hint = self._ui_text(
|
|
22534
|
+
"plan_step_hint",
|
|
22535
|
+
step_label=_ns_label,
|
|
22536
|
+
step_text=_ns_text,
|
|
22537
|
+
plan_path=PLAN_FILE_RELATIVE_PATH,
|
|
22538
|
+
parent_step_id=_ns_id,
|
|
21659
22539
|
)
|
|
21660
22540
|
self.messages.append({"role": "system", "content": _hint, "ts": now_ts()})
|
|
21661
22541
|
# Also inject into active agent context for multi-agent mode
|
|
@@ -21678,6 +22558,19 @@ class SessionState:
|
|
|
21678
22558
|
None,
|
|
21679
22559
|
)
|
|
21680
22560
|
if not current:
|
|
22561
|
+
# No plan steps — detect finish_current_task for pure-sync mode coordination.
|
|
22562
|
+
# Sets sync_worker_round_done flag so the sync loop can signal the manager.
|
|
22563
|
+
# Only reachable in pure sync mode (no plan steps); plan+sync always has `current`
|
|
22564
|
+
# set, so this branch never executes for plan modes.
|
|
22565
|
+
results = worker_step.get("tool_results", []) or []
|
|
22566
|
+
called_finish = any(
|
|
22567
|
+
isinstance(r, dict) and r.get("ok")
|
|
22568
|
+
and str(r.get("name", "")) in ("finish_current_task", "finish_task")
|
|
22569
|
+
for r in results
|
|
22570
|
+
)
|
|
22571
|
+
if called_finish:
|
|
22572
|
+
bb["sync_worker_round_done"] = True
|
|
22573
|
+
self._save_blackboard(bb)
|
|
21681
22574
|
return
|
|
21682
22575
|
# 1. Manager explicitly requested advancement
|
|
21683
22576
|
manager_requested = bool(route.get("advance_plan_step_requested", False))
|
|
@@ -21685,30 +22578,41 @@ class SessionState:
|
|
|
21685
22578
|
worker_produced_output = self._worker_step_has_evidence(worker_step)
|
|
21686
22579
|
# 3. All subtasks for this step are completed
|
|
21687
22580
|
subtasks_all_done = self._step_subtasks_all_completed(current)
|
|
21688
|
-
# 4.
|
|
22581
|
+
# 4. Phase-based file+bash evidence (implement requires BOTH write + bash)
|
|
21689
22582
|
step_content = str(current.get("full_content", "") or current.get("content", "") or "").lower()
|
|
21690
22583
|
phase = self._plan_step_phase_hint(step_content)
|
|
21691
22584
|
results = worker_step.get("tool_results", []) or []
|
|
21692
|
-
|
|
21693
|
-
|
|
21694
|
-
if isinstance(r, dict) and r.get("ok", False)
|
|
22585
|
+
wrote_files = any(
|
|
22586
|
+
isinstance(r, dict) and r.get("ok", False)
|
|
21695
22587
|
and str(r.get("name", "")) in ("write_file", "edit_file")
|
|
22588
|
+
for r in results
|
|
21696
22589
|
)
|
|
21697
22590
|
ran_bash_ok = any(
|
|
21698
22591
|
isinstance(r, dict) and r.get("ok", False) and str(r.get("name", "")) == "bash"
|
|
21699
22592
|
for r in results
|
|
21700
22593
|
)
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
22594
|
+
phase_evidence = False
|
|
22595
|
+
if phase in ("research", "design") and wrote_files:
|
|
22596
|
+
phase_evidence = True
|
|
22597
|
+
elif phase == "implement" and wrote_files and ran_bash_ok:
|
|
22598
|
+
phase_evidence = True
|
|
22599
|
+
elif phase in ("test", "review") and ran_bash_ok:
|
|
22600
|
+
phase_evidence = True
|
|
22601
|
+
# 5. finish_current_task is an explicit completion signal — override evidence check
|
|
22602
|
+
called_finish = any(
|
|
22603
|
+
isinstance(r, dict) and r.get("ok")
|
|
22604
|
+
and str(r.get("name", "")) in ("finish_current_task", "finish_task", "mark_done")
|
|
22605
|
+
for r in results
|
|
21705
22606
|
)
|
|
21706
22607
|
# Advance when:
|
|
22608
|
+
# - Worker called finish (strongest signal), OR
|
|
21707
22609
|
# - Manager requested AND worker produced output, OR
|
|
21708
22610
|
# - All subtasks completed AND worker produced output, OR
|
|
21709
|
-
# -
|
|
21710
|
-
has_strong_evidence =
|
|
21711
|
-
|
|
22611
|
+
# - Phase heuristics confirm (write+bash for implement)
|
|
22612
|
+
has_strong_evidence = called_finish or (
|
|
22613
|
+
worker_produced_output and (
|
|
22614
|
+
manager_requested or subtasks_all_done or phase_evidence
|
|
22615
|
+
)
|
|
21712
22616
|
)
|
|
21713
22617
|
if has_strong_evidence:
|
|
21714
22618
|
evidence = self._collect_step_evidence(current, worker_step)
|
|
@@ -21743,7 +22647,14 @@ class SessionState:
|
|
|
21743
22647
|
and str(r.get("parent_step_id", "") or "") == step_id
|
|
21744
22648
|
]
|
|
21745
22649
|
if not worker_items:
|
|
21746
|
-
|
|
22650
|
+
# Fallback: no parent_step_id linkage — check ALL worker items
|
|
22651
|
+
all_worker = [
|
|
22652
|
+
r for r in snap
|
|
22653
|
+
if str(r.get("owner", "") or "").lower() in worker_owners
|
|
22654
|
+
]
|
|
22655
|
+
if all_worker:
|
|
22656
|
+
return all(str(r.get("status", "")).lower() == "completed" for r in all_worker)
|
|
22657
|
+
return True # No worker items at all → nothing blocks advancement
|
|
21747
22658
|
# Extract major step number from plan step content (e.g., "1. Project init" → "1")
|
|
21748
22659
|
import re
|
|
21749
22660
|
step_content = str(plan_step.get("full_content", "") or plan_step.get("content", "") or "")
|
|
@@ -21816,12 +22727,12 @@ class SessionState:
|
|
|
21816
22727
|
subtasks_done = self._step_subtasks_all_completed(current)
|
|
21817
22728
|
if subtasks_done and (wrote_files or ran_bash_ok):
|
|
21818
22729
|
should_advance = True
|
|
21819
|
-
# Priority 2: Phase-based heuristics (
|
|
22730
|
+
# Priority 2: Phase-based heuristics (strict — implement requires BOTH write + bash)
|
|
21820
22731
|
if not should_advance:
|
|
21821
22732
|
if phase in ("research", "design") and wrote_files:
|
|
21822
22733
|
should_advance = True
|
|
21823
|
-
elif phase == "implement" and wrote_files:
|
|
21824
|
-
#
|
|
22734
|
+
elif phase == "implement" and wrote_files and ran_bash_ok:
|
|
22735
|
+
# Strict: implement step needs both file writes AND successful bash
|
|
21825
22736
|
should_advance = True
|
|
21826
22737
|
elif phase in ("test", "review") and ran_bash_ok and not any(
|
|
21827
22738
|
not r.get("ok", False) for r in tool_results if str(r.get("name", "")) == "bash"
|
|
@@ -21855,20 +22766,30 @@ class SessionState:
|
|
|
21855
22766
|
_total = int(_bb_after.get("plan_step_total", 0) or 0)
|
|
21856
22767
|
_step_text = trim(str(_new_step.get("content", "") or ""), 200)
|
|
21857
22768
|
_step_id = str(_new_step.get("id", "") or "")
|
|
21858
|
-
_step_label =
|
|
21859
|
-
_hint = (
|
|
21860
|
-
|
|
21861
|
-
|
|
21862
|
-
|
|
21863
|
-
|
|
21864
|
-
|
|
21865
|
-
f"Do NOT create subtasks for other plan steps."
|
|
22769
|
+
_step_label = self._ui_text("plan_step_label", step=_step_idx, total=_total)
|
|
22770
|
+
_hint = self._ui_text(
|
|
22771
|
+
"plan_step_hint",
|
|
22772
|
+
step_label=_step_label,
|
|
22773
|
+
step_text=_step_text,
|
|
22774
|
+
plan_path=PLAN_FILE_RELATIVE_PATH,
|
|
22775
|
+
parent_step_id=_step_id,
|
|
21866
22776
|
)
|
|
21867
22777
|
self.messages.append({"role": "system", "content": _hint, "ts": now_ts()})
|
|
21868
22778
|
except Exception:
|
|
21869
22779
|
pass
|
|
21870
22780
|
else:
|
|
21871
22781
|
self._sync_todos_from_blackboard(reason="single-agent-round")
|
|
22782
|
+
# Nudge: if agent wrote files but didn't call TodoWrite, remind it
|
|
22783
|
+
called_todo = any(str(r.get("name", "")) == "TodoWrite" for r in tool_results)
|
|
22784
|
+
if (wrote_files or ran_bash_ok) and not called_todo and current:
|
|
22785
|
+
_sid = str(current.get("id", "") or "")
|
|
22786
|
+
if _sid:
|
|
22787
|
+
_nudge = (
|
|
22788
|
+
f"[todo-sync] You made progress on the current step but did not update TodoWrite.\n"
|
|
22789
|
+
f"Call TodoWrite to mark completed subtasks and create new ones.\n"
|
|
22790
|
+
f"Each subtask must include parent_step_id='{_sid}'."
|
|
22791
|
+
)
|
|
22792
|
+
self.messages.append({"role": "system", "content": _nudge, "ts": now_ts()})
|
|
21872
22793
|
|
|
21873
22794
|
def _todo_project_rows_from_blackboard(self, board: dict | None = None) -> list[dict]:
|
|
21874
22795
|
bb = board if isinstance(board, dict) else self._ensure_blackboard()
|
|
@@ -21881,9 +22802,9 @@ class SessionState:
|
|
|
21881
22802
|
c = todo.get("content", "")
|
|
21882
22803
|
ev = todo.get("evidence", "")
|
|
21883
22804
|
af = {
|
|
21884
|
-
"in_progress":
|
|
21885
|
-
"completed": f"
|
|
21886
|
-
}.get(s,
|
|
22805
|
+
"in_progress": self._ui_text("todo_working", content=c),
|
|
22806
|
+
"completed": self._ui_text("todo_completed", content=f"{c}" + (f" ({ev})" if ev else "")),
|
|
22807
|
+
}.get(s, self._ui_text("todo_pending", content=c))
|
|
21887
22808
|
rows.append({"key": f"bb:proj:{todo.get('id', '')}", "content": c, "status": s, "activeForm": af})
|
|
21888
22809
|
return rows
|
|
21889
22810
|
|
|
@@ -21981,8 +22902,11 @@ class SessionState:
|
|
|
21981
22902
|
todo_out = self.todo.update(merged)
|
|
21982
22903
|
except Exception:
|
|
21983
22904
|
return
|
|
21984
|
-
if todo_out !=
|
|
21985
|
-
self._emit(
|
|
22905
|
+
if todo_out != self.todo.no_changes_text() and reason:
|
|
22906
|
+
self._emit(
|
|
22907
|
+
"status",
|
|
22908
|
+
{"summary": self._ui_text("status_project_todos_synced", reason=trim(reason, 120))},
|
|
22909
|
+
)
|
|
21986
22910
|
|
|
21987
22911
|
def _blackboard_set_status(self, status: str, note: str = ""):
|
|
21988
22912
|
board = self._ensure_blackboard()
|
|
@@ -22774,6 +23698,14 @@ class SessionState:
|
|
|
22774
23698
|
_level_floor = int(getattr(self, 'runtime_task_level_floor', 0) or 0)
|
|
22775
23699
|
if _level_floor > 0 and int(level) < _level_floor:
|
|
22776
23700
|
level = _level_floor
|
|
23701
|
+
# Ceiling protection: plan-approved risk lock prevents escalation above ceiling
|
|
23702
|
+
_level_ceiling = int(getattr(self, 'runtime_task_level_ceiling', 0) or 0)
|
|
23703
|
+
if _level_ceiling > 0 and int(level) > _level_ceiling:
|
|
23704
|
+
level = _level_ceiling
|
|
23705
|
+
_ceiling_policy = TASK_LEVEL_POLICIES.get(level, {})
|
|
23706
|
+
mode = str(_ceiling_policy.get("execution_mode", mode) or mode)
|
|
23707
|
+
if _ceiling_policy.get("complexity"):
|
|
23708
|
+
complexity = _ceiling_policy["complexity"]
|
|
22777
23709
|
_complexity_floor = str(getattr(self, 'runtime_complexity_floor', '') or '').strip()
|
|
22778
23710
|
if _complexity_floor == "complex" and complexity == "simple":
|
|
22779
23711
|
complexity = "complex"
|
|
@@ -24754,6 +25686,14 @@ class SessionState:
|
|
|
24754
25686
|
effective_level = int(task_level)
|
|
24755
25687
|
if int(self.runtime_task_level_floor or 0) > 0:
|
|
24756
25688
|
effective_level = max(effective_level, int(self.runtime_task_level_floor))
|
|
25689
|
+
# Ceiling protection: plan-approved risk lock prevents manager from escalating above ceiling
|
|
25690
|
+
_level_ceiling = int(self.runtime_task_level_ceiling or 0)
|
|
25691
|
+
if _level_ceiling > 0 and effective_level > _level_ceiling:
|
|
25692
|
+
effective_level = _level_ceiling
|
|
25693
|
+
# Re-derive execution_mode and participants from the capped level's policy
|
|
25694
|
+
_capped_policy = TASK_LEVEL_POLICIES.get(effective_level, {})
|
|
25695
|
+
execution_mode = str(_capped_policy.get("execution_mode", execution_mode) or execution_mode)
|
|
25696
|
+
participants = list(_capped_policy.get("participants", participants) or participants)
|
|
24757
25697
|
profile["task_level"] = effective_level
|
|
24758
25698
|
profile["execution_mode"] = execution_mode
|
|
24759
25699
|
profile["participants"] = list(participants)
|
|
@@ -25002,14 +25942,10 @@ class SessionState:
|
|
|
25002
25942
|
_active_step_id = str(_pt.get("id", "") or "")
|
|
25003
25943
|
break
|
|
25004
25944
|
todo_update_note = (
|
|
25005
|
-
f"TODO
|
|
25006
|
-
f"
|
|
25007
|
-
f"
|
|
25008
|
-
f"
|
|
25009
|
-
f"- Do NOT create subtasks for other plan steps (do NOT list step 2, 3, 4 etc.).\n"
|
|
25010
|
-
f"- Do NOT duplicate the plan step titles as subtasks.\n"
|
|
25011
|
-
f"- Each subtask should be a concrete action within THIS step.\n"
|
|
25012
|
-
f"Mark each subtask completed as you finish it. When ALL are done, the step auto-advances.\n"
|
|
25945
|
+
f"TODO PLANNING: At the START, call TodoWrite to list ALL subtasks (status=pending, parent_step_id='{_active_step_id}').\n"
|
|
25946
|
+
f"SCOPE RULE: Create 3-5 subtasks for THIS step ONLY — do NOT list other plan steps or duplicate plan step titles.\n"
|
|
25947
|
+
f"As you complete each subtask, update it to status=completed.\n"
|
|
25948
|
+
f"When ALL subtasks are done: call finish_current_task to signal step completion.\n"
|
|
25013
25949
|
)
|
|
25014
25950
|
# Build step_files context note for cross-agent file visibility
|
|
25015
25951
|
step_files_note = ""
|
|
@@ -25651,8 +26587,19 @@ class SessionState:
|
|
|
25651
26587
|
if len(self.agent_messages) > int(am_limit * 1.5):
|
|
25652
26588
|
self.agent_messages = self.agent_messages[-am_limit:]
|
|
25653
26589
|
|
|
26590
|
+
def _set_ui_language(self, language: str, *, relabel_todos: bool = True) -> str:
|
|
26591
|
+
lang = normalize_ui_language(language)
|
|
26592
|
+
self.ui_language = lang
|
|
26593
|
+
todo = getattr(self, "todo", None)
|
|
26594
|
+
if isinstance(todo, TodoManager):
|
|
26595
|
+
todo.set_language(lang, relabel=relabel_todos)
|
|
26596
|
+
return lang
|
|
26597
|
+
|
|
26598
|
+
def _ui_text(self, key: str, **kwargs) -> str:
|
|
26599
|
+
return backend_i18n_text(getattr(self, "ui_language", DEFAULT_UI_LANGUAGE), key, **kwargs)
|
|
26600
|
+
|
|
25654
26601
|
def _agent_display_name(self, role: str) -> str:
|
|
25655
|
-
return
|
|
26602
|
+
return backend_role_label(self._sanitize_agent_role(role), getattr(self, "ui_language", DEFAULT_UI_LANGUAGE))
|
|
25656
26603
|
|
|
25657
26604
|
def _emit_agent_message(self, role: str, text: str, summary: str = ""):
|
|
25658
26605
|
role_key = self._sanitize_agent_role(role)
|
|
@@ -25966,7 +26913,6 @@ class SessionState:
|
|
|
25966
26913
|
{
|
|
25967
26914
|
"content": content,
|
|
25968
26915
|
"status": "pending",
|
|
25969
|
-
"activeForm": f"Pending: {content}",
|
|
25970
26916
|
}
|
|
25971
26917
|
)
|
|
25972
26918
|
if not clean_items:
|
|
@@ -25975,7 +26921,6 @@ class SessionState:
|
|
|
25975
26921
|
if in_progress_index < 0 or in_progress_index >= len(clean_items):
|
|
25976
26922
|
in_progress_index = 0
|
|
25977
26923
|
clean_items[in_progress_index]["status"] = "in_progress"
|
|
25978
|
-
clean_items[in_progress_index]["activeForm"] = f"Working on: {clean_items[in_progress_index]['content']}"
|
|
25979
26924
|
return self.todo.update(clean_items)
|
|
25980
26925
|
|
|
25981
26926
|
def _analyze_todo_result(self, tool_name: str, output: str) -> tuple[str, str]:
|
|
@@ -25985,7 +26930,7 @@ class SessionState:
|
|
|
25985
26930
|
return ("failed", "empty output")
|
|
25986
26931
|
if txt.startswith("Error:"):
|
|
25987
26932
|
return ("failed", txt[6:].strip() or "unknown error")
|
|
25988
|
-
if "no todo changes" in low:
|
|
26933
|
+
if txt == self.todo.no_changes_text() or "no todo changes" in low:
|
|
25989
26934
|
if self.todo.snapshot():
|
|
25990
26935
|
return ("ok", "todo already up to date")
|
|
25991
26936
|
return ("repeat", "same todo payload repeated")
|
|
@@ -26362,22 +27307,18 @@ class SessionState:
|
|
|
26362
27307
|
{
|
|
26363
27308
|
"content": f"Triage failure root cause ({trim(reason, 120)})",
|
|
26364
27309
|
"status": "in_progress",
|
|
26365
|
-
"activeForm": f"Working on: Triage failure root cause ({trim(reason, 80)})",
|
|
26366
27310
|
},
|
|
26367
27311
|
{
|
|
26368
27312
|
"content": "Recover critical context with context_recall if compacted/truncated",
|
|
26369
27313
|
"status": "pending",
|
|
26370
|
-
"activeForm": "Pending: Recover critical context with context_recall if compacted/truncated",
|
|
26371
27314
|
},
|
|
26372
27315
|
{
|
|
26373
27316
|
"content": f"Split goal into 3-7 subtasks and execute one tool step at a time ({trim(goal, 90)})",
|
|
26374
27317
|
"status": "pending",
|
|
26375
|
-
"activeForm": "Pending: Split goal into 3-7 subtasks and execute one tool step at a time",
|
|
26376
27318
|
},
|
|
26377
27319
|
{
|
|
26378
27320
|
"content": "If still blocked, output explicit blocker and required next input",
|
|
26379
27321
|
"status": "pending",
|
|
26380
|
-
"activeForm": "Pending: If still blocked, output explicit blocker and required next input",
|
|
26381
27322
|
},
|
|
26382
27323
|
]
|
|
26383
27324
|
try:
|
|
@@ -26814,7 +27755,7 @@ class SessionState:
|
|
|
26814
27755
|
isinstance(it, dict) and str(it.get("status", it.get("state", ""))).lower() in {"completed", "done", "finished", "finish"}
|
|
26815
27756
|
for it in new_items
|
|
26816
27757
|
):
|
|
26817
|
-
self._refresh_loaded_skills_for_execution_focus(trigger="step-completed") # noqa:
|
|
27758
|
+
self._refresh_loaded_skills_for_execution_focus(trigger="step-completed") # noqa: E501
|
|
26818
27759
|
pass # Skills are loaded on-demand by the model
|
|
26819
27760
|
except Exception:
|
|
26820
27761
|
pass
|
|
@@ -26878,6 +27819,21 @@ class SessionState:
|
|
|
26878
27819
|
)
|
|
26879
27820
|
},
|
|
26880
27821
|
)
|
|
27822
|
+
# finish_current_task is a strong signal — advance plan step if active
|
|
27823
|
+
try:
|
|
27824
|
+
_bb_fin = self._ensure_blackboard()
|
|
27825
|
+
_cur_ps = next(
|
|
27826
|
+
(t for t in _bb_fin.get("project_todos", [])
|
|
27827
|
+
if t.get("category") == "plan_step" and t.get("status") == "in_progress"),
|
|
27828
|
+
None,
|
|
27829
|
+
)
|
|
27830
|
+
if _cur_ps:
|
|
27831
|
+
self._advance_plan_step(
|
|
27832
|
+
evidence=f"finish_current_task called: {trim(summary, 100)}",
|
|
27833
|
+
actor=str(role_key or "developer"),
|
|
27834
|
+
)
|
|
27835
|
+
except Exception:
|
|
27836
|
+
pass
|
|
26881
27837
|
return (
|
|
26882
27838
|
f"{name} acknowledged{': ' + summary if summary else ''}; "
|
|
26883
27839
|
f"todo_completed={updated}"
|
|
@@ -27985,6 +28941,8 @@ class SessionState:
|
|
|
27985
28941
|
|
|
27986
28942
|
def _multi_agent_sync_blackboard_worker(self, *, pinned_selection: str):
|
|
27987
28943
|
idle_counts = {role: 0 for role in AGENT_ROLES}
|
|
28944
|
+
_prev_delegation_hash = ""
|
|
28945
|
+
_repeat_delegation_count = 0
|
|
27988
28946
|
media_last_user_ts = -1.0
|
|
27989
28947
|
media_inputs_pool: list[dict] | None = None
|
|
27990
28948
|
media_seen_ts_by_role: dict[str, float] = {
|
|
@@ -27996,6 +28954,26 @@ class SessionState:
|
|
|
27996
28954
|
board = self._ensure_blackboard()
|
|
27997
28955
|
profile = self._ensure_blackboard_task_profile(board)
|
|
27998
28956
|
budget_val = self._blackboard_round_budget(board)
|
|
28957
|
+
# Fix 7: Pure sync no-plan — if complex task and no plan steps exist, prompt manager
|
|
28958
|
+
# to create them before delegating. Guard: only fires when no plan_step items exist,
|
|
28959
|
+
# so plan+sync mode (which already has plan steps) is completely unaffected.
|
|
28960
|
+
_sync_has_plan = any(
|
|
28961
|
+
isinstance(t, dict) and t.get("category") == "plan_step"
|
|
28962
|
+
for t in board.get("project_todos", [])
|
|
28963
|
+
)
|
|
28964
|
+
_sync_complexity = str(profile.get("complexity", "simple") or "simple")
|
|
28965
|
+
if not _sync_has_plan and _sync_complexity in ("moderate", "complex", "expert"):
|
|
28966
|
+
self.messages.append({
|
|
28967
|
+
"role": "system",
|
|
28968
|
+
"content": (
|
|
28969
|
+
"[SYNC-INIT] No plan steps found for this task. Before delegating to workers, "
|
|
28970
|
+
"use write_to_blackboard to add 3-5 plan_step items to project_todos. "
|
|
28971
|
+
'Each item: {"category":"plan_step","content":"N. Step title",'
|
|
28972
|
+
'"status":"pending","owner":"manager"}. '
|
|
28973
|
+
"This enables proper todo tracking and completion detection."
|
|
28974
|
+
),
|
|
28975
|
+
"ts": now_ts(),
|
|
28976
|
+
})
|
|
27999
28977
|
self._blackboard_set_status("INITIALIZING", "sync collaborative loop started")
|
|
28000
28978
|
self._emit(
|
|
28001
28979
|
"status",
|
|
@@ -28091,6 +29069,29 @@ class SessionState:
|
|
|
28091
29069
|
self._mark_all_done_silently(note)
|
|
28092
29070
|
self._emit("status", {"summary": "manager decided finish; run paused"})
|
|
28093
29071
|
break
|
|
29072
|
+
# Detect manager stuck: same instruction repeated N times → force advance + break
|
|
29073
|
+
import hashlib as _hl_mgr
|
|
29074
|
+
_cur_hash = _hl_mgr.sha1((target + "|" + instruction).encode("utf-8")).hexdigest()[:12]
|
|
29075
|
+
if _cur_hash == _prev_delegation_hash:
|
|
29076
|
+
_repeat_delegation_count += 1
|
|
29077
|
+
else:
|
|
29078
|
+
_repeat_delegation_count = 0
|
|
29079
|
+
_prev_delegation_hash = _cur_hash
|
|
29080
|
+
if _repeat_delegation_count >= 3:
|
|
29081
|
+
self._emit("status", {"summary": f"manager stuck: repeated identical delegation x{_repeat_delegation_count + 1}; forcing advance"})
|
|
29082
|
+
_bb_stuck = self._ensure_blackboard()
|
|
29083
|
+
_stuck_step = next(
|
|
29084
|
+
(t for t in _bb_stuck.get("project_todos", [])
|
|
29085
|
+
if t.get("category") == "plan_step" and t.get("status") == "in_progress"),
|
|
29086
|
+
None,
|
|
29087
|
+
)
|
|
29088
|
+
if _stuck_step:
|
|
29089
|
+
self._advance_plan_step(evidence="manager stuck: repeated delegation", actor="manager")
|
|
29090
|
+
else:
|
|
29091
|
+
self._blackboard_mark_approved("manager stuck loop break", "manager")
|
|
29092
|
+
self._mark_all_done_silently("manager stuck: repeated delegation break")
|
|
29093
|
+
break
|
|
29094
|
+
_repeat_delegation_count = 0
|
|
28094
29095
|
role = self._sanitize_agent_role(target) or "developer"
|
|
28095
29096
|
self._inject_manager_instruction(
|
|
28096
29097
|
role,
|
|
@@ -28119,6 +29120,42 @@ class SessionState:
|
|
|
28119
29120
|
self._blackboard_update_from_worker_step(role, step)
|
|
28120
29121
|
# Post-execution plan step advancement (replaces pre-execution advancement)
|
|
28121
29122
|
self._post_execution_plan_step_check(route, step if isinstance(step, dict) else {})
|
|
29123
|
+
# Fix 6b: Pure sync no-plan — read worker-done signal and notify manager
|
|
29124
|
+
_bb_sync = self._ensure_blackboard()
|
|
29125
|
+
if _bb_sync.pop("sync_worker_round_done", False):
|
|
29126
|
+
self._save_blackboard(_bb_sync)
|
|
29127
|
+
self._append_agent_context_message(
|
|
29128
|
+
"manager",
|
|
29129
|
+
{
|
|
29130
|
+
"role": "system",
|
|
29131
|
+
"content": (
|
|
29132
|
+
"[worker-done] Worker completed the current task and called finish_current_task. "
|
|
29133
|
+
"Assess progress: assign the next task or conclude the session."
|
|
29134
|
+
),
|
|
29135
|
+
"ts": now_ts(),
|
|
29136
|
+
"agent_role": "manager",
|
|
29137
|
+
},
|
|
29138
|
+
mirror_to_global=False,
|
|
29139
|
+
)
|
|
29140
|
+
# Nudge: if worker wrote files but didn't call TodoWrite, inject reminder
|
|
29141
|
+
_step_dict = step if isinstance(step, dict) else {}
|
|
29142
|
+
_step_results = _step_dict.get("tool_results", []) or []
|
|
29143
|
+
_wrote = any(isinstance(r, dict) and r.get("ok") and str(r.get("name", "")) in ("write_file", "edit_file") for r in _step_results)
|
|
29144
|
+
_did_todo = any(isinstance(r, dict) and str(r.get("name", "")) == "TodoWrite" for r in _step_results)
|
|
29145
|
+
if _wrote and not _did_todo:
|
|
29146
|
+
_bb_nudge = self._ensure_blackboard()
|
|
29147
|
+
_cur_step = next((t for t in _bb_nudge.get("project_todos", []) if t.get("category") == "plan_step" and t.get("status") == "in_progress"), None)
|
|
29148
|
+
if _cur_step:
|
|
29149
|
+
_nid = str(_cur_step.get("id", "") or "")
|
|
29150
|
+
if _nid:
|
|
29151
|
+
_nudge_msg = (
|
|
29152
|
+
f"[todo-sync] You made progress but did not call TodoWrite.\n"
|
|
29153
|
+
f"Update your subtasks: mark completed ones, add new ones if needed.\n"
|
|
29154
|
+
f"Each subtask must include parent_step_id='{_nid}'."
|
|
29155
|
+
)
|
|
29156
|
+
self._append_agent_context_message(role, {
|
|
29157
|
+
"role": "system", "content": _nudge_msg, "ts": now_ts(), "agent_role": role,
|
|
29158
|
+
}, mirror_to_global=False)
|
|
28122
29159
|
# ── Agent turn 结束后的终止检测:结论性回复 + 无待办 + 无错误 → 自动 finish ──
|
|
28123
29160
|
agent_text = self._latest_agent_assistant_text(role)
|
|
28124
29161
|
if (
|
|
@@ -28882,21 +29919,21 @@ class SessionState:
|
|
|
28882
29919
|
|
|
28883
29920
|
def _emit_stall_conclusion(self, trigger_source: str, last_fault_reason: str = "", stall_context: dict | None = None):
|
|
28884
29921
|
ctx = stall_context or self._collect_stall_context(last_fault_reason=last_fault_reason)
|
|
28885
|
-
lines = ["
|
|
28886
|
-
lines.append(
|
|
29922
|
+
lines = [self._ui_text("stall_execution_blocked_title")]
|
|
29923
|
+
lines.append(self._ui_text("stall_stop_reason", reason=trim(str(trigger_source), 200)))
|
|
28887
29924
|
if last_fault_reason:
|
|
28888
|
-
lines.append(
|
|
29925
|
+
lines.append(self._ui_text("stall_error_details", detail=trim(str(last_fault_reason), 400)))
|
|
28889
29926
|
error_ctx = str(ctx.get("error_context", "") or "").strip()
|
|
28890
29927
|
if error_ctx:
|
|
28891
|
-
lines.append(f"\n
|
|
29928
|
+
lines.append(f"\n{self._ui_text('stall_recent_error')}\n```\n{trim(error_ctx, 600)}\n```")
|
|
28892
29929
|
repeated = ctx.get("repeated_tools", [])
|
|
28893
29930
|
if repeated:
|
|
28894
|
-
lines.append(f"\n
|
|
28895
|
-
lines.append("\n
|
|
28896
|
-
lines.append(
|
|
28897
|
-
lines.append(
|
|
28898
|
-
lines.append(
|
|
28899
|
-
lines.append("\n
|
|
29931
|
+
lines.append(f"\n{self._ui_text('stall_repeated_tools', tools=', '.join(repeated))}")
|
|
29932
|
+
lines.append(f"\n{self._ui_text('stall_suggested_actions')}")
|
|
29933
|
+
lines.append(self._ui_text("stall_action_1"))
|
|
29934
|
+
lines.append(self._ui_text("stall_action_2"))
|
|
29935
|
+
lines.append(self._ui_text("stall_action_3"))
|
|
29936
|
+
lines.append(f"\n{self._ui_text('stall_continue_prompt')}")
|
|
28900
29937
|
conclusion_md = "\n".join(lines)
|
|
28901
29938
|
self.messages.append({
|
|
28902
29939
|
"role": "assistant",
|
|
@@ -28912,29 +29949,36 @@ class SessionState:
|
|
|
28912
29949
|
})
|
|
28913
29950
|
|
|
28914
29951
|
def _format_stall_findings(self, stall_context: dict) -> str:
|
|
28915
|
-
lines = ["
|
|
29952
|
+
lines = [self._ui_text("stall_analysis_title")]
|
|
28916
29953
|
goal = str(stall_context.get("goal", "") or "").strip()
|
|
28917
29954
|
if goal:
|
|
28918
|
-
lines.append(
|
|
28919
|
-
lines.append(
|
|
29955
|
+
lines.append(self._ui_text("stall_goal", goal=trim(goal, 400)))
|
|
29956
|
+
lines.append(self._ui_text("stall_severity", score=stall_context.get("severity_score", 0)))
|
|
28920
29957
|
events = stall_context.get("stall_events", [])
|
|
28921
29958
|
if events:
|
|
28922
|
-
lines.append("\n
|
|
29959
|
+
lines.append(f"\n{self._ui_text('stall_events')}")
|
|
28923
29960
|
for ev in events[-6:]:
|
|
28924
29961
|
if isinstance(ev, dict):
|
|
28925
|
-
lines.append(
|
|
29962
|
+
lines.append(
|
|
29963
|
+
self._ui_text(
|
|
29964
|
+
"stall_event_line",
|
|
29965
|
+
source=ev.get("source", "?"),
|
|
29966
|
+
points=ev.get("points", 0),
|
|
29967
|
+
cumulative=ev.get("cumulative", "?"),
|
|
29968
|
+
)
|
|
29969
|
+
)
|
|
28926
29970
|
error_ctx = str(stall_context.get("error_context", "") or "").strip()
|
|
28927
29971
|
if error_ctx:
|
|
28928
|
-
lines.append(f"\n
|
|
29972
|
+
lines.append(f"\n{self._ui_text('stall_error_context')}\n```\n{trim(error_ctx, 500)}\n```")
|
|
28929
29973
|
repeated = stall_context.get("repeated_tools", [])
|
|
28930
29974
|
if repeated:
|
|
28931
|
-
lines.append(f"\n
|
|
29975
|
+
lines.append(f"\n{self._ui_text('stall_repeated_tools_label', tools=', '.join(repeated))}")
|
|
28932
29976
|
fault_reason = str(stall_context.get("last_fault_reason", "") or "").strip()
|
|
28933
29977
|
if fault_reason:
|
|
28934
|
-
lines.append(
|
|
29978
|
+
lines.append(self._ui_text("stall_last_fault_reason", reason=trim(fault_reason, 200)))
|
|
28935
29979
|
open_todos = stall_context.get("open_todos", [])
|
|
28936
29980
|
if open_todos:
|
|
28937
|
-
lines.append("\n
|
|
29981
|
+
lines.append(f"\n{self._ui_text('stall_open_todos')}")
|
|
28938
29982
|
for t in open_todos[:4]:
|
|
28939
29983
|
lines.append(f"- {trim(str(t), 100)}")
|
|
28940
29984
|
return "\n".join(lines)
|
|
@@ -29108,8 +30152,9 @@ class SessionState:
|
|
|
29108
30152
|
if not content:
|
|
29109
30153
|
continue
|
|
29110
30154
|
if content in {
|
|
29111
|
-
"继续", "continue", "go on", "接着", "
|
|
29112
|
-
"方案a", "方案b", "方案c", "
|
|
30155
|
+
"继续", "繼續", "continue", "go on", "接着", "接著", "続行", "続けて",
|
|
30156
|
+
"a", "b", "c", "方案a", "方案b", "方案c", "案a", "案b", "案c",
|
|
30157
|
+
"keep going", "proceed", "確認", "确认",
|
|
29113
30158
|
}:
|
|
29114
30159
|
continue
|
|
29115
30160
|
if len(content) > 10:
|
|
@@ -29176,10 +30221,10 @@ class SessionState:
|
|
|
29176
30221
|
|
|
29177
30222
|
def _format_plan_file_preselection(self, proposal: dict) -> str:
|
|
29178
30223
|
"""Full MD content with ALL options for model review (no char limit)."""
|
|
29179
|
-
lines = ["
|
|
30224
|
+
lines = [self._ui_text("plan_file_proposals_title")]
|
|
29180
30225
|
context = str(proposal.get("context", "") or "").strip()
|
|
29181
30226
|
if context:
|
|
29182
|
-
lines.append(
|
|
30227
|
+
lines.append(self._ui_text("plan_file_background", context=context))
|
|
29183
30228
|
recommended = str(proposal.get("recommended", "") or "").strip()
|
|
29184
30229
|
options = proposal.get("options", [])
|
|
29185
30230
|
if not isinstance(options, list):
|
|
@@ -29189,9 +30234,9 @@ class SessionState:
|
|
|
29189
30234
|
continue
|
|
29190
30235
|
opt_id = str(opt.get("id", "") or "").strip()
|
|
29191
30236
|
title = str(opt.get("title", "") or "").strip()
|
|
29192
|
-
header =
|
|
30237
|
+
header = self._ui_text("plan_file_option", id=opt_id, title=title)
|
|
29193
30238
|
if opt_id == recommended:
|
|
29194
|
-
header += "
|
|
30239
|
+
header += self._ui_text("plan_file_recommended")
|
|
29195
30240
|
lines.append("---\n")
|
|
29196
30241
|
lines.append(header)
|
|
29197
30242
|
summary = str(opt.get("summary", "") or "").strip()
|
|
@@ -29199,7 +30244,7 @@ class SessionState:
|
|
|
29199
30244
|
lines.append(summary)
|
|
29200
30245
|
steps = opt.get("steps", [])
|
|
29201
30246
|
if isinstance(steps, list) and steps:
|
|
29202
|
-
lines.append("\n
|
|
30247
|
+
lines.append(f"\n{self._ui_text('plan_file_steps')}")
|
|
29203
30248
|
import re as _re_plan
|
|
29204
30249
|
_mid_re = _re_plan.compile(r"(?<=\S)\s+(\d+\.\d+\s)")
|
|
29205
30250
|
for i, s in enumerate(steps):
|
|
@@ -29218,16 +30263,16 @@ class SessionState:
|
|
|
29218
30263
|
lines.append(f"{i + 1}. {step_str}")
|
|
29219
30264
|
pros = str(opt.get("pros", "") or "").strip()
|
|
29220
30265
|
if pros:
|
|
29221
|
-
lines.append(f"\n
|
|
30266
|
+
lines.append(f"\n{self._ui_text('plan_file_pros', text=pros)}")
|
|
29222
30267
|
cons = str(opt.get("cons", "") or "").strip()
|
|
29223
30268
|
if cons:
|
|
29224
|
-
lines.append(
|
|
30269
|
+
lines.append(self._ui_text("plan_file_cons", text=cons))
|
|
29225
30270
|
risk = str(opt.get("risk", "") or "").strip()
|
|
29226
30271
|
if risk:
|
|
29227
|
-
lines.append(
|
|
30272
|
+
lines.append(self._ui_text("plan_file_risk", text=risk))
|
|
29228
30273
|
lines.append("")
|
|
29229
30274
|
lines.append("---")
|
|
29230
|
-
lines.append(
|
|
30275
|
+
lines.append(self._ui_text("plan_file_awaiting_choice"))
|
|
29231
30276
|
return "\n".join(lines)
|
|
29232
30277
|
|
|
29233
30278
|
def _format_plan_file_execution(self, choice_id: str) -> str:
|
|
@@ -29247,14 +30292,14 @@ class SessionState:
|
|
|
29247
30292
|
completed = sum(1 for t in plan_todos if t.get("status") == "completed")
|
|
29248
30293
|
current_idx = completed + 1
|
|
29249
30294
|
|
|
29250
|
-
lines = [
|
|
29251
|
-
lines.append(
|
|
29252
|
-
lines.append(
|
|
30295
|
+
lines = [self._ui_text("active_plan_title", title=title)]
|
|
30296
|
+
lines.append(self._ui_text("active_plan_status", current=current_idx, total=total))
|
|
30297
|
+
lines.append(self._ui_text("active_plan_chosen", choice=choice_id))
|
|
29253
30298
|
from datetime import datetime as _dt_cls
|
|
29254
|
-
lines.append(
|
|
30299
|
+
lines.append(self._ui_text("active_plan_updated", updated=_dt_cls.now().isoformat(timespec="seconds")))
|
|
29255
30300
|
if summary:
|
|
29256
|
-
lines.append(
|
|
29257
|
-
lines.append("
|
|
30301
|
+
lines.append(self._ui_text("active_plan_summary", summary=summary))
|
|
30302
|
+
lines.append(self._ui_text("active_plan_steps"))
|
|
29258
30303
|
import re as _re_exec
|
|
29259
30304
|
_mid_re_exec = _re_exec.compile(r"(?<=\S)\s+(\d+\.\d+\s)")
|
|
29260
30305
|
for t in plan_todos:
|
|
@@ -29268,22 +30313,22 @@ class SessionState:
|
|
|
29268
30313
|
if status == "completed":
|
|
29269
30314
|
actor = str(t.get("completed_by", "") or "")
|
|
29270
30315
|
evidence = str(t.get("evidence", "") or "")
|
|
29271
|
-
lines.append(
|
|
30316
|
+
lines.append(self._ui_text("active_plan_step_done", idx=idx, header=header))
|
|
29272
30317
|
for sub in sub_lines:
|
|
29273
30318
|
lines.append(f" - {sub.strip()}")
|
|
29274
30319
|
meta_parts = []
|
|
29275
30320
|
if actor:
|
|
29276
|
-
meta_parts.append(
|
|
30321
|
+
meta_parts.append(self._ui_text("active_plan_completed_by", actor=actor))
|
|
29277
30322
|
if evidence:
|
|
29278
|
-
meta_parts.append(
|
|
30323
|
+
meta_parts.append(self._ui_text("active_plan_evidence", evidence=evidence))
|
|
29279
30324
|
if meta_parts:
|
|
29280
30325
|
lines.append(f" > {' | '.join(meta_parts)}")
|
|
29281
30326
|
elif status == "in_progress":
|
|
29282
|
-
lines.append(
|
|
30327
|
+
lines.append(self._ui_text("active_plan_step_current", idx=idx, header=header))
|
|
29283
30328
|
for sub in sub_lines:
|
|
29284
30329
|
lines.append(f" - {sub.strip()}")
|
|
29285
30330
|
else:
|
|
29286
|
-
lines.append(
|
|
30331
|
+
lines.append(self._ui_text("active_plan_step_pending", idx=idx, header=header))
|
|
29287
30332
|
for sub in sub_lines:
|
|
29288
30333
|
lines.append(f" - {sub.strip()}")
|
|
29289
30334
|
return "\n".join(lines) + "\n"
|
|
@@ -29302,10 +30347,10 @@ class SessionState:
|
|
|
29302
30347
|
|
|
29303
30348
|
def _format_plan_bubble_preselection(self, proposal: dict) -> str:
|
|
29304
30349
|
"""Condensed bubble for UI (under PLAN_BUBBLE_MAX_CHARS). No full step listing."""
|
|
29305
|
-
lines = ["
|
|
30350
|
+
lines = [self._ui_text("plan_bubble_title")]
|
|
29306
30351
|
context = str(proposal.get("context", "") or "").strip()
|
|
29307
30352
|
if context:
|
|
29308
|
-
lines.append(
|
|
30353
|
+
lines.append(self._ui_text("plan_bubble_background", context=trim(context, 300)))
|
|
29309
30354
|
recommended = str(proposal.get("recommended", "") or "").strip()
|
|
29310
30355
|
options = proposal.get("options", [])
|
|
29311
30356
|
if not isinstance(options, list):
|
|
@@ -29316,9 +30361,9 @@ class SessionState:
|
|
|
29316
30361
|
opt_id = str(opt.get("id", "") or "").strip()
|
|
29317
30362
|
title = str(opt.get("title", "") or "").strip()
|
|
29318
30363
|
is_rec = opt_id == recommended
|
|
29319
|
-
header =
|
|
30364
|
+
header = self._ui_text("plan_bubble_option", id=opt_id, title=title)
|
|
29320
30365
|
if is_rec:
|
|
29321
|
-
header += "
|
|
30366
|
+
header += self._ui_text("plan_bubble_recommended")
|
|
29322
30367
|
lines.append(header)
|
|
29323
30368
|
summary = str(opt.get("summary", "") or "").strip()
|
|
29324
30369
|
if summary:
|
|
@@ -29326,14 +30371,14 @@ class SessionState:
|
|
|
29326
30371
|
steps = opt.get("steps", [])
|
|
29327
30372
|
step_count = len(steps) if isinstance(steps, list) else 0
|
|
29328
30373
|
risk = str(opt.get("risk", "") or "").strip()
|
|
29329
|
-
meta =
|
|
30374
|
+
meta = self._ui_text("plan_bubble_steps", count=step_count)
|
|
29330
30375
|
if risk:
|
|
29331
|
-
meta += f" |
|
|
30376
|
+
meta += f" | {self._ui_text('plan_bubble_risk', risk=risk)}"
|
|
29332
30377
|
lines.append(meta)
|
|
29333
30378
|
lines.append("")
|
|
29334
30379
|
lines.append("---")
|
|
29335
|
-
lines.append(
|
|
29336
|
-
lines.append(
|
|
30380
|
+
lines.append(self._ui_text("plan_bubble_full_ref", path=PLAN_FILE_RELATIVE_PATH))
|
|
30381
|
+
lines.append(self._ui_text("plan_bubble_reply"))
|
|
29337
30382
|
return trim("\n".join(lines), PLAN_BUBBLE_MAX_CHARS)
|
|
29338
30383
|
|
|
29339
30384
|
def _plan_file_read_instruction(self) -> str:
|
|
@@ -29349,19 +30394,14 @@ class SessionState:
|
|
|
29349
30394
|
break
|
|
29350
30395
|
todo_note = ""
|
|
29351
30396
|
if active_step_id:
|
|
29352
|
-
todo_note = (
|
|
29353
|
-
|
|
29354
|
-
|
|
29355
|
-
|
|
29356
|
-
f"Do NOT create subtasks for other plan steps. Mark each subtask completed as you finish it.\n"
|
|
30397
|
+
todo_note = self._ui_text(
|
|
30398
|
+
"plan_read_todo_note",
|
|
30399
|
+
step_label=self._ui_text("plan_step_label", step=active_step_idx, total=int(bb.get("plan_step_total", 0) or 0)),
|
|
30400
|
+
parent_step_id=active_step_id,
|
|
29357
30401
|
)
|
|
29358
30402
|
return (
|
|
29359
|
-
|
|
29360
|
-
|
|
29361
|
-
"The plan file is the authoritative source for step ordering and completion status.\n"
|
|
29362
|
-
"Execute steps IN ORDER. Do NOT skip ahead. Mark current step done before advancing.\n"
|
|
29363
|
-
"If a step references a skill or workflow, call load_skill to load it before proceeding."
|
|
29364
|
-
f"{todo_note}"
|
|
30403
|
+
self._ui_text("plan_read_instruction", path=PLAN_FILE_RELATIVE_PATH)
|
|
30404
|
+
+ todo_note
|
|
29365
30405
|
)
|
|
29366
30406
|
|
|
29367
30407
|
@staticmethod
|
|
@@ -29456,10 +30496,10 @@ class SessionState:
|
|
|
29456
30496
|
# ── (legacy) _format_plan_proposal_markdown ──────────────────────
|
|
29457
30497
|
|
|
29458
30498
|
def _format_plan_proposal_markdown(self, proposal: dict) -> str:
|
|
29459
|
-
lines = ["
|
|
30499
|
+
lines = [self._ui_text("plan_proposal_title")]
|
|
29460
30500
|
context = str(proposal.get("context", "") or "").strip()
|
|
29461
30501
|
if context:
|
|
29462
|
-
lines.append(
|
|
30502
|
+
lines.append(self._ui_text("plan_proposal_background", context=context))
|
|
29463
30503
|
recommended = str(proposal.get("recommended", "") or "").strip()
|
|
29464
30504
|
options = proposal.get("options", [])
|
|
29465
30505
|
if not isinstance(options, list):
|
|
@@ -29470,30 +30510,30 @@ class SessionState:
|
|
|
29470
30510
|
opt_id = str(opt.get("id", "") or "").strip()
|
|
29471
30511
|
title = str(opt.get("title", "") or "").strip()
|
|
29472
30512
|
is_recommended = opt_id == recommended
|
|
29473
|
-
header =
|
|
30513
|
+
header = self._ui_text("plan_proposal_option", id=opt_id, title=title)
|
|
29474
30514
|
if is_recommended:
|
|
29475
|
-
header += "
|
|
30515
|
+
header += self._ui_text("plan_proposal_recommended")
|
|
29476
30516
|
lines.append(header)
|
|
29477
30517
|
summary = str(opt.get("summary", "") or "").strip()
|
|
29478
30518
|
if summary:
|
|
29479
30519
|
lines.append(summary)
|
|
29480
30520
|
steps = opt.get("steps", [])
|
|
29481
30521
|
if isinstance(steps, list) and steps:
|
|
29482
|
-
lines.append("\n
|
|
30522
|
+
lines.append(f"\n{self._ui_text('plan_proposal_steps')}")
|
|
29483
30523
|
for i, s in enumerate(steps):
|
|
29484
30524
|
lines.append(f"{i+1}. {s}")
|
|
29485
30525
|
pros = str(opt.get("pros", "") or "").strip()
|
|
29486
30526
|
if pros:
|
|
29487
|
-
lines.append(f"\n
|
|
30527
|
+
lines.append(f"\n{self._ui_text('plan_proposal_pros', text=pros)}")
|
|
29488
30528
|
cons = str(opt.get("cons", "") or "").strip()
|
|
29489
30529
|
if cons:
|
|
29490
|
-
lines.append(
|
|
30530
|
+
lines.append(self._ui_text("plan_proposal_cons", text=cons))
|
|
29491
30531
|
risk = str(opt.get("risk", "") or "").strip()
|
|
29492
30532
|
if risk:
|
|
29493
|
-
lines.append(
|
|
30533
|
+
lines.append(self._ui_text("plan_proposal_risk", text=risk))
|
|
29494
30534
|
lines.append("")
|
|
29495
30535
|
lines.append("---")
|
|
29496
|
-
lines.append(
|
|
30536
|
+
lines.append(self._ui_text("plan_proposal_reply"))
|
|
29497
30537
|
return "\n".join(lines)
|
|
29498
30538
|
|
|
29499
30539
|
def _parse_plan_choice(self, text: str, proposal: dict) -> str:
|
|
@@ -29510,14 +30550,14 @@ class SessionState:
|
|
|
29510
30550
|
return low.upper()
|
|
29511
30551
|
# "方案A", "方案 A", "option A"
|
|
29512
30552
|
import re
|
|
29513
|
-
m = re.search(r'(
|
|
30553
|
+
m = re.search(r'(?:方案|選項|选项|option|案|プラン)\s*([a-zA-Z0-9])', low, re.IGNORECASE)
|
|
29514
30554
|
if m:
|
|
29515
30555
|
candidate = m.group(1).upper()
|
|
29516
30556
|
if candidate in option_ids:
|
|
29517
30557
|
return candidate
|
|
29518
30558
|
# "选1", "第1个", "第一个"
|
|
29519
30559
|
num_map = {"一": "1", "二": "2", "三": "3", "1": "1", "2": "2", "3": "3"}
|
|
29520
|
-
m2 = re.search(r'(
|
|
30560
|
+
m2 = re.search(r'(?:选|選|第|choose|pick)\s*([一二三1-3])', low, re.IGNORECASE)
|
|
29521
30561
|
if m2:
|
|
29522
30562
|
idx_str = num_map.get(m2.group(1), "")
|
|
29523
30563
|
if idx_str:
|
|
@@ -29526,7 +30566,10 @@ class SessionState:
|
|
|
29526
30566
|
return option_ids[idx]
|
|
29527
30567
|
# "继续"/"确认"/"推荐" → pick recommended
|
|
29528
30568
|
recommended = str(proposal.get("recommended", "") or "").strip()
|
|
29529
|
-
confirm_tokens = (
|
|
30569
|
+
confirm_tokens = (
|
|
30570
|
+
"继续", "繼續", "确认", "確認", "推荐", "推薦", "推荐方案", "推薦方案",
|
|
30571
|
+
"go", "proceed", "continue", "yes", "ok", "続行", "確認する", "おすすめ", "推奨",
|
|
30572
|
+
)
|
|
29530
30573
|
if any(tok in low for tok in confirm_tokens) and recommended:
|
|
29531
30574
|
return recommended
|
|
29532
30575
|
# --- Slow path: LLM semantic matching ---
|
|
@@ -29602,7 +30645,33 @@ class SessionState:
|
|
|
29602
30645
|
self._blackboard_history("manager", f"plan approved: option {choice_id} — {chosen.get('title', '')}")
|
|
29603
30646
|
# Lock complexity/level floor to prevent manager downgrade during plan execution
|
|
29604
30647
|
self.runtime_complexity_floor = str(self.runtime_task_complexity or "complex")
|
|
29605
|
-
|
|
30648
|
+
# --- Risk-based complexity lock: set floor AND ceiling from plan option's risk field ---
|
|
30649
|
+
# Read risk NOW — blackboard compaction later drops the risk key from options
|
|
30650
|
+
_plan_risk = str(chosen.get("risk", "") or "").strip().lower()
|
|
30651
|
+
if _plan_risk not in ("low", "medium", "high"):
|
|
30652
|
+
# Fallback: scan option summary/description for risk label
|
|
30653
|
+
import re as _re_risk
|
|
30654
|
+
_rt = str(chosen.get("summary", "") or chosen.get("description", "") or "")
|
|
30655
|
+
_rm = _re_risk.search(r'风险[::]\s*(low|medium|high)|risk[::]\s*(low|medium|high)', _rt, _re_risk.I)
|
|
30656
|
+
_plan_risk = ((_rm.group(1) or _rm.group(2)) if _rm else "medium").lower()
|
|
30657
|
+
_current_level = int(self.runtime_task_level or 3)
|
|
30658
|
+
_user_override = int(getattr(self, "user_task_level_override", 0) or 0)
|
|
30659
|
+
if _user_override > 0:
|
|
30660
|
+
# User explicitly set level → absolute lock, no up and no down
|
|
30661
|
+
self.runtime_task_level_floor = _user_override
|
|
30662
|
+
self.runtime_task_level_ceiling = _user_override
|
|
30663
|
+
elif _plan_risk == "medium":
|
|
30664
|
+
# Medium risk → exact lock at current level
|
|
30665
|
+
self.runtime_task_level_floor = _current_level
|
|
30666
|
+
self.runtime_task_level_ceiling = _current_level
|
|
30667
|
+
elif _plan_risk == "high":
|
|
30668
|
+
# High risk → allow +1 upgrade, no downgrade
|
|
30669
|
+
self.runtime_task_level_floor = _current_level
|
|
30670
|
+
self.runtime_task_level_ceiling = min(5, _current_level + 1)
|
|
30671
|
+
else: # low
|
|
30672
|
+
# Low risk → allow -1 downgrade, no upgrade
|
|
30673
|
+
self.runtime_task_level_floor = max(1, _current_level - 1)
|
|
30674
|
+
self.runtime_task_level_ceiling = _current_level
|
|
29606
30675
|
# Auto-create todos from plan steps → write into bb["project_todos"]
|
|
29607
30676
|
steps = self._group_plan_steps(chosen.get("steps", []))
|
|
29608
30677
|
if steps and isinstance(steps, list):
|
|
@@ -29640,7 +30709,6 @@ class SessionState:
|
|
|
29640
30709
|
"key": f"bb:proj:{t['id']}",
|
|
29641
30710
|
"content": t["content"],
|
|
29642
30711
|
"status": t["status"],
|
|
29643
|
-
"activeForm": f"Working on: {t['content']}" if t["status"] == "in_progress" else f"Pending: {t['content']}",
|
|
29644
30712
|
}
|
|
29645
30713
|
for t in plan_todos[:40]
|
|
29646
30714
|
])
|
|
@@ -30985,6 +32053,11 @@ class SessionState:
|
|
|
30985
32053
|
)
|
|
30986
32054
|
},
|
|
30987
32055
|
)
|
|
32056
|
+
# Generate completion summary bubble before finishing
|
|
32057
|
+
try:
|
|
32058
|
+
self._generate_run_completion_summary()
|
|
32059
|
+
except Exception:
|
|
32060
|
+
pass
|
|
30988
32061
|
self._emit("status", {"summary": "run finished"})
|
|
30989
32062
|
cb = self.run_finished_callback
|
|
30990
32063
|
if cb:
|
|
@@ -31748,7 +32821,7 @@ class SessionManager:
|
|
|
31748
32821
|
if clear_cap_cache:
|
|
31749
32822
|
sess.multimodal_capability_cache = {}
|
|
31750
32823
|
sess.ollama.clear_probe_cache()
|
|
31751
|
-
sess.
|
|
32824
|
+
sess._set_ui_language(self.user_language, relabel_todos=True)
|
|
31752
32825
|
sess.auto_model_switch = bool(self.auto_model_switch)
|
|
31753
32826
|
sess.arbiter_enabled = bool(self.arbiter_enabled)
|
|
31754
32827
|
sess.arbiter_model = str(self.arbiter_model or "").strip()
|
|
@@ -32223,7 +33296,7 @@ class SessionManager:
|
|
|
32223
33296
|
with self.lock:
|
|
32224
33297
|
self.user_language = lang
|
|
32225
33298
|
for sess in self.sessions.values():
|
|
32226
|
-
sess.
|
|
33299
|
+
sess._set_ui_language(lang, relabel_todos=True)
|
|
32227
33300
|
sess.updated_at = now_ts()
|
|
32228
33301
|
sess._persist()
|
|
32229
33302
|
self._persist_user_prefs()
|
|
@@ -32235,7 +33308,7 @@ class SessionManager:
|
|
|
32235
33308
|
sess = self.sessions.get(session_id)
|
|
32236
33309
|
if not sess:
|
|
32237
33310
|
raise KeyError(session_id)
|
|
32238
|
-
sess.
|
|
33311
|
+
sess._set_ui_language(lang, relabel_todos=True)
|
|
32239
33312
|
sess.updated_at = now_ts()
|
|
32240
33313
|
sess._persist()
|
|
32241
33314
|
if set_user_default:
|
|
@@ -32958,7 +34031,7 @@ const I18N={
|
|
|
32958
34031
|
btn_send:'送出',btn_interrupt:'中斷',btn_compact:'壓縮',btn_refresh:'重新整理',btn_export_session:'匯出會話',
|
|
32959
34032
|
btn_clear_stale_todos:'清除陳舊待辦',
|
|
32960
34033
|
prompt_placeholder:'描述你的任務,或將檔案拖入此處...',
|
|
32961
|
-
upload_drop:'拖曳上傳程式碼 / Markdown / PDF / Excel / Word / PPT / CSV
|
|
34034
|
+
upload_drop:'拖曳上傳程式碼 / Markdown / PDF / Excel / Word / PPT / CSV,或點擊此處選擇檔案',
|
|
32962
34035
|
upload_file_hint:'支援拖入檔案:程式碼 / Markdown / PDF / Excel / Word / PPT / CSV',
|
|
32963
34036
|
upload_pick_file:'選擇檔案',
|
|
32964
34037
|
upload_drop_release:'釋放以上傳檔案',
|
|
@@ -32977,7 +34050,7 @@ const I18N={
|
|
|
32977
34050
|
copy_code:'複製程式碼',copy_done:'已複製',
|
|
32978
34051
|
btn_tools:'工具 ▾',btn_compact_action:'壓縮',btn_refresh_action:'重新整理',
|
|
32979
34052
|
btn_level:'等級',level_auto:'自動',level_1_simple:'L1 簡單',level_2_multi:'L2 多輪',
|
|
32980
|
-
level_3_collab:'L3
|
|
34053
|
+
level_3_collab:'L3 協作',level_4_complex:'L4 複雜',level_5_system:'L5 系統',
|
|
32981
34054
|
|
|
32982
34055
|
|
|
32983
34056
|
llm_fill_config:'填寫 LLM 設定',llm_provider:'供應商',llm_confirm:'確認',llm_import_config:'匯入設定',
|
|
@@ -33023,6 +34096,88 @@ const I18N={
|
|
|
33023
34096
|
todo_plan_steps:'計画ステップ',todo_subtasks:'サブタスク'
|
|
33024
34097
|
}
|
|
33025
34098
|
};
|
|
34099
|
+
Object.assign(I18N['en'],{
|
|
34100
|
+
sec_todos:'Todos',sec_tasks:'Tasks',sec_activity:'Activity',sec_commands:'Commands',sec_diffs:'File Diffs',sec_catalog:'Catalog',
|
|
34101
|
+
role_explorer:'Explorer',role_developer:'Developer',role_reviewer:'Reviewer',role_manager:'Manager',role_planner:'Planner',role_agent:'Agent',
|
|
34102
|
+
callout_warning:'Warning',callout_notice:'Notice',callout_instruction:'Instruction',callout_tip:'Tip',callout_reminder:'Reminder',
|
|
34103
|
+
event_manager_delegate_title:'Manager Delegate',event_objective:'Objective',event_instruction:'Instruction',event_intent:'intent',
|
|
34104
|
+
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.',
|
|
34105
|
+
event_skill_loaded_title:'Skill Loaded',event_skill_loaded_note:'Skill context was auto-loaded into the current run.',event_skill_loaded_empty:'No public description was attached to this skill notification.',event_skill_label:'skill',
|
|
34106
|
+
event_loaded:'loaded',event_preview_truncated:'preview truncated',
|
|
34107
|
+
event_file_patch_title:'File Patch',event_session:'session',
|
|
34108
|
+
event_upload_title:'Upload',event_upload_path:'path',event_upload_filename:'filename',event_preview_unavailable:'Preview unavailable for this upload.',event_upload_parsing:'Parsing uploaded file in background. The bubble will refresh when parsing completes.',event_upload_failed:'Upload parsing failed',
|
|
34109
|
+
event_command_title:'Command',event_command_label:'command',event_cwd:'cwd',event_changed:'changed',event_command_empty:'No command output captured.',event_ui_truncated:'UI truncated',event_model_truncated:'Model truncated',event_temp_read_file:'Temp read_file',event_buffered:'Buffered',
|
|
34110
|
+
event_truncation_recovery:'Truncation Recovery',event_truncation_state:'Structured truncation recovery state',event_truncation_note:'Model output hit a truncation boundary and entered recovery mode.',
|
|
34111
|
+
event_live_model_call_title:'Agent Turn Model Call',event_live_model_call_note:'The active agent is in a model call. This timer updates live while generation is in progress.',
|
|
34112
|
+
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',
|
|
34113
|
+
state_on:'on',state_off:'off',
|
|
34114
|
+
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',
|
|
34115
|
+
fe_nodes:'nodes={n}',fe_loading:'loading...',fe_tree_truncated:'tree truncated at {n} nodes',fe_items:'{n} item(s)',
|
|
34116
|
+
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'
|
|
34117
|
+
});
|
|
34118
|
+
Object.assign(I18N['zh-CN'],{
|
|
34119
|
+
sec_todos:'待办',sec_tasks:'任务',sec_activity:'活动',sec_commands:'命令',sec_diffs:'文件差异',sec_catalog:'目录',
|
|
34120
|
+
no_todos:'暂无待办',no_tasks:'暂无任务',no_catalog:'暂无目录',
|
|
34121
|
+
role_explorer:'探索者',role_developer:'开发者',role_reviewer:'审查者',role_manager:'管理者',role_planner:'规划者',role_agent:'Agent',
|
|
34122
|
+
callout_warning:'警告',callout_notice:'提示',callout_instruction:'指令',callout_tip:'建议',callout_reminder:'提醒',
|
|
34123
|
+
event_manager_delegate_title:'管理者委派',event_objective:'目标',event_instruction:'指令',event_intent:'意图',
|
|
34124
|
+
event_tool_calls_title:'工具调用',event_tool_calls_note:'模型已为当前轮安排以下工具调用。',event_tool_calls_empty:'当前轮没有附带结构化工具元数据。',
|
|
34125
|
+
event_skill_loaded_title:'Skill 已加载',event_skill_loaded_note:'Skill 上下文已自动加载到当前运行。',event_skill_loaded_empty:'该 skill 通知没有附带公开描述。',event_skill_label:'skill',
|
|
34126
|
+
event_loaded:'已加载',event_preview_truncated:'预览被截断',
|
|
34127
|
+
event_file_patch_title:'文件补丁',event_session:'会话',
|
|
34128
|
+
event_upload_title:'上传',event_upload_path:'路径',event_upload_filename:'文件名',event_preview_unavailable:'该上传暂不支持预览。',event_upload_parsing:'正在后台解析上传文件。解析完成后气泡会自动刷新。',event_upload_failed:'上传解析失败',
|
|
34129
|
+
event_command_title:'命令',event_command_label:'命令',event_cwd:'工作目录',event_changed:'变更',event_command_empty:'未捕获到命令输出。',event_ui_truncated:'UI 截断',event_model_truncated:'模型截断',event_temp_read_file:'临时 read_file',event_buffered:'已缓冲',
|
|
34130
|
+
event_truncation_recovery:'截断恢复',event_truncation_state:'结构化截断恢复状态',event_truncation_note:'模型输出触发了截断边界,已进入恢复流程。',
|
|
34131
|
+
event_live_model_call_title:'Agent 轮次模型调用',event_live_model_call_note:'当前活跃 agent 正在进行模型调用。计时器会在生成期间实时更新。',
|
|
34132
|
+
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:'编辑恢复',
|
|
34133
|
+
state_on:'开',state_off:'关',
|
|
34134
|
+
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:'界面模式',
|
|
34135
|
+
fe_nodes:'节点={n}',fe_loading:'加载中...',fe_tree_truncated:'目录树在 {n} 个节点处被截断',fe_items:'{n} 项',
|
|
34136
|
+
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:'命令'
|
|
34137
|
+
});
|
|
34138
|
+
Object.assign(I18N['zh-TW'],{
|
|
34139
|
+
upload_drop:'拖曳上傳程式碼 / Markdown / PDF / Excel / Word / PPT / CSV,或點擊此處選擇檔案',
|
|
34140
|
+
sec_todos:'待辦',sec_tasks:'任務',sec_activity:'活動',sec_commands:'命令',sec_diffs:'檔案差異',sec_catalog:'目錄',
|
|
34141
|
+
no_todos:'尚無待辦',no_tasks:'尚無任務',no_catalog:'尚無目錄',
|
|
34142
|
+
level_3_collab:'L3 協作',
|
|
34143
|
+
role_explorer:'探索者',role_developer:'開發者',role_reviewer:'審查者',role_manager:'管理者',role_planner:'規劃者',role_agent:'Agent',
|
|
34144
|
+
callout_warning:'警告',callout_notice:'提示',callout_instruction:'指令',callout_tip:'建議',callout_reminder:'提醒',
|
|
34145
|
+
event_manager_delegate_title:'管理者委派',event_objective:'目標',event_instruction:'指令',event_intent:'意圖',
|
|
34146
|
+
event_tool_calls_title:'工具呼叫',event_tool_calls_note:'模型已為目前輪安排以下工具呼叫。',event_tool_calls_empty:'目前輪沒有附帶結構化工具中繼資料。',
|
|
34147
|
+
event_skill_loaded_title:'Skill 已載入',event_skill_loaded_note:'Skill 上下文已自動載入到目前執行。',event_skill_loaded_empty:'此 skill 通知沒有附帶公開描述。',event_skill_label:'skill',
|
|
34148
|
+
event_loaded:'已載入',event_preview_truncated:'預覽已截斷',
|
|
34149
|
+
event_file_patch_title:'檔案補丁',event_session:'會話',
|
|
34150
|
+
event_upload_title:'上傳',event_upload_path:'路徑',event_upload_filename:'檔名',event_preview_unavailable:'此上傳暫時無法預覽。',event_upload_parsing:'正在背景解析上傳檔案。解析完成後氣泡會自動更新。',event_upload_failed:'上傳解析失敗',
|
|
34151
|
+
event_command_title:'命令',event_command_label:'命令',event_cwd:'工作目錄',event_changed:'變更',event_command_empty:'未擷取到命令輸出。',event_ui_truncated:'UI 截斷',event_model_truncated:'模型截斷',event_temp_read_file:'暫存 read_file',event_buffered:'已緩衝',
|
|
34152
|
+
event_truncation_recovery:'截斷恢復',event_truncation_state:'結構化截斷恢復狀態',event_truncation_note:'模型輸出觸發截斷邊界,已進入恢復流程。',
|
|
34153
|
+
event_live_model_call_title:'Agent 輪次模型呼叫',event_live_model_call_note:'目前活躍 agent 正在進行模型呼叫。計時器會在生成期間即時更新。',
|
|
34154
|
+
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:'編輯恢復',
|
|
34155
|
+
state_on:'開',state_off:'關',
|
|
34156
|
+
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:'介面模式',
|
|
34157
|
+
fe_nodes:'節點={n}',fe_loading:'載入中...',fe_tree_truncated:'目錄樹在 {n} 個節點處被截斷',fe_items:'{n} 項',
|
|
34158
|
+
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:'命令'
|
|
34159
|
+
});
|
|
34160
|
+
Object.assign(I18N['ja'],{
|
|
34161
|
+
sec_todos:'Todo',sec_tasks:'タスク',sec_activity:'アクティビティ',sec_commands:'コマンド',sec_diffs:'ファイル差分',sec_catalog:'カタログ',
|
|
34162
|
+
thinking:'思考',thinking_stream:'思考(ストリーム)',copy_code:'コードをコピー',copy_done:'コピーしました',
|
|
34163
|
+
no_todos:'Todo はありません',no_tasks:'タスクはありません',no_catalog:'カタログなし',
|
|
34164
|
+
role_explorer:'探索担当',role_developer:'開発担当',role_reviewer:'レビュー担当',role_manager:'マネージャー',role_planner:'プランナー',role_agent:'Agent',
|
|
34165
|
+
callout_warning:'警告',callout_notice:'通知',callout_instruction:'指示',callout_tip:'ヒント',callout_reminder:'リマインダー',
|
|
34166
|
+
event_manager_delegate_title:'マネージャー委任',event_objective:'目的',event_instruction:'指示',event_intent:'意図',
|
|
34167
|
+
event_tool_calls_title:'ツール呼び出し',event_tool_calls_note:'モデルはこのターンで次のツール呼び出しを予定しました。',event_tool_calls_empty:'このターンには構造化されたツールメタデータがありません。',
|
|
34168
|
+
event_skill_loaded_title:'Skill 読み込み完了',event_skill_loaded_note:'Skill コンテキストが現在の実行に自動読み込みされました。',event_skill_loaded_empty:'この skill 通知には公開説明が付いていません。',event_skill_label:'skill',
|
|
34169
|
+
event_loaded:'読み込み済み',event_preview_truncated:'プレビュー切り詰め',
|
|
34170
|
+
event_file_patch_title:'ファイルパッチ',event_session:'セッション',
|
|
34171
|
+
event_upload_title:'アップロード',event_upload_path:'パス',event_upload_filename:'ファイル名',event_preview_unavailable:'このアップロードではプレビューを利用できません。',event_upload_parsing:'アップロードファイルをバックグラウンドで解析中です。完了するとバブルが更新されます。',event_upload_failed:'アップロード解析失敗',
|
|
34172
|
+
event_command_title:'コマンド',event_command_label:'コマンド',event_cwd:'作業ディレクトリ',event_changed:'変更',event_command_empty:'コマンド出力は取得されませんでした。',event_ui_truncated:'UI 切り詰め',event_model_truncated:'モデル切り詰め',event_temp_read_file:'一時 read_file',event_buffered:'バッファ済み',
|
|
34173
|
+
event_truncation_recovery:'切り詰め復旧',event_truncation_state:'構造化切り詰め復旧状態',event_truncation_note:'モデル出力が切り詰め境界に達したため、復旧フローに入りました。',
|
|
34174
|
+
event_live_model_call_title:'Agent ターンモデル呼び出し',event_live_model_call_note:'現在のアクティブ agent はモデル呼び出し中です。生成中はこのタイマーがリアルタイム更新されます。',
|
|
34175
|
+
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:'編集復旧',
|
|
34176
|
+
state_on:'オン',state_off:'オフ',
|
|
34177
|
+
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モード',
|
|
34178
|
+
fe_nodes:'ノード={n}',fe_loading:'読み込み中...',fe_tree_truncated:'ツリーは {n} ノードで切り詰められました',fe_items:'{n} 件',
|
|
34179
|
+
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:'コマンド'
|
|
34180
|
+
});
|
|
33026
34181
|
function currentLang(){const fromSnap=String(S.snap?.ui_language||'').trim();if(fromSnap&&I18N[fromSnap])return fromSnap;const fromCfg=String(S.config?.language||'').trim();if(fromCfg&&I18N[fromCfg])return fromCfg;return 'zh-CN'}
|
|
33027
34182
|
function normalizeUiStyle(raw){const key=String(raw||'').trim().toLowerCase().replace(/-/g,'_');if(['trad','traditional','classic','legacy','old'].includes(key))return'trad';return'neo'}
|
|
33028
34183
|
function applyUiStyle(){const style=normalizeUiStyle(S.config?.ui_style||'neo');if(document.body)document.body.setAttribute('data-ui-style',style);document.documentElement.setAttribute('data-ui-style',style)}
|
|
@@ -33031,7 +34186,7 @@ function setText(id,key){const el=E(id);if(el)el.textContent=t(key)}
|
|
|
33031
34186
|
function setPlaceholder(id,key){const el=E(id);if(el)el.placeholder=t(key)}
|
|
33032
34187
|
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()}
|
|
33033
34188
|
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}
|
|
33034
|
-
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;applyMainI18n();renderLanguageControls();renderStats();renderSessions();renderBoards();renderSkillsEntryLink()}
|
|
34189
|
+
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();renderChat('language');renderSkillsEntryLink()}
|
|
33035
34190
|
async function api(path,opt={}){const o=(opt&&typeof opt==='object')?{...opt}:{};const timeoutMs=Math.max(1000,Math.min(180000,Number(o.timeoutMs||45000)||45000));delete o.timeoutMs;const ctl=(typeof AbortController==='function')?new AbortController():null;let timer=0;try{if(ctl){timer=setTimeout(()=>{try{ctl.abort()}catch(_){ }},timeoutMs)}const hdr={...(o.headers||{}), 'Content-Type':'application/json'};const r=await fetch(path,{...o,headers:hdr,signal:(ctl?ctl.signal:o.signal)});const t=await r.text();if(!r.ok){let msg=t;try{msg=JSON.parse(t).error||t}catch(_){}throw new Error(msg||'request failed')}return t?JSON.parse(t):{}}catch(err){if(err&&err.name==='AbortError'){throw new Error('request timeout')}throw err}finally{if(timer)clearTimeout(timer)}}
|
|
33036
34191
|
function esc(s){return String(s??'').replace(/[&<>"]/g,c=>({ '&':'&','<':'<','>':'>','\"':'"' }[c]))}
|
|
33037
34192
|
function showError(msg){const el=E('errorBox');if(!msg){el.classList.add('hidden');el.textContent='';return}el.textContent=msg;el.classList.remove('hidden')}
|
|
@@ -33086,7 +34241,7 @@ function setPanelHtml(id,html){
|
|
|
33086
34241
|
}
|
|
33087
34242
|
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)}%)`}
|
|
33088
34243
|
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})}
|
|
33089
|
-
function renderCtxLive(snap){const box=E('ctxLive');const textEl=E('ctxLiveText');const fill=E('ctxLiveFill');if(!box||!textEl||!fill)return;const left=Number(snap?.context_left_tokens);const pct=Number(snap?.context_left_percent);if(!Number.isFinite(left)||!Number.isFinite(pct)){textEl.textContent
|
|
34244
|
+
function renderCtxLive(snap){const box=E('ctxLive');const textEl=E('ctxLiveText');const fill=E('ctxLiveFill');if(!box||!textEl||!fill)return;const left=Number(snap?.context_left_tokens);const pct=Number(snap?.context_left_percent);if(!Number.isFinite(left)||!Number.isFinite(pct)){textEl.textContent=`${t('rt_ctx_left')}=-`;fill.style.width='0%';box.classList.remove('warn','danger');return}const safePct=Math.max(0,Math.min(100,pct));textEl.textContent=`${t('rt_ctx_left')}=${left} (${safePct.toFixed(1)}%)`;fill.style.width=`${safePct}%`;box.classList.toggle('warn',safePct<=35&&safePct>15);box.classList.toggle('danger',safePct<=15)}
|
|
33090
34245
|
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)}
|
|
33091
34246
|
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():''}
|
|
33092
34247
|
function isRenderRuntimeEventType(evtType){return RENDER_EVT_TYPES.has(String(evtType||''))}
|
|
@@ -33639,11 +34794,11 @@ function renderInlineMarkdown(raw){
|
|
|
33639
34794
|
}
|
|
33640
34795
|
function _mdCalloutLabel(tag){
|
|
33641
34796
|
const low=String(tag||'').toLowerCase();
|
|
33642
|
-
if(low==='warning')return '
|
|
33643
|
-
if(low==='notice')return '
|
|
33644
|
-
if(low==='instruction')return '
|
|
33645
|
-
if(low==='tip')return '
|
|
33646
|
-
return '
|
|
34797
|
+
if(low==='warning')return t('callout_warning');
|
|
34798
|
+
if(low==='notice')return t('callout_notice');
|
|
34799
|
+
if(low==='instruction')return t('callout_instruction');
|
|
34800
|
+
if(low==='tip')return t('callout_tip');
|
|
34801
|
+
return t('callout_reminder');
|
|
33647
34802
|
}
|
|
33648
34803
|
function _mdExtractCallouts(src,inlineRenderer){
|
|
33649
34804
|
const blocks=[];
|
|
@@ -33758,6 +34913,13 @@ function _mdWorkerEnsure(){
|
|
|
33758
34913
|
if(S.mdWorker)return S.mdWorker;
|
|
33759
34914
|
if(typeof Worker!=='function'||typeof Blob!=='function'||typeof URL==='undefined'||typeof URL.createObjectURL!=='function')return null;
|
|
33760
34915
|
const workerSrc=String.raw`
|
|
34916
|
+
const CALLOUT_LABELS=${JSON.stringify({
|
|
34917
|
+
warning:t('callout_warning'),
|
|
34918
|
+
notice:t('callout_notice'),
|
|
34919
|
+
instruction:t('callout_instruction'),
|
|
34920
|
+
tip:t('callout_tip'),
|
|
34921
|
+
reminder:t('callout_reminder'),
|
|
34922
|
+
})};
|
|
33761
34923
|
const esc=s=>String(s??'').replace(/[&<>"]/g,c=>(c==='&'?'&':(c==='<'?'<':(c==='>'?'>':'"'))));
|
|
33762
34924
|
function inline(raw){
|
|
33763
34925
|
let s=esc(String(raw||''));
|
|
@@ -33783,11 +34945,8 @@ function isTableSeparator(line){
|
|
|
33783
34945
|
}
|
|
33784
34946
|
function calloutLabel(tag){
|
|
33785
34947
|
const low=String(tag||'').toLowerCase();
|
|
33786
|
-
if(low
|
|
33787
|
-
|
|
33788
|
-
if(low==='instruction')return 'Instruction';
|
|
33789
|
-
if(low==='tip')return 'Tip';
|
|
33790
|
-
return 'Reminder';
|
|
34948
|
+
if(Object.prototype.hasOwnProperty.call(CALLOUT_LABELS,low))return CALLOUT_LABELS[low];
|
|
34949
|
+
return CALLOUT_LABELS.reminder||'Reminder';
|
|
33791
34950
|
}
|
|
33792
34951
|
function extractCallouts(src){
|
|
33793
34952
|
const blocks=[];
|
|
@@ -34665,7 +35824,7 @@ function _chatVirtReleaseNode(node){
|
|
|
34665
35824
|
}
|
|
34666
35825
|
function _chatVirtReleaseRendered(root){if(!root)return;for(const node of root.querySelectorAll('.msg[data-vk]')){_chatVirtReleaseNode(node)}}
|
|
34667
35826
|
function _chatVirtAgentRoleKey(raw){const role=String(raw||'').trim().toLowerCase();return(role==='explorer'||role==='developer'||role==='reviewer'||role==='manager'||role==='planner')?role:''}
|
|
34668
|
-
function _chatVirtAgentRoleLabel(role){if(role==='explorer')return'
|
|
35827
|
+
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')}
|
|
34669
35828
|
function _stripLeadingAgentTitle(raw,agentRole){
|
|
34670
35829
|
let txt=String(raw||'').replace(/^\\uFEFF/,'').trimStart();
|
|
34671
35830
|
const role=_chatVirtAgentRoleKey(agentRole);
|
|
@@ -34706,21 +35865,21 @@ function _stripObjectiveInstructionForWorker(raw){
|
|
|
34706
35865
|
return txt;
|
|
34707
35866
|
}
|
|
34708
35867
|
const RUNTIME_HINT_RENDER_META={
|
|
34709
|
-
'auto-continue':{
|
|
34710
|
-
'arbiter-continue':{
|
|
34711
|
-
'continuation-briefing':{
|
|
34712
|
-
'reminder':{
|
|
34713
|
-
'todo-rescue':{
|
|
34714
|
-
'tool-retry':{
|
|
34715
|
-
'segmented-retry':{
|
|
34716
|
-
'forced-converge':{
|
|
34717
|
-
'no-tool-recovery':{
|
|
34718
|
-
'auto-context-recall':{
|
|
34719
|
-
'failure-recovery':{
|
|
34720
|
-
'truncate-rescue':{
|
|
34721
|
-
'thinking-empty-recovery':{
|
|
34722
|
-
'fault-prefill':{
|
|
34723
|
-
'edit-recovery':{
|
|
35868
|
+
'auto-continue':{labelKey:'event_auto_continue',tone:'instruction'},
|
|
35869
|
+
'arbiter-continue':{labelKey:'event_arbiter_continue',tone:'instruction'},
|
|
35870
|
+
'continuation-briefing':{labelKey:'event_continuation_briefing',tone:'instruction'},
|
|
35871
|
+
'reminder':{labelKey:'event_reminder',tone:'reminder'},
|
|
35872
|
+
'todo-rescue':{labelKey:'event_todo_rescue',tone:'warning'},
|
|
35873
|
+
'tool-retry':{labelKey:'event_tool_retry',tone:'warning'},
|
|
35874
|
+
'segmented-retry':{labelKey:'event_segmented_retry',tone:'warning'},
|
|
35875
|
+
'forced-converge':{labelKey:'event_forced_converge',tone:'warning'},
|
|
35876
|
+
'no-tool-recovery':{labelKey:'event_no_tool_recovery',tone:'warning'},
|
|
35877
|
+
'auto-context-recall':{labelKey:'event_context_recall',tone:'notice'},
|
|
35878
|
+
'failure-recovery':{labelKey:'event_failure_recovery',tone:'warning'},
|
|
35879
|
+
'truncate-rescue':{labelKey:'event_truncate_rescue',tone:'warning'},
|
|
35880
|
+
'thinking-empty-recovery':{labelKey:'event_thinking_recovery',tone:'warning'},
|
|
35881
|
+
'fault-prefill':{labelKey:'event_fault_prefill',tone:'warning'},
|
|
35882
|
+
'edit-recovery':{labelKey:'event_edit_recovery',tone:'warning'},
|
|
34724
35883
|
};
|
|
34725
35884
|
function _chatVirtParseRuntimeHint(raw){
|
|
34726
35885
|
const txt=String(raw||'').trim();
|
|
@@ -34729,7 +35888,8 @@ function _chatVirtParseRuntimeHint(raw){
|
|
|
34729
35888
|
if(!m)return null;
|
|
34730
35889
|
const name=String(m[1]||'').trim().toLowerCase();
|
|
34731
35890
|
if(!Object.prototype.hasOwnProperty.call(RUNTIME_HINT_RENDER_META,name))return null;
|
|
34732
|
-
|
|
35891
|
+
const meta=RUNTIME_HINT_RENDER_META[name]||{labelKey:'event_reminder',tone:'notice'};
|
|
35892
|
+
return {name,body:String(m[2]||'').trim(),meta:{label:t(String(meta.labelKey||'event_reminder')),tone:String(meta.tone||'notice')}};
|
|
34733
35893
|
}
|
|
34734
35894
|
function _chatVirtBuildMessageNode(m){
|
|
34735
35895
|
let kind='assistant_text';
|
|
@@ -34763,7 +35923,7 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34763
35923
|
if(m.type==='manager_delegate'){
|
|
34764
35924
|
const info=(m&&typeof m.data==='object')?m.data:{};
|
|
34765
35925
|
const targetRole=_chatVirtAgentRoleKey(info.target);
|
|
34766
|
-
const targetLabel=String(info.target_label||_chatVirtAgentRoleLabel(targetRole)||info.target||'
|
|
35926
|
+
const targetLabel=String(info.target_label||_chatVirtAgentRoleLabel(targetRole)||info.target||t('role_agent'));
|
|
34767
35927
|
const level=Math.floor(Number(info.task_level||0));
|
|
34768
35928
|
const mode=String(info.execution_mode||'').trim();
|
|
34769
35929
|
const taskType=String(info.task_type||'').trim();
|
|
@@ -34784,24 +35944,24 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34784
35944
|
pills.push(`budget=${budgetNum<=0?'unlimited':budgetNum}`);
|
|
34785
35945
|
if(Number.isFinite(remainNum))pills.push(`remaining=${remainNum<0?'unlimited':remainNum}`);
|
|
34786
35946
|
const pillsHtml=pills.map(x=>`<span class=\"manager-delegate-pill\">${esc(String(x))}</span>`).join('');
|
|
34787
|
-
const routeHtml=`<div class=\"manager-delegate-route\"><span class=\"agent-bus-pill manager\"
|
|
34788
|
-
const objectiveHtml=(objective&&instruction&&objective.toLowerCase()===instruction.toLowerCase())?'':(objective?`<div class=\"manager-delegate-line\"><span
|
|
34789
|
-
const instructionHtml=instruction?`<div class=\"manager-delegate-line\"><span
|
|
34790
|
-
d.innerHTML=`${roleBadge}<div class=\"manager-delegate-card\"><div class=\"manager-delegate-head\"
|
|
35947
|
+
const routeHtml=`<div class=\"manager-delegate-route\"><span class=\"agent-bus-pill manager\">${esc(t('role_manager'))}</span><span class=\"agent-bus-arrow\">→</span><span class=\"agent-bus-pill${targetRole?(' '+targetRole):''}\">${esc(targetLabel)}</span></div>`;
|
|
35948
|
+
const objectiveHtml=(objective&&instruction&&objective.toLowerCase()===instruction.toLowerCase())?'':(objective?`<div class=\"manager-delegate-line\"><span>${esc(t('event_objective'))}</span><div>${esc(objective)}</div></div>`:'');
|
|
35949
|
+
const instructionHtml=instruction?`<div class=\"manager-delegate-line\"><span>${esc(t('event_instruction'))}</span><div>${esc(instruction)}</div></div>`:'';
|
|
35950
|
+
d.innerHTML=`${roleBadge}<div class=\"manager-delegate-card\"><div class=\"manager-delegate-head\">${esc(t('event_manager_delegate_title'))}</div>${routeHtml}<div class=\"manager-delegate-pills\">${pillsHtml}</div>${objectiveHtml}${instructionHtml}</div>`;
|
|
34791
35951
|
return d;
|
|
34792
35952
|
}
|
|
34793
35953
|
if(m.type==='agent_bus'){
|
|
34794
35954
|
const info=(m&&typeof m.data==='object')?m.data:{};
|
|
34795
35955
|
const fromRole=_chatVirtAgentRoleKey(info.from)||agentRole;
|
|
34796
35956
|
const toRole=_chatVirtAgentRoleKey(info.to);
|
|
34797
|
-
const fromLabel=fromRole?_chatVirtAgentRoleLabel(fromRole):String(info.from||'
|
|
34798
|
-
const toLabel=toRole?_chatVirtAgentRoleLabel(toRole):String(info.to||'
|
|
35957
|
+
const fromLabel=fromRole?_chatVirtAgentRoleLabel(fromRole):String(info.from||t('role_agent'));
|
|
35958
|
+
const toLabel=toRole?_chatVirtAgentRoleLabel(toRole):String(info.to||t('role_agent'));
|
|
34799
35959
|
const intent=String(info.intent||'message').trim()||'message';
|
|
34800
35960
|
const payloadRaw=String(info.payload||'').trim()||String(m.text||'').trim();
|
|
34801
35961
|
const payload=_stripObjectiveInstructionForWorker(payloadRaw)||payloadRaw;
|
|
34802
35962
|
const fromCls=fromRole?` ${fromRole}`:'';
|
|
34803
35963
|
const toCls=toRole?` ${toRole}`:'';
|
|
34804
|
-
d.innerHTML=`${roleBadge}<div class=\"agent-bus-card\"><div class=\"agent-bus-route\"><span class=\"agent-bus-pill${fromCls}\">${esc(fromLabel)}</span><span class=\"agent-bus-arrow\">→</span><span class=\"agent-bus-pill${toCls}\">${esc(toLabel)}</span></div><div class=\"agent-bus-intent\"
|
|
35964
|
+
d.innerHTML=`${roleBadge}<div class=\"agent-bus-card\"><div class=\"agent-bus-route\"><span class=\"agent-bus-pill${fromCls}\">${esc(fromLabel)}</span><span class=\"agent-bus-arrow\">→</span><span class=\"agent-bus-pill${toCls}\">${esc(toLabel)}</span></div><div class=\"agent-bus-intent\">${esc(t('event_intent'))}: ${esc(intent)}</div><div class=\"agent-bus-payload\">${esc(payload)}</div></div>`;
|
|
34805
35965
|
return d;
|
|
34806
35966
|
}
|
|
34807
35967
|
if(m.type==='tool_calls'){
|
|
@@ -34811,27 +35971,27 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34811
35971
|
const txt=String(m.text||'').trim().replace(/^\\[tool calls\\]\\s*/i,'');
|
|
34812
35972
|
tools=txt?txt.split(',').map(x=>String(x||'').trim()).filter(Boolean):[];
|
|
34813
35973
|
}
|
|
34814
|
-
const pills=[_chatVirtEventPillHtml(
|
|
35974
|
+
const pills=[_chatVirtEventPillHtml(String(tools.length||0),'neutral')];
|
|
34815
35975
|
const bodyHtml=tools.length
|
|
34816
|
-
? `<div class=\"msg-event-body\"><div class=\"msg-event-note\"
|
|
34817
|
-
: `<div class=\"msg-event-body\"><div class=\"msg-event-note\"
|
|
34818
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml('
|
|
35976
|
+
? `<div class=\"msg-event-body\"><div class=\"msg-event-note\">${esc(t('event_tool_calls_note'))}</div><div class=\"msg-event-tool-grid\">${tools.slice(0,24).map(name=>_chatVirtEventPillHtml(String(name||'?'),'info')).join('')}</div></div>`
|
|
35977
|
+
: `<div class=\"msg-event-body\"><div class=\"msg-event-note\">${esc(t('event_tool_calls_empty'))}</div></div>`;
|
|
35978
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(t('event_tool_calls_title'),tools.length?`${tools.length}`:'',pills,[],bodyHtml,'msg-event-card-tools')}`;
|
|
34819
35979
|
return d;
|
|
34820
35980
|
}
|
|
34821
35981
|
if(kind==='skill_loaded'){
|
|
34822
35982
|
const parsed=_chatVirtParseSkillLoaded(String(m.text||''))||{name:'skill',desc:String(m.text||''),truncated:false};
|
|
34823
35983
|
const pills=[
|
|
34824
|
-
_chatVirtEventPillHtml('
|
|
34825
|
-
parsed.truncated?_chatVirtEventPillHtml('
|
|
35984
|
+
_chatVirtEventPillHtml(t('event_loaded'),'ok'),
|
|
35985
|
+
parsed.truncated?_chatVirtEventPillHtml(t('event_preview_truncated'),'warn'):'',
|
|
34826
35986
|
];
|
|
34827
35987
|
const grid=[
|
|
34828
|
-
_chatVirtEventCellHtml('
|
|
35988
|
+
_chatVirtEventCellHtml(t('event_skill_label'),String(parsed.name||''),{mono:true}),
|
|
34829
35989
|
];
|
|
34830
35990
|
const descHtml=String(parsed.desc||'').trim()
|
|
34831
35991
|
? `<div class="msg-md">${renderMarkdownCached(String(parsed.desc||''),`${String(m._vk||'')}:skill`)}</div>`
|
|
34832
|
-
: `<div class="msg-event-note"
|
|
34833
|
-
const bodyHtml=`<div class="msg-event-body"><div class="msg-event-note"
|
|
34834
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml('
|
|
35992
|
+
: `<div class="msg-event-note">${esc(t('event_skill_loaded_empty'))}</div>`;
|
|
35993
|
+
const bodyHtml=`<div class="msg-event-body"><div class="msg-event-note">${esc(t('event_skill_loaded_note'))}</div>${descHtml}</div>`;
|
|
35994
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(t('event_skill_loaded_title'),String(parsed.name||'').trim()||'skill context',pills,grid,bodyHtml,'msg-event-card-skill')}`;
|
|
34835
35995
|
d.setAttribute('data-math-request',`${String(m._vk||'')}:skill`);
|
|
34836
35996
|
return d;
|
|
34837
35997
|
}
|
|
@@ -34843,10 +36003,10 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34843
36003
|
const pills=[_chatVirtEventPillHtml(`+${p.added??0}`,'ok'),_chatVirtEventPillHtml(`-${p.deleted??0}`,'warn')];
|
|
34844
36004
|
const grid=[
|
|
34845
36005
|
_chatVirtEventCellHtml(t('rel_path'),String(loc||''),{mono:true}),
|
|
34846
|
-
_chatVirtEventCellHtml('
|
|
36006
|
+
_chatVirtEventCellHtml(t('event_session'),String(root||''),{mono:true}),
|
|
34847
36007
|
];
|
|
34848
36008
|
const bodyHtml=`<div class=\"msg-event-body\">${preview}<div class=\"msg-diff-shell\">${diffHtml(p.diff_numbered||p.diff||'')}</div></div>`;
|
|
34849
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml('
|
|
36009
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(t('event_file_patch_title'),String(loc||'').trim()||'workspace update',pills,grid,bodyHtml,'msg-event-card-diff')}`;
|
|
34850
36010
|
return d;
|
|
34851
36011
|
}
|
|
34852
36012
|
if(m.type==='upload'&&m.data){
|
|
@@ -34861,19 +36021,19 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34861
36021
|
parseStatus?_chatVirtEventPillHtml(`parse ${parseStatus}`,parseStatus==='done'?'ok':(parseStatus==='failed'?'error':'warn')):'',
|
|
34862
36022
|
];
|
|
34863
36023
|
const grid=[
|
|
34864
|
-
_chatVirtEventCellHtml('
|
|
34865
|
-
_chatVirtEventCellHtml('
|
|
36024
|
+
_chatVirtEventCellHtml(t('event_upload_path'),String(upath||''),{mono:true}),
|
|
36025
|
+
_chatVirtEventCellHtml(t('event_upload_filename'),String(u.filename||''),{mono:true}),
|
|
34866
36026
|
];
|
|
34867
|
-
let previewHtml=`<div class=\"msg-event-note\"
|
|
36027
|
+
let previewHtml=`<div class=\"msg-event-note\">${esc(t('event_preview_unavailable'))}</div>`;
|
|
34868
36028
|
if(parseStatus==='pending'){
|
|
34869
|
-
previewHtml=`<div class=\"msg-event-note\"
|
|
36029
|
+
previewHtml=`<div class=\"msg-event-note\">${esc(t('event_upload_parsing'))}</div>`;
|
|
34870
36030
|
}else if(parseStatus==='failed'){
|
|
34871
|
-
previewHtml=`<div class=\"msg-event-note\"
|
|
36031
|
+
previewHtml=`<div class=\"msg-event-note\">${esc(t('event_upload_failed'))}${parseError?`: ${esc(parseError)}`:''}</div>`;
|
|
34872
36032
|
}else if(String(u.preview||'').trim()){
|
|
34873
36033
|
previewHtml=`<pre class=\"msg-code-shell\">${esc(u.preview||'')}</pre>`;
|
|
34874
36034
|
}
|
|
34875
36035
|
const bodyHtml=`<div class=\"msg-event-body\">${preview}${previewHtml}</div>`;
|
|
34876
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml('
|
|
36036
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(t('event_upload_title'),String(u.filename||'').trim()||'session upload',pills,grid,bodyHtml,'msg-event-card-upload')}`;
|
|
34877
36037
|
return d;
|
|
34878
36038
|
}
|
|
34879
36039
|
if(m.type==='command'&&m.data){
|
|
@@ -34888,19 +36048,19 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34888
36048
|
_chatVirtEventPillHtml(`exit ${exitTxt}`,exitTone),
|
|
34889
36049
|
durationTxt?_chatVirtEventPillHtml(durationTxt,'neutral','mono'):'',
|
|
34890
36050
|
pageCount>1?_chatVirtEventPillHtml(`page ${pageIndex||1}/${pageCount}`,'info','mono'):'',
|
|
34891
|
-
x.ui_truncated?_chatVirtEventPillHtml('
|
|
34892
|
-
x.model_truncated?_chatVirtEventPillHtml('
|
|
34893
|
-
x.temp_output_path?_chatVirtEventPillHtml('
|
|
34894
|
-
x.buffer_ref?_chatVirtEventPillHtml('
|
|
36051
|
+
x.ui_truncated?_chatVirtEventPillHtml(t('event_ui_truncated'),'warn'):'',
|
|
36052
|
+
x.model_truncated?_chatVirtEventPillHtml(t('event_model_truncated'),'warn'):'',
|
|
36053
|
+
x.temp_output_path?_chatVirtEventPillHtml(t('event_temp_read_file'),'info'):'',
|
|
36054
|
+
x.buffer_ref?_chatVirtEventPillHtml(t('event_buffered'),'neutral'):'',
|
|
34895
36055
|
];
|
|
34896
36056
|
const grid=[
|
|
34897
|
-
_chatVirtEventCellHtml('
|
|
34898
|
-
_chatVirtEventCellHtml('
|
|
34899
|
-
changedFiles?_chatVirtEventCellHtml('
|
|
36057
|
+
_chatVirtEventCellHtml(t('event_command_label'),`$ ${String(x.command||'')}`,{mono:true}),
|
|
36058
|
+
_chatVirtEventCellHtml(t('event_cwd'),String(x.cwd||''),{mono:true}),
|
|
36059
|
+
changedFiles?_chatVirtEventCellHtml(t('event_changed'),changedFiles,{mono:true}):'',
|
|
34900
36060
|
];
|
|
34901
36061
|
const outputTxt=String(x.output||'');
|
|
34902
|
-
const bodyHtml=`<div class=\"msg-event-body\">${outputTxt?`<pre class=\"msg-code-shell\">${esc(outputTxt)}</pre
|
|
34903
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml('
|
|
36062
|
+
const bodyHtml=`<div class=\"msg-event-body\">${outputTxt?`<pre class=\"msg-code-shell\">${esc(outputTxt)}</pre>`:`<div class=\"msg-event-note\">${esc(t('event_command_empty'))}</div>`}</div>`;
|
|
36063
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(t('event_command_title'),String(x.name||'command'),pills,grid,bodyHtml,'msg-event-card-command')}`;
|
|
34904
36064
|
return d;
|
|
34905
36065
|
}
|
|
34906
36066
|
if(m.type==='live_thinking'){
|
|
@@ -34918,7 +36078,7 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34918
36078
|
const toolTxt=String(m.tool||'').trim();
|
|
34919
36079
|
const extra=[];if(kindTxt)extra.push('kind='+kindTxt);if(toolTxt)extra.push('tool='+toolTxt);
|
|
34920
36080
|
const extraTxt=extra.length?(' · '+extra.map(x=>esc(x)).join(' · ')):'';
|
|
34921
|
-
const label=
|
|
36081
|
+
const label=t('event_truncation_recovery');
|
|
34922
36082
|
const stateTxt=lang.startsWith('zh')?(active?'进行中':'已完成'):(lang.startsWith('ja')?(active?'進行中':'完了'):(active?'active':'done'));
|
|
34923
36083
|
const key=`${m._vk}:live-trunc`;
|
|
34924
36084
|
const pills=[
|
|
@@ -34928,8 +36088,8 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34928
36088
|
kindTxt?_chatVirtEventPillHtml(`kind ${kindTxt}`,'neutral'):'',
|
|
34929
36089
|
toolTxt?_chatVirtEventPillHtml(`tool ${toolTxt}`,'info'):'',
|
|
34930
36090
|
];
|
|
34931
|
-
const noteHtml=`<div class=\"msg-event-body\"><div class=\"msg-event-note\"
|
|
34932
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(label,'
|
|
36091
|
+
const noteHtml=`<div class=\"msg-event-body\"><div class=\"msg-event-note\">${esc(t('event_truncation_note'))}${extraTxt?` ${extraTxt}`:''}</div><div class=\"msg-md\">${renderMarkdownCached(String(m.text||''),key)}</div></div>`;
|
|
36092
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(label,t('event_truncation_state'),pills,[],noteHtml,'msg-event-card-truncation')}`;
|
|
34933
36093
|
d.setAttribute('data-math-request',key);
|
|
34934
36094
|
return d;
|
|
34935
36095
|
}
|
|
@@ -34947,8 +36107,8 @@ function _chatVirtBuildMessageNode(m){
|
|
|
34947
36107
|
_chatVirtEventPillHtml(t('running'),'live'),
|
|
34948
36108
|
_chatVirtEventPillHtml(_chatVirtLiveRunText(label,elapsedNow),'neutral','mono'),
|
|
34949
36109
|
];
|
|
34950
|
-
const bodyHtml=`<div class=\"msg-event-body\"><div class=\"msg-event-note\"
|
|
34951
|
-
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml('
|
|
36110
|
+
const bodyHtml=`<div class=\"msg-event-body\"><div class=\"msg-event-note\">${esc(t('event_live_model_call_note'))}</div></div>`;
|
|
36111
|
+
d.innerHTML=`${roleBadge}${_chatVirtEventCardHtml(t('event_live_model_call_title'),label,pills,[],bodyHtml,'msg-event-card-live')}`;
|
|
34952
36112
|
const elapsedEl=d.querySelector('.msg-event-pill.mono');
|
|
34953
36113
|
if(elapsedEl)elapsedEl.setAttribute('data-run-elapsed-text','1');
|
|
34954
36114
|
return d;
|
|
@@ -35386,15 +36546,47 @@ function _feSize(bytes){const n=Number(bytes||0);if(!Number.isFinite(n)||n<0)ret
|
|
|
35386
36546
|
function _feTs(ts){const n=Number(ts||0);if(!Number.isFinite(n)||n<=0)return'';try{return new Date(n*1000).toLocaleString()}catch(_){return''}}
|
|
35387
36547
|
function _feKindLabel(kind){const k=String(kind||'').trim().toLowerCase();if(k==='html')return'HTML';if(k==='markdown')return'MD';if(k==='code')return'CODE';return''}
|
|
35388
36548
|
function _feIcon(kind,type='file'){if(type==='dir')return'📁';const k=String(kind||'').trim().toLowerCase();if(k==='html')return'🌐';if(k==='markdown')return'📝';if(k==='code')return'⌘';return'📄'}
|
|
35389
|
-
function _feRenderNodes(nodes,depth,st){const rows=Array.isArray(nodes)?nodes:[];if(!rows.length)return'';let out='';for(const node of rows){const type=String(node?.type||'');const name=String(node?.name||'').trim();const path=String(node?.path||'').trim();if(!name)continue;if(type==='dir'){const hasOwn=Object.prototype.hasOwnProperty.call(st.expanded,path);const open=hasOwn?!!st.expanded[path]:(depth<1);const kids=Array.isArray(node?.children)?node.children:[];out+=`<div class=\"fe-row dir\" style=\"--depth:${depth}\"><button class=\"fe-toggle\" data-fe-toggle=\"${esc(path)}\" data-fe-open=\"${open?'1':'0'}\">${open?'▾':'▸'}</button><span class=\"fe-icon\">${_feIcon('', 'dir')}</span><span class=\"fe-name\">${esc(name)}</span><span class=\"fe-meta\">${esc(kids.length)}
|
|
35390
|
-
function renderFileExplorer(){const host=E('fileExplorer');if(!host)return;const sid=String(S.activeId||'').trim();if(!sid){host.innerHTML=`<div class=\"fe-empty mono\">${esc(t('no_files'))}</div>`;return}const st=ensureFileExplorerState(sid);if(!st){host.innerHTML=`<div class=\"fe-empty mono\">${esc(t('no_files'))}</div>`;return}const tree=(st&&typeof st.tree==='object')?st.tree:null;const children=Array.isArray(tree?.children)?tree.children:[];const rootText=String(st.root||S.snap?.session_files_root||'').trim();const summary
|
|
36549
|
+
function _feRenderNodes(nodes,depth,st){const rows=Array.isArray(nodes)?nodes:[];if(!rows.length)return'';let out='';for(const node of rows){const type=String(node?.type||'');const name=String(node?.name||'').trim();const path=String(node?.path||'').trim();if(!name)continue;if(type==='dir'){const hasOwn=Object.prototype.hasOwnProperty.call(st.expanded,path);const open=hasOwn?!!st.expanded[path]:(depth<1);const kids=Array.isArray(node?.children)?node.children:[];out+=`<div class=\"fe-row dir\" style=\"--depth:${depth}\"><button class=\"fe-toggle\" data-fe-toggle=\"${esc(path)}\" data-fe-open=\"${open?'1':'0'}\">${open?'▾':'▸'}</button><span class=\"fe-icon\">${_feIcon('', 'dir')}</span><span class=\"fe-name\">${esc(name)}</span><span class=\"fe-meta\">${esc(t('fe_items',{n:kids.length}))}</span></div>`;if(open&&kids.length){out+=_feRenderNodes(kids,depth+1,st)}continue}const kind=String(node?.preview_kind||'').trim();const canPreview=kind==='html'||kind==='markdown'||kind==='code';const active=(String(st.selected||'')===path);const sizeText=_feSize(node?.size);const timeText=_feTs(node?.mtime);const kindLabel=_feKindLabel(kind);const kindHtml=kindLabel?`<span class=\"fe-kind\">${esc(kindLabel)}</span>`:'';const clickAttr=canPreview?` data-fe-open-path=\"${esc(path)}\"`:'';out+=`<div class=\"fe-row file${active?' active':''}\" style=\"--depth:${depth}\"${clickAttr}><span class=\"fe-icon\">${_feIcon(kind,'file')}</span><span class=\"fe-name\">${esc(name)}</span>${kindHtml}<span class=\"fe-meta\">${esc(sizeText)}${timeText?` · ${esc(timeText)}`:''}</span></div>`}return out}
|
|
36550
|
+
function renderFileExplorer(){const host=E('fileExplorer');if(!host)return;const sid=String(S.activeId||'').trim();if(!sid){host.innerHTML=`<div class=\"fe-empty mono\">${esc(t('no_files'))}</div>`;return}const st=ensureFileExplorerState(sid);if(!st){host.innerHTML=`<div class=\"fe-empty mono\">${esc(t('no_files'))}</div>`;return}const tree=(st&&typeof st.tree==='object')?st.tree:null;const children=Array.isArray(tree?.children)?tree.children:[];const rootText=String(st.root||S.snap?.session_files_root||'').trim();const summary=[t('fe_nodes',{n:Number(st.nodeCount||0)}),st.inflight?t('fe_loading'):''].filter(Boolean).join(' · ');const treeHtml=children.length?`<div class=\"file-explorer-tree\">${_feRenderNodes(children,0,st)}</div>`:`<div class=\"fe-empty mono\">${esc(t('no_files'))}</div>`;const truncHtml=st.truncated?`<div class=\"fe-trunc mono\">${esc(t('fe_tree_truncated',{n:Number(st.maxNodes||0)}))}</div>`:'';host.innerHTML=`<div class=\"file-explorer-wrap\"><div class=\"file-explorer-head\"><span class=\"mono\">${esc(rootText||'/workspace')}</span><span>${esc(summary)}</span></div>${treeHtml}${truncHtml}</div>`;for(const btn of host.querySelectorAll('[data-fe-toggle]')){btn.onclick=(ev)=>{ev.preventDefault();ev.stopPropagation();const p=String(btn.getAttribute('data-fe-toggle')||'');const open=String(btn.getAttribute('data-fe-open')||'')==='1';st.expanded[p]=!open;renderFileExplorer()}}for(const row of host.querySelectorAll('[data-fe-open-path]')){row.onclick=(ev)=>{if(ev.target&&ev.target.closest&&ev.target.closest('[data-fe-toggle]'))return;const rel=String(row.getAttribute('data-fe-open-path')||'').trim();if(!rel)return;st.selected=rel;renderFileExplorer();openPreviewTab(rel)}}}
|
|
35391
36551
|
function renderUploadList(){const host=E('uploadList');if(!host)return;const enabled=!!S.config?.show_upload_list;host.classList.toggle('hidden',!enabled);if(!enabled){host.innerHTML='';return}const uploads=(S.snap?.uploads||[]).slice(-8).reverse();host.innerHTML=uploads.map(u=>{const status=String(u.parse_status||'').trim();const statusTxt=status?` · parse=${status}`:'';const err=String(u.parse_error||'').trim();return `<div class="upload-entry"><div class="upload-entry-top"><span class="upload-entry-name">${esc(u.filename||'')}</span><span class="upload-entry-meta">${esc(u.kind||'file')} · ${esc(_feSize(u.size||0))}${esc(statusTxt)}</span></div><div class="upload-entry-path">${esc(u.workspace_path||'')}</div>${err?`<div class="upload-entry-path">${esc(err)}</div>`:''}</div>`}).join('')||`<div class="upload-empty">${esc(t('no_uploads'))}</div>`}
|
|
35392
36552
|
async function refreshFileExplorer(force=false){const sid=String(S.activeId||'').trim();if(!sid)return;const st=ensureFileExplorerState(sid);if(!st)return;const now=Date.now();if(st.inflight)return;if(!force&&st.tree&&(now-Number(st.fetchedAt||0)<1400))return;st.inflight=true;const btn=E('refreshFilesBtn');if(btn)btn.disabled=true;renderFileExplorer();try{const payload=await api(_fePath(sid));if(String(S.activeId||'')!==sid)return;st.tree=(payload&&typeof payload==='object'&&payload.tree&&typeof payload.tree==='object')?payload.tree:null;st.root=String(payload?.root||S.snap?.session_files_root||'');st.nodeCount=Number(payload?.node_count||0);st.truncated=!!payload?.truncated;st.maxNodes=Number(payload?.max_nodes||0);st.fetchedAt=Date.now();renderFileExplorer()}catch(err){if(String(S.activeId||'')===sid){const host=E('fileExplorer');if(host)host.innerHTML=`<div class=\"fe-empty mono\">${esc(err?.message||String(err))}</div>`}}finally{st.inflight=false;if(btn)btn.disabled=false}}
|
|
35393
36553
|
function _cmdStateKey(op){const d=(op&&typeof op==='object'&&op.data&&typeof op.data==='object')?op.data:{};return String(op?.id||op?.seq||`${String(d.name||'cmd')}:${String(d.command||'')}:${Number(op?.ts||0)}`)}
|
|
35394
36554
|
function _cmdPageCount(op){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:[];return Math.max(1,pages.length||Number(d.ui_output_page_count||0)||1)}
|
|
35395
36555
|
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}
|
|
35396
36556
|
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]||'')}
|
|
35397
|
-
function renderBoards(){const uiState=S.staticMode?(S.frozen?'static':'live'):'live';
|
|
36557
|
+
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):'-';E('status').textContent=[
|
|
36558
|
+
`${t('rt_session')}=${S.snap?.id||'-'}`,
|
|
36559
|
+
`${t('rt_model')}=${S.snap?.model||'-'}`,
|
|
36560
|
+
`${t('rt_thinking')}=${boolWord(S.snap?.thinking)}`,
|
|
36561
|
+
`${t('rt_thinking_stream')}=${boolWord(S.snap?.thinking_stream)}`,
|
|
36562
|
+
`${t('rt_mode')}=${S.snap?.execution_mode||S.config?.execution_mode||'sync'}`,
|
|
36563
|
+
`${t('rt_active_agent')}=${activeRoleLabel}`,
|
|
36564
|
+
`${t('rt_blackboard')}=${S.snap?.blackboard?.status||'-'}`,
|
|
36565
|
+
`${t('rt_task')}=${S.snap?.blackboard?.task_profile?.task_type||'-'}`,
|
|
36566
|
+
`${t('rt_complexity')}=${S.snap?.blackboard?.task_profile?.complexity||'-'}`,
|
|
36567
|
+
`${t('rt_judgement')}=${S.snap?.blackboard?.manager_judgement?.progress||'-'}`,
|
|
36568
|
+
`${t('rt_budget')}=${S.snap?.blackboard?.task_profile?.round_budget??'-'}`,
|
|
36569
|
+
`${t('rt_remaining')}=${S.snap?.blackboard?.manager_judgement?.remaining_rounds??'-'}`,
|
|
36570
|
+
`${t('rt_blackboard_cycles')}=${S.snap?.blackboard?.manager_cycles??'-'}`,
|
|
36571
|
+
`${t('rt_round_limit')}=${S.snap?.max_agent_rounds||'-'}`,
|
|
36572
|
+
`${t('rt_round')}=${S.snap?.agent_round_index??'-'}`,
|
|
36573
|
+
`${t('rt_phase')}=${S.snap?.agent_phase||t('idle')}`,
|
|
36574
|
+
`${t('rt_queued_inputs')}=${S.snap?.queued_user_inputs_count??0}`,
|
|
36575
|
+
`${t('rt_run_timeout')}=${S.snap?.max_run_seconds??'-'}s`,
|
|
36576
|
+
`${t('rt_ctx_used')}=${S.snap?.context_tokens_estimate??'-'}`,
|
|
36577
|
+
`${t('rt_ctx_limit')}=${S.snap?.context_token_upper_bound||'-'}`,
|
|
36578
|
+
`${t('rt_ctx_mode')}=${t(S.snap?.context_token_limit_locked?'rt_manual_lock':'rt_adaptive')}`,
|
|
36579
|
+
`${t('rt_ctx_left')}=${formatContextLeft(S.snap)}`,
|
|
36580
|
+
`${t('rt_truncation')}=${S.snap?.truncation_count||0}`,
|
|
36581
|
+
`${t('rt_trunc_retry')}=${S.snap?.live_truncation_attempts||0}`,
|
|
36582
|
+
`${t('rt_trunc_tokens')}=${S.snap?.live_truncation_tokens||0}`,
|
|
36583
|
+
`${t('rt_archive')}=${S.snap?.compact_segments_count||0}`,
|
|
36584
|
+
`${t('rt_last_compact')}=${S.snap?.last_compact_reason||'-'}`,
|
|
36585
|
+
`${t('rt_ollama')}=${S.snap?.ollama_base_url||'-'}`,
|
|
36586
|
+
`${t('rt_files')}=${S.snap?.session_files_root||'-'}`,
|
|
36587
|
+
`${t('rt_ui_mode')}=${uiState}`,
|
|
36588
|
+
S.snap?.running?t('running'):t('idle')
|
|
36589
|
+
].join(' | ');
|
|
35398
36590
|
renderCtxLive(S.snap);
|
|
35399
36591
|
const _pmBtn=E('planModeBtn');if(_pmBtn){const _pm=S.snap?.plan_mode_preference||'auto';_pmBtn.textContent='Plan: '+_pm.charAt(0).toUpperCase()+_pm.slice(1)}
|
|
35400
36592
|
const _lvl=S.snap?.user_task_level||0;updateLevelBtn(_lvl)
|
|
@@ -35404,7 +36596,7 @@ setPanelHtml('tasks',renderTaskBoard(S.snap?.tasks||[]));
|
|
|
35404
36596
|
setPanelHtml('activity',(S.snap?.activity||[]).slice(-80).sort((a,b)=>Number(a.ts||0)-Number(b.ts||0)).map(a=>`<div class=\"mono\">${new Date(a.ts*1000).toLocaleTimeString()} · ${esc(a.summary)}</div>`).join('')||`<div class=\"mono\">${esc(t('no_activity'))}</div>`);
|
|
35405
36597
|
const ops=S.snap?.operations||[];
|
|
35406
36598
|
const cmds=ops.filter(x=>x.type==='command').slice(-30).reverse();
|
|
35407
|
-
setPanelHtml('commands',cmds.map(e=>{const d=(e&&typeof e==='object'&&e.data&&typeof e.data==='object')?e.data:{};const page=_cmdCurrentPage(e);const total=_cmdPageCount(e);const totalAll=Math.max(total,Number(d.ui_output_page_total||0)||total);const flags=[d.ui_truncated?`<span class=\"cmd-flag warn\"
|
|
36599
|
+
setPanelHtml('commands',cmds.map(e=>{const d=(e&&typeof e==='object'&&e.data&&typeof e.data==='object')?e.data:{};const page=_cmdCurrentPage(e);const total=_cmdPageCount(e);const totalAll=Math.max(total,Number(d.ui_output_page_total||0)||total);const flags=[d.ui_truncated?`<span class=\"cmd-flag warn\">${esc(t('cmd_ui_preview_truncated'))}</span>`:'',d.model_truncated?`<span class=\"cmd-flag info\">${esc(t('cmd_model_context_truncated'))}</span>`:'',d.temp_output_path?`<span class=\"cmd-flag info\">${esc(t('cmd_temp_read_file_ready'))}</span>`:'',d.buffer_ref?`<span class=\"cmd-flag\">${esc(t('cmd_buffered_copy'))}</span>`:''].filter(Boolean).join('');const pager=total>1?`<div class=\"cmd-pager\"><button data-cmd-key=\"${esc(_cmdStateKey(e))}\" data-cmd-page=\"-1\" data-cmd-total=\"${esc(total)}\" ${page<=1?'disabled':''}>${esc(t('cmd_prev'))}</button><span class=\"cmd-sub\">${esc(t('cmd_preview'))} ${esc(page)}/${esc(total)}${totalAll>total?` · ${esc(t('cmd_of'))} ${esc(totalAll)}`:''}</span><button data-cmd-key=\"${esc(_cmdStateKey(e))}\" data-cmd-page=\"1\" data-cmd-total=\"${esc(total)}\" ${page>=total?'disabled':''}>${esc(t('cmd_next'))}</button></div>`:'';const extra=[d.temp_output_path?`<div class=\"cmd-sub\">${esc(t('cmd_read_file_path'))}: ${esc(d.temp_output_path)}</div>`:'',d.buffer_ref?`<div class=\"cmd-sub\">${esc(t('cmd_buffer_ref'))}: ${esc(d.buffer_ref)} · ${esc(t('cmd_chars'))}=${esc(d.buffer_chars||0)}</div>`:'',Number(d.output_full_chars||0)>0?`<div class=\"cmd-sub\">${esc(t('cmd_full_output'))}: ${esc(d.output_full_chars)} ${esc(t('cmd_chars'))} · ${esc(d.output_full_lines||0)} ${esc(t('cmd_lines'))} · ${esc(t('cmd_strategy'))}=${esc(d.long_output_strategy||'inline')}</div>`:''].filter(Boolean).join('');const output=String(_cmdPageText(e,page)||'').trim();return `<div class=\"cmd-item\"><div class=\"cmd-main\">${esc(d.name||t('cmd_default_name'))} · ${esc(t('cmd_exit'))}=${esc(d.exit_code??'-')}</div><div class=\"cmd-sub\">${esc(d.command||'')}<br>${esc(d.cwd||'')}</div>${flags?`<div class=\"cmd-flags\">${flags}</div>`:''}${extra}${output?`<div class=\"cmd-output\">${esc(output)}</div>`:''}${pager}</div>`}).join('')||`<div class=\"mono\">${esc(t('no_commands'))}</div>`);
|
|
35408
36600
|
const cmdHost=E('commands');if(cmdHost){for(const btn of cmdHost.querySelectorAll('[data-cmd-page]')){btn.onclick=(ev)=>{ev.preventDefault();const key=String(btn.getAttribute('data-cmd-key')||'').trim();const step=Number(btn.getAttribute('data-cmd-page')||0);const total=Math.max(1,Number(btn.getAttribute('data-cmd-total')||1));if(!key||!step)return;if(!S.commandPageState||typeof S.commandPageState!=='object')S.commandPageState={};const cur=Number(S.commandPageState[key]||1);S.commandPageState[key]=Math.max(1,Math.min(total,cur+step));renderBoards()}}}
|
|
35409
36601
|
const diffs=ops.filter(x=>x.type==='file_patch').slice(-20).reverse();
|
|
35410
36602
|
setPanelHtml('diffs',diffs.map(e=>`<div class=\"diff-item\"><div class=\"diff-head\">${esc(e.data.path)} (+${esc(e.data.added)} / -${esc(e.data.deleted)})</div><div class=\"cmd-sub\">${esc(e.data.session_rel_path||e.data.path||'')}<br>${esc(e.data.session_root||'')}</div><div class=\"diff-body\">${diffHtml(e.data.diff_numbered||e.data.diff)}</div></div>`).join('')||`<div class=\"mono\">${esc(t('no_diffs'))}</div>`);
|
|
@@ -35735,88 +36927,88 @@ SKILLS_INDEX_HTML = """<!doctype html>
|
|
|
35735
36927
|
<div class="actions">
|
|
35736
36928
|
<select id="skillsLangSelect"></select>
|
|
35737
36929
|
<select id="modelSelect"></select>
|
|
35738
|
-
<button id="applyModelBtn" class="subtle"
|
|
35739
|
-
<button id="refreshBtn" class="subtle"
|
|
35740
|
-
<a id="agentLink" href="#"
|
|
36930
|
+
<button id="applyModelBtn" class="subtle">应用模型</button>
|
|
36931
|
+
<button id="refreshBtn" class="subtle">刷新</button>
|
|
36932
|
+
<a id="agentLink" href="#">打开 Agent UI</a>
|
|
35741
36933
|
</div>
|
|
35742
36934
|
</header>
|
|
35743
36935
|
<div class="status-cards" id="topStats"></div>
|
|
35744
36936
|
<main class="skills-main">
|
|
35745
36937
|
<aside class="panel skills-panel-left">
|
|
35746
|
-
<div class="panel-title"
|
|
36938
|
+
<div class="panel-title">规则与知识</div>
|
|
35747
36939
|
<div class="row">
|
|
35748
|
-
<button id="analyzeBtn"
|
|
35749
|
-
<button id="scanBtn" class="subtle"
|
|
36940
|
+
<button id="analyzeBtn">分析 agents/docs</button>
|
|
36941
|
+
<button id="scanBtn" class="subtle">扫描 Skills</button>
|
|
35750
36942
|
</div>
|
|
35751
36943
|
<div id="rulesSummary" class="mono block-scroll compact-block"></div>
|
|
35752
|
-
<h3
|
|
36944
|
+
<h3>规则</h3>
|
|
35753
36945
|
<div id="rulesList" class="block-scroll grow-block"></div>
|
|
35754
|
-
<h3
|
|
36946
|
+
<h3>来源</h3>
|
|
35755
36947
|
<div id="sourceList" class="block-scroll grow-block"></div>
|
|
35756
36948
|
</aside>
|
|
35757
36949
|
<section class="panel skills-panel-center">
|
|
35758
|
-
<div class="panel-title"
|
|
36950
|
+
<div class="panel-title">流程构建器</div>
|
|
35759
36951
|
<div class="row compact flow-tabs">
|
|
35760
|
-
<button id="flowTabNodeBtn" class="subtle active"
|
|
35761
|
-
<button id="flowTabLinkBtn" class="subtle"
|
|
36952
|
+
<button id="flowTabNodeBtn" class="subtle active">节点</button>
|
|
36953
|
+
<button id="flowTabLinkBtn" class="subtle">手动连线</button>
|
|
35762
36954
|
</div>
|
|
35763
36955
|
<div id="flowPanelNode" class="flow-panel active">
|
|
35764
36956
|
<div class="row compact">
|
|
35765
|
-
<input id="nodeTitle" placeholder="
|
|
36957
|
+
<input id="nodeTitle" placeholder="节点标题">
|
|
35766
36958
|
<select id="nodeType">
|
|
35767
|
-
<option value="goal"
|
|
35768
|
-
<option value="input"
|
|
35769
|
-
<option value="process"
|
|
35770
|
-
<option value="check"
|
|
35771
|
-
<option value="output"
|
|
36959
|
+
<option value="goal">目标</option>
|
|
36960
|
+
<option value="input">输入</option>
|
|
36961
|
+
<option value="process">流程</option>
|
|
36962
|
+
<option value="check">检查</option>
|
|
36963
|
+
<option value="output">输出</option>
|
|
35772
36964
|
</select>
|
|
35773
|
-
<button id="addNodeBtn"
|
|
36965
|
+
<button id="addNodeBtn">添加节点</button>
|
|
35774
36966
|
</div>
|
|
35775
|
-
<textarea id="nodeContent" class="node-content" placeholder="
|
|
36967
|
+
<textarea id="nodeContent" class="node-content" placeholder="节点内容..."></textarea>
|
|
35776
36968
|
</div>
|
|
35777
36969
|
<div id="flowPanelLink" class="flow-panel">
|
|
35778
36970
|
<div class="row compact">
|
|
35779
36971
|
<select id="edgeFrom"></select>
|
|
35780
36972
|
<select id="edgeFromSide">
|
|
35781
|
-
<option value=""
|
|
35782
|
-
<option value="top"
|
|
35783
|
-
<option value="right"
|
|
35784
|
-
<option value="bottom"
|
|
35785
|
-
<option value="left"
|
|
36973
|
+
<option value="">起点:自动</option>
|
|
36974
|
+
<option value="top">起点:上</option>
|
|
36975
|
+
<option value="right">起点:右</option>
|
|
36976
|
+
<option value="bottom">起点:下</option>
|
|
36977
|
+
<option value="left">起点:左</option>
|
|
35786
36978
|
</select>
|
|
35787
36979
|
<select id="edgeTo"></select>
|
|
35788
36980
|
<select id="edgeToSide">
|
|
35789
|
-
<option value=""
|
|
35790
|
-
<option value="top"
|
|
35791
|
-
<option value="right"
|
|
35792
|
-
<option value="bottom"
|
|
35793
|
-
<option value="left"
|
|
36981
|
+
<option value="">终点:自动</option>
|
|
36982
|
+
<option value="top">终点:上</option>
|
|
36983
|
+
<option value="right">终点:右</option>
|
|
36984
|
+
<option value="bottom">终点:下</option>
|
|
36985
|
+
<option value="left">终点:左</option>
|
|
35794
36986
|
</select>
|
|
35795
|
-
<input id="edgeLabel" placeholder="
|
|
35796
|
-
<button id="addEdgeBtn" class="subtle"
|
|
35797
|
-
<button id="removeNodeBtn" class="subtle danger"
|
|
36987
|
+
<input id="edgeLabel" placeholder="连线标签">
|
|
36988
|
+
<button id="addEdgeBtn" class="subtle">连接</button>
|
|
36989
|
+
<button id="removeNodeBtn" class="subtle danger">删除节点</button>
|
|
35798
36990
|
</div>
|
|
35799
36991
|
<div class="row compact edge-meta-row">
|
|
35800
36992
|
<label class="inline-check">
|
|
35801
36993
|
<input id="edgeBidirectional" type="checkbox">
|
|
35802
|
-
|
|
36994
|
+
双向
|
|
35803
36995
|
</label>
|
|
35804
|
-
<input id="edgeReturnN" type="number" min="1" step="1" value="1" placeholder="
|
|
35805
|
-
<span class="mono edge-tip"
|
|
36996
|
+
<input id="edgeReturnN" type="number" min="1" step="1" value="1" placeholder="返回 n">
|
|
36997
|
+
<span class="mono edge-tip">拖拽端口并按住 Shift => 双向链路 (n)</span>
|
|
35806
36998
|
</div>
|
|
35807
36999
|
</div>
|
|
35808
37000
|
<div class="flow-stage">
|
|
35809
37001
|
<div id="flowZoomPill" class="flow-zoom-pill">
|
|
35810
|
-
<button id="flowZoomOutBtn" class="subtle" title="
|
|
37002
|
+
<button id="flowZoomOutBtn" class="subtle" title="缩小">-</button>
|
|
35811
37003
|
<span id="flowZoomText" class="mono">100%</span>
|
|
35812
|
-
<button id="flowZoomInBtn" class="subtle" title="
|
|
37004
|
+
<button id="flowZoomInBtn" class="subtle" title="放大">+</button>
|
|
35813
37005
|
</div>
|
|
35814
37006
|
<div id="flowWrap" class="flow-wrap">
|
|
35815
37007
|
<svg id="flowSvg"></svg>
|
|
35816
37008
|
<div id="flowCanvas"></div>
|
|
35817
37009
|
</div>
|
|
35818
37010
|
<div id="flowHelpOverlay" class="flow-wrap-help">
|
|
35819
|
-
<div class="t"
|
|
37011
|
+
<div class="t">画布提示</div>
|
|
35820
37012
|
<div>1. 拖拽节点移动位置</div>
|
|
35821
37013
|
<div>2. 从节点四边圆点拖拽连线</div>
|
|
35822
37014
|
<div>3. 按住 Shift 拖拽 => 双向链路</div>
|
|
@@ -35825,41 +37017,41 @@ SKILLS_INDEX_HTML = """<!doctype html>
|
|
|
35825
37017
|
</div>
|
|
35826
37018
|
</div>
|
|
35827
37019
|
<div class="row">
|
|
35828
|
-
<button id="resetFlowBtn" class="subtle"
|
|
35829
|
-
<button id="exportFlowBtn" class="subtle"
|
|
35830
|
-
<button id="importFlowBtn" class="subtle"
|
|
37020
|
+
<button id="resetFlowBtn" class="subtle">重置流程</button>
|
|
37021
|
+
<button id="exportFlowBtn" class="subtle">导出 Flow JSON</button>
|
|
37022
|
+
<button id="importFlowBtn" class="subtle">导入 Flow JSON</button>
|
|
35831
37023
|
</div>
|
|
35832
37024
|
<textarea id="flowJson" class="mono flow-json" placeholder="Flow JSON..."></textarea>
|
|
35833
37025
|
</section>
|
|
35834
37026
|
<aside class="panel skills-panel-right">
|
|
35835
|
-
<div class="panel-title"
|
|
35836
|
-
<input id="skillName" placeholder="
|
|
35837
|
-
<input id="skillPath" placeholder="
|
|
35838
|
-
<input id="skillDesc" placeholder="
|
|
35839
|
-
<textarea id="requirements" class="req-box" placeholder="
|
|
37027
|
+
<div class="panel-title">技能草稿与发布</div>
|
|
37028
|
+
<input id="skillName" placeholder="技能名称(例如 web-api-review)">
|
|
37029
|
+
<input id="skillPath" placeholder="技能路径(例如 generated/web-api-review)">
|
|
37030
|
+
<input id="skillDesc" placeholder="简短描述">
|
|
37031
|
+
<textarea id="requirements" class="req-box" placeholder="额外要求..."></textarea>
|
|
35840
37032
|
<div class="row">
|
|
35841
|
-
<button id="generateBtn"
|
|
35842
|
-
<button id="saveBtn" class="subtle"
|
|
37033
|
+
<button id="generateBtn">生成并注入</button>
|
|
37034
|
+
<button id="saveBtn" class="subtle">保存当前 Markdown</button>
|
|
35843
37035
|
</div>
|
|
35844
|
-
<textarea id="skillMarkdown" class="mono skill-md" placeholder="
|
|
37036
|
+
<textarea id="skillMarkdown" class="mono skill-md" placeholder="生成的 SKILL.md 内容..."></textarea>
|
|
35845
37037
|
<div class="skills-catalog">
|
|
35846
37038
|
<div class="row compact">
|
|
35847
|
-
<h3
|
|
37039
|
+
<h3>技能浏览器</h3>
|
|
35848
37040
|
<span id="skillsStats" class="mono"></span>
|
|
35849
37041
|
</div>
|
|
35850
37042
|
<div class="row compact explorer-actions">
|
|
35851
|
-
<button id="previewToFlowBtn" class="subtle"
|
|
37043
|
+
<button id="previewToFlowBtn" class="subtle">载入到流程构建器</button>
|
|
35852
37044
|
</div>
|
|
35853
37045
|
<div id="skillsUploadDrop" class="upload-drop skills-upload-drop">拖拽上传 skills(SKILL.md / .zip),或使用下方上传按钮</div>
|
|
35854
37046
|
<div class="row compact upload-actions">
|
|
35855
|
-
<button id="skillsUploadFileBtn" class="subtle"
|
|
35856
|
-
<button id="skillsUploadFolderBtn" class="subtle"
|
|
37047
|
+
<button id="skillsUploadFileBtn" class="subtle">上传文件</button>
|
|
37048
|
+
<button id="skillsUploadFolderBtn" class="subtle">上传文件夹</button>
|
|
35857
37049
|
</div>
|
|
35858
37050
|
<input id="skillsUploadInput" type="file" multiple accept=".zip,.md,.markdown,.txt">
|
|
35859
37051
|
<input id="skillsUploadDirInput" type="file" multiple webkitdirectory directory>
|
|
35860
37052
|
<div id="skillsUploadList" class="mono upload-list"></div>
|
|
35861
37053
|
<div id="skillsTree" class="block-scroll tree-scroll"></div>
|
|
35862
|
-
<h3
|
|
37054
|
+
<h3>已选 Skill</h3>
|
|
35863
37055
|
<div id="skillPreview" class="mono block-scroll preview-scroll"></div>
|
|
35864
37056
|
</div>
|
|
35865
37057
|
<div id="errorBox" class="error-box hidden"></div>
|
|
@@ -35974,22 +37166,70 @@ const I18N={'en':{title:'Fona Skills Studio',subtitle:'Visual Skills authoring p
|
|
|
35974
37166
|
'zh-CN':{title:'Fona Skills Studio',subtitle:'基于现有 WebUI 风格的图形化 Skills 制作平台',flow_line:'Flowchart → LLM 解析 → SKILL.md 注入 ./skills',apply_model:'应用模型',refresh:'刷新',open_agent:'打开 Agent UI',rules_knowledge:'Rules & Knowledge',analyze:'分析 agents/docs',scan:'扫描 Skills',rules:'规则',sources:'来源',flow_builder:'流程构建器',tab_node:'节点',tab_manual_link:'手动连线',add_node:'添加节点',connect:'连接',delete_node:'删除节点',bidirectional:'双向',drag_tip:'拖拽端口并按 Shift => 双向链路 (n)',canvas_tips:'画布提示',tip1:'1. 拖拽节点移动位置',tip2:'2. 从节点四边端口拖拽连线',tip3:'3. 按住 Shift 拖拽 => 双向链路',tip4:'4. 双击连线附近 => 删除连线',tip5:'5. 使用 +/- 按钮缩放',reset_flow:'重置流程',export_flow:'导出 Flow JSON',import_flow:'导入 Flow JSON',draft_publish:'技能草稿与发布',generate_inject:'生成并注入',save_markdown:'保存当前 Markdown',skills_explorer:'技能浏览器',load_to_flow:'载入到流程构建器',upload_drop:'拖拽上传 skills(SKILL.md / .zip),或使用下方上传按钮',upload_files:'上传文件',upload_folder:'上传文件夹',selected_skill:'已选 Skill',stat_rules:'规则',stat_skills:'技能',stat_model:'模型',stat_nodes:'流程节点',no_rules:'暂无规则',no_sources:'暂无来源',no_skill_selected:'未选择 Skill',no_uploads:'暂无上传',select_skill_first:'请先选择一个 skill',no_model_selected:'未选择模型',invalid_flow_json:'无效的 flow json',summary:'摘要',generated_at:'生成时间',skills_unit:'个 skills',upload_parse_failed:'上传解析失败',folder:'目录',empty:'(空)'},
|
|
35975
37167
|
'zh-TW':{title:'Fona Skills Studio',subtitle:'基於現有 WebUI 風格的圖形化 Skills 製作平台',flow_line:'Flowchart → LLM 解析 → SKILL.md 注入 ./skills',apply_model:'套用模型',refresh:'重新整理',open_agent:'開啟 Agent UI',rules_knowledge:'Rules & Knowledge',analyze:'分析 agents/docs',scan:'掃描 Skills',rules:'規則',sources:'來源',flow_builder:'流程建構器',tab_node:'節點',tab_manual_link:'手動連線',add_node:'新增節點',connect:'連線',delete_node:'刪除節點',bidirectional:'雙向',drag_tip:'拖曳端口並按 Shift => 雙向鏈路 (n)',canvas_tips:'畫布提示',tip1:'1. 拖曳節點移動位置',tip2:'2. 從節點四邊端口拖曳連線',tip3:'3. 按住 Shift 拖曳 => 雙向鏈路',tip4:'4. 雙擊連線附近 => 刪除連線',tip5:'5. 使用 +/- 按鈕縮放',reset_flow:'重設流程',export_flow:'匯出 Flow JSON',import_flow:'匯入 Flow JSON',draft_publish:'技能草稿與發布',generate_inject:'生成並注入',save_markdown:'儲存目前 Markdown',skills_explorer:'技能瀏覽器',load_to_flow:'載入至流程建構器',upload_drop:'拖曳上傳 skills(SKILL.md / .zip),或使用下方上傳按鈕',upload_files:'上傳檔案',upload_folder:'上傳資料夾',selected_skill:'已選 Skill',stat_rules:'規則',stat_skills:'技能',stat_model:'模型',stat_nodes:'流程節點',no_rules:'尚無規則',no_sources:'尚無來源',no_skill_selected:'未選擇 Skill',no_uploads:'尚無上傳',select_skill_first:'請先選擇一個 skill',no_model_selected:'尚未選擇模型',invalid_flow_json:'無效的 flow json',summary:'摘要',generated_at:'產生時間',skills_unit:'個 skills',upload_parse_failed:'上傳解析失敗',folder:'資料夾',empty:'(空)'},
|
|
35976
37168
|
'ja':{title:'Fona Skills Studio',subtitle:'既存 WebUI スタイルのビジュアル Skills 制作プラットフォーム',flow_line:'Flowchart → LLM 解析 → SKILL.md を ./skills へ注入',apply_model:'モデル適用',refresh:'更新',open_agent:'Agent UI を開く',rules_knowledge:'Rules & Knowledge',analyze:'agents/docs を解析',scan:'Skills をスキャン',rules:'ルール',sources:'ソース',flow_builder:'フロービルダー',tab_node:'ノード',tab_manual_link:'手動リンク',add_node:'ノード追加',connect:'接続',delete_node:'ノード削除',bidirectional:'双方向',drag_tip:'ポートをドラッグ + Shift で双方向リンク (n)',canvas_tips:'Canvas Tips',tip1:'1. ノードをドラッグして移動',tip2:'2. ノード端子からドラッグして接続',tip3:'3. Shift を押しながらドラッグで双方向',tip4:'4. エッジ付近をダブルクリックで削除',tip5:'5. +/- ボタンでズーム',reset_flow:'Flow をリセット',export_flow:'Flow JSON をエクスポート',import_flow:'Flow JSON をインポート',draft_publish:'Skill 下書きと公開',generate_inject:'生成して注入',save_markdown:'現在の Markdown を保存',skills_explorer:'Skills Explorer',load_to_flow:'Flow Builder に読み込む',upload_drop:'skills(SKILL.md / .zip)をドラッグ&ドロップ、または下のアップロードを使用',upload_files:'ファイルをアップロード',upload_folder:'フォルダをアップロード',selected_skill:'選択中 Skill',stat_rules:'ルール',stat_skills:'Skills',stat_model:'モデル',stat_nodes:'Flow ノード',no_rules:'ルールなし',no_sources:'ソースなし',no_skill_selected:'Skill が選択されていません',no_uploads:'アップロードなし',select_skill_first:'先に skill を選択してください',no_model_selected:'モデルが未選択です',invalid_flow_json:'無効な flow json',summary:'summary',generated_at:'generated_at',skills_unit:'skills',upload_parse_failed:'upload parse failed',folder:'folder',empty:'(empty)'}};
|
|
37169
|
+
Object.assign(I18N['en'],{
|
|
37170
|
+
summary:'Summary',generated_at:'Generated At',skills_unit:'skills',upload_parse_failed:'Upload parse failed',select_skill_first:'Select a skill first',no_model_selected:'No model selected',invalid_flow_json:'Invalid flow JSON',folder:'folder',empty:'(empty)',file_too_large:'File too large',
|
|
37171
|
+
placeholder_node_title:'Node title',placeholder_node_content:'Node content...',placeholder_edge_label:'edge label',placeholder_return_n:'return n',placeholder_flow_json:'Flow JSON...',placeholder_skill_name:'skill name (e.g. web-api-review)',placeholder_skill_path:'skill path (e.g. generated/web-api-review)',placeholder_skill_desc:'short description',placeholder_requirements:'extra requirements...',placeholder_skill_markdown:'Generated SKILL.md content...',
|
|
37172
|
+
node_type_goal:'Goal',node_type_input:'Input',node_type_process:'Process',node_type_check:'Check',node_type_output:'Output',
|
|
37173
|
+
edge_from_auto:'from:auto',edge_from_top:'from:top',edge_from_right:'from:right',edge_from_bottom:'from:bottom',edge_from_left:'from:left',edge_to_auto:'to:auto',edge_to_top:'to:top',edge_to_right:'to:right',edge_to_bottom:'to:bottom',edge_to_left:'to:left',
|
|
37174
|
+
zoom_out:'Zoom out',zoom_in:'Zoom in',source_bytes:'bytes',
|
|
37175
|
+
meta_name:'name',meta_path:'path',meta_provider:'provider',meta_protocol:'protocol',meta_description:'description',
|
|
37176
|
+
no_preview:'(no preview)',upload_imported:'imported',upload_skipped:'skipped',upload_errors:'errors',imported_skill:'Imported Skill',
|
|
37177
|
+
flow_goal_title:'Goal',flow_inputs_title:'Inputs',flow_process_title:'Process',flow_checks_title:'Checks',flow_output_title:'Output',
|
|
37178
|
+
flow_goal_desc:'Define target skill behavior.',flow_inputs_desc:'List user intent, constraints, and context.',flow_process_desc:'Translate into deterministic workflow.',flow_checks_desc:'Validate quality and failure handling.',flow_output_desc:'Emit SKILL.md and inject to ./skills.',
|
|
37179
|
+
parse_goal_fallback:'Define target behavior.',parse_input_fallback:'Collect user intent, constraints, and required files.',parse_process_fallback:'Execute deterministic workflow with clear tool usage and outputs.',parse_checks_fallback:'Validate outputs, handle failure paths, and enforce quality gates.',parse_output_fallback:'Produce final answer and artifacts with traceable evidence.'
|
|
37180
|
+
});
|
|
37181
|
+
Object.assign(I18N['zh-CN'],{
|
|
37182
|
+
rules_knowledge:'规则与知识',canvas_tips:'画布提示',skills_explorer:'技能浏览器',summary:'摘要',generated_at:'生成时间',skills_unit:'项技能',upload_parse_failed:'上传解析失败',select_skill_first:'请先选择一个 Skill',no_model_selected:'未选择模型',invalid_flow_json:'无效的 Flow JSON',folder:'目录',empty:'(空)',file_too_large:'文件过大',
|
|
37183
|
+
placeholder_node_title:'节点标题',placeholder_node_content:'节点内容...',placeholder_edge_label:'连线标签',placeholder_return_n:'返回 n',placeholder_flow_json:'Flow JSON...',placeholder_skill_name:'技能名称(例如 web-api-review)',placeholder_skill_path:'技能路径(例如 generated/web-api-review)',placeholder_skill_desc:'简短描述',placeholder_requirements:'额外要求...',placeholder_skill_markdown:'生成的 SKILL.md 内容...',
|
|
37184
|
+
node_type_goal:'目标',node_type_input:'输入',node_type_process:'流程',node_type_check:'检查',node_type_output:'输出',
|
|
37185
|
+
edge_from_auto:'起点:自动',edge_from_top:'起点:上',edge_from_right:'起点:右',edge_from_bottom:'起点:下',edge_from_left:'起点:左',edge_to_auto:'终点:自动',edge_to_top:'终点:上',edge_to_right:'终点:右',edge_to_bottom:'终点:下',edge_to_left:'终点:左',
|
|
37186
|
+
zoom_out:'缩小',zoom_in:'放大',source_bytes:'字节',
|
|
37187
|
+
meta_name:'名称',meta_path:'路径',meta_provider:'提供方',meta_protocol:'协议',meta_description:'描述',
|
|
37188
|
+
no_preview:'(无预览)',upload_imported:'导入',upload_skipped:'跳过',upload_errors:'错误',imported_skill:'导入的 Skill',
|
|
37189
|
+
flow_goal_title:'目标',flow_inputs_title:'输入',flow_process_title:'流程',flow_checks_title:'检查',flow_output_title:'输出',
|
|
37190
|
+
flow_goal_desc:'定义目标 Skill 的行为。',flow_inputs_desc:'列出用户意图、约束和上下文。',flow_process_desc:'转换为确定性工作流。',flow_checks_desc:'校验质量与失败处理。',flow_output_desc:'生成 SKILL.md 并注入到 ./skills。',
|
|
37191
|
+
parse_goal_fallback:'定义目标行为。',parse_input_fallback:'收集用户意图、约束和所需文件。',parse_process_fallback:'执行具备清晰工具调用与输出的确定性工作流。',parse_checks_fallback:'校验输出、处理失败路径并落实质量门禁。',parse_output_fallback:'产出最终答复和可追溯工件。'
|
|
37192
|
+
});
|
|
37193
|
+
Object.assign(I18N['zh-TW'],{
|
|
37194
|
+
rules_knowledge:'規則與知識',canvas_tips:'畫布提示',skills_explorer:'技能瀏覽器',summary:'摘要',generated_at:'產生時間',skills_unit:'項技能',upload_parse_failed:'上傳解析失敗',select_skill_first:'請先選擇一個 Skill',no_model_selected:'尚未選擇模型',invalid_flow_json:'無效的 Flow JSON',folder:'資料夾',empty:'(空)',file_too_large:'檔案過大',
|
|
37195
|
+
placeholder_node_title:'節點標題',placeholder_node_content:'節點內容...',placeholder_edge_label:'連線標籤',placeholder_return_n:'返回 n',placeholder_flow_json:'Flow JSON...',placeholder_skill_name:'技能名稱(例如 web-api-review)',placeholder_skill_path:'技能路徑(例如 generated/web-api-review)',placeholder_skill_desc:'簡短描述',placeholder_requirements:'額外要求...',placeholder_skill_markdown:'生成的 SKILL.md 內容...',
|
|
37196
|
+
node_type_goal:'目標',node_type_input:'輸入',node_type_process:'流程',node_type_check:'檢查',node_type_output:'輸出',
|
|
37197
|
+
edge_from_auto:'起點:自動',edge_from_top:'起點:上',edge_from_right:'起點:右',edge_from_bottom:'起點:下',edge_from_left:'起點:左',edge_to_auto:'終點:自動',edge_to_top:'終點:上',edge_to_right:'終點:右',edge_to_bottom:'終點:下',edge_to_left:'終點:左',
|
|
37198
|
+
zoom_out:'縮小',zoom_in:'放大',source_bytes:'位元組',
|
|
37199
|
+
meta_name:'名稱',meta_path:'路徑',meta_provider:'提供方',meta_protocol:'協定',meta_description:'描述',
|
|
37200
|
+
no_preview:'(無預覽)',upload_imported:'匯入',upload_skipped:'略過',upload_errors:'錯誤',imported_skill:'匯入的 Skill',
|
|
37201
|
+
flow_goal_title:'目標',flow_inputs_title:'輸入',flow_process_title:'流程',flow_checks_title:'檢查',flow_output_title:'輸出',
|
|
37202
|
+
flow_goal_desc:'定義目標 Skill 的行為。',flow_inputs_desc:'列出使用者意圖、約束與上下文。',flow_process_desc:'轉換為確定性工作流程。',flow_checks_desc:'檢查品質與失敗處理。',flow_output_desc:'產出 SKILL.md 並注入到 ./skills。',
|
|
37203
|
+
parse_goal_fallback:'定義目標行為。',parse_input_fallback:'蒐集使用者意圖、約束與所需檔案。',parse_process_fallback:'執行具有清楚工具呼叫與輸出的確定性流程。',parse_checks_fallback:'檢查輸出、處理失敗路徑並落實品質門檻。',parse_output_fallback:'產出最終回覆與可追溯工件。'
|
|
37204
|
+
});
|
|
37205
|
+
Object.assign(I18N['ja'],{
|
|
37206
|
+
rules_knowledge:'ルールと知識',canvas_tips:'キャンバスのヒント',skills_explorer:'スキルエクスプローラー',summary:'概要',generated_at:'生成時刻',skills_unit:'件のスキル',upload_parse_failed:'アップロード解析失敗',select_skill_first:'先に Skill を選択してください',no_model_selected:'モデルが未選択です',invalid_flow_json:'無効な Flow JSON',folder:'フォルダ',empty:'(空)',file_too_large:'ファイルが大きすぎます',
|
|
37207
|
+
placeholder_node_title:'ノードタイトル',placeholder_node_content:'ノード内容...',placeholder_edge_label:'エッジラベル',placeholder_return_n:'戻り n',placeholder_flow_json:'Flow JSON...',placeholder_skill_name:'スキル名(例: web-api-review)',placeholder_skill_path:'スキルパス(例: generated/web-api-review)',placeholder_skill_desc:'短い説明',placeholder_requirements:'追加要件...',placeholder_skill_markdown:'生成された SKILL.md 内容...',
|
|
37208
|
+
node_type_goal:'目標',node_type_input:'入力',node_type_process:'処理',node_type_check:'検証',node_type_output:'出力',
|
|
37209
|
+
edge_from_auto:'始点:自動',edge_from_top:'始点:上',edge_from_right:'始点:右',edge_from_bottom:'始点:下',edge_from_left:'始点:左',edge_to_auto:'終点:自動',edge_to_top:'終点:上',edge_to_right:'終点:右',edge_to_bottom:'終点:下',edge_to_left:'終点:左',
|
|
37210
|
+
zoom_out:'縮小',zoom_in:'拡大',source_bytes:'バイト',
|
|
37211
|
+
meta_name:'名前',meta_path:'パス',meta_provider:'プロバイダー',meta_protocol:'プロトコル',meta_description:'説明',
|
|
37212
|
+
no_preview:'(プレビューなし)',upload_imported:'取込',upload_skipped:'スキップ',upload_errors:'エラー',imported_skill:'インポート済み Skill',
|
|
37213
|
+
flow_goal_title:'目標',flow_inputs_title:'入力',flow_process_title:'処理',flow_checks_title:'検証',flow_output_title:'出力',
|
|
37214
|
+
flow_goal_desc:'対象 Skill の振る舞いを定義します。',flow_inputs_desc:'ユーザー意図、制約、文脈を整理します。',flow_process_desc:'確定的なワークフローへ変換します。',flow_checks_desc:'品質と失敗時処理を検証します。',flow_output_desc:'SKILL.md を生成し ./skills へ注入します。',
|
|
37215
|
+
parse_goal_fallback:'目標の振る舞いを定義します。',parse_input_fallback:'ユーザー意図、制約、必要ファイルを収集します。',parse_process_fallback:'明確なツール利用と出力を伴う確定的ワークフローを実行します。',parse_checks_fallback:'出力を検証し、失敗経路を処理し、品質ゲートを適用します。',parse_output_fallback:'最終回答と追跡可能な成果物を生成します。'
|
|
37216
|
+
});
|
|
35977
37217
|
function currentLang(){const c=String(S.config?.language||'').trim();if(c&&I18N[c])return c;return 'zh-CN'}
|
|
35978
|
-
function t(key){const lang=currentLang();const pack=I18N[lang]||I18N['en'];
|
|
37218
|
+
function t(key,vars){const lang=currentLang();const pack=I18N[lang]||I18N['en'];let txt=String((pack&&pack[key])??(I18N['en']&&I18N['en'][key])??key);if(vars&&typeof vars==='object'){for(const [k,v] of Object.entries(vars)){txt=txt.replaceAll('{'+k+'}',String(v??''))}}return txt}
|
|
35979
37219
|
function setText(id,key){const el=E(id);if(el)el.textContent=t(key)}
|
|
35980
37220
|
function setPlaceholder(id,key){const el=E(id);if(el)el.placeholder=t(key)}
|
|
35981
37221
|
function renderLanguageControls(){const sel=E('skillsLangSelect');if(!sel)return;const langs=Array.isArray(S.config?.supported_languages)?S.config.supported_languages:[];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(S.config?.language)sel.value=S.config.language}
|
|
35982
37222
|
async function setLanguage(lang){const code=String(lang||'').trim();if(!code)return;await api('/api/skillslab/language',{method:'POST',body:JSON.stringify({language:code})});S.config=S.config||{};S.config.language=code;applySkillsI18n();renderLanguageControls();setStats();renderRules();renderSkills()}
|
|
35983
|
-
function applySkillsI18n(){document.documentElement.lang=currentLang();const h1=document.querySelector('header h1');if(h1)h1.textContent=t('title');const hp=document.querySelectorAll('header p');if(hp&&hp[0])hp[0].textContent=t('subtitle');if(hp&&hp[1])hp[1].textContent=t('flow_line');setText('applyModelBtn','apply_model');setText('refreshBtn','refresh');setText('agentLink','open_agent');const panels=document.querySelectorAll('.panel-title');if(panels&&panels[0])panels[0].textContent=t('rules_knowledge');if(panels&&panels[1])panels[1].textContent=t('flow_builder');if(panels&&panels[2])panels[2].textContent=t('draft_publish');setText('analyzeBtn','analyze');setText('scanBtn','scan');setText('flowTabNodeBtn','tab_node');setText('flowTabLinkBtn','tab_manual_link');setText('addNodeBtn','add_node');setText('addEdgeBtn','connect');setText('removeNodeBtn','delete_node');setText('resetFlowBtn','reset_flow');setText('exportFlowBtn','export_flow');setText('importFlowBtn','import_flow');setText('generateBtn','generate_inject');setText('saveBtn','save_markdown');setText('previewToFlowBtn','load_to_flow');setText('skillsUploadFileBtn','upload_files');setText('skillsUploadFolderBtn','upload_folder');const ud=E('skillsUploadDrop');if(ud)ud.textContent=t('upload_drop');const edgeTip=document.querySelector('.edge-tip');if(edgeTip)edgeTip.textContent=t('drag_tip');setPlaceholder('nodeTitle','
|
|
37223
|
+
function applySkillsI18n(){document.documentElement.lang=currentLang();const h1=document.querySelector('header h1');if(h1)h1.textContent=t('title');const hp=document.querySelectorAll('header p');if(hp&&hp[0])hp[0].textContent=t('subtitle');if(hp&&hp[1])hp[1].textContent=t('flow_line');setText('applyModelBtn','apply_model');setText('refreshBtn','refresh');setText('agentLink','open_agent');const panels=document.querySelectorAll('.panel-title');if(panels&&panels[0])panels[0].textContent=t('rules_knowledge');if(panels&&panels[1])panels[1].textContent=t('flow_builder');if(panels&&panels[2])panels[2].textContent=t('draft_publish');setText('analyzeBtn','analyze');setText('scanBtn','scan');setText('flowTabNodeBtn','tab_node');setText('flowTabLinkBtn','tab_manual_link');setText('addNodeBtn','add_node');setText('addEdgeBtn','connect');setText('removeNodeBtn','delete_node');setText('resetFlowBtn','reset_flow');setText('exportFlowBtn','export_flow');setText('importFlowBtn','import_flow');setText('generateBtn','generate_inject');setText('saveBtn','save_markdown');setText('previewToFlowBtn','load_to_flow');setText('skillsUploadFileBtn','upload_files');setText('skillsUploadFolderBtn','upload_folder');const ud=E('skillsUploadDrop');if(ud)ud.textContent=t('upload_drop');const edgeTip=document.querySelector('.edge-tip');if(edgeTip)edgeTip.textContent=t('drag_tip');setPlaceholder('nodeTitle','placeholder_node_title');setPlaceholder('nodeContent','placeholder_node_content');setPlaceholder('edgeLabel','placeholder_edge_label');setPlaceholder('edgeReturnN','placeholder_return_n');setPlaceholder('flowJson','placeholder_flow_json');setPlaceholder('skillName','placeholder_skill_name');setPlaceholder('skillPath','placeholder_skill_path');setPlaceholder('skillDesc','placeholder_skill_desc');setPlaceholder('requirements','placeholder_requirements');setPlaceholder('skillMarkdown','placeholder_skill_markdown');const hs=document.querySelectorAll('.skills-panel-left h3, .skills-panel-right h3');if(hs&&hs[0])hs[0].textContent=t('rules');if(hs&&hs[1])hs[1].textContent=t('sources');if(hs&&hs[2])hs[2].textContent=t('skills_explorer');if(hs&&hs[3])hs[3].textContent=t('selected_skill');const tip=document.querySelector('#flowHelpOverlay .t');if(tip)tip.textContent=t('canvas_tips');const tipRows=document.querySelectorAll('#flowHelpOverlay div');if(tipRows&&tipRows[1])tipRows[1].textContent=t('tip1');if(tipRows&&tipRows[2])tipRows[2].textContent=t('tip2');if(tipRows&&tipRows[3])tipRows[3].textContent=t('tip3');if(tipRows&&tipRows[4])tipRows[4].textContent=t('tip4');if(tipRows&&tipRows[5])tipRows[5].textContent=t('tip5');const inlineCheck=document.querySelector('.inline-check');if(inlineCheck){const txt=inlineCheck.childNodes[inlineCheck.childNodes.length-1];if(txt&&txt.nodeType===Node.TEXT_NODE)txt.textContent=' '+t('bidirectional')}const nodeType=E('nodeType');if(nodeType){for(const op of Array.from(nodeType.options||[])){const key=String(op.value||'').trim();if(key)op.textContent=t('node_type_'+key)}}const fromSel=E('edgeFromSide');if(fromSel){const map=['edge_from_auto','edge_from_top','edge_from_right','edge_from_bottom','edge_from_left'];Array.from(fromSel.options||[]).forEach((op,idx)=>{if(map[idx])op.textContent=t(map[idx])})}const toSel=E('edgeToSide');if(toSel){const map=['edge_to_auto','edge_to_top','edge_to_right','edge_to_bottom','edge_to_left'];Array.from(toSel.options||[]).forEach((op,idx)=>{if(map[idx])op.textContent=t(map[idx])})}const zoomOut=E('flowZoomOutBtn');if(zoomOut)zoomOut.title=t('zoom_out');const zoomIn=E('flowZoomInBtn');if(zoomIn)zoomIn.title=t('zoom_in')}
|
|
35984
37224
|
async function api(path,opt={}){const o=(opt&&typeof opt==='object')?{...opt}:{};const timeoutMs=Math.max(1000,Math.min(180000,Number(o.timeoutMs||45000)||45000));delete o.timeoutMs;const ctl=(typeof AbortController==='function')?new AbortController():null;let timer=0;try{if(ctl){timer=setTimeout(()=>{try{ctl.abort()}catch(_){ }},timeoutMs)}const hdr={...(o.headers||{}), 'Content-Type':'application/json'};const r=await fetch(path,{...o,headers:hdr,signal:(ctl?ctl.signal:o.signal)});const t=await r.text();if(!r.ok){let msg=t;try{msg=JSON.parse(t).error||t}catch(_){}throw new Error(msg||'request failed')}return t?JSON.parse(t):{}}catch(err){if(err&&err.name==='AbortError'){throw new Error('request timeout')}throw err}finally{if(timer)clearTimeout(timer)}}
|
|
35985
37225
|
function esc(s){return String(s??'').replace(/[&<>"]/g,c=>({ '&':'&','<':'<','>':'>','\"':'"' }[c]))}
|
|
35986
37226
|
function showError(msg){const el=E('errorBox');if(!msg){el.classList.add('hidden');el.textContent='';return}el.textContent=msg;el.classList.remove('hidden')}
|
|
35987
37227
|
function ab2b64(buf){let bin='';const bytes=new Uint8Array(buf);const chunk=0x8000;for(let i=0;i<bytes.length;i+=chunk){bin+=String.fromCharCode(...bytes.subarray(i,i+chunk))}return btoa(bin)}
|
|
35988
|
-
function defaultFlow(){return{nodes:[{id:'goal',type:'goal',title:'
|
|
37228
|
+
function defaultFlow(){return{nodes:[{id:'goal',type:'goal',title:t('flow_goal_title'),content:t('flow_goal_desc'),x:30,y:30},{id:'inputs',type:'input',title:t('flow_inputs_title'),content:t('flow_inputs_desc'),x:280,y:30},{id:'process',type:'process',title:t('flow_process_title'),content:t('flow_process_desc'),x:530,y:30},{id:'checks',type:'check',title:t('flow_checks_title'),content:t('flow_checks_desc'),x:280,y:210},{id:'output',type:'output',title:t('flow_output_title'),content:t('flow_output_desc'),x:530,y:210}],edges:[{from:'goal',to:'inputs',label:''},{from:'inputs',to:'process',label:''},{from:'process',to:'checks',label:''},{from:'checks',to:'output',label:''}]}}
|
|
35989
37229
|
function normalizeSkillScan(payload){if(Array.isArray(payload)){const skills=payload.map(x=>({name:x.name||x.qualified_name||'skill',description:x.description||'',path:'',skill_file:x.qualified_name||x.name||'',provider:x.provider_id||'',protocol:x.protocol||'',preview:''}));return{skills_count:skills.length,skills,tree:{type:'dir',name:'skills',path:'',children:skills.map(x=>({type:'skill',...x}))},warnings:[]}}const obj=(payload&&typeof payload==='object')?payload:{};const skills=Array.isArray(obj.skills)?obj.skills:[];const tree=(obj.tree&&typeof obj.tree==='object')?obj.tree:{type:'dir',name:'skills',path:'',children:[]};const warnings=Array.isArray(obj.warnings)?obj.warnings:[];return{skills_count:Number(obj.skills_count||skills.length)||skills.length,skills,tree,warnings}}
|
|
35990
37230
|
function setStats(){const model=S.config?.model_catalog?.selected||'-';const skills=S.skillScan?.skills_count||0;const rules=(S.rules?.rules||[]).length;E('topStats').innerHTML=[[t('stat_rules'),rules],[t('stat_skills'),skills],[t('stat_model'),model],[t('stat_nodes'),(S.flow.nodes||[]).length]].map(([k,v])=>`<div class=\"stat\"><div class=\"k\">${esc(k)}</div><div class=\"v\">${esc(v)}</div></div>`).join('');const st=E('skillsStats');if(st)st.textContent=`${skills} ${t('skills_unit')}`}
|
|
35991
37231
|
function renderModelControls(){const sel=E('modelSelect');if(!sel)return;sel.innerHTML='';const cat=S.config?.model_catalog||{};const opts=cat.options||[];if(opts.length){for(const it of opts){const op=document.createElement('option');op.value=it.selection;op.textContent=it.label||it.selection;sel.appendChild(op)}}else{for(const m of (cat.models||[])){const op=document.createElement('option');op.value=m;op.textContent=m;sel.appendChild(op)}}if(cat.selected)sel.value=cat.selected}
|
|
35992
|
-
function renderRules(){const r=S.rules||{};E('rulesSummary').innerHTML=`<div>${esc(t('summary'))}: ${esc(r.summary||'-')}</div><div>${esc(t('generated_at'))}: ${esc(r.generated_at||'-')}</div>`;E('rulesList').innerHTML=(r.rules||[]).map(x=>`<div>• ${esc(x)}</div>`).join('')||`<div class=\"mono\">${esc(t('no_rules'))}</div>`;E('sourceList').innerHTML=(r.sources||[]).map(s=>`<div class=\"mono\">${esc(s.path)} (${esc(s.bytes)}
|
|
37232
|
+
function renderRules(){const r=S.rules||{};E('rulesSummary').innerHTML=`<div>${esc(t('summary'))}: ${esc(r.summary||'-')}</div><div>${esc(t('generated_at'))}: ${esc(r.generated_at||'-')}</div>`;E('rulesList').innerHTML=(r.rules||[]).map(x=>`<div>• ${esc(x)}</div>`).join('')||`<div class=\"mono\">${esc(t('no_rules'))}</div>`;E('sourceList').innerHTML=(r.sources||[]).map(s=>`<div class=\"mono\">${esc(s.path)} (${esc(s.bytes)} ${esc(t('source_bytes'))})</div>`).join('')||`<div class=\"mono\">${esc(t('no_sources'))}</div>`}
|
|
35993
37233
|
function buildSkillMap(){S.skillMap={};for(const row of (S.skillScan?.skills||[])){const key=String(row.skill_file||row.path||row.name||'').trim();if(!key)continue;S.skillMap[key]=row}}
|
|
35994
37234
|
function skillItemHtml(skill){const file=String(skill.skill_file||skill.path||'');const active=(file&&file===S.activeSkillFile)?' active':'';return `<div class=\"skill-item${active}\" data-skill-file=\"${esc(file)}\"><div class=\"name\">${esc(skill.name||'skill')}</div><div class=\"desc\">${esc(skill.description||'')}</div><div class=\"path\">${esc(file||skill.path||'')}</div></div>`}
|
|
35995
37235
|
function treeNodeHtml(node,depth=0){if(!node||typeof node!=='object')return'';if(String(node.type)==='skill')return skillItemHtml(node);const children=Array.isArray(node.children)?node.children:[];const openAttr=depth<=1?' open':'';const title=esc(node.name||t('folder'));const childHtml=children.length?children.map(x=>treeNodeHtml(x,depth+1)).join(''):`<div class=\"mono\">${esc(t('empty'))}</div>`;return `<details${openAttr}><summary><span class=\"tree-folder\">${title}</span></summary><div class=\"tree-children\">${childHtml}</div></details>`}
|
|
@@ -35999,12 +37239,12 @@ function safeNodeId(base,taken){const b=flowSlug(base,'n');if(!taken.has(b)){tak
|
|
|
35999
37239
|
function parseFrontMatterMd(md){const src=String(md||'');const m=src.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*(?:\\n|$)/);if(!m)return{meta:{},body:src};const meta={};for(const line of String(m[1]||'').split(/\\r?\\n/)){const idx=line.indexOf(':');if(idx<=0)continue;const k=line.slice(0,idx).trim();const v=line.slice(idx+1).trim();if(k)meta[k]=v}return{meta,body:src.slice(m[0].length)}}
|
|
36000
37240
|
function collectSections(body){const out=[];const lines=String(body||'').split(/\\r?\\n/);let cur={title:'Overview',content:''};for(const line of lines){const hm=String(line||'').match(/^#{1,3}\\s+(.+?)\\s*$/);if(hm){if(String(cur.content||'').trim())out.push(cur);cur={title:String(hm[1]||'').trim()||'Section',content:''};continue}cur.content+=(cur.content?'\\n':'')+line}if(String(cur.content||'').trim())out.push(cur);return out}
|
|
36001
37241
|
function pickSectionText(sections,keywords,fallback=''){const keys=(keywords||[]).map(k=>String(k).toLowerCase());for(const sec of sections){const t=String(sec?.title||'').toLowerCase();if(keys.some(k=>t.includes(k)))return String(sec?.content||'').trim()}return String(fallback||'').trim()}
|
|
36002
|
-
function parseSkillToFlow(md,fallbackName,fallbackDesc){const parsed=parseFrontMatterMd(md);const meta=parsed.meta||{};const body=String(parsed.body||md||'').trim();const sections=collectSections(body);const nonEmptyLines=body.split(/\\r?\\n/).map(x=>x.trim()).filter(Boolean);const overview=nonEmptyLines.slice(0,8).join('\\n');const title=String(meta.name||fallbackName||'
|
|
37242
|
+
function parseSkillToFlow(md,fallbackName,fallbackDesc){const parsed=parseFrontMatterMd(md);const meta=parsed.meta||{};const body=String(parsed.body||md||'').trim();const sections=collectSections(body);const nonEmptyLines=body.split(/\\r?\\n/).map(x=>x.trim()).filter(Boolean);const overview=nonEmptyLines.slice(0,8).join('\\n');const title=String(meta.name||fallbackName||t('imported_skill')).trim()||t('imported_skill');const desc=String(meta.description||fallbackDesc||'').trim();const goalText=trimText(desc||pickSectionText(sections,['goal','purpose','overview','objective','目标','目標','概述','概要','ゴール','目的'],overview||t('parse_goal_fallback')),680);const inputText=trimText(pickSectionText(sections,['input','parameter','context','prerequisite','输入','輸入','上下文','前置','前提','入力','文脈'],t('parse_input_fallback')),720);const processText=trimText(pickSectionText(sections,['workflow','process','steps','instruction','procedure','流程','步骤','步驟','执行','執行','ワークフロー','手順'],body||t('parse_process_fallback')),900);const checkText=trimText(pickSectionText(sections,['check','validation','verify','quality','guardrail','failure','test','检查','檢查','校验','驗證','検証','品質'],t('parse_checks_fallback')),720);const outputText=trimText(pickSectionText(sections,['output','deliverable','response','result','输出','輸出','产出','產出','成果','出力'],t('parse_output_fallback')),680);const taken=new Set();const nodes=[{id:safeNodeId('goal',taken),type:'goal',title:t('flow_goal_title'),content:goalText,x:30,y:30},{id:safeNodeId('inputs',taken),type:'input',title:t('flow_inputs_title'),content:inputText,x:280,y:30},{id:safeNodeId('process',taken),type:'process',title:t('flow_process_title'),content:processText,x:530,y:30},{id:safeNodeId('checks',taken),type:'check',title:t('flow_checks_title'),content:checkText,x:280,y:220},{id:safeNodeId('output',taken),type:'output',title:t('flow_output_title'),content:outputText,x:530,y:220}];const edges=[{from:nodes[0].id,to:nodes[1].id,label:''},{from:nodes[1].id,to:nodes[2].id,label:''},{from:nodes[2].id,to:nodes[3].id,label:''},{from:nodes[3].id,to:nodes[4].id,label:''}];return{title,description:desc,nodes,edges}}
|
|
36003
37243
|
function upsertSkillRow(row){if(!row||!row.skill_file)return;const key=String(row.skill_file);if(Array.isArray(S.skillScan?.skills)){const idx=S.skillScan.skills.findIndex(x=>String(x.skill_file||x.path||x.name||'')===key);if(idx>=0){S.skillScan.skills[idx]={...S.skillScan.skills[idx],...row}}}if(S.skillMap)S.skillMap[key]={...(S.skillMap[key]||{}),...row}}
|
|
36004
|
-
async function ensureSkillContent(skillFile){const key=String(skillFile||S.activeSkillFile||'').trim();if(!key)throw new Error('
|
|
37244
|
+
async function ensureSkillContent(skillFile){const key=String(skillFile||S.activeSkillFile||'').trim();if(!key)throw new Error(t('no_skill_selected'));const row=S.skillMap[key]||{};if(String(row.content||'').trim())return row;const out=await api('/api/skillslab/skill?skill_file='+encodeURIComponent(key));const merged={...row,...out};upsertSkillRow(merged);return merged}
|
|
36005
37245
|
async function selectSkill(skillFile){S.activeSkillFile=String(skillFile||'').trim();renderSkills();try{await ensureSkillContent(S.activeSkillFile);renderSkillPreview();showError('')}catch(err){renderSkillPreview();showError(err.message||String(err))}}
|
|
36006
|
-
function renderSkillPreview(){const el=E('skillPreview');if(!el)return;const key=S.activeSkillFile||Object.keys(S.skillMap||{})[0]||'';if(!key){el.innerHTML=`<div class=\"mono\">${esc(t('no_skill_selected'))}</div>`;return}S.activeSkillFile=key;const row=S.skillMap[key]||{};const header
|
|
36007
|
-
function renderUploadReports(){const el=E('skillsUploadList');if(!el)return;const rows=(S.uploadReports||[]).slice(-20).reverse();el.innerHTML=rows.map(r=>`${esc(r.filename)} ·
|
|
37246
|
+
function renderSkillPreview(){const el=E('skillPreview');if(!el)return;const key=S.activeSkillFile||Object.keys(S.skillMap||{})[0]||'';if(!key){el.innerHTML=`<div class=\"mono\">${esc(t('no_skill_selected'))}</div>`;return}S.activeSkillFile=key;const row=S.skillMap[key]||{};const header=[`${t('meta_name')}: ${row.name||'-'}`,`${t('meta_path')}: ${row.skill_file||row.path||'-'}`,`${t('meta_provider')}: ${row.provider||'-'}`,`${t('meta_protocol')}: ${row.protocol||'-'}`,`${t('meta_description')}: ${row.description||'-'}`].join('\\n');const fullBody=String(row.content||row.preview||'').trim();el.innerHTML=`<div>${esc(header)}</div><hr><pre>${esc(trimText(fullBody||t('no_preview'),3600))}</pre>`}
|
|
37247
|
+
function renderUploadReports(){const el=E('skillsUploadList');if(!el)return;const rows=(S.uploadReports||[]).slice(-20).reverse();el.innerHTML=rows.map(r=>`${esc(r.filename)} · ${esc(t('upload_imported'))}=${esc(r.imported_count||0)} ${esc(t('upload_skipped'))}=${esc(r.skipped_count||0)} ${esc(t('upload_errors'))}=${esc(r.error_count||0)}`).join('<br>')||`<div>${esc(t('no_uploads'))}</div>`}
|
|
36008
37248
|
function renderSkills(){buildSkillMap();const treeEl=E('skillsTree');if(treeEl){const root=S.skillScan?.tree||{type:'dir',name:'skills',path:'',children:[]};treeEl.innerHTML=`<div class=\"skill-tree\">${treeNodeHtml(root,0)}</div>`;for(const el of treeEl.querySelectorAll('.skill-item')){el.onclick=()=>selectSkill(el.getAttribute('data-skill-file')||'').catch(err=>showError(err.message||String(err)));el.ondblclick=()=>loadSelectedSkillToFlow().catch(err=>showError(err.message||String(err)))}}renderSkillPreview();renderUploadReports();setStats()}
|
|
36009
37249
|
async function loadSelectedSkillToFlow(){const key=String(S.activeSkillFile||'').trim();if(!key)throw new Error(t('select_skill_first'));const row=await ensureSkillContent(key);const parsed=parseSkillToFlow(row.content||row.preview||'',row.name,row.description);S.flow={nodes:parsed.nodes,edges:parsed.edges};S.selectedNodeId=S.flow.nodes[0]?.id||null;renderFlow();renderNodeEditor();exportFlow();if(E('skillMarkdown'))E('skillMarkdown').value=String(row.content||'');if(E('skillName')&&!E('skillName').value.trim())E('skillName').value=flowSlug(row.name||parsed.title||'skill','skill');if(E('skillDesc')&&!E('skillDesc').value.trim())E('skillDesc').value=String(row.description||parsed.description||'');showError('')}
|
|
36010
37250
|
const FLOW_SIDES=['top','right','bottom','left'];
|
|
@@ -36050,7 +37290,7 @@ async function scanSkills(){try{const out=await api('/api/skillslab/skills');S.s
|
|
|
36050
37290
|
async function generateSkill(){try{const payload={skill_name:E('skillName').value.trim(),skill_path:E('skillPath').value.trim(),description:E('skillDesc').value.trim(),requirements:E('requirements').value.trim(),nodes:S.flow.nodes,edges:S.flow.edges,auto_inject:true,overwrite:true};const out=await api('/api/skillslab/generate',{method:'POST',body:JSON.stringify(payload)});if(out.skill_name)E('skillName').value=out.skill_name;if(out.skill_path)E('skillPath').value=out.skill_path;if(out.description)E('skillDesc').value=out.description;E('skillMarkdown').value=out.skill_markdown||'';await scanSkills();showError('')}catch(err){showError(err.message||String(err))}}
|
|
36051
37291
|
async function saveSkill(){try{const payload={path:E('skillPath').value.trim()||E('skillName').value.trim(),content:E('skillMarkdown').value,overwrite:true};await api('/api/skillslab/save',{method:'POST',body:JSON.stringify(payload)});await scanSkills();showError('')}catch(err){showError(err.message||String(err))}}
|
|
36052
37292
|
function isUploadSkillCandidate(name){const n=String(name||'').toLowerCase();if(n.endsWith('.zip')||n.endsWith('.md')||n.endsWith('.markdown')||n.endsWith('.txt'))return true;return n.endsWith('/skill.md')||n.endsWith('skill.md')}
|
|
36053
|
-
async function uploadSkillFiles(fileList){if(!fileList||!fileList.length)return;for(const file of Array.from(fileList)){const relName=String(file.webkitRelativePath||file.name||'').replace(/\\\\/g,'/');if(!isUploadSkillCandidate(relName)){S.uploadReports.push({filename:relName||file.name||'unknown',imported_count:0,skipped_count:1,error_count:0});continue}try{if(file.size>30*1024*1024){throw new Error(
|
|
37293
|
+
async function uploadSkillFiles(fileList){if(!fileList||!fileList.length)return;for(const file of Array.from(fileList)){const relName=String(file.webkitRelativePath||file.name||'').replace(/\\\\/g,'/');if(!isUploadSkillCandidate(relName)){S.uploadReports.push({filename:relName||file.name||'unknown',imported_count:0,skipped_count:1,error_count:0});continue}try{if(file.size>30*1024*1024){throw new Error(`${t('file_too_large')}: ${file.name} (>30MB)`)}const arr=await file.arrayBuffer();const payload={filename:relName||file.name,mime:file.type||'',content_b64:ab2b64(arr),overwrite:false};const out=await api('/api/skillslab/upload',{method:'POST',body:JSON.stringify(payload)});S.uploadReports.push({filename:relName||file.name,imported_count:Number(out.imported_count||0),skipped_count:Number(out.skipped_count||0),error_count:Number(out.error_count||0)});if(out.scan)S.skillScan=normalizeSkillScan(out.scan);if(Array.isArray(out.errors)&&out.errors.length){showError(String(out.errors[0].error||t('upload_parse_failed')))}else{showError('')}}catch(err){S.uploadReports.push({filename:relName||file.name,imported_count:0,skipped_count:0,error_count:1});showError(err.message||String(err))}}renderSkills()}
|
|
36054
37294
|
function bindSkillUpload(){const drop=E('skillsUploadDrop');const input=E('skillsUploadInput');const dirInput=E('skillsUploadDirInput');const fileBtn=E('skillsUploadFileBtn');const folderBtn=E('skillsUploadFolderBtn');if(!drop||!input||!dirInput)return;const consume=async(files,resetter)=>{try{await uploadSkillFiles(files)}catch(err){showError(err.message||String(err))}if(typeof resetter==='function')resetter()};drop.onclick=()=>input.click();if(fileBtn)fileBtn.onclick=()=>input.click();if(folderBtn)folderBtn.onclick=()=>dirInput.click();input.onchange=()=>consume(input.files,()=>{input.value=''});dirInput.onchange=()=>consume(dirInput.files,()=>{dirInput.value=''});for(const evt of ['dragenter','dragover']){drop.addEventListener(evt,e=>{e.preventDefault();drop.classList.add('dragover')})}for(const evt of ['dragleave','dragend']){drop.addEventListener(evt,e=>{e.preventDefault();drop.classList.remove('dragover')})}drop.addEventListener('drop',e=>{e.preventDefault();drop.classList.remove('dragover');consume(e.dataTransfer?.files||[])})}
|
|
36055
37295
|
function bindFlowZoom(){const outBtn=E('flowZoomOutBtn');const inBtn=E('flowZoomInBtn');if(outBtn)outBtn.onclick=()=>zoomFlowBy(-0.1);if(inBtn)inBtn.onclick=()=>zoomFlowBy(0.1);updateFlowZoomUI()}
|
|
36056
37296
|
function bindFlowPan(){const wrap=E('flowWrap');if(!wrap)return;wrap.addEventListener('mousedown',ev=>{if(ev.button!==0)return;const target=ev.target;if(!target||!target.closest||!target.closest('#flowWrap'))return;if(target.closest('.flow-node,.flow-port,input,textarea,select,button,a,label,.flow-zoom-pill'))return;S.drag=null;S.linkDrag=null;S.pan={sx:ev.clientX,sy:ev.clientY,left:wrap.scrollLeft,top:wrap.scrollTop};wrap.classList.add('flow-panning');ev.preventDefault()})}
|