keepai 0.6.1__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.
- keep/__init__.py +0 -0
- keep/agent.py +72 -0
- keep/agents/__init__.py +7 -0
- keep/agents/cc.py +75 -0
- keep/cli.py +1780 -0
- keep/cmux.py +63 -0
- keep/daemon.py +1167 -0
- keep/daemon_runner.py +6 -0
- keep/hooks.py +141 -0
- keep/state.py +873 -0
- keep/worker.py +248 -0
- keepai-0.6.1.dist-info/METADATA +9 -0
- keepai-0.6.1.dist-info/RECORD +16 -0
- keepai-0.6.1.dist-info/WHEEL +4 -0
- keepai-0.6.1.dist-info/entry_points.txt +2 -0
- keepai-0.6.1.dist-info/licenses/LICENSE +21 -0
keep/__init__.py
ADDED
|
File without changes
|
keep/agent.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Agent abstraction — entry point for multi-agent support (Plan #14 walking skeleton).
|
|
2
|
+
|
|
3
|
+
This module defines the `Agent` Protocol that keep's daemon and CLI will
|
|
4
|
+
talk to once multi-agent support is fully wired up. Plan #11 designed the
|
|
5
|
+
full 5-method interface; Plan #14 is a spike that implements **only** the
|
|
6
|
+
`last_message()` method to validate the abstraction is buildable.
|
|
7
|
+
|
|
8
|
+
Concrete implementations live under `keep.agents.*`:
|
|
9
|
+
- `keep.agents.*` — Claude Code implementation (this plan)
|
|
10
|
+
- (future) `keep.agents.*` — OpenAI Codex CLI
|
|
11
|
+
|
|
12
|
+
Design discrepancy from Plan #11 §3.1 captured in the walking skeleton
|
|
13
|
+
report: `AgentMessage.ts` is typed `str | None` here, not `datetime | None`
|
|
14
|
+
as originally specced. Reason: the current CC JSONL timestamp format
|
|
15
|
+
(`2026-04-09T01:55:03.323Z`) does not round-trip byte-identically through
|
|
16
|
+
Python's `datetime.isoformat()` (pads microseconds to 6 digits, rewrites
|
|
17
|
+
`Z` to `+00:00`). The `keep read` CLI must stay byte-identical per Plan
|
|
18
|
+
#14's acceptance criterion, and preserving the raw source string is the
|
|
19
|
+
least invasive way to satisfy both Plan #14 and Plan #11's spirit.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Protocol
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, ConfigDict
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentMessage(BaseModel):
|
|
30
|
+
"""Single assistant reply from an agent session."""
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(extra="forbid")
|
|
33
|
+
|
|
34
|
+
text: str
|
|
35
|
+
ts: str | None = None
|
|
36
|
+
"""Raw ISO8601 timestamp string from the source, preserved verbatim.
|
|
37
|
+
|
|
38
|
+
Deliberately `str`, not `datetime`: CC JSONL writes `2026-04-09T01:55:03.323Z`
|
|
39
|
+
but `datetime.fromisoformat(...).isoformat()` round-trips to
|
|
40
|
+
`2026-04-09T01:55:03.323000+00:00` — not byte-identical. Plan #14 accepts
|
|
41
|
+
this field-level deviation from Plan #11 §3.1 so the `keep read` CLI can
|
|
42
|
+
re-emit the timestamp unchanged. Plan #15+ may revisit.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
turn_id: str | None = None
|
|
46
|
+
"""Opaque, agent-specific identifier for cross-referencing turns.
|
|
47
|
+
Unused in Plan #14; reserved for future use."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Agent(Protocol):
|
|
51
|
+
"""Protocol for an AI coding agent that keep orchestrates.
|
|
52
|
+
|
|
53
|
+
Plan #14 walking skeleton only implements `last_message()`. The other
|
|
54
|
+
four methods from Plan #11 §3.2 (`status`, `poll_events`, `send`,
|
|
55
|
+
`interrupt`) are intentionally **not** declared here yet — adding them
|
|
56
|
+
as stubs would force every implementation to `NotImplementedError`
|
|
57
|
+
them, which defeats the point of Protocol typing. They will be added
|
|
58
|
+
in subsequent plans once their own walking skeletons pass.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def last_message(self, session: str) -> AgentMessage | None:
|
|
62
|
+
"""Return the most recent assistant reply for `session`, or None.
|
|
63
|
+
|
|
64
|
+
None means no retrievable message: session log missing, malformed,
|
|
65
|
+
empty, or no assistant entries yet. Query method — never raises;
|
|
66
|
+
all errors collapse to None. Idempotent and non-consuming.
|
|
67
|
+
|
|
68
|
+
`session` is an agent-specific identifier (CC session name or
|
|
69
|
+
UUID, Codex thread_id, etc.). The concrete `Agent` implementation
|
|
70
|
+
interprets it.
|
|
71
|
+
"""
|
|
72
|
+
...
|
keep/agents/__init__.py
ADDED
keep/agents/cc.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Claude Code implementation of `keep.agent.Agent`.
|
|
2
|
+
|
|
3
|
+
Plan #14 walking skeleton — only `last_message()` is implemented. The impl
|
|
4
|
+
wraps the existing `keep.worker.resolve_worker` + `read_last_message`
|
|
5
|
+
helpers rather than duplicating their logic, so behavior stays consistent
|
|
6
|
+
with the pre-abstraction `keep read` command.
|
|
7
|
+
|
|
8
|
+
Plan #14 Part 3 requires the `last_message` read path to be exercisable on
|
|
9
|
+
my-laptop (Linux Python 3.12.3, no cmux installed). `resolve_worker`
|
|
10
|
+
unconditionally requires cmux env vars (CMUX_WORKSPACE_ID /
|
|
11
|
+
CMUX_SURFACE_ID) and `ps` reachability for the CC process, which is a hard
|
|
12
|
+
block on cmux-less environments. To keep the Protocol signature clean
|
|
13
|
+
while making Part 3 possible, `last_message()` accepts an **optional**
|
|
14
|
+
`jsonl_path` keyword argument that short-circuits `resolve_worker`. The
|
|
15
|
+
Protocol itself only advertises `last_message(session)`; the keyword arg
|
|
16
|
+
is a CC-impl-specific dependency-injection hatch for tests and for
|
|
17
|
+
cmux-less realistic testing.
|
|
18
|
+
|
|
19
|
+
This DI hatch is **the minimum deviation from Plan #11 §3.2** required to
|
|
20
|
+
make the walking skeleton testable end-to-end without cmux. It is called
|
|
21
|
+
out explicitly in the Plan #14 report.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
from keep.agent import AgentMessage
|
|
29
|
+
from keep.worker import read_last_message, resolve_worker
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CCAgent:
|
|
33
|
+
"""Plan #14 walking skeleton CCAgent — implements `Agent.last_message` only."""
|
|
34
|
+
|
|
35
|
+
name = "claude-code"
|
|
36
|
+
|
|
37
|
+
def last_message(
|
|
38
|
+
self,
|
|
39
|
+
session: str,
|
|
40
|
+
*,
|
|
41
|
+
jsonl_path: Path | str | None = None,
|
|
42
|
+
) -> AgentMessage | None:
|
|
43
|
+
"""Return the most recent assistant reply for a CC session.
|
|
44
|
+
|
|
45
|
+
Resolution order for the JSONL file:
|
|
46
|
+
|
|
47
|
+
1. If `jsonl_path` is provided explicitly, use it directly. This is
|
|
48
|
+
the DI hatch for tests and for cmux-less realistic testing (see
|
|
49
|
+
module docstring).
|
|
50
|
+
2. Otherwise call `resolve_worker(session)` to discover the JSONL
|
|
51
|
+
via the `ps` + cmux env var path used by the rest of keep.
|
|
52
|
+
|
|
53
|
+
Returns None for any failure mode — missing file, malformed file,
|
|
54
|
+
no assistant entries yet, or unreachable session. Query method,
|
|
55
|
+
never raises. All three error branches of the underlying
|
|
56
|
+
`worker.read_last_message` collapse into None (see Plan #14 report
|
|
57
|
+
for the specific CLI error-message specificity loss this causes
|
|
58
|
+
in `keep read`).
|
|
59
|
+
"""
|
|
60
|
+
if jsonl_path is not None:
|
|
61
|
+
path = Path(jsonl_path)
|
|
62
|
+
else:
|
|
63
|
+
info = resolve_worker(session)
|
|
64
|
+
if info.jsonl_path is None:
|
|
65
|
+
return None
|
|
66
|
+
path = info.jsonl_path
|
|
67
|
+
|
|
68
|
+
result = read_last_message(path)
|
|
69
|
+
if not result.get("ok"):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
return AgentMessage(
|
|
73
|
+
text=result["message"],
|
|
74
|
+
ts=result.get("timestamp") or None,
|
|
75
|
+
)
|