codexapi 0.6.3__tar.gz → 0.6.4__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 (25) hide show
  1. {codexapi-0.6.3/src/codexapi.egg-info → codexapi-0.6.4}/PKG-INFO +11 -5
  2. {codexapi-0.6.3 → codexapi-0.6.4}/README.md +10 -4
  3. {codexapi-0.6.3 → codexapi-0.6.4}/pyproject.toml +1 -1
  4. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/__init__.py +1 -1
  5. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/cli.py +2 -0
  6. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/pushover.py +1 -1
  7. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/watch.py +102 -3
  8. {codexapi-0.6.3 → codexapi-0.6.4/src/codexapi.egg-info}/PKG-INFO +11 -5
  9. {codexapi-0.6.3 → codexapi-0.6.4}/LICENSE +0 -0
  10. {codexapi-0.6.3 → codexapi-0.6.4}/setup.cfg +0 -0
  11. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/__main__.py +0 -0
  12. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/agent.py +0 -0
  13. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/foreach.py +0 -0
  14. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/gh_integration.py +0 -0
  15. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/ralph.py +0 -0
  16. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/rate_limits.py +0 -0
  17. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/science.py +0 -0
  18. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/task.py +0 -0
  19. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/taskfile.py +0 -0
  20. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi/welfare.py +0 -0
  21. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi.egg-info/SOURCES.txt +0 -0
  22. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi.egg-info/dependency_links.txt +0 -0
  23. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi.egg-info/entry_points.txt +0 -0
  24. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi.egg-info/requires.txt +0 -0
  25. {codexapi-0.6.3 → codexapi-0.6.4}/src/codexapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -128,7 +128,11 @@ Use `--no-yolo` to run Codex with `--full-auto` instead.
128
128
 
129
129
  Watch mode periodically ticks a long-running agent session with the current time
130
130
  and prints JSON status updates. The agent controls the loop by setting
131
- `continue` to true/false in its JSON response.
131
+ `continue` to true/false in its JSON response. Each tick expects JSON keys:
132
+ `status` (one line), `continue` (bool), and optional `comments` (string). If the
133
+ JSON is invalid, watch asks the agent once to retry before stopping with an
134
+ error. When `~/.pushover` is configured, watch sends a notification when it
135
+ stops.
132
136
 
133
137
  ```bash
134
138
  codexapi watch 5 "Run the benchmark and wait for results."
@@ -162,7 +166,8 @@ Optional Pushover notifications: create `~/.pushover` with two non-empty lines.
162
166
  Line 1 is your user or group key, line 2 is the app API token. When this file
163
167
  exists, Science will send a notification whenever it detects a new best result,
164
168
  including the metric values and percent improvement. Task runs will also send a
165
- ✅/❌ notification with the task summary.
169
+ ✅/❌ notification with the task summary. Watch runs send a notification when the
170
+ loop stops.
166
171
 
167
172
  Run a task file across a list file:
168
173
 
@@ -201,8 +206,9 @@ the same conversation and returns only the agent's message.
201
206
 
202
207
  Runs a long-lived agent session and periodically "ticks" it with the current
203
208
  local time and a reminder of `prompt`. Each tick expects JSON with keys:
204
- `status` (one line), `continue` (bool), and `comments` (string). The loop stops
205
- when `continue` is false.
209
+ `status` (one line), `continue` (bool), and optional `comments` (string). If the
210
+ JSON is invalid, watch asks the agent once to retry. The loop stops when
211
+ `continue` is false and sends a Pushover notification (when configured).
206
212
 
207
213
  ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None) -> str`
208
214
 
@@ -113,7 +113,11 @@ Use `--no-yolo` to run Codex with `--full-auto` instead.
113
113
 
114
114
  Watch mode periodically ticks a long-running agent session with the current time
115
115
  and prints JSON status updates. The agent controls the loop by setting
116
- `continue` to true/false in its JSON response.
116
+ `continue` to true/false in its JSON response. Each tick expects JSON keys:
117
+ `status` (one line), `continue` (bool), and optional `comments` (string). If the
118
+ JSON is invalid, watch asks the agent once to retry before stopping with an
119
+ error. When `~/.pushover` is configured, watch sends a notification when it
120
+ stops.
117
121
 
118
122
  ```bash
119
123
  codexapi watch 5 "Run the benchmark and wait for results."
@@ -147,7 +151,8 @@ Optional Pushover notifications: create `~/.pushover` with two non-empty lines.
147
151
  Line 1 is your user or group key, line 2 is the app API token. When this file
148
152
  exists, Science will send a notification whenever it detects a new best result,
149
153
  including the metric values and percent improvement. Task runs will also send a
150
- ✅/❌ notification with the task summary.
154
+ ✅/❌ notification with the task summary. Watch runs send a notification when the
155
+ loop stops.
151
156
 
152
157
  Run a task file across a list file:
153
158
 
@@ -186,8 +191,9 @@ the same conversation and returns only the agent's message.
186
191
 
187
192
  Runs a long-lived agent session and periodically "ticks" it with the current
188
193
  local time and a reminder of `prompt`. Each tick expects JSON with keys:
189
- `status` (one line), `continue` (bool), and `comments` (string). The loop stops
190
- when `continue` is false.
194
+ `status` (one line), `continue` (bool), and optional `comments` (string). If the
195
+ JSON is invalid, watch asks the agent once to retry. The loop stops when
196
+ `continue` is false and sends a Pushover notification (when configured).
191
197
 
192
198
  ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None) -> str`
193
199
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codexapi"
7
- version = "0.6.3"
7
+ version = "0.6.4"
8
8
  description = "Minimal Python API for running the Codex CLI."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -27,4 +27,4 @@ __all__ = [
27
27
  "task_result",
28
28
  "watch",
29
29
  ]
30
- __version__ = "0.6.3"
30
+ __version__ = "0.6.4"
@@ -1543,6 +1543,8 @@ def main(argv=None):
1543
1543
  watch(args.minutes, prompt, args.cwd, args.yolo, args.flags)
1544
1544
  except KeyboardInterrupt:
1545
1545
  raise SystemExit(130)
1546
+ except Exception as exc:
1547
+ raise SystemExit(str(exc) or "watch failed") from None
1546
1548
  return
1547
1549
  if args.command == "task":
1548
1550
  if args.project:
@@ -15,7 +15,7 @@ _PUSHOVER_URL = "https://api.pushover.net/1/messages.json"
15
15
  _MAX_MESSAGE = 1024
16
16
 
17
17
  _STARTUP_MESSAGE = (
18
- "Pushover user and app keys read, notifications for task and science enabled."
18
+ "Pushover user and app keys read, notifications for task/science/watch enabled."
19
19
  )
20
20
 
21
21
 
@@ -6,17 +6,19 @@ small JSON status payload so the loop can decide whether to continue.
6
6
  """
7
7
 
8
8
  import json
9
+ import sys
9
10
  import time
10
11
  from datetime import datetime
11
12
 
12
13
  from .agent import Agent
14
+ from .pushover import Pushover
13
15
 
14
16
  _JSON_INSTRUCTIONS = (
15
17
  "Respond with JSON only (no markdown/backticks/extra text).\n"
16
18
  "Return a single JSON object with keys:\n"
17
19
  " status: string (one line)\n"
18
20
  " continue: boolean\n"
19
- " comments: string\n"
21
+ " comments: string (optional)\n"
20
22
  "To stop this watch loop, set continue to false."
21
23
  )
22
24
 
@@ -43,6 +45,9 @@ def watch(minutes, prompt, cwd=None, yolo=True, flags=None):
43
45
 
44
46
  interval = minutes * 60
45
47
  session = Agent(cwd, yolo, None, flags)
48
+ pushover = Pushover()
49
+ pushover.ensure_ready()
50
+ title = _format_title(prompt)
46
51
 
47
52
  last_sent = None
48
53
  last_result = None
@@ -57,11 +62,34 @@ def watch(minutes, prompt, cwd=None, yolo=True, flags=None):
57
62
  now = datetime.now().astimezone().isoformat(timespec="seconds")
58
63
  message = _build_tick_prompt(prompt, now, elapsed, tick)
59
64
  output = session(message)
60
- result = _parse_status(output)
65
+ try:
66
+ result = _parse_status(output)
67
+ except ValueError as exc:
68
+ print(
69
+ f"[watch {tick} {now}] Invalid JSON from agent, requesting retry: {exc}",
70
+ file=sys.stderr,
71
+ )
72
+ retry_prompt = _json_retry_prompt(prompt, tick, str(exc), output)
73
+ retry_output = session(retry_prompt)
74
+ try:
75
+ result = _parse_status(retry_output)
76
+ except ValueError as exc2:
77
+ details = _format_json_double_failure(
78
+ str(exc),
79
+ output,
80
+ str(exc2),
81
+ retry_output,
82
+ )
83
+ pushover.send(title, f"Watch stopped (invalid JSON).\n{details}")
84
+ raise RuntimeError(
85
+ "Agent was unable to provide valid JSON output after retry.\n"
86
+ + details
87
+ ) from None
61
88
  last_result = result
62
89
  _print_status(now, elapsed, tick, result)
63
90
 
64
91
  if not result["continue"]:
92
+ pushover.send(title, _format_stop_message(tick, now, result))
65
93
  return last_result
66
94
 
67
95
  next_tick = sent_at + interval
@@ -131,6 +159,78 @@ def _parse_status(output):
131
159
  }
132
160
 
133
161
 
162
+ def _json_retry_prompt(prompt, tick, error, output):
163
+ snippet = _snippet(output, 600)
164
+ lines = [
165
+ f"Your last message (tick {tick}) was not valid JSON.",
166
+ f"Error: {error}",
167
+ "",
168
+ "Here is your previous output (truncated):",
169
+ snippet,
170
+ "",
171
+ "Please try again and respond with JSON only.",
172
+ "",
173
+ "A reminder: your instructions are:",
174
+ prompt.strip(),
175
+ "",
176
+ _JSON_INSTRUCTIONS,
177
+ ]
178
+ return "\n".join(lines).strip()
179
+
180
+
181
+ def _format_title(prompt):
182
+ text = _single_line(prompt).strip() or "codexapi watch"
183
+ if len(text) > 60:
184
+ text = text[:57] + "..."
185
+ return f"Watch: {text}"
186
+
187
+
188
+ def _format_stop_message(tick, now, result):
189
+ status = _single_line(result.get("status") or "").strip()
190
+ header = f"Watch stopped at tick {tick} ({now})."
191
+ if status:
192
+ header = f"{header} {status}"
193
+ comments = (result.get("comments") or "").strip()
194
+ if comments:
195
+ return f"{header}\n{comments}"
196
+ return header
197
+
198
+
199
+ def _format_json_failure(error, output):
200
+ snippet = _snippet(output, 600)
201
+ return "\n".join(
202
+ [
203
+ f"Error: {error}",
204
+ "",
205
+ "Last output (truncated):",
206
+ snippet,
207
+ ]
208
+ ).strip()
209
+
210
+
211
+ def _format_json_double_failure(error_1, output_1, error_2, output_2):
212
+ first = _format_json_failure(error_1, output_1)
213
+ second = _format_json_failure(error_2, output_2)
214
+ return "\n".join(
215
+ [
216
+ "First attempt:",
217
+ first,
218
+ "",
219
+ "Second attempt:",
220
+ second,
221
+ ]
222
+ ).strip()
223
+
224
+
225
+ def _snippet(text, limit):
226
+ text = str(text or "").strip()
227
+ if not text:
228
+ return "(empty)"
229
+ if len(text) <= limit:
230
+ return text
231
+ return text[:limit].rstrip() + "..."
232
+
233
+
134
234
  def _maybe_strip_code_fence(text):
135
235
  if not text.startswith("```"):
136
236
  return text
@@ -177,4 +277,3 @@ def _print_status(now, elapsed, tick, result):
177
277
  comments = result.get("comments") or ""
178
278
  if comments.strip():
179
279
  print(comments.rstrip())
180
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -128,7 +128,11 @@ Use `--no-yolo` to run Codex with `--full-auto` instead.
128
128
 
129
129
  Watch mode periodically ticks a long-running agent session with the current time
130
130
  and prints JSON status updates. The agent controls the loop by setting
131
- `continue` to true/false in its JSON response.
131
+ `continue` to true/false in its JSON response. Each tick expects JSON keys:
132
+ `status` (one line), `continue` (bool), and optional `comments` (string). If the
133
+ JSON is invalid, watch asks the agent once to retry before stopping with an
134
+ error. When `~/.pushover` is configured, watch sends a notification when it
135
+ stops.
132
136
 
133
137
  ```bash
134
138
  codexapi watch 5 "Run the benchmark and wait for results."
@@ -162,7 +166,8 @@ Optional Pushover notifications: create `~/.pushover` with two non-empty lines.
162
166
  Line 1 is your user or group key, line 2 is the app API token. When this file
163
167
  exists, Science will send a notification whenever it detects a new best result,
164
168
  including the metric values and percent improvement. Task runs will also send a
165
- ✅/❌ notification with the task summary.
169
+ ✅/❌ notification with the task summary. Watch runs send a notification when the
170
+ loop stops.
166
171
 
167
172
  Run a task file across a list file:
168
173
 
@@ -201,8 +206,9 @@ the same conversation and returns only the agent's message.
201
206
 
202
207
  Runs a long-lived agent session and periodically "ticks" it with the current
203
208
  local time and a reminder of `prompt`. Each tick expects JSON with keys:
204
- `status` (one line), `continue` (bool), and `comments` (string). The loop stops
205
- when `continue` is false.
209
+ `status` (one line), `continue` (bool), and optional `comments` (string). If the
210
+ JSON is invalid, watch asks the agent once to retry. The loop stops when
211
+ `continue` is false and sends a Pushover notification (when configured).
206
212
 
207
213
  ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None) -> str`
208
214
 
File without changes
File without changes
File without changes
File without changes
File without changes