codee-agent 0.6.6__tar.gz → 0.6.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.
- {codee_agent-0.6.6 → codee_agent-0.6.7}/PKG-INFO +1 -1
- {codee_agent-0.6.6 → codee_agent-0.6.7}/pyproject.toml +1 -1
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/admin.py +9 -1
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/runs_db.py +17 -9
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/test_runs_db.py +47 -17
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_abstract/provider.py +11 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_github_copilot/provider.py +8 -3
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_github_copilot/test.py +29 -1
- {codee_agent-0.6.6 → codee_agent-0.6.7}/LICENSE +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/README.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/.gitignore +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/admin_api.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/admin_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/admin_service.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/agent_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/coding_agents.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/executor.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/init_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/claude_key_rotation.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/cron_describe.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/mcp_config.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/test_claude_key_rotation.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/test_mcp_config.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/test_trigger_cron_skills.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/test_trigger_issue_skills.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/trigger_aws_sqs_skills.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/trigger_cron_skills.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/trigger_email_skills.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/lib/trigger_issue_skills.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/mail_server.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/setup_wizard.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/start_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/tasks_providers.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/AGENTS.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/CLAUDE.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-code-reviewer/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-developer/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-planner/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-planner/assets/readme-template.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-qa/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-security-reviewer/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/task-code-reviewer/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/task-developer/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/task-qa/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/task-security-reviewer/SKILL.md +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_admin_api.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_admin_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_admin_service.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_agent_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_coding_agents.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_executor.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_init_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_memory_index.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_setup_wizard.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/test_start_cli.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/workflow_graph.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_admin/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_admin/codee_admin.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_abstract/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/account.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/credentials.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/oauth.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/provider.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/test.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_claude_code/usage.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_codex/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_codex/provider.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_codex/test.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_agent_github_copilot/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_database/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_database/claude_code_accounts.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_database/database.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_database/oauth_tokens.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_main_context/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_main_context/context.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_main_context/logging.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_main_context/test_context.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_main_context/test_logging.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_abstract/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_abstract/provider.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_azure_devops/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_azure_devops/oauth.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_azure_devops/provider.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_azure_devops/test.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_jira/__init__.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_jira/provider.py +0 -0
- {codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee_tasks_jira/test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codee-agent
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: A virtual co-worker that picks up Jira and Azure DevOps tasks and solves them with coding agents.
|
|
5
5
|
Keywords: ai,agent,jira,azure-devops,claude-code,github-copilot,codex,automation
|
|
6
6
|
Author: Denis Kibalko
|
|
@@ -273,6 +273,7 @@ class RunRecord(BaseModel):
|
|
|
273
273
|
message: str
|
|
274
274
|
user_message: str
|
|
275
275
|
response: str
|
|
276
|
+
debug_logs: str
|
|
276
277
|
preview: str
|
|
277
278
|
viewer_url: str
|
|
278
279
|
|
|
@@ -856,6 +857,7 @@ class AdminState(rx.State):
|
|
|
856
857
|
message=message,
|
|
857
858
|
user_message=(run.get("user_message") or message).strip(),
|
|
858
859
|
response=(run.get("response") or "").strip(),
|
|
860
|
+
debug_logs=run.get("debug_logs") or "",
|
|
859
861
|
preview=preview[:120] + ("..." if len(preview) > 120 else ""),
|
|
860
862
|
viewer_url=(SERVICE.session_viewer.format(session_id=run["session_id"])
|
|
861
863
|
if SERVICE.session_viewer and run.get("session_id") else ""),
|
|
@@ -2547,7 +2549,7 @@ def run_row(run: RunRecord) -> rx.Component:
|
|
|
2547
2549
|
rx.cond(run.viewer_url != "", rx.link(rx.icon("external-link", size=16), href=run.viewer_url,
|
|
2548
2550
|
is_external=True, aria_label="View session", color=ACCENT)),
|
|
2549
2551
|
gap="1rem", align="start", width="100%"),
|
|
2550
|
-
rx.cond((run.user_message != "") | (run.response != ""), rx.accordion.root(rx.accordion.item(
|
|
2552
|
+
rx.cond((run.user_message != "") | (run.response != "") | (run.debug_logs != ""), rx.accordion.root(rx.accordion.item(
|
|
2551
2553
|
header="Run info", content=rx.vstack(
|
|
2552
2554
|
rx.text("User message", font_weight="600"),
|
|
2553
2555
|
rx.text(run.user_message, white_space="pre-wrap"),
|
|
@@ -2555,6 +2557,12 @@ def run_row(run: RunRecord) -> rx.Component:
|
|
|
2555
2557
|
rx.text("LLM response", font_weight="600",
|
|
2556
2558
|
margin_top="0.75rem"),
|
|
2557
2559
|
rx.text(run.response, white_space="pre-wrap"))),
|
|
2560
|
+
rx.cond(run.debug_logs != "", rx.fragment(
|
|
2561
|
+
rx.text("Debug logs", font_weight="600",
|
|
2562
|
+
margin_top="0.75rem"),
|
|
2563
|
+
rx.text(run.debug_logs, white_space="pre-wrap",
|
|
2564
|
+
font_family="IBM Plex Mono, monospace",
|
|
2565
|
+
font_size="0.8rem"))),
|
|
2558
2566
|
spacing="2", align="start", width="100%"), value=run.started_at),
|
|
2559
2567
|
collapsible=True, width="100%")),
|
|
2560
2568
|
padding="1rem", background=SURFACE, border=BORDER, width="100%")
|
|
@@ -11,7 +11,7 @@ from codee_main_context.context import CodeeMainContext
|
|
|
11
11
|
from codee_database.database import get_db_connection
|
|
12
12
|
|
|
13
13
|
_COLUMNS = ("id", "skill_name", "trigger_type", "session_id", "status", "error",
|
|
14
|
-
"started_at", "message", "user_message", "response")
|
|
14
|
+
"started_at", "message", "user_message", "response", "debug_logs")
|
|
15
15
|
|
|
16
16
|
# Codee names a session before the agent runs, but not every agent runs under
|
|
17
17
|
# the name it was given: Codex mints its own thread id and reports it back mid
|
|
@@ -52,7 +52,8 @@ def init(main_context: CodeeMainContext) -> None:
|
|
|
52
52
|
started_at TEXT NOT NULL,
|
|
53
53
|
message TEXT,
|
|
54
54
|
user_message TEXT,
|
|
55
|
-
response TEXT
|
|
55
|
+
response TEXT,
|
|
56
|
+
debug_logs TEXT
|
|
56
57
|
)"""
|
|
57
58
|
)
|
|
58
59
|
conn.execute(
|
|
@@ -65,6 +66,8 @@ def init(main_context: CodeeMainContext) -> None:
|
|
|
65
66
|
conn.execute("ALTER TABLE runs ADD COLUMN response TEXT")
|
|
66
67
|
if "user_message" not in cols:
|
|
67
68
|
conn.execute("ALTER TABLE runs ADD COLUMN user_message TEXT")
|
|
69
|
+
if "debug_logs" not in cols:
|
|
70
|
+
conn.execute("ALTER TABLE runs ADD COLUMN debug_logs TEXT")
|
|
68
71
|
# In-flight claude runs; a row lives only while its subprocess is running.
|
|
69
72
|
conn.execute(
|
|
70
73
|
"""CREATE TABLE IF NOT EXISTS active_jobs (
|
|
@@ -77,28 +80,33 @@ def init(main_context: CodeeMainContext) -> None:
|
|
|
77
80
|
)"""
|
|
78
81
|
)
|
|
79
82
|
# Migrate DBs created before the dashboard named the agent behind a run.
|
|
80
|
-
job_cols = {row[1] for row in conn.execute(
|
|
83
|
+
job_cols = {row[1] for row in conn.execute(
|
|
84
|
+
"PRAGMA table_info(active_jobs)")}
|
|
81
85
|
for column in ("agent", "model"):
|
|
82
86
|
if column not in job_cols:
|
|
83
|
-
conn.execute(
|
|
87
|
+
conn.execute(
|
|
88
|
+
f"ALTER TABLE active_jobs ADD COLUMN {column} TEXT")
|
|
84
89
|
|
|
85
90
|
|
|
86
91
|
def record_run(skill_name, trigger_type, session_id, status, error=None, started_at=None,
|
|
87
|
-
message=None, user_message=None, response=None,
|
|
92
|
+
message=None, user_message=None, response=None, debug_logs=None,
|
|
88
93
|
*, main_context: CodeeMainContext) -> None:
|
|
89
94
|
"""Insert one run row. Never raises to the caller (FR-009)."""
|
|
90
95
|
try:
|
|
91
96
|
init(main_context)
|
|
92
97
|
session_id = agent_session(session_id)
|
|
98
|
+
if debug_logs is None:
|
|
99
|
+
debug_logs = getattr(response, "debug_logs", None)
|
|
93
100
|
if started_at is None:
|
|
94
101
|
started_at = datetime.now(timezone.utc).isoformat()
|
|
95
102
|
with get_db_connection(main_context) as conn:
|
|
96
103
|
conn.execute(
|
|
97
104
|
"INSERT INTO runs (skill_name, trigger_type, session_id, status, error,"
|
|
98
|
-
" started_at, message, user_message, response)"
|
|
99
|
-
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
105
|
+
" started_at, message, user_message, response, debug_logs)"
|
|
106
|
+
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
100
107
|
(skill_name, trigger_type, session_id,
|
|
101
|
-
status, error, started_at, message, user_message, response
|
|
108
|
+
status, error, started_at, message, user_message, response,
|
|
109
|
+
debug_logs),
|
|
102
110
|
)
|
|
103
111
|
except Exception as exc: # ponytail: a logging miss must never abort the skill run
|
|
104
112
|
print(f"[runs_db] Failed to record run for {skill_name}: {exc}")
|
|
@@ -112,7 +120,7 @@ def recent_runs(limit: int = 100, offset: int = 0, *,
|
|
|
112
120
|
with get_db_connection(main_context) as conn:
|
|
113
121
|
rows = conn.execute(
|
|
114
122
|
"SELECT id, skill_name, trigger_type, session_id, status, error,"
|
|
115
|
-
" started_at, message, user_message, response"
|
|
123
|
+
" started_at, message, user_message, response, debug_logs"
|
|
116
124
|
" FROM runs ORDER BY started_at DESC, id DESC LIMIT ? OFFSET ?",
|
|
117
125
|
(limit, max(offset, 0)),
|
|
118
126
|
).fetchall()
|
|
@@ -30,7 +30,8 @@ def test_round_trip_newest_first(tmp_path):
|
|
|
30
30
|
started_at="2026-06-27T11:00:00+00:00", main_context=ctx)
|
|
31
31
|
|
|
32
32
|
rows = runs_db.recent_runs(main_context=ctx)
|
|
33
|
-
assert [r["skill_name"] for r in rows] == [
|
|
33
|
+
assert [r["skill_name"] for r in rows] == [
|
|
34
|
+
"skill-b", "skill-a"] # newest first
|
|
34
35
|
assert rows[0]["trigger_type"] == "email"
|
|
35
36
|
assert rows[0]["status"] == "failed"
|
|
36
37
|
assert rows[0]["error"] == "boom"
|
|
@@ -74,7 +75,8 @@ def test_main_context_is_required():
|
|
|
74
75
|
call()
|
|
75
76
|
except TypeError:
|
|
76
77
|
continue
|
|
77
|
-
raise AssertionError(
|
|
78
|
+
raise AssertionError(
|
|
79
|
+
"main_context must be a required keyword argument")
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
# ---------------------------------------------------------------- counts() (US1)
|
|
@@ -85,9 +87,12 @@ def _ago(hours):
|
|
|
85
87
|
def test_counts_total_and_last_24h(tmp_path):
|
|
86
88
|
ctx = _ctx(tmp_path)
|
|
87
89
|
# Two inside the 24h window, one well outside, one exactly at the boundary (excluded, strict >).
|
|
88
|
-
runs_db.record_run("a", "cron", "s1", "succeeded",
|
|
89
|
-
|
|
90
|
-
runs_db.record_run("
|
|
90
|
+
runs_db.record_run("a", "cron", "s1", "succeeded",
|
|
91
|
+
started_at=_ago(1), main_context=ctx)
|
|
92
|
+
runs_db.record_run("b", "cron", "s2", "succeeded",
|
|
93
|
+
started_at=_ago(23), main_context=ctx)
|
|
94
|
+
runs_db.record_run("c", "cron", "s3", "succeeded",
|
|
95
|
+
started_at=_ago(48), main_context=ctx)
|
|
91
96
|
assert runs_db.counts(ctx) == {"total": 3, "last_24h": 2}
|
|
92
97
|
|
|
93
98
|
|
|
@@ -101,17 +106,22 @@ def test_counts_never_raises_on_bad_path(tmp_path):
|
|
|
101
106
|
|
|
102
107
|
def test_runs_by_hour_buckets(tmp_path):
|
|
103
108
|
ctx = _ctx(tmp_path)
|
|
104
|
-
runs_db.record_run("a", "cron", "s1", "succeeded",
|
|
105
|
-
|
|
106
|
-
runs_db.record_run("
|
|
109
|
+
runs_db.record_run("a", "cron", "s1", "succeeded",
|
|
110
|
+
started_at=_ago(2), main_context=ctx)
|
|
111
|
+
runs_db.record_run("b", "cron", "s2", "succeeded",
|
|
112
|
+
started_at=_ago(2.1), main_context=ctx)
|
|
113
|
+
runs_db.record_run("c", "cron", "s3", "succeeded",
|
|
114
|
+
started_at=_ago(48), main_context=ctx) # out of window
|
|
107
115
|
|
|
108
116
|
hourly = runs_db.runs_by_hour(ctx)
|
|
109
117
|
assert len(hourly) == 24 # always 24 buckets
|
|
110
118
|
# oldest-first: buckets are the last 24 hour labels in ascending time order
|
|
111
119
|
now = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0)
|
|
112
|
-
expected = [(now - timedelta(hours=h)).strftime("%H:00")
|
|
120
|
+
expected = [(now - timedelta(hours=h)).strftime("%H:00")
|
|
121
|
+
for h in range(23, -1, -1)]
|
|
113
122
|
assert [h["hour"] for h in hourly] == expected
|
|
114
|
-
|
|
123
|
+
# 48h-old run excluded; the two ~2h-old runs counted
|
|
124
|
+
assert sum(h["runs"] for h in hourly) == 2
|
|
115
125
|
|
|
116
126
|
|
|
117
127
|
def test_runs_by_hour_empty_and_bad_path(tmp_path):
|
|
@@ -123,10 +133,14 @@ def test_runs_by_hour_empty_and_bad_path(tmp_path):
|
|
|
123
133
|
# ---------------------------------------------------------------- message column (US2)
|
|
124
134
|
def test_message_round_trip(tmp_path):
|
|
125
135
|
ctx = _ctx(tmp_path)
|
|
126
|
-
runs_db.record_run("a", "cron", "s1", "succeeded",
|
|
127
|
-
|
|
128
|
-
runs_db.record_run("
|
|
129
|
-
|
|
136
|
+
runs_db.record_run("a", "cron", "s1", "succeeded",
|
|
137
|
+
message="hello", main_context=ctx)
|
|
138
|
+
runs_db.record_run("b", "email", "s2", "succeeded",
|
|
139
|
+
message="", main_context=ctx)
|
|
140
|
+
runs_db.record_run("c", "aws-sqs", "s3", "succeeded",
|
|
141
|
+
main_context=ctx) # message defaults None
|
|
142
|
+
by_skill = {r["skill_name"]: r["message"]
|
|
143
|
+
for r in runs_db.recent_runs(main_context=ctx)}
|
|
130
144
|
assert by_skill == {"a": "hello", "b": "", "c": None}
|
|
131
145
|
|
|
132
146
|
|
|
@@ -141,6 +155,19 @@ def test_response_round_trip(tmp_path):
|
|
|
141
155
|
assert run["response"] == "final answer"
|
|
142
156
|
|
|
143
157
|
|
|
158
|
+
def test_debug_logs_are_extracted_from_the_agent_response(tmp_path):
|
|
159
|
+
from codee_agent_abstract.provider import AgentResponse
|
|
160
|
+
|
|
161
|
+
ctx = _ctx(tmp_path)
|
|
162
|
+
response = AgentResponse("final answer", "debug one\ndebug two\n")
|
|
163
|
+
runs_db.record_run("a", "cron", "s1", "succeeded", response=response,
|
|
164
|
+
main_context=ctx)
|
|
165
|
+
|
|
166
|
+
run, = runs_db.recent_runs(main_context=ctx)
|
|
167
|
+
assert run["response"] == "final answer"
|
|
168
|
+
assert run["debug_logs"] == "debug one\ndebug two\n"
|
|
169
|
+
|
|
170
|
+
|
|
144
171
|
# ---------------------------------------------------------------- active_jobs
|
|
145
172
|
def test_active_job_lifecycle(tmp_path):
|
|
146
173
|
ctx = _ctx(tmp_path)
|
|
@@ -195,7 +222,8 @@ def test_active_jobs_elapsed_and_order(tmp_path):
|
|
|
195
222
|
runs_db.start_job("young", "b", started_at=_ago(0.01), main_context=ctx)
|
|
196
223
|
runs_db.start_job("old", "a", started_at=_ago(1), main_context=ctx)
|
|
197
224
|
jobs = runs_db.active_jobs(ctx)
|
|
198
|
-
assert [j["session_id"]
|
|
225
|
+
assert [j["session_id"]
|
|
226
|
+
for j in jobs] == ["young", "old"] # youngest first
|
|
199
227
|
assert jobs[1]["elapsed"] >= 3500 # ~1h old
|
|
200
228
|
|
|
201
229
|
|
|
@@ -262,7 +290,8 @@ def test_set_job_session_repoints_a_live_job(tmp_path):
|
|
|
262
290
|
|
|
263
291
|
runs_db.set_job_session(job_id, "codex-thread", main_context=ctx)
|
|
264
292
|
|
|
265
|
-
assert runs_db.active_jobs(main_context=ctx)[
|
|
293
|
+
assert runs_db.active_jobs(main_context=ctx)[
|
|
294
|
+
0]["session_id"] == "codex-thread"
|
|
266
295
|
|
|
267
296
|
|
|
268
297
|
def test_set_job_session_is_a_no_op_without_a_job(tmp_path):
|
|
@@ -280,7 +309,8 @@ def test_a_noted_agent_session_is_what_the_run_records(tmp_path):
|
|
|
280
309
|
runs_db.record_run("skill-a", "issue", "codee-sid", "succeeded",
|
|
281
310
|
main_context=ctx)
|
|
282
311
|
|
|
283
|
-
assert runs_db.recent_runs(main_context=ctx)[
|
|
312
|
+
assert runs_db.recent_runs(main_context=ctx)[
|
|
313
|
+
0]["session_id"] == "codex-thread"
|
|
284
314
|
|
|
285
315
|
|
|
286
316
|
def test_the_note_is_consumed_so_a_later_run_keeps_its_own_id(tmp_path):
|
|
@@ -19,6 +19,17 @@ class AgentModel:
|
|
|
19
19
|
name: str
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
class AgentResponse(str):
|
|
23
|
+
"""Agent text with optional diagnostic output for the run record."""
|
|
24
|
+
|
|
25
|
+
debug_logs: str
|
|
26
|
+
|
|
27
|
+
def __new__(cls, response: str, debug_logs: str = "") -> "AgentResponse":
|
|
28
|
+
value = super().__new__(cls, response)
|
|
29
|
+
value.debug_logs = debug_logs
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
22
33
|
class AbstractCodingAgent(ABC):
|
|
23
34
|
"""Base class every coding agent (e.g. Claude Code) inherits from.
|
|
24
35
|
|
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
from collections.abc import Callable
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from codee_agent_abstract.provider import AbstractCodingAgent, AgentModel
|
|
10
|
+
from codee_agent_abstract.provider import AbstractCodingAgent, AgentModel, AgentResponse
|
|
11
11
|
from codee_main_context.context import Settings
|
|
12
12
|
from codee_main_context.logging import get_logger
|
|
13
13
|
|
|
@@ -30,6 +30,7 @@ _SESSION_NEW_ID = 2
|
|
|
30
30
|
# Ceiling on what one run may spend. AI credits bill at $0.04 each, so the
|
|
31
31
|
# default is the same $20 cap the Claude Code agent puts on a run.
|
|
32
32
|
MAX_AI_CREDITS_ENV_VAR = "CODEE_COPILOT_MAX_AI_CREDITS"
|
|
33
|
+
COPILOT_DEBUG_ENV_VAR = "COPILOT_DEBUG"
|
|
33
34
|
DEFAULT_MAX_AI_CREDITS = 1000
|
|
34
35
|
# The CLI rejects anything lower outright, which reads as a broken agent rather
|
|
35
36
|
# than a misconfigured cap, so a smaller override is raised to it instead.
|
|
@@ -119,6 +120,10 @@ class GitHubCopilotAgent(AbstractCodingAgent):
|
|
|
119
120
|
# body alone — so a skill's model only takes effect via this flag.
|
|
120
121
|
if model:
|
|
121
122
|
cmd += ["--model", model]
|
|
123
|
+
debug_enabled = os.environ.get(
|
|
124
|
+
COPILOT_DEBUG_ENV_VAR, "").strip().lower() == "true"
|
|
125
|
+
if debug_enabled:
|
|
126
|
+
cmd += ["--log-level", "debug"]
|
|
122
127
|
|
|
123
128
|
log.info("Running copilot with message: %s", user_message)
|
|
124
129
|
log.debug("cwd=%s cmd=%s", self._cwd, " ".join(cmd))
|
|
@@ -156,7 +161,7 @@ class GitHubCopilotAgent(AbstractCodingAgent):
|
|
|
156
161
|
# rather than failing a run that the CLI itself called successful.
|
|
157
162
|
log.warning(
|
|
158
163
|
"copilot produced no result event; returning raw output")
|
|
159
|
-
return stdout
|
|
164
|
+
return AgentResponse(stdout, stderr if debug_enabled else "")
|
|
160
165
|
if outcome.get("exitCode"):
|
|
161
166
|
raise RuntimeError(
|
|
162
167
|
f"Copilot run errored (exit code {outcome['exitCode']}): "
|
|
@@ -166,7 +171,7 @@ class GitHubCopilotAgent(AbstractCodingAgent):
|
|
|
166
171
|
raise RuntimeError(
|
|
167
172
|
f"Copilot run produced no response: {_detail(errors, stderr)}"
|
|
168
173
|
)
|
|
169
|
-
return reply
|
|
174
|
+
return AgentResponse(reply, stderr if debug_enabled else "")
|
|
170
175
|
|
|
171
176
|
|
|
172
177
|
def _relative(path: Path, cwd: Path) -> str:
|
|
@@ -7,7 +7,8 @@ from pathlib import Path
|
|
|
7
7
|
from unittest.mock import Mock, patch
|
|
8
8
|
|
|
9
9
|
from codee_agent_github_copilot.provider import (
|
|
10
|
-
MAX_AI_CREDITS_ENV_VAR, GitHubCopilotAgent,
|
|
10
|
+
COPILOT_DEBUG_ENV_VAR, MAX_AI_CREDITS_ENV_VAR, GitHubCopilotAgent,
|
|
11
|
+
_await_result)
|
|
11
12
|
from codee_main_context.context import Settings
|
|
12
13
|
|
|
13
14
|
SESSION = "82232f47-df60-4cb3-8c3a-de12074c9205"
|
|
@@ -153,6 +154,26 @@ class CopilotRunTest(unittest.TestCase):
|
|
|
153
154
|
|
|
154
155
|
self.assertNotIn("--model", self.captured.call_args.args[0])
|
|
155
156
|
|
|
157
|
+
def test_debug_env_adds_log_level_and_preserves_stderr(self) -> None:
|
|
158
|
+
stdout = _stream(_event("assistant.message", {"content": "ok"}),
|
|
159
|
+
_event("result", exitCode=0))
|
|
160
|
+
with patch.dict(os.environ, {COPILOT_DEBUG_ENV_VAR: "true"}):
|
|
161
|
+
response = self._run(_completed(stdout, stderr="debug line\n"))
|
|
162
|
+
|
|
163
|
+
cmd = self.captured.call_args.args[0]
|
|
164
|
+
self.assertEqual(cmd[cmd.index("--log-level") + 1], "debug")
|
|
165
|
+
self.assertEqual(response, "ok")
|
|
166
|
+
self.assertEqual(response.debug_logs, "debug line\n")
|
|
167
|
+
|
|
168
|
+
def test_debug_env_must_be_true(self) -> None:
|
|
169
|
+
stdout = _stream(_event("assistant.message", {"content": "ok"}),
|
|
170
|
+
_event("result", exitCode=0))
|
|
171
|
+
with patch.dict(os.environ, {COPILOT_DEBUG_ENV_VAR: "1"}):
|
|
172
|
+
response = self._run(_completed(stdout, stderr="not retained"))
|
|
173
|
+
|
|
174
|
+
self.assertNotIn("--log-level", self.captured.call_args.args[0])
|
|
175
|
+
self.assertEqual(response.debug_logs, "")
|
|
176
|
+
|
|
156
177
|
def test_the_best_model_is_an_anthropic_catalog_id(self) -> None:
|
|
157
178
|
# Copilot has no latest-tier alias, so this id is pinned by hand and only
|
|
158
179
|
# a real catalog id will be accepted by the CLI.
|
|
@@ -188,6 +209,13 @@ class CopilotRunTest(unittest.TestCase):
|
|
|
188
209
|
self.assertEqual(self._run(_completed("plain text reply\n")),
|
|
189
210
|
"plain text reply\n")
|
|
190
211
|
|
|
212
|
+
def test_raw_output_keeps_debug_logs(self) -> None:
|
|
213
|
+
with patch.dict(os.environ, {COPILOT_DEBUG_ENV_VAR: "TRUE"}):
|
|
214
|
+
response = self._run(_completed("plain text reply\n", "debug\n"))
|
|
215
|
+
|
|
216
|
+
self.assertEqual(response, "plain text reply\n")
|
|
217
|
+
self.assertEqual(response.debug_logs, "debug\n")
|
|
218
|
+
|
|
191
219
|
def test_a_timeout_raises(self) -> None:
|
|
192
220
|
with patch("subprocess.run",
|
|
193
221
|
side_effect=subprocess.TimeoutExpired(cmd="copilot", timeout=7200)):
|
|
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
|
{codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md
RENAMED
|
File without changes
|
{codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md
RENAMED
|
File without changes
|
{codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-code-reviewer/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/story-security-reviewer/SKILL.md
RENAMED
|
File without changes
|
{codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/task-code-reviewer/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codee_agent-0.6.6 → codee_agent-0.6.7}/src/codee/templates/skills/task-security-reviewer/SKILL.md
RENAMED
|
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
|