xg-cli 1.0__py3-none-any.whl

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.
Files changed (130) hide show
  1. xg/__init__.py +3 -0
  2. xg/__main__.py +3 -0
  3. xg/adaptive/__init__.py +27 -0
  4. xg/adaptive/calibrate.py +191 -0
  5. xg/adaptive/feedback.py +170 -0
  6. xg/adaptive/learned_rules.py +284 -0
  7. xg/adaptive/signals.py +125 -0
  8. xg/adaptive/store.py +164 -0
  9. xg/agent/__init__.py +0 -0
  10. xg/agent/plan.py +631 -0
  11. xg/agent/react.py +268 -0
  12. xg/agent/team.py +1793 -0
  13. xg/assets/router.lgb +0 -0
  14. xg/assets/router_semantics.json +21278 -0
  15. xg/assets/router_semantics.onnx +0 -0
  16. xg/cli/__init__.py +0 -0
  17. xg/cli/app.py +1372 -0
  18. xg/cli/commands.py +921 -0
  19. xg/cli/completion.py +740 -0
  20. xg/cli/help.py +185 -0
  21. xg/cli/train.py +200 -0
  22. xg/config/__init__.py +0 -0
  23. xg/config/env_writer.py +121 -0
  24. xg/config/manager.py +446 -0
  25. xg/config/mcp.py +229 -0
  26. xg/config/provider_service.py +267 -0
  27. xg/config/providers.py +46 -0
  28. xg/config/settings.py +258 -0
  29. xg/config/skills.py +100 -0
  30. xg/config/smart_router_service.py +121 -0
  31. xg/config/web.py +140 -0
  32. xg/input_history/__init__.py +7 -0
  33. xg/input_history/models.py +28 -0
  34. xg/input_history/persistence.py +126 -0
  35. xg/input_history/policy.py +39 -0
  36. xg/input_history/prompt_toolkit.py +36 -0
  37. xg/input_history/store.py +118 -0
  38. xg/llm/__init__.py +0 -0
  39. xg/llm/client.py +49 -0
  40. xg/llm/factory.py +40 -0
  41. xg/llm/openai_compat.py +275 -0
  42. xg/llm/types.py +98 -0
  43. xg/mcp/__init__.py +4 -0
  44. xg/mcp/http.py +192 -0
  45. xg/mcp/manager.py +726 -0
  46. xg/mcp/models.py +86 -0
  47. xg/mcp/protocol.py +62 -0
  48. xg/mcp/resources.py +72 -0
  49. xg/mcp/schema.py +137 -0
  50. xg/mcp/stdio.py +210 -0
  51. xg/mcp/transport.py +66 -0
  52. xg/memory/__init__.py +15 -0
  53. xg/memory/context.py +327 -0
  54. xg/memory/manager.py +111 -0
  55. xg/memory/models.py +41 -0
  56. xg/memory/project.py +187 -0
  57. xg/memory/store.py +144 -0
  58. xg/router/__init__.py +124 -0
  59. xg/router/features.py +66 -0
  60. xg/router/keywords.py +50 -0
  61. xg/router/ml_router.py +178 -0
  62. xg/router/model_tiers.py +73 -0
  63. xg/router/postprocess.py +167 -0
  64. xg/router/rule_router.py +77 -0
  65. xg/router/semantic.py +138 -0
  66. xg/safety/__init__.py +0 -0
  67. xg/safety/audit.py +96 -0
  68. xg/safety/guards.py +106 -0
  69. xg/safety/hitl.py +73 -0
  70. xg/skill/__init__.py +9 -0
  71. xg/skill/errors.py +45 -0
  72. xg/skill/loader.py +42 -0
  73. xg/skill/models.py +57 -0
  74. xg/skill/parser.py +93 -0
  75. xg/skill/policy.py +40 -0
  76. xg/skill/prompt.py +45 -0
  77. xg/skill/registry.py +169 -0
  78. xg/tool/__init__.py +0 -0
  79. xg/tool/builtin.py +356 -0
  80. xg/tool/registry.py +228 -0
  81. xg/tui/__init__.py +34 -0
  82. xg/tui/app.py +612 -0
  83. xg/tui/controller.py +1296 -0
  84. xg/tui/diagrams/__init__.py +22 -0
  85. xg/tui/diagrams/layout.py +110 -0
  86. xg/tui/diagrams/markdown.py +39 -0
  87. xg/tui/diagrams/model.py +36 -0
  88. xg/tui/diagrams/parser.py +119 -0
  89. xg/tui/diagrams/renderer.py +551 -0
  90. xg/tui/i18n.py +169 -0
  91. xg/tui/messages.py +45 -0
  92. xg/tui/plan_renderables.py +147 -0
  93. xg/tui/reducer.py +1029 -0
  94. xg/tui/renderables.py +252 -0
  95. xg/tui/state.py +240 -0
  96. xg/tui/theme.tcss +208 -0
  97. xg/tui/widgets/__init__.py +1 -0
  98. xg/tui/widgets/action_card.py +94 -0
  99. xg/tui/widgets/agent_group_card.py +39 -0
  100. xg/tui/widgets/approval_modal.py +59 -0
  101. xg/tui/widgets/collapsible_card.py +40 -0
  102. xg/tui/widgets/command_suggestions.py +128 -0
  103. xg/tui/widgets/composer.py +151 -0
  104. xg/tui/widgets/config_panel.py +198 -0
  105. xg/tui/widgets/confirm_modal.py +31 -0
  106. xg/tui/widgets/footer.py +9 -0
  107. xg/tui/widgets/header.py +118 -0
  108. xg/tui/widgets/inspector.py +378 -0
  109. xg/tui/widgets/plan_modal.py +53 -0
  110. xg/tui/widgets/provider_form.py +141 -0
  111. xg/tui/widgets/queue_status.py +30 -0
  112. xg/tui/widgets/smart_router_form.py +95 -0
  113. xg/tui/widgets/transcript.py +354 -0
  114. xg/tui/workers.py +14 -0
  115. xg/web/__init__.py +21 -0
  116. xg/web/errors.py +57 -0
  117. xg/web/extract.py +127 -0
  118. xg/web/fetch.py +106 -0
  119. xg/web/markdown.py +77 -0
  120. xg/web/models.py +91 -0
  121. xg/web/providers.py +79 -0
  122. xg/web/search.py +118 -0
  123. xg/web/searxng.py +27 -0
  124. xg/web/serpapi.py +29 -0
  125. xg/web/url_policy.py +110 -0
  126. xg/web/zhipu.py +27 -0
  127. xg_cli-1.0.dist-info/METADATA +284 -0
  128. xg_cli-1.0.dist-info/RECORD +130 -0
  129. xg_cli-1.0.dist-info/WHEEL +4 -0
  130. xg_cli-1.0.dist-info/entry_points.txt +2 -0
xg/tui/i18n.py ADDED
@@ -0,0 +1,169 @@
1
+ """Small, dependency-free UI translations used by the Inspector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+
8
+ UiLanguage = Literal["en", "zh"]
9
+ SUPPORTED_UI_LANGUAGES: tuple[UiLanguage, ...] = ("en", "zh")
10
+
11
+
12
+ def normalize_language(value: object) -> UiLanguage:
13
+ """Return a supported UI language, falling back to the default English."""
14
+ return "zh" if str(value).strip().lower() == "zh" else "en"
15
+
16
+
17
+ INSPECTOR_TEXT: dict[str, dict[str, str]] = {
18
+ "en": {
19
+ "view.session": "Session",
20
+ "view.plan": "Plan",
21
+ "view.memory": "Memory",
22
+ "view.safety": "Safety",
23
+ "label.provider": "provider",
24
+ "label.model": "model",
25
+ "label.status": "status",
26
+ "label.estimated_input": "estimated input",
27
+ "label.model_window": "model window",
28
+ "label.window_usage": "window usage",
29
+ "label.input_budget": "input budget",
30
+ "label.budget_usage": "budget usage",
31
+ "label.source": "source",
32
+ "label.last_request": "last request",
33
+ "label.prompt": "prompt",
34
+ "label.completion": "completion",
35
+ "label.total": "total",
36
+ "label.session_usage": "session usage",
37
+ "label.compaction": "compaction",
38
+ "label.count": "count",
39
+ "label.last": "last",
40
+ "label.goal": "goal",
41
+ "label.current_round": "current round",
42
+ "label.progress": "progress",
43
+ "label.failures": "failures",
44
+ "label.project_root": "project root",
45
+ "label.warnings": "warnings",
46
+ "label.entries": "entries",
47
+ "label.last_operation": "last operation",
48
+ "label.store": "store",
49
+ "label.session_allow_all": "session allow all",
50
+ "label.tool": "tool",
51
+ "label.level": "level",
52
+ "label.decision": "decision",
53
+ "label.reason": "reason",
54
+ "label.last_rejection": "last rejection",
55
+ "section.context": "Context",
56
+ "section.last_request": "Last request",
57
+ "section.session_usage": "Session usage",
58
+ "section.compaction": "Compaction",
59
+ "section.tasks": "Tasks",
60
+ "section.project_memory": "Project memory",
61
+ "section.long_term_memory": "Long-term memory",
62
+ "section.current_approval": "Current approval",
63
+ "section.last_decision": "Last decision",
64
+ "section.policy": "Policy",
65
+ "empty.no_plan": "No plan",
66
+ "unit.token": "token",
67
+ "unit.tasks": "tasks",
68
+ "unit.round": "Round {current} / {total}",
69
+ },
70
+ "zh": {
71
+ "view.session": "Session",
72
+ "view.plan": "Plan",
73
+ "view.memory": "Memory",
74
+ "view.safety": "Safety",
75
+ "label.provider": "Provider",
76
+ "label.model": "Model",
77
+ "label.status": "Status",
78
+ "label.estimated_input": "预估输入",
79
+ "label.model_window": "上下文窗口",
80
+ "label.window_usage": "窗口使用率",
81
+ "label.input_budget": "输入预算",
82
+ "label.budget_usage": "预算使用率",
83
+ "label.source": "来源",
84
+ "label.last_request": "最近一次请求",
85
+ "label.prompt": "Prompt",
86
+ "label.completion": "Completion",
87
+ "label.total": "Total",
88
+ "label.session_usage": "Session 使用量",
89
+ "label.compaction": "Compaction",
90
+ "label.count": "次数",
91
+ "label.last": "最近一次",
92
+ "label.goal": "目标",
93
+ "label.current_round": "当前轮次",
94
+ "label.progress": "进度",
95
+ "label.failures": "失败次数",
96
+ "label.project_root": "项目根目录",
97
+ "label.warnings": "警告数",
98
+ "label.entries": "条目数",
99
+ "label.last_operation": "最近一次操作",
100
+ "label.store": "Store",
101
+ "label.session_allow_all": "Session 全部允许",
102
+ "label.tool": "Tool",
103
+ "label.level": "级别",
104
+ "label.decision": "Decision",
105
+ "label.reason": "原因",
106
+ "label.last_rejection": "最近一次拒绝",
107
+ "section.context": "Context",
108
+ "section.last_request": "最近一次请求",
109
+ "section.session_usage": "Session 使用量",
110
+ "section.compaction": "Compaction",
111
+ "section.tasks": "Tasks",
112
+ "section.project_memory": "项目 Memory",
113
+ "section.long_term_memory": "长期 Memory",
114
+ "section.current_approval": "当前 approval",
115
+ "section.last_decision": "最近一次决策",
116
+ "section.policy": "Policy",
117
+ "empty.no_plan": "暂无 Plan",
118
+ "unit.token": "token",
119
+ "unit.tasks": "个 Task",
120
+ "unit.round": "第 {current} / {total} 轮",
121
+ },
122
+ }
123
+
124
+
125
+ STATUS_ZH: dict[str, str] = {
126
+ "idle": "空闲 (Idle)",
127
+ "running": "执行中 (Running)",
128
+ "working": "工作中 (Working)",
129
+ "waiting": "等待中 (waiting)",
130
+ "waiting approval": "等待审批 (Waiting approval)",
131
+ "review": "审阅中 (review)",
132
+ "plan review": "等待 Plan 审阅 (Plan review)",
133
+ "done": "已完成 (Done)",
134
+ "success": "成功 (success)",
135
+ "approved": "已批准 (approved)",
136
+ "active": "生效中 (active)",
137
+ "on": "开启 (on)",
138
+ "off": "关闭 (off)",
139
+ "yes": "是 (yes)",
140
+ "no": "否 (no)",
141
+ "loaded": "已加载 (loaded)",
142
+ "available": "可用 (available)",
143
+ "failed": "失败 (failed)",
144
+ "error": "错误 (Error)",
145
+ "rejected": "已拒绝 (rejected)",
146
+ "denied": "已拒绝 (denied)",
147
+ "cancelled": "已取消 (cancelled)",
148
+ "inactive": "未生效 (inactive)",
149
+ "unavailable": "不可用 (unavailable)",
150
+ "not found": "未找到 (not found)",
151
+ "blocked": "已阻塞 (blocked)",
152
+ "skipped": "已跳过 (skipped)",
153
+ }
154
+
155
+
156
+ def translate(language: UiLanguage, key: str, **values: object) -> str:
157
+ """Translate a fixed UI key and format only its explicit placeholders."""
158
+ lang = normalize_language(language)
159
+ value = INSPECTOR_TEXT.get(lang, {}).get(key)
160
+ if value is None:
161
+ value = INSPECTOR_TEXT["en"].get(key, key)
162
+ return value.format(**values) if values else value
163
+
164
+
165
+ def translate_status(language: UiLanguage, status: str) -> str:
166
+ """Translate a canonical status while retaining its English meaning."""
167
+ if normalize_language(language) == "en":
168
+ return status
169
+ return STATUS_ZH.get(status.lower(), status)
xg/tui/messages.py ADDED
@@ -0,0 +1,45 @@
1
+ """Small Textual messages used to bridge controller callbacks to widgets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.message import Message
6
+
7
+ from xg.tui.state import InspectorView, TuiState
8
+
9
+
10
+ class StateChanged(Message):
11
+ def __init__(self, state: TuiState) -> None:
12
+ super().__init__()
13
+ self.state = state
14
+
15
+
16
+ class TraceCardToggled(Message):
17
+ """A user clicked or activated one collapsible execution card."""
18
+
19
+ def __init__(self, item_id: str) -> None:
20
+ super().__init__()
21
+ self.item_id = item_id
22
+
23
+
24
+ class AgentGroupToggled(Message):
25
+ """A user expanded or collapsed one Team Agent conversation group."""
26
+
27
+ def __init__(self, group_id: str) -> None:
28
+ super().__init__()
29
+ self.group_id = group_id
30
+
31
+
32
+ class CommandSuggestionSelected(Message):
33
+ """A local command suggestion was selected without being submitted."""
34
+
35
+ def __init__(self, candidate) -> None:
36
+ super().__init__()
37
+ self.candidate = candidate
38
+
39
+
40
+ class InspectorViewSelected(Message):
41
+ """A user selected one of the Inspector's stable view tabs."""
42
+
43
+ def __init__(self, view: InspectorView) -> None:
44
+ super().__init__()
45
+ self.view = view
@@ -0,0 +1,147 @@
1
+ """Plan review flowchart renderables for the Textual transcript."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from rich.console import Console, ConsoleOptions, Group, RenderResult
8
+ from rich.panel import Panel
9
+ from rich.text import Text
10
+
11
+ from xg.agent.plan import Plan, PlanTask
12
+ from xg.agent.team import TeamPlan
13
+ from xg.tui.diagrams.model import FlowchartEdge, FlowchartModel, FlowchartNode
14
+ from xg.tui.diagrams.renderer import render_flowchart
15
+ from xg.tui.state import TranscriptItem
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class PlanFlowchartData:
20
+ model: FlowchartModel
21
+ rank_by_node: dict[str, int]
22
+ ordered_ids: tuple[str, ...]
23
+
24
+
25
+ def plan_to_flowchart(plan: Plan | TeamPlan) -> PlanFlowchartData:
26
+ """Adapt an already validated Plan DAG to the text flowchart model."""
27
+ tasks = {task.id: task for task in plan.tasks}
28
+ ordered_ids: list[str] = []
29
+ rank_by_node: dict[str, int] = {}
30
+ for batch_no, batch in enumerate(plan.batches):
31
+ for task_id in batch:
32
+ if task_id in tasks and task_id not in ordered_ids:
33
+ ordered_ids.append(task_id)
34
+ rank_by_node[task_id] = batch_no
35
+ # Defensive fallback for a manually-created Plan whose batches are stale.
36
+ for task in plan.tasks:
37
+ if task.id not in ordered_ids:
38
+ ordered_ids.append(task.id)
39
+ rank_by_node[task.id] = len(plan.batches)
40
+
41
+ nodes = [
42
+ FlowchartNode(id=task_id, label=f"{task_id} {tasks[task_id].title}")
43
+ for task_id in ordered_ids
44
+ ]
45
+ known = set(tasks)
46
+ edges = [
47
+ FlowchartEdge(source=dependency, target=task.id)
48
+ for task in plan.tasks
49
+ for dependency in task.deps
50
+ if dependency in known and task.id in known
51
+ ]
52
+ warnings = []
53
+ missing = [
54
+ f"{task.id} 依赖不存在的任务 {dependency}"
55
+ for task in plan.tasks
56
+ for dependency in task.deps
57
+ if dependency not in known
58
+ ]
59
+ warnings.extend(missing)
60
+ return PlanFlowchartData(
61
+ model=FlowchartModel(direction="TD", nodes=nodes, edges=edges, warnings=warnings),
62
+ rank_by_node=rank_by_node,
63
+ ordered_ids=tuple(ordered_ids),
64
+ )
65
+
66
+
67
+ def _task_title_text(task: PlanTask) -> Text:
68
+ """Render a task title with a consistent visual hierarchy."""
69
+ line = Text(style="bold")
70
+ line.append(task.id, style="cyan bold")
71
+ line.append(f" {task.title}", style="bold")
72
+ return line
73
+
74
+
75
+ def _task_summary_renderable(plan: Plan | TeamPlan, ordered_ids: tuple[str, ...], detailed: bool) -> Group:
76
+ """Render the task heading, keyboard hint, and task content separately."""
77
+ tasks = {task.id: task for task in plan.tasks}
78
+ renderables: list[object] = [
79
+ Text("任务详情" if detailed else "任务概括"),
80
+ Text("按 d 收起详情" if detailed else "按 d 显示详情", style="dim"),
81
+ ]
82
+ for task_id in ordered_ids:
83
+ task = tasks.get(task_id)
84
+ if task is None:
85
+ continue
86
+ renderables.append(_task_title_text(task))
87
+ if detailed:
88
+ deps = ", ".join(task.deps) if task.deps else "无"
89
+ renderables.extend([
90
+ Text(f" 描述:{task.description or '暂无描述'}", style="dim"),
91
+ Text(f" 依赖:{deps}", style="dim"),
92
+ ])
93
+ tools = getattr(task, "allowed_tools", ())
94
+ if tools:
95
+ renderables.append(Text(f" 工具:{', '.join(tools)}", style="dim"))
96
+ for warning in getattr(task, "tool_warnings", ()):
97
+ renderables.append(Text(f" 工具提示:{warning}", style="yellow"))
98
+ mode = getattr(task, "resource_scope_mode", "")
99
+ if mode:
100
+ renderables.append(Text(f" 资源模式:{mode}", style="dim"))
101
+ for claim in getattr(task, "resource_claims", ()):
102
+ renderables.append(
103
+ Text(f" 资源声明:{claim.access} {claim.pattern}", style="dim")
104
+ )
105
+ return Group(*renderables)
106
+
107
+
108
+ class PlanReviewCard:
109
+ """Static review card: flowchart plus summary/details below it."""
110
+
111
+ def __init__(self, item: TranscriptItem) -> None:
112
+ self.item = item
113
+
114
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
115
+ plan = self.item.plan
116
+ # /plan uses Plan while /team uses TeamPlan. Both expose the same
117
+ # validated task/batch shape needed by this read-only renderer.
118
+ if not isinstance(plan, (Plan, TeamPlan)):
119
+ yield Panel(Text(self.item.text or "计划不可用"), title="计划审阅", border_style="magenta")
120
+ return
121
+ data = plan_to_flowchart(plan)
122
+ chart = render_flowchart(
123
+ data.model,
124
+ width=max(1, options.max_width - 8),
125
+ rank_by_node=data.rank_by_node,
126
+ )
127
+ parts: list[object] = [
128
+ Text(f"目标:{plan.goal}\n共 {len(plan.batches)} 轮", style="bold"),
129
+ ]
130
+ # Skip the flow-chart panel entirely when rendering falls back to
131
+ # structured text: the panel would just repeat the task IDs already
132
+ # listed below, and the warning inside a cyan border looks noisy.
133
+ if chart.mode != "structured":
134
+ chart_parts: list[object] = [Text(chart.text, no_wrap=True, overflow="crop")]
135
+ if chart.warnings:
136
+ chart_parts.append(Text("\n\n⚠ " + ";".join(chart.warnings)))
137
+ parts.append(Panel(Group(*chart_parts), title="轮次流程", border_style="cyan"))
138
+ elif chart.warnings:
139
+ parts.append(Text("⚠ " + ";".join(chart.warnings), style="dim"))
140
+ batch_lines = [
141
+ f"第 {batch_no} 轮:{', '.join(batch)}"
142
+ for batch_no, batch in enumerate(plan.batches, 1)
143
+ ]
144
+ parts.append(Text("\n".join(batch_lines), style="dim"))
145
+ parts.append(_task_summary_renderable(plan, data.ordered_ids, not self.item.collapsed))
146
+ parts.append(Text("\nEnter 执行 · r 重规划 · Esc 取消", style="dim"))
147
+ yield Panel(Group(*parts), title="计划审阅", border_style="magenta")