subtask-mcp 0.2.6__tar.gz → 0.2.7__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.
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/PKG-INFO +2 -2
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/README.md +1 -1
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/__init__.py +1 -1
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/events.py +23 -5
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask_mcp.egg-info/PKG-INFO +2 -2
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_security.py +16 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/LICENSE +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/pyproject.toml +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/setup.cfg +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/admission.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/capacity.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/cli.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/config.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/core.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/doctor.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/__init__.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/base.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/claude_api.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/codex.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/demo.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/dsh.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/kimi.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/opencode.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/engines/registry.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/evidence.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/fastserver.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/gc.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/__init__.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/claude_plugin.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/clients.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/commands.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/doctor.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/fileops.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/installer.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/ownership.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/install/tomledit.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/jobs.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/ledger.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/narrate.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/policy.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/procid.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/progress.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/redact.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/roi.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/router.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/runners.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/schemacheck.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/server.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/skill/SKILL.md +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/telemetry.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/tools.json +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask/worker.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask_mcp.egg-info/SOURCES.txt +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask_mcp.egg-info/dependency_links.txt +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask_mcp.egg-info/entry_points.txt +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask_mcp.egg-info/requires.txt +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/subtask_mcp.egg-info/top_level.txt +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_concurrency.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_engines.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_falsify.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_independent_client.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_install.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_protocol.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_routing_results.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_schema.py +0 -0
- {subtask_mcp-0.2.6 → subtask_mcp-0.2.7}/tests/test_subtask.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subtask-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Background sub-agents for Claude Code, Codex, Kimi and OpenCode, over MCP or CLI, with a spend cap
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/tedin7/subtask
|
|
@@ -70,7 +70,7 @@ The distribution is `subtask-mcp` (the PyPI name `subtask` is an unrelated
|
|
|
70
70
|
project). Full guide, per client, with native commands: [docs/install.md](docs/install.md).
|
|
71
71
|
|
|
72
72
|
```sh
|
|
73
|
-
uv tool install "git+https://github.com/tedin7/subtask@v0.2.
|
|
73
|
+
uv tool install "git+https://github.com/tedin7/subtask@v0.2.7" # or: uv tool install /path/to/checkout
|
|
74
74
|
subtask install --client all # prints the plan for every detected client
|
|
75
75
|
subtask install --client all --yes # applies it: MCP server + skill
|
|
76
76
|
subtask doctor --client all # checks app, config entry and a real handshake
|
|
@@ -39,7 +39,7 @@ The distribution is `subtask-mcp` (the PyPI name `subtask` is an unrelated
|
|
|
39
39
|
project). Full guide, per client, with native commands: [docs/install.md](docs/install.md).
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
|
-
uv tool install "git+https://github.com/tedin7/subtask@v0.2.
|
|
42
|
+
uv tool install "git+https://github.com/tedin7/subtask@v0.2.7" # or: uv tool install /path/to/checkout
|
|
43
43
|
subtask install --client all # prints the plan for every detected client
|
|
44
44
|
subtask install --client all --yes # applies it: MCP server + skill
|
|
45
45
|
subtask doctor --client all # checks app, config entry and a real handshake
|
|
@@ -35,11 +35,24 @@ def emit(
|
|
|
35
35
|
state: str | None = None,
|
|
36
36
|
source: str = "lifecycle",
|
|
37
37
|
attempt: int | None = None,
|
|
38
|
+
) -> dict | None:
|
|
39
|
+
"""Append one event. Never raises: progress must not be able to fail a job."""
|
|
40
|
+
try:
|
|
41
|
+
return _emit(job_id, phase, msg, state, source, attempt)
|
|
42
|
+
except (OSError, ValueError, TypeError) as exc:
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
print(f"subtask: progress event not written: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _emit(
|
|
50
|
+
job_id: str, phase: str, msg: str, state: str | None, source: str, attempt: int | None
|
|
38
51
|
) -> dict | None:
|
|
39
52
|
path = _path(job_id)
|
|
40
53
|
if not path.parent.is_dir():
|
|
41
54
|
return None
|
|
42
|
-
with path.open("
|
|
55
|
+
with path.open("ab+") as fh:
|
|
43
56
|
fcntl.flock(fh, fcntl.LOCK_EX)
|
|
44
57
|
last = _last(fh)
|
|
45
58
|
seq = int(last.get("seq", 0)) + 1
|
|
@@ -57,16 +70,21 @@ def emit(
|
|
|
57
70
|
"source": source,
|
|
58
71
|
}
|
|
59
72
|
fh.seek(0, os.SEEK_END)
|
|
60
|
-
fh.write(json.dumps(event, ensure_ascii=False) + "\n")
|
|
73
|
+
fh.write((json.dumps(event, ensure_ascii=False) + "\n").encode("utf-8"))
|
|
61
74
|
fh.flush()
|
|
62
75
|
return event
|
|
63
76
|
|
|
64
77
|
|
|
65
78
|
def _last(fh) -> dict:
|
|
79
|
+
"""The last complete event, reading only the tail (bytes: the cut may split a UTF-8 character)."""
|
|
66
80
|
fh.seek(0, os.SEEK_END)
|
|
67
81
|
size = fh.tell()
|
|
68
82
|
fh.seek(max(0, size - 4096))
|
|
69
|
-
|
|
83
|
+
data = fh.read()
|
|
84
|
+
text = data.decode("utf-8", errors="replace") if isinstance(data, bytes) else data
|
|
85
|
+
lines = text.splitlines()
|
|
86
|
+
if size > 4096 and lines:
|
|
87
|
+
lines = lines[1:] # the first line is cut by the seek: never trust it
|
|
70
88
|
for line in reversed(lines):
|
|
71
89
|
try:
|
|
72
90
|
return json.loads(line)
|
|
@@ -99,7 +117,7 @@ def read_since(job_id: str, offset: int = 0, limit: int = 500) -> tuple[list[dic
|
|
|
99
117
|
def last(job_id: str) -> dict:
|
|
100
118
|
path = _path(job_id)
|
|
101
119
|
try:
|
|
102
|
-
with path.open("
|
|
120
|
+
with path.open("rb") as fh:
|
|
103
121
|
return _last(fh)
|
|
104
|
-
except FileNotFoundError:
|
|
122
|
+
except (FileNotFoundError, OSError):
|
|
105
123
|
return {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: subtask-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: Background sub-agents for Claude Code, Codex, Kimi and OpenCode, over MCP or CLI, with a spend cap
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/tedin7/subtask
|
|
@@ -70,7 +70,7 @@ The distribution is `subtask-mcp` (the PyPI name `subtask` is an unrelated
|
|
|
70
70
|
project). Full guide, per client, with native commands: [docs/install.md](docs/install.md).
|
|
71
71
|
|
|
72
72
|
```sh
|
|
73
|
-
uv tool install "git+https://github.com/tedin7/subtask@v0.2.
|
|
73
|
+
uv tool install "git+https://github.com/tedin7/subtask@v0.2.7" # or: uv tool install /path/to/checkout
|
|
74
74
|
subtask install --client all # prints the plan for every detected client
|
|
75
75
|
subtask install --client all --yes # applies it: MCP server + skill
|
|
76
76
|
subtask doctor --client all # checks app, config entry and a real handshake
|
|
@@ -180,3 +180,19 @@ def test_isolated_is_refused_when_bwrap_cannot_run(home, monkeypatch):
|
|
|
180
180
|
monkeypatch.setattr(runners, "bwrap_available", lambda: False)
|
|
181
181
|
out = core.start("x", engine="kimi", cwd=str(home), policy="isolated")
|
|
182
182
|
assert "bubblewrap" in out and "started" not in out
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_progress_log_survives_multibyte_text_across_the_tail_boundary(home):
|
|
186
|
+
"""Reading the last event seeks back 4096 bytes: that may land inside a UTF-8 character."""
|
|
187
|
+
import json
|
|
188
|
+
|
|
189
|
+
from subtask import events, jobs
|
|
190
|
+
|
|
191
|
+
folder = jobs.job_dir("st-utf8utf800")
|
|
192
|
+
folder.mkdir(parents=True)
|
|
193
|
+
line = json.dumps({"seq": 1, "msg": "…" * 50}, ensure_ascii=False) + "\n" # "…" is 3 bytes
|
|
194
|
+
for pad in range(3): # one of the three alignments puts byte -4096 inside a "…"
|
|
195
|
+
body = ("x" * pad + "\n" + line * 40).encode()
|
|
196
|
+
(folder / "progress.jsonl").write_bytes(body)
|
|
197
|
+
assert events.emit("st-utf8utf800", "tool", "dopo", source="adapter") is not None, pad
|
|
198
|
+
assert events.last("st-utf8utf800")["msg"] == "dopo"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|