oh-my-codex 0.21.16 → 0.21.17
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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/crates/omx-runtime/src/main.rs +14 -2
- package/crates/omx-runtime/tests/execution.rs +63 -1
- package/crates/omx-runtime-core/src/dispatch.rs +53 -0
- package/crates/omx-runtime-core/src/engine.rs +637 -35
- package/crates/omx-runtime-core/src/lib.rs +14 -0
- package/crates/omx-runtime-core/src/mailbox.rs +168 -38
- package/dist/cli/__tests__/plugin-cache-live-pin-3790.test.js +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js +3 -3
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +1006 -10
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +1 -1
- package/dist/runtime/__tests__/bridge.test.js +91 -8
- package/dist/runtime/__tests__/bridge.test.js.map +1 -1
- package/dist/runtime/bridge.d.ts +13 -2
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +94 -41
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +12 -0
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +127 -27
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +2 -2
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +19 -5
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +21 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +1532 -11
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/mcp-comm.js +2 -2
- package/dist/team/mcp-comm.js.map +1 -1
- package/dist/team/state/dispatch-recovery.d.ts +8 -0
- package/dist/team/state/dispatch-recovery.d.ts.map +1 -0
- package/dist/team/state/dispatch-recovery.js +88 -0
- package/dist/team/state/dispatch-recovery.js.map +1 -0
- package/dist/team/state/dispatch.d.ts.map +1 -1
- package/dist/team/state/dispatch.js +94 -69
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/mailbox.d.ts +9 -2
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +186 -68
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +116 -30
- package/dist/team/state.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- 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.
|
|
35
|
+
version = "0.21.17"
|
|
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.
|
|
43
|
+
version = "0.21.17"
|
|
44
44
|
dependencies = [
|
|
45
45
|
"libc",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
[[package]]
|
|
49
49
|
name = "omx-mux"
|
|
50
|
-
version = "0.21.
|
|
50
|
+
version = "0.21.17"
|
|
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.
|
|
58
|
+
version = "0.21.17"
|
|
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.
|
|
70
|
+
version = "0.21.17"
|
|
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.
|
|
79
|
+
version = "0.21.17"
|
|
80
80
|
dependencies = [
|
|
81
81
|
"omx-mux",
|
|
82
82
|
]
|
package/Cargo.toml
CHANGED
|
@@ -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(&
|
|
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
|
}
|