mindcode 0.2.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 (116) hide show
  1. mindcode/__init__.py +5 -0
  2. mindcode/__main__.py +4 -0
  3. mindcode/__pycache__/__init__.cpython-313.pyc +0 -0
  4. mindcode/__pycache__/__main__.cpython-313.pyc +0 -0
  5. mindcode/__pycache__/_version.cpython-313.pyc +0 -0
  6. mindcode/__pycache__/config.cpython-313.pyc +0 -0
  7. mindcode/__pycache__/policy.cpython-313.pyc +0 -0
  8. mindcode/__pycache__/remote.cpython-313.pyc +0 -0
  9. mindcode/__pycache__/render.cpython-313.pyc +0 -0
  10. mindcode/__pycache__/runtime.cpython-313.pyc +0 -0
  11. mindcode/__pycache__/tasking.cpython-313.pyc +0 -0
  12. mindcode/__pycache__/terminal_core.cpython-313.pyc +0 -0
  13. mindcode/_version.py +11 -0
  14. mindcode/approval.py +105 -0
  15. mindcode/bench/__init__.py +19 -0
  16. mindcode/bench/__pycache__/__init__.cpython-313.pyc +0 -0
  17. mindcode/bench/__pycache__/fake_tools.cpython-313.pyc +0 -0
  18. mindcode/bench/__pycache__/jsonutil.cpython-313.pyc +0 -0
  19. mindcode/bench/__pycache__/paths.cpython-313.pyc +0 -0
  20. mindcode/bench/__pycache__/predictions.cpython-313.pyc +0 -0
  21. mindcode/bench/__pycache__/report.cpython-313.pyc +0 -0
  22. mindcode/bench/__pycache__/runner.cpython-313.pyc +0 -0
  23. mindcode/bench/__pycache__/schema.cpython-313.pyc +0 -0
  24. mindcode/bench/__pycache__/trace.cpython-313.pyc +0 -0
  25. mindcode/bench/__pycache__/workspace.cpython-313.pyc +0 -0
  26. mindcode/bench/adapters/__init__.py +22 -0
  27. mindcode/bench/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
  28. mindcode/bench/adapters/__pycache__/base.cpython-313.pyc +0 -0
  29. mindcode/bench/adapters/__pycache__/mini_bfcl.cpython-313.pyc +0 -0
  30. mindcode/bench/adapters/__pycache__/mini_gaia.cpython-313.pyc +0 -0
  31. mindcode/bench/adapters/__pycache__/mini_terminal.cpython-313.pyc +0 -0
  32. mindcode/bench/adapters/__pycache__/swe_bench.cpython-313.pyc +0 -0
  33. mindcode/bench/adapters/__pycache__/terminal_bench.cpython-313.pyc +0 -0
  34. mindcode/bench/adapters/base.py +45 -0
  35. mindcode/bench/adapters/mini_bfcl.py +18 -0
  36. mindcode/bench/adapters/mini_gaia.py +18 -0
  37. mindcode/bench/adapters/mini_terminal.py +18 -0
  38. mindcode/bench/adapters/swe_bench.py +86 -0
  39. mindcode/bench/adapters/terminal_bench.py +107 -0
  40. mindcode/bench/fake_tools.py +73 -0
  41. mindcode/bench/jsonutil.py +20 -0
  42. mindcode/bench/paths.py +10 -0
  43. mindcode/bench/predictions.py +65 -0
  44. mindcode/bench/report.py +48 -0
  45. mindcode/bench/runner.py +250 -0
  46. mindcode/bench/schema.py +78 -0
  47. mindcode/bench/scorers/__pycache__/base.cpython-313.pyc +0 -0
  48. mindcode/bench/scorers/__pycache__/composite.cpython-313.pyc +0 -0
  49. mindcode/bench/scorers/__pycache__/exact.cpython-313.pyc +0 -0
  50. mindcode/bench/scorers/__pycache__/json_call.cpython-313.pyc +0 -0
  51. mindcode/bench/scorers/__pycache__/swe.cpython-313.pyc +0 -0
  52. mindcode/bench/scorers/__pycache__/terminal.cpython-313.pyc +0 -0
  53. mindcode/bench/scorers/__pycache__/terminal_bench.cpython-313.pyc +0 -0
  54. mindcode/bench/scorers/base.py +22 -0
  55. mindcode/bench/scorers/composite.py +57 -0
  56. mindcode/bench/scorers/exact.py +30 -0
  57. mindcode/bench/scorers/json_call.py +44 -0
  58. mindcode/bench/scorers/swe.py +29 -0
  59. mindcode/bench/scorers/terminal.py +43 -0
  60. mindcode/bench/scorers/terminal_bench.py +25 -0
  61. mindcode/bench/trace.py +34 -0
  62. mindcode/bench/workspace.py +127 -0
  63. mindcode/cli/__init__.py +112 -0
  64. mindcode/cli/__pycache__/__init__.cpython-313.pyc +0 -0
  65. mindcode/cli/__pycache__/_shared.cpython-313.pyc +0 -0
  66. mindcode/cli/_shared.py +43 -0
  67. mindcode/cli/commands/__init__.py +1 -0
  68. mindcode/cli/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  69. mindcode/cli/commands/__pycache__/bench.cpython-313.pyc +0 -0
  70. mindcode/cli/commands/__pycache__/chat.cpython-313.pyc +0 -0
  71. mindcode/cli/commands/__pycache__/config_cmd.cpython-313.pyc +0 -0
  72. mindcode/cli/commands/__pycache__/remote.cpython-313.pyc +0 -0
  73. mindcode/cli/commands/__pycache__/shell.cpython-313.pyc +0 -0
  74. mindcode/cli/commands/__pycache__/status.cpython-313.pyc +0 -0
  75. mindcode/cli/commands/__pycache__/task.cpython-313.pyc +0 -0
  76. mindcode/cli/commands/__pycache__/terminal.cpython-313.pyc +0 -0
  77. mindcode/cli/commands/bench.py +365 -0
  78. mindcode/cli/commands/chat.py +86 -0
  79. mindcode/cli/commands/config_cmd.py +197 -0
  80. mindcode/cli/commands/remote.py +146 -0
  81. mindcode/cli/commands/shell.py +108 -0
  82. mindcode/cli/commands/status.py +37 -0
  83. mindcode/cli/commands/task.py +417 -0
  84. mindcode/cli/commands/terminal.py +124 -0
  85. mindcode/cli/shell/__init__.py +5 -0
  86. mindcode/cli/shell/__pycache__/__init__.cpython-313.pyc +0 -0
  87. mindcode/cli/shell/__pycache__/completion.cpython-313.pyc +0 -0
  88. mindcode/cli/shell/__pycache__/menu.cpython-313.pyc +0 -0
  89. mindcode/cli/shell/__pycache__/repl.cpython-313.pyc +0 -0
  90. mindcode/cli/shell/__pycache__/slash.cpython-313.pyc +0 -0
  91. mindcode/cli/shell/__pycache__/startup.cpython-313.pyc +0 -0
  92. mindcode/cli/shell/completion.py +72 -0
  93. mindcode/cli/shell/menu.py +88 -0
  94. mindcode/cli/shell/repl.py +597 -0
  95. mindcode/cli/shell/slash.py +480 -0
  96. mindcode/cli/shell/startup.py +55 -0
  97. mindcode/cli/shell/tui.py +897 -0
  98. mindcode/config.py +173 -0
  99. mindcode/mcp.py +205 -0
  100. mindcode/policy.py +173 -0
  101. mindcode/remote.py +216 -0
  102. mindcode/render.py +458 -0
  103. mindcode/runtime.py +541 -0
  104. mindcode/skills.py +154 -0
  105. mindcode/subagents.py +43 -0
  106. mindcode/tasking.py +281 -0
  107. mindcode/terminal/__init__.py +1 -0
  108. mindcode/terminal/__main__.py +17 -0
  109. mindcode/terminal/__pycache__/__init__.cpython-313.pyc +0 -0
  110. mindcode/terminal/__pycache__/__main__.cpython-313.pyc +0 -0
  111. mindcode/terminal_core.py +264 -0
  112. mindcode-0.2.0.dist-info/METADATA +244 -0
  113. mindcode-0.2.0.dist-info/RECORD +116 -0
  114. mindcode-0.2.0.dist-info/WHEEL +5 -0
  115. mindcode-0.2.0.dist-info/entry_points.txt +2 -0
  116. mindcode-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from mindagent.core import AgentResult
6
+
7
+ from ..schema import BenchmarkCase, CaseTrace, ScoreResult
8
+
9
+
10
+ class TerminalBenchRunScorer:
11
+ def score(
12
+ self,
13
+ case: BenchmarkCase,
14
+ run_result: AgentResult | None,
15
+ workspace: Path,
16
+ trace: CaseTrace,
17
+ ) -> ScoreResult:
18
+ passed = run_result is not None and run_result.error is None
19
+ return ScoreResult(
20
+ passed=passed,
21
+ score=1.0 if passed else 0.0,
22
+ metrics={"workspace_diff_bytes": len(trace.workspace_diff.encode("utf-8"))},
23
+ failure_phase=None if passed else "tool_execution",
24
+ failure_reason=None if passed else "agent_run_failed",
25
+ )
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from mindagent.core import AgentEvent, EventType
6
+
7
+ from .jsonutil import to_jsonable
8
+
9
+
10
+ class BenchmarkTraceRecorder:
11
+ def __init__(self) -> None:
12
+ self.agent_events: list[dict[str, Any]] = []
13
+ self.tool_calls: list[dict[str, Any]] = []
14
+
15
+ async def __call__(self, event: AgentEvent) -> None:
16
+ serialized = {
17
+ "event_id": event.event_id,
18
+ "run_id": event.run_id,
19
+ "event_type": event.event_type.value,
20
+ "state": event.state.value,
21
+ "timestamp": event.timestamp,
22
+ "payload": to_jsonable(event.payload),
23
+ }
24
+ self.agent_events.append(serialized)
25
+ if event.event_type == EventType.ACTION_STARTED:
26
+ payload = serialized["payload"]
27
+ self.tool_calls.append(
28
+ {
29
+ "name": payload.get("action_name") or payload.get("name"),
30
+ "arguments": payload.get("arguments", {}),
31
+ "event_id": event.event_id,
32
+ "timestamp": event.timestamp,
33
+ }
34
+ )
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import difflib
4
+ from pathlib import Path
5
+
6
+ from mindagent.core.artifact import (
7
+ ensure_artifact_directory,
8
+ validate_artifact_path,
9
+ )
10
+
11
+ from .schema import BenchmarkCase
12
+
13
+
14
+ def prepare_case_workspace(root: Path, case: BenchmarkCase) -> Path:
15
+ root = root.expanduser()
16
+ ensure_artifact_directory(root)
17
+ root = root.resolve()
18
+ workspace = (root / case.case_id).resolve()
19
+ try:
20
+ workspace.relative_to(root)
21
+ except ValueError as exc:
22
+ raise ValueError("benchmark case_id 不能超出 workspace root") from exc
23
+ workspace.mkdir(parents=True, exist_ok=True)
24
+ for item in case.files:
25
+ candidate = workspace / item.path
26
+ try:
27
+ validate_artifact_path(workspace, candidate)
28
+ except ValueError as exc:
29
+ raise ValueError(
30
+ f"benchmark file 不能超出 case workspace: {item.path}"
31
+ ) from exc
32
+ target = candidate.resolve()
33
+ try:
34
+ target.relative_to(workspace)
35
+ except ValueError as exc:
36
+ raise ValueError(
37
+ f"benchmark file 不能超出 case workspace: {item.path}"
38
+ ) from exc
39
+ target.parent.mkdir(parents=True, exist_ok=True)
40
+ target.write_text(item.content, encoding="utf-8")
41
+ return workspace
42
+
43
+
44
+ def workspace_diff(case: BenchmarkCase, workspace: Path) -> str:
45
+ workspace = Path(workspace)
46
+ try:
47
+ # A symlinked workspace is outside the benchmark observation boundary
48
+ # even when its resolved target happens to exist.
49
+ if workspace.is_symlink():
50
+ return ""
51
+ resolved_workspace = workspace.resolve(strict=False)
52
+ if not resolved_workspace.is_dir():
53
+ return ""
54
+ except (OSError, RuntimeError):
55
+ return ""
56
+
57
+ parts: list[str] = []
58
+ initial = {item.path: item.content for item in case.files}
59
+ seen = set(initial)
60
+
61
+ for rel_path, before in sorted(initial.items()):
62
+ path = workspace / rel_path
63
+ after = _read_workspace_file(path, resolved_workspace)
64
+ if before != after:
65
+ parts.extend(
66
+ difflib.unified_diff(
67
+ before.splitlines(keepends=True),
68
+ after.splitlines(keepends=True),
69
+ fromfile=f"a/{rel_path}",
70
+ tofile=f"b/{rel_path}",
71
+ )
72
+ )
73
+
74
+ for path in sorted(workspace.rglob("*")):
75
+ if _workspace_entry(path, resolved_workspace) is None:
76
+ continue
77
+ if not path.is_file():
78
+ continue
79
+ rel_path = path.relative_to(workspace).as_posix()
80
+ if rel_path in seen:
81
+ continue
82
+ after = _read_workspace_file(path, resolved_workspace, errors="replace")
83
+ parts.extend(
84
+ difflib.unified_diff(
85
+ [],
86
+ after.splitlines(keepends=True),
87
+ fromfile=f"a/{rel_path}",
88
+ tofile=f"b/{rel_path}",
89
+ )
90
+ )
91
+
92
+ return "".join(parts)
93
+
94
+
95
+ def _read_workspace_file(
96
+ path: Path,
97
+ workspace: Path,
98
+ *,
99
+ errors: str | None = None,
100
+ ) -> str:
101
+ """Read only ordinary files contained by the resolved workspace."""
102
+ if _workspace_entry(path, workspace) is None or not path.is_file():
103
+ return ""
104
+ try:
105
+ if errors is None:
106
+ return path.read_text(encoding="utf-8")
107
+ return path.read_text(encoding="utf-8", errors=errors)
108
+ except (OSError, UnicodeError):
109
+ return ""
110
+
111
+
112
+ def _workspace_entry(path: Path, workspace: Path) -> Path | None:
113
+ """Return a path only when no component follows a symlink or escapes."""
114
+ try:
115
+ if path.is_symlink():
116
+ return None
117
+ resolved = path.resolve(strict=False)
118
+ resolved.relative_to(workspace)
119
+ relative = path.relative_to(workspace)
120
+ current = workspace
121
+ for component in relative.parts:
122
+ current /= component
123
+ if current.is_symlink():
124
+ return None
125
+ return path
126
+ except (OSError, RuntimeError, ValueError):
127
+ return None
@@ -0,0 +1,112 @@
1
+ """mindcode CLI 入口:typer app + 命令注册。
2
+
3
+ 模块结构(对齐 mindbot/src/mindbot/cli/):
4
+ cli/__init__.py typer app + 命令注册
5
+ cli/_shared.py 共享 console + find_config_file
6
+ cli/commands/
7
+ shell.py `mindcode shell`(默认子命令)
8
+ chat.py `mindcode chat -m "..."`
9
+ task.py `mindcode task run/list/status/tail`
10
+ status.py `mindcode status`
11
+ config_cmd.py `mindcode config providers/models/api-key/...`
12
+ cli/shell/
13
+ __init__.py 暴露 Shell / run_shell
14
+ repl.py Shell 类 + REPL 主循环
15
+ slash.py slash 命令分发
16
+ menu.py interactive_menu(ChoiceInput 包装)
17
+ startup.py build_welcome_banner
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import typer
23
+
24
+ from .._version import __version__
25
+ from ._shared import console
26
+
27
+ KNOWN_SUBCOMMANDS = {
28
+ "shell",
29
+ "chat",
30
+ "status",
31
+ "config",
32
+ "bench",
33
+ "task",
34
+ "remote",
35
+ "terminal",
36
+ }
37
+
38
+
39
+ class DefaultShellGroup(typer.core.TyperGroup):
40
+ """让 `mindcode`(不带子命令)默认进 shell。
41
+
42
+ 当第一个非 flag 参数不是已知子命令时,把 "shell" 注入到 args 前面,
43
+ 让 click 走完整的参数解析路径。
44
+ """
45
+
46
+ def parse_args(self, ctx, args):
47
+ if args:
48
+ first = args[0]
49
+ if (
50
+ first in KNOWN_SUBCOMMANDS
51
+ or first in {"--help", "-h", "--version", "-v"}
52
+ ):
53
+ return super().parse_args(ctx, args)
54
+ # 无子命令或第一参数是 shell 的 flag/位置参数 → 注入 shell
55
+ new_args = ["shell", *args]
56
+ return super().parse_args(ctx, new_args)
57
+
58
+
59
+ app = typer.Typer(
60
+ name="mindcode",
61
+ help=(
62
+ f"mindcode v{__version__} — task execution and development "
63
+ "supervision agent"
64
+ ),
65
+ cls=DefaultShellGroup,
66
+ no_args_is_help=False,
67
+ add_completion=False,
68
+ )
69
+
70
+
71
+ def _version_callback(value: bool) -> None:
72
+ if value:
73
+ console.print(f"mindcode v{__version__}")
74
+ raise typer.Exit()
75
+
76
+
77
+ @app.callback()
78
+ def main(
79
+ version: bool = typer.Option(
80
+ None,
81
+ "--version",
82
+ "-v",
83
+ callback=_version_callback,
84
+ is_eager=True,
85
+ help="显示版本号",
86
+ ),
87
+ ) -> None:
88
+ """mindcode — task execution and development supervision agent."""
89
+
90
+
91
+ # 子命令注册
92
+ from .commands.chat import chat # noqa: E402
93
+ from .commands.bench import app as bench_app # noqa: E402
94
+ from .commands.config_cmd import app as config_app # noqa: E402
95
+ from .commands.remote import app as remote_app # noqa: E402
96
+ from .commands.shell import shell # noqa: E402
97
+ from .commands.status import status # noqa: E402
98
+ from .commands.task import app as task_app # noqa: E402
99
+ from .commands.terminal import app as terminal_app # noqa: E402
100
+
101
+ app.command("shell")(shell)
102
+ app.command()(chat)
103
+ app.command()(status)
104
+ app.add_typer(config_app, name="config")
105
+ app.add_typer(bench_app, name="bench")
106
+ app.add_typer(task_app, name="task")
107
+ app.add_typer(remote_app, name="remote")
108
+ app.add_typer(terminal_app, name="terminal")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ app()
@@ -0,0 +1,43 @@
1
+ """CLI 共享工具:全局 console 实例与配置文件定位。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+
10
+ from ..config import CONFIG_FILE
11
+
12
+
13
+ class _StdoutProxy:
14
+ """每次 write 时动态查 sys.stdout,使 patch_stdout 能拦截 Rich 输出。"""
15
+
16
+ def write(self, data: str) -> int:
17
+ return sys.stdout.write(data)
18
+
19
+ def flush(self) -> None:
20
+ sys.stdout.flush()
21
+
22
+ def isatty(self) -> bool:
23
+ return sys.stdout.isatty()
24
+
25
+
26
+ console = Console(file=_StdoutProxy())
27
+
28
+
29
+ def mask_key(key: str) -> str:
30
+ if not key:
31
+ return "[red](未设置)[/red]"
32
+ if len(key) <= 8:
33
+ return "*" * len(key)
34
+ return key[:4] + "..." + key[-4:]
35
+
36
+
37
+ def find_config_file() -> Path:
38
+ """返回配置文件路径(首次访问时自动 bootstrap)。"""
39
+ if not CONFIG_FILE.exists():
40
+ from ..config import load_config
41
+
42
+ load_config()
43
+ return CONFIG_FILE
@@ -0,0 +1 @@
1
+ """commands 子包入口。"""