@sakanano-moe/agenthelm 6.7.0 → 6.11.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/runtime/index.json +6 -6
- package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/agent-pack/hooks/hooks.json +32 -0
- package/runtime/plugins/agent-pack/plugin.json +1 -1
- package/runtime/plugins/agent-pack/references/local-only.md +126 -0
- package/runtime/plugins/agent-pack/references/nudge.en.md +58 -0
- package/runtime/plugins/agent-pack/references/nudge.md +58 -0
- package/runtime/plugins/agent-pack/runtime/agent_pack_nudge.py +573 -0
- package/runtime/plugins/agent-pack/runtime/agenthelm.py +419 -107
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +77 -53
- package/runtime/plugins/agent-pack/runtime/i18n_agent_pack.py +78 -0
- package/runtime/plugins/agent-pack/runtime/local_only.py +1130 -0
- package/runtime/plugins/agent-pack/runtime/locale/en.json +385 -0
- package/runtime/plugins/agent-pack/runtime/mcp_server.py +25 -0
- package/runtime/plugins/agent-pack/runtime/nudge_text.py +107 -0
- package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +9 -1
- package/runtime/plugins/agents-init/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/agents-init/content.json +17 -2
- package/runtime/plugins/agents-init/plugin.json +1 -1
- package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +47 -0
- package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +39 -1
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +404 -70
- package/runtime/plugins/agents-init/skills/agents-init/scripts/adopt.py +596 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/i18n_init.py +78 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +245 -72
- package/runtime/plugins/agents-init/skills/agents-init/scripts/locale/en.json +210 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/regions.py +99 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/scaffold.py +1081 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_adopt.py +551 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +46 -2
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_scaffold.py +555 -0
- package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +66 -0
- package/runtime/plugins/agents-init/skills/agents-init/skill.json +1 -1
- package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/references/inbox.en.md +48 -0
- package/runtime/plugins/opinion-manager/references/inbox.md +48 -0
- package/runtime/plugins/opinion-manager/runtime/context.py +19 -18
- package/runtime/plugins/opinion-manager/runtime/i18n_opinion.py +78 -0
- package/runtime/plugins/opinion-manager/runtime/inbox.py +397 -0
- package/runtime/plugins/opinion-manager/runtime/inbox_text.py +101 -0
- package/runtime/plugins/opinion-manager/runtime/lifecycle.py +45 -44
- package/runtime/plugins/opinion-manager/runtime/locale/en.json +184 -0
- package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +55 -50
- package/runtime/plugins/opinion-manager/runtime/versioned.py +84 -83
- package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +15 -2
- package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/content.json +30 -3
- package/runtime/plugins/project-orchestrator/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/references/case-workspaces.md +9 -1
- package/runtime/plugins/project-orchestrator/references/case.schema.json +1 -0
- package/runtime/plugins/project-orchestrator/references/sop-interview.md +83 -0
- package/runtime/plugins/project-orchestrator/references/sop.md +156 -0
- package/runtime/plugins/project-orchestrator/runtime/assets.py +52 -57
- package/runtime/plugins/project-orchestrator/runtime/case.py +139 -80
- package/runtime/plugins/project-orchestrator/runtime/case_structure.py +71 -0
- package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +47 -46
- package/runtime/plugins/project-orchestrator/runtime/i18n_orchestrator.py +78 -0
- package/runtime/plugins/project-orchestrator/runtime/locale/en.json +1409 -0
- package/runtime/plugins/project-orchestrator/runtime/memory.py +44 -43
- package/runtime/plugins/project-orchestrator/runtime/private_paths.py +119 -0
- package/runtime/plugins/project-orchestrator/runtime/project_base.py +37 -19
- package/runtime/plugins/project-orchestrator/runtime/project_cli.py +267 -174
- package/runtime/plugins/project-orchestrator/runtime/project_demo.py +130 -48
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +524 -134
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +159 -85
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +191 -106
- package/runtime/plugins/project-orchestrator/runtime/project_write.py +58 -55
- package/runtime/plugins/project-orchestrator/runtime/sop.py +1670 -0
- package/runtime/plugins/project-orchestrator/runtime/sop_structure.py +226 -0
- package/runtime/plugins/project-orchestrator/runtime/wf.py +74 -70
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +22 -0
- package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.en.md +57 -0
- package/runtime/plugins/project-orchestrator/sop-templates/blank/SOP.md +57 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.en.md +77 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/SOP.md +77 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.en.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/knowledge-digest/templates/digest.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.en.md +87 -0
- package/runtime/plugins/project-orchestrator/sop-templates/video-to-transcript/SOP.md +87 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.en.md +75 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/SOP.md +75 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.en.md +11 -0
- package/runtime/plugins/project-orchestrator/sop-templates/weekly-review/templates/review.md +12 -0
- package/runtime/plugins/project-orchestrator/templates/CASE.en.md +39 -0
|
@@ -3,14 +3,27 @@
|
|
|
3
3
|
用来在没有项目时体验菜单,也是界面测试的固定数据。数据形状与真实来源(project_real)
|
|
4
4
|
一致:资料结果沿用 ``assets.find`` 的字段,事项沿用 ``case.py`` 的 Frontmatter 字段。所有
|
|
5
5
|
“执行”都只返回模拟结果,并带 ``simulated: True``。
|
|
6
|
+
|
|
7
|
+
流程(SOP)的演示不同:它直接运行真实的 sop.py,但项目、SOP 与 Case 都在一个用完即删的
|
|
8
|
+
临时目录里(进程结束自动清理),不碰任何真实项目文件。唯一读取真实环境的是“执行前检查”:
|
|
9
|
+
它看的是这台机器上有没有对应的命令与环境变量,这正是它要演示的东西。
|
|
6
10
|
"""
|
|
7
11
|
from __future__ import annotations
|
|
8
12
|
|
|
13
|
+
import contextlib
|
|
9
14
|
import copy
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
10
17
|
import re
|
|
18
|
+
import shutil
|
|
19
|
+
import tempfile
|
|
20
|
+
import weakref
|
|
21
|
+
from pathlib import Path
|
|
11
22
|
from typing import Any
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
import sop_structure # noqa: E402
|
|
25
|
+
from project_base import EntryError, sop_call
|
|
26
|
+
from i18n_orchestrator import _, resolve_language # noqa: E402
|
|
14
27
|
|
|
15
28
|
DEMO_NOW = "2026-10-25T09:12:00+08:00"
|
|
16
29
|
FAULTS = ("index-broken", "offline", "runtime-old", "conflict")
|
|
@@ -264,6 +277,34 @@ DEFAULT_GROUPS = {"active": "active", "waiting": "waiting", "captured": "capture
|
|
|
264
277
|
DemoError = EntryError
|
|
265
278
|
|
|
266
279
|
|
|
280
|
+
# 演示数据本身是中文原文;按当前语言整棵翻译。旧编号、编号与路径是用来匹配和查找的标识,保持原样;
|
|
281
|
+
# 项目自定义的状态词(status 与 status_map 的键)一起翻译,两边保持对应。
|
|
282
|
+
KEEP_KEYS = {"legacy_ids", "id", "path", "command", "launcher"}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def translate_tree(value: Any, key: str | None = None) -> Any:
|
|
286
|
+
if isinstance(value, dict):
|
|
287
|
+
return {(_(item_key) if key == "status_map" and isinstance(item_key, str) else item_key):
|
|
288
|
+
item if item_key in KEEP_KEYS else translate_tree(item, item_key) for item_key, item in value.items()}
|
|
289
|
+
if isinstance(value, (list, tuple)):
|
|
290
|
+
return type(value)(translate_tree(item) for item in value)
|
|
291
|
+
return _(value) if isinstance(value, str) else value
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@contextlib.contextmanager
|
|
295
|
+
def _sop_home(root: Path):
|
|
296
|
+
"""演示期间把用户级 SOP 目录指到临时目录,结束后原样恢复环境变量。"""
|
|
297
|
+
previous = os.environ.get("AGENTHELM_SOP_HOME")
|
|
298
|
+
os.environ["AGENTHELM_SOP_HOME"] = str(root.parent / "user-sops")
|
|
299
|
+
try:
|
|
300
|
+
yield
|
|
301
|
+
finally:
|
|
302
|
+
if previous is None:
|
|
303
|
+
os.environ.pop("AGENTHELM_SOP_HOME", None)
|
|
304
|
+
else:
|
|
305
|
+
os.environ["AGENTHELM_SOP_HOME"] = previous
|
|
306
|
+
|
|
307
|
+
|
|
267
308
|
class DemoProvider:
|
|
268
309
|
"""内存里的虚构项目;所有结果带 demo 标记,所有写入只模拟。"""
|
|
269
310
|
|
|
@@ -271,11 +312,11 @@ class DemoProvider:
|
|
|
271
312
|
|
|
272
313
|
def __init__(self, scenario: str, fault: str | None = None) -> None:
|
|
273
314
|
if scenario not in SCENARIOS:
|
|
274
|
-
raise DemoError(
|
|
315
|
+
raise DemoError(_("没有这个演示场景:{0}").format(scenario), "usage", [_("可选:{0}").format(_('、').join(SCENARIOS))])
|
|
275
316
|
if fault is not None and fault not in FAULTS:
|
|
276
|
-
raise DemoError(
|
|
317
|
+
raise DemoError(_("没有这个演示故障:{0}").format(fault), "usage", [_("可选:{0}").format(_('、').join(FAULTS))])
|
|
277
318
|
self.scenario_id = scenario
|
|
278
|
-
self.data = copy.deepcopy(SCENARIOS[scenario])
|
|
319
|
+
self.data = translate_tree(copy.deepcopy(SCENARIOS[scenario]))
|
|
279
320
|
self.fault = fault
|
|
280
321
|
titles = {item["id"]: item["title"] for item in self.data["items"]}
|
|
281
322
|
for asset in self.data["assets"]:
|
|
@@ -283,6 +324,45 @@ class DemoProvider:
|
|
|
283
324
|
self.name = self.data["name"]
|
|
284
325
|
self.entry_api = 2 if fault == "runtime-old" else 1
|
|
285
326
|
self.resolved: set[str] = set() # fixes simulated in this session
|
|
327
|
+
self._sandbox: Path | None = None
|
|
328
|
+
|
|
329
|
+
# -------------------------------------------------------------- 流程(SOP):临时目录里的真实代码
|
|
330
|
+
|
|
331
|
+
def sop(self, argv: list[str]) -> tuple[int, dict, list[str]]:
|
|
332
|
+
root = self._sop_sandbox()
|
|
333
|
+
with _sop_home(root):
|
|
334
|
+
return sop_call(argv, root)
|
|
335
|
+
|
|
336
|
+
def _sop_sandbox(self) -> Path:
|
|
337
|
+
if self._sandbox is not None:
|
|
338
|
+
return self._sandbox / "project"
|
|
339
|
+
self._sandbox = Path(tempfile.mkdtemp(prefix="agenthelm-demo-"))
|
|
340
|
+
weakref.finalize(self, shutil.rmtree, self._sandbox, True) # 进程结束或对象回收时清理
|
|
341
|
+
root = self._sandbox / "project"
|
|
342
|
+
profile = root / ".agents" / "moe.sakanano.agent-pack" / "project.json"
|
|
343
|
+
profile.parent.mkdir(parents=True)
|
|
344
|
+
# 演示项目的文件语言跟着当前界面语言:演示本身就是按界面语言讲的故事
|
|
345
|
+
profile.write_text(json.dumps({"schema_version": "1.0", "name": self.name, "language": resolve_language(), "sop": {"root": "sops"},
|
|
346
|
+
"work": {"mode": "case-workspace", "case": {"root": "cases"}}}), encoding="utf-8")
|
|
347
|
+
if self.scenario_id != "blank":
|
|
348
|
+
self._seed_sops(root)
|
|
349
|
+
return root
|
|
350
|
+
|
|
351
|
+
def _seed_sops(self, root: Path) -> None:
|
|
352
|
+
"""三个已启用的样例流程:让 list / preflight / run / step 马上有东西可看。"""
|
|
353
|
+
habits = ("--set", "on_error=ask", "--set", "on_missing_tool=ask", "--set", "artifact_keep=ask",
|
|
354
|
+
"--set", "resume=from-failed-step", "--set", "confirm_costly=ask")
|
|
355
|
+
with _sop_home(root):
|
|
356
|
+
sop_call(["defaults", *habits, "--apply"], root)
|
|
357
|
+
for slug in ("knowledge-digest", "weekly-review", "video-to-transcript"):
|
|
358
|
+
sop_call(["new", "--slug", slug, "--template", slug, "--apply"], root)
|
|
359
|
+
path = root / "sops" / slug / "SOP.md"
|
|
360
|
+
text = path.read_text(encoding="utf-8")
|
|
361
|
+
for marker in sop_structure.PLACEHOLDERS:
|
|
362
|
+
text = text.replace(marker, _("演示"))
|
|
363
|
+
path.write_text(text, encoding="utf-8")
|
|
364
|
+
sop_call(["decide", slug, "--confirm", "trigger", "--confirm", "requires", "--confirm", "scope", "--apply"], root)
|
|
365
|
+
sop_call(["activate", slug, "--apply"], root)
|
|
286
366
|
|
|
287
367
|
# -------------------------------------------------------------- overview
|
|
288
368
|
|
|
@@ -302,7 +382,7 @@ class DemoProvider:
|
|
|
302
382
|
levels: dict[str, int] = {}
|
|
303
383
|
for finding in self.health()["findings"]:
|
|
304
384
|
levels[finding["level"]] = levels.get(finding["level"], 0) + 1
|
|
305
|
-
return {"name": self.data["name"], "kind": self.data["kind"], "kind_label": self.data["kind"]
|
|
385
|
+
return {"name": self.data["name"], "kind": self.data["kind"], "kind_label": _("{0}项目").format(self.data["kind"]),
|
|
306
386
|
"launcher": self.data["launcher"],
|
|
307
387
|
"index": self.index_state(), "items": counts, "health": levels, "read_at": DEMO_NOW,
|
|
308
388
|
"tools": {"declared": len(self.data["tools"]),
|
|
@@ -313,8 +393,8 @@ class DemoProvider:
|
|
|
313
393
|
def find(self, terms: list[str], *, ext: str | None = None, container: str | None = None,
|
|
314
394
|
limit: int = 10) -> dict:
|
|
315
395
|
if self.index_state()["status"] == "broken":
|
|
316
|
-
raise DemoError("资料索引损坏,暂时无法查找", "index-broken",
|
|
317
|
-
["重建索引:只改用户缓存里的索引,不改项目文件(5 修复与更新 → 重建索引,或 fix rebuild-index --apply)"])
|
|
396
|
+
raise DemoError(_("资料索引损坏,暂时无法查找"), "index-broken",
|
|
397
|
+
[_("重建索引:只改用户缓存里的索引,不改项目文件(5 修复与更新 → 重建索引,或 fix rebuild-index --apply)")])
|
|
318
398
|
results = []
|
|
319
399
|
for asset in self.data["assets"]:
|
|
320
400
|
haystack = " ".join([asset["path"], asset["title"], asset["case_title"], asset["modified"]]).lower()
|
|
@@ -333,9 +413,9 @@ class DemoProvider:
|
|
|
333
413
|
return {"path": path, "inbound": known.get("inbound", []), "outbound": known.get("outbound", [])}
|
|
334
414
|
|
|
335
415
|
def act(self, action: str, path: str) -> dict:
|
|
336
|
-
verbs = {"open": "打开", "reveal": "在文件管理器中显示", "copy": "复制路径"}
|
|
416
|
+
verbs = {"open": _("打开"), "reveal": _("在文件管理器中显示"), "copy": _("复制路径")}
|
|
337
417
|
return {"action": action, "path": path, "simulated": True,
|
|
338
|
-
"message":
|
|
418
|
+
"message": _("【演示】将{0} {1}(未实际执行)").format(verbs.get(action, action), path)}
|
|
339
419
|
|
|
340
420
|
# -------------------------------------------------------------- progress
|
|
341
421
|
|
|
@@ -354,9 +434,9 @@ class DemoProvider:
|
|
|
354
434
|
exact = [item for item in self.data["items"] if token in (item["id"], *item["legacy_ids"], *item["aliases"])]
|
|
355
435
|
matches = exact or [item for item in self.data["items"] if _item_hits(token, item)]
|
|
356
436
|
if not matches:
|
|
357
|
-
raise DemoError(
|
|
437
|
+
raise DemoError(_("没有找到事项:{0}").format(token), "not-found", [_("试试更短的词,或在「看进展」里浏览全部")])
|
|
358
438
|
if len(matches) > 1:
|
|
359
|
-
raise DemoError(
|
|
439
|
+
raise DemoError(_("有 {0} 个事项都匹配“{1}”").format(len(matches), token), "ambiguous",
|
|
360
440
|
[f"{item['id']} {item['title']}" for item in matches])
|
|
361
441
|
item = matches[0]
|
|
362
442
|
return {**item, "group": self._group(item), "source": self.data["items_source"]}
|
|
@@ -372,29 +452,31 @@ class DemoProvider:
|
|
|
372
452
|
index = self.index_state()["status"]
|
|
373
453
|
record = self.data["record"]
|
|
374
454
|
return [
|
|
375
|
-
{"id": "find", "title": "找资料", "state": "unavailable" if index == "broken" else "available",
|
|
376
|
-
"reason": "资料索引损坏,可重建" if index == "broken" else "", "offline": True},
|
|
377
|
-
{"id": "progress", "title": "看进展", "state": "available", "reason": "", "offline": True},
|
|
378
|
-
{"id": "guide", "title": "项目指南", "state": "available", "reason": "", "offline": True},
|
|
379
|
-
{"id": "health", "title": "检查项目与环境", "state": "available",
|
|
380
|
-
"reason": "联网项使用缓存" if self.fault == "offline" else "", "offline": True},
|
|
381
|
-
{"id": "fix", "title": "修复与更新", "state": "available",
|
|
382
|
-
"reason": "离线:需要联网的更新只能预览" if self.fault == "offline" else "", "offline": False},
|
|
383
|
-
{"id": "record", "title": "记录事项", "state": "available" if record["write"] else "unavailable",
|
|
384
|
-
"reason": "" if record["write"] else "项目工具没有写接口", "offline": True},
|
|
385
|
-
{"id": "tools", "title": "项目工具",
|
|
455
|
+
{"id": "find", "title": _("找资料"), "state": "unavailable" if index == "broken" else "available",
|
|
456
|
+
"reason": _("资料索引损坏,可重建") if index == "broken" else "", "offline": True},
|
|
457
|
+
{"id": "progress", "title": _("看进展"), "state": "available", "reason": "", "offline": True},
|
|
458
|
+
{"id": "guide", "title": _("项目指南"), "state": "available", "reason": "", "offline": True},
|
|
459
|
+
{"id": "health", "title": _("检查项目与环境"), "state": "available",
|
|
460
|
+
"reason": _("联网项使用缓存") if self.fault == "offline" else "", "offline": True},
|
|
461
|
+
{"id": "fix", "title": _("修复与更新"), "state": "available",
|
|
462
|
+
"reason": _("离线:需要联网的更新只能预览") if self.fault == "offline" else "", "offline": False},
|
|
463
|
+
{"id": "record", "title": _("记录事项"), "state": "available" if record["write"] else "unavailable",
|
|
464
|
+
"reason": "" if record["write"] else _("项目工具没有写接口"), "offline": True},
|
|
465
|
+
{"id": "tools", "title": _("项目工具"),
|
|
386
466
|
"state": "available" if self.data["tools"] else "unavailable",
|
|
387
|
-
"reason": "" if self.data["tools"] else "项目没有声明专用工具", "offline": True},
|
|
467
|
+
"reason": "" if self.data["tools"] else _("项目没有声明专用工具"), "offline": True},
|
|
468
|
+
{"id": "sop", "title": _("流程(SOP)"), "state": "available", "offline": True,
|
|
469
|
+
"reason": _("演示:在临时目录里运行,结束后自动清理")},
|
|
388
470
|
]
|
|
389
471
|
|
|
390
472
|
def health(self) -> dict:
|
|
391
473
|
findings = [finding for finding in copy.deepcopy(self.data["health"])
|
|
392
474
|
if not any(step["action"] == f"fix:{fix_id}" for step in finding["next"] for fix_id in self.resolved)]
|
|
393
475
|
if self.index_state()["status"] == "broken":
|
|
394
|
-
findings.insert(0, {"id": "rebuild-index", "level": "must", "area": "资料", "title": "资料索引损坏",
|
|
395
|
-
"reason": "缓存里的索引文件无法读取(可能是上次重建被中断)",
|
|
396
|
-
"impact": "「找资料」暂时不可用;项目文件本身没有受影响",
|
|
397
|
-
"next": [{"label": "重建索引(只改用户缓存)", "action": "fix:rebuild-index"}]})
|
|
476
|
+
findings.insert(0, {"id": "rebuild-index", "level": "must", "area": _("资料"), "title": _("资料索引损坏"),
|
|
477
|
+
"reason": _("缓存里的索引文件无法读取(可能是上次重建被中断)"),
|
|
478
|
+
"impact": _("「找资料」暂时不可用;项目文件本身没有受影响"),
|
|
479
|
+
"next": [{"label": _("重建索引(只改用户缓存)"), "action": "fix:rebuild-index"}]})
|
|
398
480
|
online = {"status": "offline", "cached_at": "2026-10-22T09:00:00+08:00"} if self.fault == "offline" \
|
|
399
481
|
else {"status": "online", "cached_at": DEMO_NOW}
|
|
400
482
|
return {"findings": findings, "ok": self.data["ok"], "read_at": DEMO_NOW, "update_source": online}
|
|
@@ -405,20 +487,20 @@ class DemoProvider:
|
|
|
405
487
|
fixes = {key: value for key, value in self.data["fixes"].items() if key not in self.resolved}
|
|
406
488
|
if self.index_state()["status"] == "broken":
|
|
407
489
|
fixes["rebuild-index"] = {
|
|
408
|
-
"title": "重建资料索引", "kind": "index", "network": False,
|
|
409
|
-
"changes": ["删除损坏的索引并按当前文件重新登记"],
|
|
410
|
-
"writes": [{"path": "~/.cache/agenthelm/assets/<项目摘要>/", "scope": "用户缓存"}],
|
|
411
|
-
"custom": [], "breaking": False, "recovery": "不涉及项目文件;索引可随时再次重建",
|
|
412
|
-
"verify": "重新读取索引状态:最新,文件数与项目一致"}
|
|
490
|
+
"title": _("重建资料索引"), "kind": "index", "network": False,
|
|
491
|
+
"changes": [_("删除损坏的索引并按当前文件重新登记")],
|
|
492
|
+
"writes": [{"path": _("~/.cache/agenthelm/assets/<项目摘要>/"), "scope": _("用户缓存")}],
|
|
493
|
+
"custom": [], "breaking": False, "recovery": _("不涉及项目文件;索引可随时再次重建"),
|
|
494
|
+
"verify": _("重新读取索引状态:最新,文件数与项目一致")}
|
|
413
495
|
chosen = ids or sorted(fixes)
|
|
414
496
|
unknown = [fix_id for fix_id in chosen if fix_id not in fixes]
|
|
415
497
|
if unknown:
|
|
416
|
-
raise DemoError("没有这些可处理项:" + "、".join(unknown), "not-found",
|
|
417
|
-
["可处理:" + ("、".join(sorted(fixes)) or "(无)")])
|
|
498
|
+
raise DemoError(_("没有这些可处理项:") + _("、").join(unknown), "not-found",
|
|
499
|
+
[_("可处理:") + (_("、").join(sorted(fixes)) or _("(无)"))])
|
|
418
500
|
steps = []
|
|
419
501
|
for fix_id in chosen:
|
|
420
502
|
step = {"id": fix_id, **copy.deepcopy(fixes[fix_id])}
|
|
421
|
-
step["blocked"] = "离线:需要联网,只能预览" if step["network"] and self.fault == "offline" else ""
|
|
503
|
+
step["blocked"] = _("离线:需要联网,只能预览") if step["network"] and self.fault == "offline" else ""
|
|
422
504
|
steps.append(step)
|
|
423
505
|
return {"steps": steps, "simulated": True, "update_source": self.health()["update_source"]}
|
|
424
506
|
|
|
@@ -441,12 +523,12 @@ class DemoProvider:
|
|
|
441
523
|
def record_preview(self, target: str | None, change: dict) -> dict:
|
|
442
524
|
record = self.data["record"]
|
|
443
525
|
if not record["write"]:
|
|
444
|
-
raise DemoError("这个项目不能在本入口记录事项", "unavailable",
|
|
445
|
-
[record["gap"], "需要时请直接使用项目工具,或请项目维护者补充写接口"])
|
|
526
|
+
raise DemoError(_("这个项目不能在本入口记录事项"), "unavailable",
|
|
527
|
+
[record["gap"], _("需要时请直接使用项目工具,或请项目维护者补充写接口")])
|
|
446
528
|
if target is None:
|
|
447
529
|
slug = re.sub(r"[^a-z0-9]+", "-", (change.get("slug") or "new-item").lower()).strip("-") or "new-item"
|
|
448
530
|
path = f"cases/CASE-20261025-001-{slug}/CASE.md"
|
|
449
|
-
lines = [
|
|
531
|
+
lines = [_("+ 新建 {0}").format(path), f"+ title: {change.get('title', '')}", "+ status: active",
|
|
450
532
|
f"+ goal: {change.get('goal', '')}", f"+ next_action: {change.get('next_action', '')}"]
|
|
451
533
|
return {"target": None, "path": path, "lines": lines, "expect_updated_at": None, "simulated": True}
|
|
452
534
|
item = self.item(target)
|
|
@@ -455,22 +537,22 @@ class DemoProvider:
|
|
|
455
537
|
if change.get(key) is not None:
|
|
456
538
|
lines += [f"- {label}: {item.get(key) or ''}", f"+ {label}: {change[key]}"]
|
|
457
539
|
if change.get("log"):
|
|
458
|
-
lines.append(
|
|
540
|
+
lines.append(_("+ 工作记录:- 2026-10-25T09:15 {0}").format(change['log']))
|
|
459
541
|
for path in change.get("add_deliverable") or []:
|
|
460
542
|
lines.append(f"+ deliverables: {path}")
|
|
461
543
|
if not lines:
|
|
462
|
-
raise DemoError("没有要修改的内容", "usage", ["至少给出一项:记录、下一动作、等待对象或状态"])
|
|
544
|
+
raise DemoError(_("没有要修改的内容"), "usage", [_("至少给出一项:记录、下一动作、等待对象或状态")])
|
|
463
545
|
lines.append(f" updated_at: {item['updated_at']} → 2026-10-25T09:15:00+08:00")
|
|
464
546
|
return {"target": item["id"], "path": item["path"], "lines": lines,
|
|
465
547
|
"expect_updated_at": item["updated_at"], "simulated": True}
|
|
466
548
|
|
|
467
549
|
def record_apply(self, preview: dict) -> dict:
|
|
468
550
|
if self.fault == "conflict" and preview["target"]:
|
|
469
|
-
raise DemoError("这个事项在你预览之后被修改过,没有写入", "conflict",
|
|
470
|
-
["最新修改:2026-10-25T09:14(CASE.md 的 updated_at 已变化)",
|
|
471
|
-
"重新打开预览,确认对方的改动后再提交"])
|
|
551
|
+
raise DemoError(_("这个事项在你预览之后被修改过,没有写入"), "conflict",
|
|
552
|
+
[_("最新修改:2026-10-25T09:14(CASE.md 的 updated_at 已变化)"),
|
|
553
|
+
_("重新打开预览,确认对方的改动后再提交")])
|
|
472
554
|
return {"path": preview["path"], "simulated": True,
|
|
473
|
-
"verify": "【演示】写入后重新读取:内容与预览一致,未知字段与原有正文保留"}
|
|
555
|
+
"verify": _("【演示】写入后重新读取:内容与预览一致,未知字段与原有正文保留")}
|
|
474
556
|
|
|
475
557
|
# -------------------------------------------------------------- tools
|
|
476
558
|
|
|
@@ -480,11 +562,11 @@ class DemoProvider:
|
|
|
480
562
|
def run_tool(self, tool_id: str) -> dict:
|
|
481
563
|
tool = next((tool for tool in self.data["tools"] if tool["id"] == tool_id), None)
|
|
482
564
|
if tool is None:
|
|
483
|
-
raise DemoError(
|
|
565
|
+
raise DemoError(_("项目没有声明这个工具:{0}").format(tool_id), "not-found")
|
|
484
566
|
if tool["state"] != "available":
|
|
485
|
-
raise DemoError(
|
|
567
|
+
raise DemoError(_("“{0}”当前不可用").format(tool['title']), "unavailable", [tool["reason"]])
|
|
486
568
|
return {"id": tool_id, "simulated": True,
|
|
487
|
-
"message":
|
|
569
|
+
"message": _("【演示】将运行:{0}(未实际执行)").format(' '.join(tool['command']))}
|
|
488
570
|
|
|
489
571
|
|
|
490
572
|
def _term_hits(term: str, haystack: str) -> bool:
|
|
@@ -501,4 +583,4 @@ def _item_hits(term: str, item: dict) -> bool:
|
|
|
501
583
|
|
|
502
584
|
|
|
503
585
|
def describe() -> list[dict[str, Any]]:
|
|
504
|
-
return [{"id": key, "name": value["name"], "kind": value["kind"]} for key, value in SCENARIOS.items()]
|
|
586
|
+
return [{"id": key, "name": _(value["name"]), "kind": _(value["kind"])} for key, value in SCENARIOS.items()]
|