oh-my-codex 0.21.16 → 0.21.18

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 (58) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/crates/omx-runtime/src/main.rs +14 -2
  4. package/crates/omx-runtime/tests/execution.rs +63 -1
  5. package/crates/omx-runtime-core/src/dispatch.rs +53 -0
  6. package/crates/omx-runtime-core/src/engine.rs +637 -35
  7. package/crates/omx-runtime-core/src/lib.rs +14 -0
  8. package/crates/omx-runtime-core/src/mailbox.rs +168 -38
  9. package/dist/cli/__tests__/doctor-invalid-config.test.js +57 -0
  10. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  11. package/dist/cli/__tests__/plugin-cache-live-pin-3790.test.js +1 -1
  12. package/dist/cli/__tests__/setup-refresh.test.js +54 -0
  13. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  14. package/dist/compat/__tests__/rust-runtime-compat.test.js +3 -3
  15. package/dist/config/__tests__/generator-idempotent.test.js +64 -1
  16. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  17. package/dist/config/generator.d.ts.map +1 -1
  18. package/dist/config/generator.js +66 -21
  19. package/dist/config/generator.js.map +1 -1
  20. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +1006 -10
  21. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  22. package/dist/mcp/state-server.d.ts +1 -1
  23. package/dist/runtime/__tests__/bridge.test.js +91 -8
  24. package/dist/runtime/__tests__/bridge.test.js.map +1 -1
  25. package/dist/runtime/bridge.d.ts +13 -2
  26. package/dist/runtime/bridge.d.ts.map +1 -1
  27. package/dist/runtime/bridge.js +94 -41
  28. package/dist/runtime/bridge.js.map +1 -1
  29. package/dist/scripts/notify-hook/team-dispatch.d.ts +12 -0
  30. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  31. package/dist/scripts/notify-hook/team-dispatch.js +127 -27
  32. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  33. package/dist/team/__tests__/api-interop.test.js +2 -2
  34. package/dist/team/__tests__/delivery-e2e-smoke.test.js +19 -5
  35. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  36. package/dist/team/__tests__/runtime.test.js +21 -1
  37. package/dist/team/__tests__/runtime.test.js.map +1 -1
  38. package/dist/team/__tests__/state.test.js +1532 -11
  39. package/dist/team/__tests__/state.test.js.map +1 -1
  40. package/dist/team/mcp-comm.js +2 -2
  41. package/dist/team/mcp-comm.js.map +1 -1
  42. package/dist/team/state/dispatch-recovery.d.ts +8 -0
  43. package/dist/team/state/dispatch-recovery.d.ts.map +1 -0
  44. package/dist/team/state/dispatch-recovery.js +88 -0
  45. package/dist/team/state/dispatch-recovery.js.map +1 -0
  46. package/dist/team/state/dispatch.d.ts.map +1 -1
  47. package/dist/team/state/dispatch.js +94 -69
  48. package/dist/team/state/dispatch.js.map +1 -1
  49. package/dist/team/state/mailbox.d.ts +9 -2
  50. package/dist/team/state/mailbox.d.ts.map +1 -1
  51. package/dist/team/state/mailbox.js +186 -68
  52. package/dist/team/state/mailbox.js.map +1 -1
  53. package/dist/team/state.d.ts.map +1 -1
  54. package/dist/team/state.js +116 -30
  55. package/dist/team/state.js.map +1 -1
  56. package/package.json +1 -1
  57. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  58. package/src/scripts/notify-hook/team-dispatch.ts +117 -26
package/Cargo.lock CHANGED
@@ -32,7 +32,7 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
32
32
 
33
33
  [[package]]
34
34
  name = "omx-api"
35
- version = "0.21.16"
35
+ version = "0.21.18"
36
36
  dependencies = [
37
37
  "serde",
38
38
  "serde_json",
@@ -40,14 +40,14 @@ dependencies = [
40
40
 
41
41
  [[package]]
42
42
  name = "omx-explore-harness"
43
- version = "0.21.16"
43
+ version = "0.21.18"
44
44
  dependencies = [
45
45
  "libc",
46
46
  ]
47
47
 
48
48
  [[package]]
49
49
  name = "omx-mux"
50
- version = "0.21.16"
50
+ version = "0.21.18"
51
51
  dependencies = [
52
52
  "serde",
53
53
  "serde_json",
@@ -55,7 +55,7 @@ dependencies = [
55
55
 
56
56
  [[package]]
57
57
  name = "omx-runtime"
58
- version = "0.21.16"
58
+ version = "0.21.18"
59
59
  dependencies = [
60
60
  "fs2",
61
61
  "libc",
@@ -67,7 +67,7 @@ dependencies = [
67
67
 
68
68
  [[package]]
69
69
  name = "omx-runtime-core"
70
- version = "0.21.16"
70
+ version = "0.21.18"
71
71
  dependencies = [
72
72
  "fs2",
73
73
  "serde",
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "omx-sparkshell"
79
- version = "0.21.16"
79
+ version = "0.21.18"
80
80
  dependencies = [
81
81
  "omx-mux",
82
82
  ]
package/Cargo.toml CHANGED
@@ -11,7 +11,7 @@ resolver = "2"
11
11
 
12
12
  [workspace.package]
13
13
 
14
- version = "0.21.16"
14
+ version = "0.21.18"
15
15
 
16
16
  edition = "2021"
17
17
  rust-version = "1.73"
@@ -27,6 +27,7 @@ fn run() -> Result<(), String> {
27
27
  if second == Some("--json") {
28
28
  let summary = serde_json::json!({
29
29
  "schema_version": omx_runtime_core::RUNTIME_SCHEMA_VERSION,
30
+ "capabilities": omx_runtime_core::RUNTIME_CAPABILITIES,
30
31
  "commands": omx_runtime_core::RUNTIME_COMMAND_NAMES,
31
32
  "events": omx_runtime_core::RUNTIME_EVENT_NAMES,
32
33
  "transport": "tmux",
@@ -70,7 +71,12 @@ fn run() -> Result<(), String> {
70
71
  }
71
72
  Ok(())
72
73
  }
73
- Some("exec") => {
74
+ Some("exec") | Some("exec-scoped") => {
75
+ if first == Some("exec-scoped")
76
+ && !omx_runtime_core::RUNTIME_CAPABILITIES.contains(&"team-mailbox-scope-v1")
77
+ {
78
+ return Err("omx-runtime requires team-mailbox-scope-v1".to_string());
79
+ }
74
80
  let json_input = second.ok_or("exec requires a JSON command argument")?;
75
81
  let state_dir = args.iter().find_map(|a| a.strip_prefix("--state-dir="));
76
82
  let compact = args.iter().any(|a| a == "--compact");
@@ -119,9 +125,14 @@ fn run() -> Result<(), String> {
119
125
  .map_err(|e| format!("compatibility view failed: {e}"))?;
120
126
  }
121
127
 
128
+ let mut response = serde_json::to_value(&event).map_err(|e| e.to_string())?;
129
+ if matches!(event, omx_runtime_core::RuntimeEvent::SnapshotCaptured) {
130
+ response["dispatch_records"] =
131
+ serde_json::to_value(engine.dispatch_records()).map_err(|e| e.to_string())?;
132
+ }
122
133
  println!(
123
134
  "{}",
124
- serde_json::to_string_pretty(&event).map_err(|e| e.to_string())?
135
+ serde_json::to_string_pretty(&response).map_err(|e| e.to_string())?
125
136
  );
126
137
  Ok(())
127
138
  }
@@ -536,6 +547,7 @@ fn print_usage() {
536
547
  " snapshot [--json] [--state-dir=DIR] print a runtime snapshot\n",
537
548
  " mux-contract print the mux boundary summary\n",
538
549
  " exec <json> [--state-dir=DIR] process a runtime command from JSON\n",
550
+ " exec-scoped <json> [--state-dir=DIR] require Team isolation in the executing process\n",
539
551
  " lease-mutex <absolute-canonical-dir> <expected-dev> <expected-ino> <safe-leaf> hold native lease mutex over stdin JSON protocol\n",
540
552
  " init <state-dir> initialize a fresh state directory\n",
541
553
  ));
@@ -178,6 +178,10 @@ fn schema_json_subcommand_prints_valid_json() {
178
178
  let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
179
179
  let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("valid JSON");
180
180
  assert_eq!(parsed["schema_version"], 1);
181
+ assert!(parsed["capabilities"]
182
+ .as_array()
183
+ .unwrap()
184
+ .contains(&serde_json::json!("team-mailbox-scope-v1")));
181
185
  assert!(parsed["commands"].is_array());
182
186
  assert!(parsed["events"].is_array());
183
187
  }
@@ -240,6 +244,64 @@ fn exec_subcommand_processes_json_command() {
240
244
  let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
241
245
  let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("valid JSON");
242
246
  assert_eq!(parsed["event"], "SnapshotCaptured");
247
+ assert_eq!(parsed["dispatch_records"], serde_json::json!([]));
248
+ }
249
+
250
+ #[test]
251
+ fn capture_snapshot_returns_event_authoritative_dispatch_records() {
252
+ let dir = std::env::temp_dir().join(format!("omx-capture-records-{}", std::process::id()));
253
+ let _ = std::fs::remove_dir_all(&dir);
254
+ let state_arg = format!("--state-dir={}", dir.display());
255
+ let run = |command: &str| {
256
+ let output = Command::new(env!("CARGO_BIN_EXE_omx-runtime"))
257
+ .args(["exec-scoped", command, &state_arg])
258
+ .output()
259
+ .unwrap();
260
+ assert!(
261
+ output.status.success(),
262
+ "{}",
263
+ String::from_utf8_lossy(&output.stderr)
264
+ );
265
+ serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap()
266
+ };
267
+ run(
268
+ r#"{"command":"QueueDispatch","request_id":"queued","target":"worker-1","metadata":{"team_name":"alpha"}}"#,
269
+ );
270
+ let stale = std::fs::read(dir.join("dispatch.json")).unwrap();
271
+ run(r#"{"command":"MarkNotified","request_id":"queued","channel":"tmux"}"#);
272
+ std::fs::write(dir.join("dispatch.json"), stale).unwrap();
273
+ let response = run(r#"{"command":"CaptureSnapshot"}"#);
274
+ let expected: serde_json::Value =
275
+ serde_json::from_slice(&std::fs::read(dir.join("dispatch.json")).unwrap()).unwrap();
276
+ assert_eq!(response["dispatch_records"], expected["records"]);
277
+ assert_eq!(response["dispatch_records"][0]["status"], "notified");
278
+ std::fs::remove_dir_all(dir).unwrap();
279
+ }
280
+
281
+ #[test]
282
+ fn exec_scoped_checks_and_persists_team_scope_in_the_mutating_process() {
283
+ let dir = std::env::temp_dir().join("omx-runtime-test-exec-scoped");
284
+ let _ = std::fs::remove_dir_all(&dir);
285
+ let state_arg = format!("--state-dir={}", dir.display());
286
+ let command = r#"{"command":"CreateMailboxMessage","team_name":"alpha","message_id":"scoped","from_worker":"leader-fixed","to_worker":"worker-1","body":"private"}"#;
287
+ let output = Command::new(env!("CARGO_BIN_EXE_omx-runtime"))
288
+ .args(["exec-scoped", command, &state_arg, "--compact"])
289
+ .output()
290
+ .expect("ran omx-runtime");
291
+ assert!(
292
+ output.status.success(),
293
+ "{}",
294
+ String::from_utf8_lossy(&output.stderr)
295
+ );
296
+ let event: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
297
+ assert_eq!(event["team_name"], "alpha");
298
+ let mailbox: serde_json::Value =
299
+ serde_json::from_slice(&std::fs::read(dir.join("mailbox.json")).unwrap()).unwrap();
300
+ assert_eq!(mailbox["records"][0]["team_name"], "alpha");
301
+ let events: serde_json::Value =
302
+ serde_json::from_slice(&std::fs::read(dir.join("events.json")).unwrap()).unwrap();
303
+ assert_eq!(events[0]["team_name"], "alpha");
304
+ std::fs::remove_dir_all(dir).unwrap();
243
305
  }
244
306
 
245
307
  #[test]
@@ -522,7 +584,7 @@ fn exec_compact_immediately_persists_only_nonterminal_dispatch_records() {
522
584
  )
523
585
  .expect("valid events JSON");
524
586
  let events = events.as_array().expect("events array");
525
- assert!(events
587
+ assert!(!events
526
588
  .iter()
527
589
  .any(|event| event["event"] == "SnapshotCaptured"));
528
590
  assert!(events
@@ -180,6 +180,59 @@ impl DispatchLog {
180
180
  &self.records
181
181
  }
182
182
 
183
+ pub(crate) fn recover_pending_hook_attempts(&mut self, projection: &Self) {
184
+ let projected: std::collections::HashMap<_, _> = projection
185
+ .records()
186
+ .iter()
187
+ .map(|record| (record.request_id.as_str(), record))
188
+ .collect();
189
+ for record in &mut self.records {
190
+ let Some(previous) = projected.get(record.request_id.as_str()) else {
191
+ continue;
192
+ };
193
+ if record.status != DispatchStatus::Pending
194
+ || previous.status != DispatchStatus::Pending
195
+ || record.target != previous.target
196
+ {
197
+ continue;
198
+ }
199
+ let Some(metadata) = record
200
+ .metadata
201
+ .as_mut()
202
+ .and_then(|value| value.as_object_mut())
203
+ else {
204
+ continue;
205
+ };
206
+ let Some(previous_metadata) = previous
207
+ .metadata
208
+ .as_ref()
209
+ .and_then(|value| value.as_object())
210
+ else {
211
+ continue;
212
+ };
213
+ let team = metadata.get("team_name").and_then(|value| value.as_str());
214
+ if team.map_or(true, str::is_empty)
215
+ || team
216
+ != previous_metadata
217
+ .get("team_name")
218
+ .and_then(|value| value.as_str())
219
+ {
220
+ continue;
221
+ }
222
+ // Hook retries have no runtime event; all other fields remain event-owned.
223
+ for key in ["attempt_count", "last_attempt_at"] {
224
+ if let Some(value) = previous_metadata.get(key) {
225
+ if (key == "attempt_count" && value.as_u64().is_some())
226
+ || (key == "last_attempt_at" && value.is_string())
227
+ {
228
+ metadata.insert(key.into(), value.clone());
229
+ }
230
+ }
231
+ }
232
+ record.reason = previous.reason.clone();
233
+ }
234
+ }
235
+
183
236
  pub fn request_ids(&self) -> impl Iterator<Item = &str> {
184
237
  self.records.iter().map(|record| record.request_id.as_str())
185
238
  }