codee-agent 0.6.1__tar.gz → 0.6.3__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.
Files changed (92) hide show
  1. {codee_agent-0.6.1 → codee_agent-0.6.3}/PKG-INFO +1 -1
  2. {codee_agent-0.6.1 → codee_agent-0.6.3}/pyproject.toml +1 -1
  3. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/admin.py +31 -4
  4. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/executor.py +3 -1
  5. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/runs_db.py +14 -6
  6. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/test_runs_db.py +21 -5
  7. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/trigger_aws_sqs_skills.py +2 -0
  8. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/trigger_cron_skills.py +2 -0
  9. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/trigger_email_skills.py +2 -0
  10. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_executor.py +3 -0
  11. {codee_agent-0.6.1 → codee_agent-0.6.3}/LICENSE +0 -0
  12. {codee_agent-0.6.1 → codee_agent-0.6.3}/README.md +0 -0
  13. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/.gitignore +0 -0
  14. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/__init__.py +0 -0
  15. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/admin_api.py +0 -0
  16. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/admin_cli.py +0 -0
  17. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/admin_service.py +0 -0
  18. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/agent_cli.py +0 -0
  19. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/coding_agents.py +0 -0
  20. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/init_cli.py +0 -0
  21. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/__init__.py +0 -0
  22. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/claude_key_rotation.py +0 -0
  23. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/cron_describe.py +0 -0
  24. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/mcp_config.py +0 -0
  25. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/test_claude_key_rotation.py +0 -0
  26. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/test_mcp_config.py +0 -0
  27. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/test_trigger_cron_skills.py +0 -0
  28. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/test_trigger_issue_skills.py +0 -0
  29. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/lib/trigger_issue_skills.py +0 -0
  30. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/mail_server.py +0 -0
  31. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/setup_wizard.py +0 -0
  32. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/start_cli.py +0 -0
  33. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/tasks_providers.py +0 -0
  34. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/AGENTS.md +0 -0
  35. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/CLAUDE.md +0 -0
  36. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md +0 -0
  37. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md +0 -0
  38. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/story-code-reviewer/SKILL.md +0 -0
  39. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/story-developer/SKILL.md +0 -0
  40. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/story-planner/SKILL.md +0 -0
  41. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/story-planner/assets/readme-template.md +0 -0
  42. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/story-qa/SKILL.md +0 -0
  43. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/story-security-reviewer/SKILL.md +0 -0
  44. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/task-code-reviewer/SKILL.md +0 -0
  45. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/task-developer/SKILL.md +0 -0
  46. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/task-qa/SKILL.md +0 -0
  47. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/templates/skills/task-security-reviewer/SKILL.md +0 -0
  48. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_admin_api.py +0 -0
  49. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_admin_cli.py +0 -0
  50. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_admin_service.py +0 -0
  51. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_agent_cli.py +0 -0
  52. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_coding_agents.py +0 -0
  53. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_init_cli.py +0 -0
  54. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_memory_index.py +0 -0
  55. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_setup_wizard.py +0 -0
  56. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/test_start_cli.py +0 -0
  57. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee/workflow_graph.py +0 -0
  58. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_admin/__init__.py +0 -0
  59. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_admin/codee_admin.py +0 -0
  60. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_abstract/__init__.py +0 -0
  61. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_abstract/provider.py +0 -0
  62. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/__init__.py +0 -0
  63. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/account.py +0 -0
  64. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/credentials.py +0 -0
  65. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/oauth.py +0 -0
  66. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/provider.py +0 -0
  67. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/test.py +0 -0
  68. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_claude_code/usage.py +0 -0
  69. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_codex/__init__.py +0 -0
  70. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_codex/provider.py +0 -0
  71. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_codex/test.py +0 -0
  72. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_github_copilot/__init__.py +0 -0
  73. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_github_copilot/provider.py +0 -0
  74. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_agent_github_copilot/test.py +0 -0
  75. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_database/__init__.py +0 -0
  76. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_database/claude_code_accounts.py +0 -0
  77. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_database/database.py +0 -0
  78. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_database/oauth_tokens.py +0 -0
  79. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_main_context/__init__.py +0 -0
  80. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_main_context/context.py +0 -0
  81. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_main_context/logging.py +0 -0
  82. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_main_context/test_context.py +0 -0
  83. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_main_context/test_logging.py +0 -0
  84. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_abstract/__init__.py +0 -0
  85. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_abstract/provider.py +0 -0
  86. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_azure_devops/__init__.py +0 -0
  87. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_azure_devops/oauth.py +0 -0
  88. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_azure_devops/provider.py +0 -0
  89. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_azure_devops/test.py +0 -0
  90. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_jira/__init__.py +0 -0
  91. {codee_agent-0.6.1 → codee_agent-0.6.3}/src/codee_tasks_jira/provider.py +0 -0
  92. {codee_agent-0.6.1 → codee_agent-0.6.3}/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.1
3
+ Version: 0.6.3
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codee-agent"
3
- version = "0.6.1"
3
+ version = "0.6.3"
4
4
  description = "A virtual co-worker that picks up Jira and Azure DevOps tasks and solves them with coding agents."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -64,6 +64,16 @@ def _percent(value: float) -> int:
64
64
  return -1 if value < 0 else round(value)
65
65
 
66
66
 
67
+ def local_datetime(value: rx.Var) -> rx.Component:
68
+ """Render an ISO timestamp in the browser's locale and timezone."""
69
+ return rx.moment(
70
+ date=value,
71
+ local=True,
72
+ locale=rx.Var(_js_expr="navigator.language"),
73
+ format="L LT",
74
+ )
75
+
76
+
67
77
  def _save_toast(persisted: bool, pushed: bool, message: str) -> Any:
68
78
  """Warn instead of erroring when the change landed on disk but not in Git."""
69
79
  if not persisted:
@@ -254,7 +264,10 @@ class RunRecord(BaseModel):
254
264
  status: str
255
265
  error: str
256
266
  started_at: str
267
+ session_id: str
257
268
  message: str
269
+ user_message: str
270
+ response: str
258
271
  preview: str
259
272
  viewer_url: str
260
273
 
@@ -798,7 +811,10 @@ class AdminState(rx.State):
798
811
  status=run["status"],
799
812
  error=run.get("error") or "",
800
813
  started_at=run["started_at"],
814
+ session_id=run.get("session_id") or "",
801
815
  message=message,
816
+ user_message=(run.get("user_message") or message).strip(),
817
+ response=(run.get("response") or "").strip(),
802
818
  preview=preview[:120] + ("..." if len(preview) > 120 else ""),
803
819
  viewer_url=(SERVICE.session_viewer.format(session_id=run["session_id"])
804
820
  if SERVICE.session_viewer and run.get("session_id") else ""),
@@ -1843,7 +1859,8 @@ def usage_meter(label: str, percent: rx.Var, resets: rx.Var) -> rx.Component:
1843
1859
  # reset time on an account at 4% is noise.
1844
1860
  rx.cond(
1845
1861
  (resets != "") & (percent >= 80),
1846
- rx.text("resets " + resets, color=MUTED, font_size="0.68rem"),
1862
+ rx.text("resets ", local_datetime(resets), color=MUTED,
1863
+ font_size="0.68rem"),
1847
1864
  rx.fragment()),
1848
1865
  spacing="1", width="100%")
1849
1866
 
@@ -2384,7 +2401,11 @@ def run_row(run: RunRecord) -> rx.Component:
2384
2401
  rx.flex(
2385
2402
  rx.vstack(rx.hstack(rx.text(run.skill_name, font_weight="600"),
2386
2403
  rx.badge(run.status, color_scheme=rx.cond(run.status == "succeeded", "green", "red"))),
2387
- rx.text(run.started_at, color=MUTED, font_size="0.8rem",
2404
+ rx.text(local_datetime(run.started_at), color=MUTED,
2405
+ font_size="0.8rem",
2406
+ font_family="IBM Plex Mono, monospace"),
2407
+ rx.text("Thread ID: ", run.session_id, color=MUTED,
2408
+ font_size="0.8rem",
2388
2409
  font_family="IBM Plex Mono, monospace"),
2389
2410
  rx.text(run.preview, color=MUTED),
2390
2411
  rx.cond(run.error != "", rx.text(
@@ -2394,8 +2415,14 @@ def run_row(run: RunRecord) -> rx.Component:
2394
2415
  rx.cond(run.viewer_url != "", rx.link(rx.icon("external-link", size=16), href=run.viewer_url,
2395
2416
  is_external=True, aria_label="View session", color=ACCENT)),
2396
2417
  gap="1rem", align="start", width="100%"),
2397
- rx.cond(run.message != "", rx.accordion.root(rx.accordion.item(
2398
- header="Full message", content=rx.text(run.message, white_space="pre-wrap"), value=run.started_at),
2418
+ rx.cond((run.user_message != "") | (run.response != ""), rx.accordion.root(rx.accordion.item(
2419
+ header="Run info", content=rx.vstack(
2420
+ rx.text("User message", font_weight="600"),
2421
+ rx.text(run.user_message, white_space="pre-wrap"),
2422
+ rx.cond(run.response != "", rx.fragment(
2423
+ rx.text("LLM response", font_weight="600", margin_top="0.75rem"),
2424
+ rx.text(run.response, white_space="pre-wrap"))),
2425
+ spacing="2", align="start", width="100%"), value=run.started_at),
2399
2426
  collapsible=True, width="100%")),
2400
2427
  padding="1rem", background=SURFACE, border=BORDER, width="100%")
2401
2428
 
@@ -314,6 +314,7 @@ def _run_task(task_id: str, message: str, session_id: str, skill_name: str,
314
314
  task_id, len(response), response)
315
315
  runs_db.record_run(skill_name, "issue", session_id, "succeeded",
316
316
  started_at=started_at, message=shown,
317
+ user_message=message, response=response,
317
318
  main_context=context)
318
319
  except Exception as exc:
319
320
  # Over-limit / transient failure: leave the task in its current
@@ -322,7 +323,8 @@ def _run_task(task_id: str, message: str, session_id: str, skill_name: str,
322
323
  log.debug("%s failed with:\n%s", task_id, traceback.format_exc())
323
324
  runs_db.record_run(skill_name, "issue", session_id, "failed",
324
325
  error=str(exc)[:500], started_at=started_at,
325
- message=shown, main_context=context)
326
+ message=shown, user_message=message,
327
+ main_context=context)
326
328
  finally:
327
329
  with _inflight_lock:
328
330
  _inflight.discard(task_id)
@@ -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")
14
+ "started_at", "message", "user_message", "response")
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
@@ -50,7 +50,9 @@ def init(main_context: CodeeMainContext) -> None:
50
50
  status TEXT NOT NULL,
51
51
  error TEXT,
52
52
  started_at TEXT NOT NULL,
53
- message TEXT
53
+ message TEXT,
54
+ user_message TEXT,
55
+ response TEXT
54
56
  )"""
55
57
  )
56
58
  conn.execute(
@@ -59,6 +61,10 @@ def init(main_context: CodeeMainContext) -> None:
59
61
  cols = {row[1] for row in conn.execute("PRAGMA table_info(runs)")}
60
62
  if "message" not in cols:
61
63
  conn.execute("ALTER TABLE runs ADD COLUMN message TEXT")
64
+ if "response" not in cols:
65
+ conn.execute("ALTER TABLE runs ADD COLUMN response TEXT")
66
+ if "user_message" not in cols:
67
+ conn.execute("ALTER TABLE runs ADD COLUMN user_message TEXT")
62
68
  # In-flight claude runs; a row lives only while its subprocess is running.
63
69
  conn.execute(
64
70
  """CREATE TABLE IF NOT EXISTS active_jobs (
@@ -78,7 +84,8 @@ def init(main_context: CodeeMainContext) -> None:
78
84
 
79
85
 
80
86
  def record_run(skill_name, trigger_type, session_id, status, error=None, started_at=None,
81
- message=None, *, main_context: CodeeMainContext) -> None:
87
+ message=None, user_message=None, response=None,
88
+ *, main_context: CodeeMainContext) -> None:
82
89
  """Insert one run row. Never raises to the caller (FR-009)."""
83
90
  try:
84
91
  init(main_context)
@@ -88,9 +95,10 @@ def record_run(skill_name, trigger_type, session_id, status, error=None, started
88
95
  with get_db_connection(main_context) as conn:
89
96
  conn.execute(
90
97
  "INSERT INTO runs (skill_name, trigger_type, session_id, status, error,"
91
- " started_at, message) VALUES (?, ?, ?, ?, ?, ?, ?)",
98
+ " started_at, message, user_message, response)"
99
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
92
100
  (skill_name, trigger_type, session_id,
93
- status, error, started_at, message),
101
+ status, error, started_at, message, user_message, response),
94
102
  )
95
103
  except Exception as exc: # ponytail: a logging miss must never abort the skill run
96
104
  print(f"[runs_db] Failed to record run for {skill_name}: {exc}")
@@ -104,7 +112,7 @@ def recent_runs(limit: int = 100, offset: int = 0, *,
104
112
  with get_db_connection(main_context) as conn:
105
113
  rows = conn.execute(
106
114
  "SELECT id, skill_name, trigger_type, session_id, status, error,"
107
- " started_at, message"
115
+ " started_at, message, user_message, response"
108
116
  " FROM runs ORDER BY started_at DESC, id DESC LIMIT ? OFFSET ?",
109
117
  (limit, max(offset, 0)),
110
118
  ).fetchall()
@@ -130,6 +130,17 @@ def test_message_round_trip(tmp_path):
130
130
  assert by_skill == {"a": "hello", "b": "", "c": None}
131
131
 
132
132
 
133
+ def test_response_round_trip(tmp_path):
134
+ ctx = _ctx(tmp_path)
135
+ runs_db.record_run("a", "cron", "s1", "succeeded", message="question",
136
+ user_message="full question", response="final answer",
137
+ main_context=ctx)
138
+
139
+ run, = runs_db.recent_runs(main_context=ctx)
140
+ assert run["user_message"] == "full question"
141
+ assert run["response"] == "final answer"
142
+
143
+
133
144
  # ---------------------------------------------------------------- active_jobs
134
145
  def test_active_job_lifecycle(tmp_path):
135
146
  ctx = _ctx(tmp_path)
@@ -210,7 +221,7 @@ def test_fmt_elapsed():
210
221
  assert runs_db.fmt_elapsed(3700) == "1h 1m"
211
222
 
212
223
 
213
- def test_migration_adds_message_column_without_data_loss(tmp_path):
224
+ def test_migration_adds_run_detail_columns_without_data_loss(tmp_path):
214
225
  ctx = _ctx(tmp_path)
215
226
  # Build an old-schema DB (spec 001, no message column) with one row.
216
227
  with sqlite3.connect(_db_file(tmp_path)) as conn:
@@ -231,10 +242,15 @@ def test_migration_adds_message_column_without_data_loss(tmp_path):
231
242
  assert len(rows) == 1
232
243
  assert rows[0]["skill_name"] == "old"
233
244
  assert rows[0]["message"] is None # pre-feature row reads as NULL
234
-
235
- # New writes carry message; the upgraded DB round-trips it.
236
- runs_db.record_run("new", "cron", "sid-new", "succeeded", message="m", main_context=ctx)
237
- assert runs_db.recent_runs(main_context=ctx)[0]["message"] == "m"
245
+ assert rows[0]["user_message"] is None
246
+ assert rows[0]["response"] is None
247
+
248
+ # New writes carry both details; the upgraded DB round-trips them.
249
+ runs_db.record_run("new", "cron", "sid-new", "succeeded", message="m",
250
+ user_message="u", response="r", main_context=ctx)
251
+ newest = runs_db.recent_runs(main_context=ctx)[0]
252
+ assert (newest["message"], newest["user_message"], newest["response"]) == (
253
+ "m", "u", "r")
238
254
 
239
255
 
240
256
  # The agent Codee names a session for is not always the agent that runs under
@@ -80,11 +80,13 @@ def trigger_aws_sqs_skills(
80
80
  sqs_message_source.delete(message)
81
81
  runs_db.record_run(skill.name, "aws-sqs",
82
82
  session_id, "succeeded", message=prompt,
83
+ user_message=prompt, response=response,
83
84
  main_context=main_context)
84
85
  except Exception as exc:
85
86
  print(f"[aws_sqs_skills] Failed to run {skill.name}: {exc}")
86
87
  runs_db.record_run(skill.name, "aws-sqs", session_id, "failed",
87
88
  error=str(exc)[:500], message=prompt,
89
+ user_message=prompt,
88
90
  main_context=main_context)
89
91
 
90
92
 
@@ -126,6 +126,7 @@ def trigger_cron_skills(
126
126
  f"[cron_skills] Claude response for {skill.name} ({len(response)} chars)")
127
127
  runs_db.record_run(skill.name, "cron", session_id,
128
128
  "succeeded", message=skill.body,
129
+ user_message=skill.body, response=response,
129
130
  main_context=main_context)
130
131
  except Exception as exc:
131
132
  # Don't advance the state slot: leave the job "due" so a later tick
@@ -135,6 +136,7 @@ def trigger_cron_skills(
135
136
  f"[cron_skills] Failed to run {skill.name}, will retry: {exc}")
136
137
  runs_db.record_run(skill.name, "cron", session_id, "failed",
137
138
  error=str(exc)[:500], message=skill.body,
139
+ user_message=skill.body,
138
140
  main_context=main_context)
139
141
  continue
140
142
  if is_forced:
@@ -89,12 +89,14 @@ def trigger_email_skills(
89
89
  path.unlink(missing_ok=True)
90
90
  runs_db.record_run(skill.name, "email", session_id,
91
91
  "succeeded", message=prompt,
92
+ user_message=prompt, response=response,
92
93
  main_context=main_context)
93
94
  except Exception as exc:
94
95
  print(
95
96
  f"[email_skills] Failed to run {skill.name} for {path.name}: {exc}")
96
97
  runs_db.record_run(skill.name, "email", session_id, "failed",
97
98
  error=str(exc)[:500], message=prompt,
99
+ user_message=prompt,
98
100
  main_context=main_context)
99
101
 
100
102
 
@@ -266,6 +266,8 @@ class RunTaskLoggingTest(unittest.TestCase):
266
266
  self.assertEqual(run["status"], "succeeded")
267
267
  self.assertEqual(run["session_id"], "sid-1")
268
268
  self.assertEqual(run["message"], "/story-developer NIM-1")
269
+ self.assertEqual(run["user_message"], "/story-developer NIM-1")
270
+ self.assertEqual(run["response"], "done")
269
271
 
270
272
  def test_failed_run_is_recorded_with_the_error(self) -> None:
271
273
  with patch.object(executor, "_run_agent", side_effect=RuntimeError("over limit")):
@@ -298,6 +300,7 @@ class RunTaskLoggingTest(unittest.TestCase):
298
300
 
299
301
  run_record, = self._runs()
300
302
  self.assertEqual(run_record["message"], "/story-developer 4124")
303
+ self.assertEqual(run_record["user_message"], prompt)
301
304
  # The agent still gets the wording it can act on.
302
305
  self.assertEqual(run.call_args.args[0], prompt)
303
306
 
File without changes
File without changes