codexapi 0.6.3__tar.gz → 0.6.5__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.5}/PKG-INFO +11 -5
  2. {codexapi-0.6.3 → codexapi-0.6.5}/README.md +10 -4
  3. {codexapi-0.6.3 → codexapi-0.6.5}/pyproject.toml +1 -1
  4. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/__init__.py +1 -1
  5. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/cli.py +2 -0
  6. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/pushover.py +1 -1
  7. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/watch.py +127 -10
  8. {codexapi-0.6.3 → codexapi-0.6.5/src/codexapi.egg-info}/PKG-INFO +11 -5
  9. {codexapi-0.6.3 → codexapi-0.6.5}/LICENSE +0 -0
  10. {codexapi-0.6.3 → codexapi-0.6.5}/setup.cfg +0 -0
  11. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/__main__.py +0 -0
  12. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/agent.py +0 -0
  13. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/foreach.py +0 -0
  14. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/gh_integration.py +0 -0
  15. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/ralph.py +0 -0
  16. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/rate_limits.py +0 -0
  17. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/science.py +0 -0
  18. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/task.py +0 -0
  19. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/taskfile.py +0 -0
  20. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi/welfare.py +0 -0
  21. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi.egg-info/SOURCES.txt +0 -0
  22. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi.egg-info/dependency_links.txt +0 -0
  23. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi.egg-info/entry_points.txt +0 -0
  24. {codexapi-0.6.3 → codexapi-0.6.5}/src/codexapi.egg-info/requires.txt +0 -0
  25. {codexapi-0.6.3 → codexapi-0.6.5}/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.5
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.5"
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.5"
@@ -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,26 @@ 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
13
-
14
+ from .pushover import Pushover
15
+
16
+ _WELCOME_PROMPT = (
17
+ "Welcome! Today you are running in an autonomous loop that will enable you to return to a long-running task "
18
+ "or system at regular intervals to perform tasks or move towards goals defined by the user's instructions below. "
19
+ "Please follow the instructions completely before responding to the user. Each time you respond to the user, the "
20
+ "system will wait for {minutes} minutes and will then wake you up to check for any changes or progress and continue "
21
+ "your work. Every reply must be JSON in the specific format described at the end of this message."
22
+ )
14
23
  _JSON_INSTRUCTIONS = (
15
24
  "Respond with JSON only (no markdown/backticks/extra text).\n"
16
25
  "Return a single JSON object with keys:\n"
17
26
  " status: string (one line)\n"
18
27
  " continue: boolean\n"
19
- " comments: string\n"
28
+ " comments: string (optional)\n"
20
29
  "To stop this watch loop, set continue to false."
21
30
  )
22
31
 
@@ -43,6 +52,9 @@ def watch(minutes, prompt, cwd=None, yolo=True, flags=None):
43
52
 
44
53
  interval = minutes * 60
45
54
  session = Agent(cwd, yolo, None, flags)
55
+ pushover = Pushover()
56
+ pushover.ensure_ready()
57
+ title = _format_title(prompt)
46
58
 
47
59
  last_sent = None
48
60
  last_result = None
@@ -55,13 +67,36 @@ def watch(minutes, prompt, cwd=None, yolo=True, flags=None):
55
67
  last_sent = sent_at
56
68
 
57
69
  now = datetime.now().astimezone().isoformat(timespec="seconds")
58
- message = _build_tick_prompt(prompt, now, elapsed, tick)
70
+ message = _build_tick_prompt(prompt, now, elapsed, tick, minutes)
59
71
  output = session(message)
60
- result = _parse_status(output)
72
+ try:
73
+ result = _parse_status(output)
74
+ except ValueError as exc:
75
+ print(
76
+ f"[watch {tick} {now}] Invalid JSON from agent, requesting retry: {exc}",
77
+ file=sys.stderr,
78
+ )
79
+ retry_prompt = _json_retry_prompt(prompt, tick, str(exc), output)
80
+ retry_output = session(retry_prompt)
81
+ try:
82
+ result = _parse_status(retry_output)
83
+ except ValueError as exc2:
84
+ details = _format_json_double_failure(
85
+ str(exc),
86
+ output,
87
+ str(exc2),
88
+ retry_output,
89
+ )
90
+ pushover.send(title, f"Watch stopped (invalid JSON).\n{details}")
91
+ raise RuntimeError(
92
+ "Agent was unable to provide valid JSON output after retry.\n"
93
+ + details
94
+ ) from None
61
95
  last_result = result
62
96
  _print_status(now, elapsed, tick, result)
63
97
 
64
98
  if not result["continue"]:
99
+ pushover.send(title, _format_stop_message(tick, now, result))
65
100
  return last_result
66
101
 
67
102
  next_tick = sent_at + interval
@@ -70,11 +105,23 @@ def watch(minutes, prompt, cwd=None, yolo=True, flags=None):
70
105
  time.sleep(sleep_seconds)
71
106
 
72
107
 
73
- def _build_tick_prompt(prompt, now, elapsed, tick):
74
- lines = [
75
- f"Tick {tick}.",
76
- f"Local time now: {now}",
77
- ]
108
+ def _build_tick_prompt(prompt, now, elapsed, tick, minutes):
109
+ lines = []
110
+
111
+ if tick == 1:
112
+ lines.extend(
113
+ [
114
+ _WELCOME_PROMPT.format(minutes=minutes),
115
+ "",
116
+ ]
117
+ )
118
+
119
+ lines.extend(
120
+ [
121
+ f"Tick {tick}.",
122
+ f"Local time now: {now}",
123
+ ]
124
+ )
78
125
  if elapsed is not None:
79
126
  lines.append(
80
127
  "Time since last tick: "
@@ -131,6 +178,77 @@ def _parse_status(output):
131
178
  }
132
179
 
133
180
 
181
+ def _json_retry_prompt(prompt, tick, error, output):
182
+ snippet = _snippet(output, 600)
183
+ lines = [
184
+ f"Your last message (tick {tick}) was not valid JSON.",
185
+ f"Error: {error}",
186
+ "",
187
+ "Here is your previous output (truncated):",
188
+ snippet,
189
+ "",
190
+ "Please try again and respond with JSON only.",
191
+ "Return a fresh status update in the required JSON format.",
192
+ "If you want to ask the user a question, put it in comments.",
193
+ "",
194
+ _JSON_INSTRUCTIONS,
195
+ ]
196
+ return "\n".join(lines).strip()
197
+
198
+
199
+ def _format_title(prompt):
200
+ text = _single_line(prompt).strip() or "codexapi watch"
201
+ if len(text) > 60:
202
+ text = text[:57] + "..."
203
+ return f"Watch: {text}"
204
+
205
+
206
+ def _format_stop_message(tick, now, result):
207
+ status = _single_line(result.get("status") or "").strip()
208
+ header = f"Watch stopped at tick {tick} ({now})."
209
+ if status:
210
+ header = f"{header} {status}"
211
+ comments = (result.get("comments") or "").strip()
212
+ if comments:
213
+ return f"{header}\n{comments}"
214
+ return header
215
+
216
+
217
+ def _format_json_failure(error, output):
218
+ snippet = _snippet(output, 600)
219
+ return "\n".join(
220
+ [
221
+ f"Error: {error}",
222
+ "",
223
+ "Last output (truncated):",
224
+ snippet,
225
+ ]
226
+ ).strip()
227
+
228
+
229
+ def _format_json_double_failure(error_1, output_1, error_2, output_2):
230
+ first = _format_json_failure(error_1, output_1)
231
+ second = _format_json_failure(error_2, output_2)
232
+ return "\n".join(
233
+ [
234
+ "First attempt:",
235
+ first,
236
+ "",
237
+ "Second attempt:",
238
+ second,
239
+ ]
240
+ ).strip()
241
+
242
+
243
+ def _snippet(text, limit):
244
+ text = str(text or "").strip()
245
+ if not text:
246
+ return "(empty)"
247
+ if len(text) <= limit:
248
+ return text
249
+ return text[:limit].rstrip() + "..."
250
+
251
+
134
252
  def _maybe_strip_code_fence(text):
135
253
  if not text.startswith("```"):
136
254
  return text
@@ -177,4 +295,3 @@ def _print_status(now, elapsed, tick, result):
177
295
  comments = result.get("comments") or ""
178
296
  if comments.strip():
179
297
  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.5
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