refactor: rename pod crate to worker

This commit is contained in:
2026-06-26 00:05:57 +09:00
parent 4c677640f4
commit 6c59fe927b
194 changed files with 6637 additions and 6146 deletions
+132 -126
View File
@@ -20,7 +20,7 @@ fn is_false(value: &bool) -> bool {
}
// ---------------------------------------------------------------------------
// Method (Client → Pod via Unix Socket)
// Method (Client → Worker via Unix Socket)
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -30,34 +30,34 @@ pub enum Method {
Run {
input: Vec<Segment>,
},
/// Human-readable text injected into the target Pod's LLM context
/// Human-readable text injected into the target Worker's LLM context
/// as a non-blocking system message. `auto_run` controls whether an
/// idle target is kicked into `RunForNotification`; weak notifications
/// (`auto_run: false`) are only queued for the next turn/resume/run.
/// No side effects beyond LLM context; use `PodEvent` for typed
/// No side effects beyond LLM context; use `WorkerEvent` for typed
/// lifecycle reports.
Notify {
message: String,
#[serde(default = "default_true", skip_serializing_if = "is_true")]
auto_run: bool,
},
/// Typed lifecycle report from a child Pod to its direct parent.
PodEvent(PodEvent),
/// Typed lifecycle report from a child Worker to its direct parent.
WorkerEvent(WorkerEvent),
Resume,
Cancel,
/// Stop the in-flight turn and transition to `Paused`.
///
/// Unlike `Cancel` (which discards and returns to `Idle`), a paused
/// Pod can resume the interrupted work via `Resume`, or start a
/// Worker can resume the interrupted work via `Resume`, or start a
/// fresh turn via `Run` (orphan `tool_use` items are closed with a
/// synthetic tool result before the new user message is appended).
Pause,
/// Request an explicit compaction while the Pod is otherwise idle.
/// Request an explicit compaction while the Worker is otherwise idle.
///
/// This is a typed control method: clients must not send `compact` as a
/// `Method::Run` user message.
Compact,
/// Ask the Pod to list valid rewind targets from its authoritative session log.
/// Ask the Worker to list valid rewind targets from its authoritative session log.
ListRewindTargets,
/// Truncate the current session back to the selected rewind target and
/// return the selected user input to the client composer.
@@ -66,7 +66,7 @@ pub enum Method {
expected_head_entries: usize,
},
Shutdown,
/// Request a list of completion candidates from the Pod.
/// Request a list of completion candidates from the Worker.
///
/// Reply is sent on the same socket as `Event::Completions` (not
/// broadcast). The IPC server handles this directly and writes
@@ -77,15 +77,15 @@ pub enum Method {
kind: CompletionKind,
prefix: String,
},
/// List Pods visible to this Pod from durable Pod state and the spawned-child
/// registry. This is not a host-wide Pod universe query.
ListPods,
/// Restore a visible stopped/restorable Pod, or report that it is already
/// List Pods visible to this Worker from durable Worker state and the spawned-child
/// registry. This is not a host-wide Worker universe query.
ListWorkers,
/// Restore a visible stopped/restorable Worker, or report that it is already
/// live. Missing state and not-visible state are distinct errors.
RestorePod {
RestoreWorker {
name: String,
},
/// Register another existing Pod as a reciprocal peer of this Pod.
/// Register another existing Worker as a reciprocal peer of this Worker.
///
/// This is metadata/control state only: it does not ask the target's live
/// controller for consent, and it must not grant delegated scope,
@@ -95,9 +95,9 @@ pub enum Method {
},
}
/// Typed lifecycle events sent from a child Pod to its parent.
/// Typed lifecycle events sent from a child Worker to its parent.
///
/// Delivered as `Method::PodEvent` over the parent's Unix socket. The
/// Delivered as `Method::WorkerEvent` over the parent's Unix socket. The
/// parent Controller always applies variant-specific side effects
/// (registry / pod-registry updates). Agent-visible variants are also
/// queued into the notification buffer; control-plane-only variants are
@@ -105,39 +105,42 @@ pub enum Method {
///
/// Transport is fire-and-forget; receivers must tolerate out-of-order
/// delivery (e.g. `TurnEnded` arriving after `ShutDown` for the same
/// child Pod).
/// child Worker).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum PodEvent {
pub enum WorkerEvent {
/// Child finished one turn and is back to IDLE.
TurnEnded { pod_name: String },
TurnEnded { worker_name: String },
/// Engine execution error occurred inside the child's turn.
///
/// Limited to worker runtime failures (provider / tool errors) —
/// does not include transient method-rejection responses such as
/// `AlreadyRunning`.
Errored { pod_name: String, message: String },
Errored {
worker_name: String,
message: String,
},
/// Child has stopped (controller loop is exiting).
ShutDown { pod_name: String },
ShutDown { worker_name: String },
/// Child sub-delegated scope to a grandchild Pod via `SpawnPod`.
/// Child sub-delegated scope to a grandchild Worker via `SpawnWorker`.
///
/// Control-plane only: receivers apply registry side effects and
/// propagate upward, but do not expose this as an agent notification.
///
/// The parent uses this to add the grandchild to its own
/// `spawned_pods.json` so it can manage the grandchild directly
/// `spawned_workers.json` so it can manage the grandchild directly
/// even if the intermediate child dies. The parent then re-fires
/// this event upward (if it has a parent of its own) to maintain
/// the chain to root.
ScopeSubDelegated {
/// Sub-delegating Pod (= the sender itself).
parent_pod: String,
/// Name of the grandchild Pod.
sub_pod: String,
/// Sub-delegating Worker (= the sender itself).
parent_worker: String,
/// Name of the grandchild Worker.
sub_worker: String,
/// Unix-socket path where the grandchild is reachable.
sub_socket: PathBuf,
/// Scope delegated to the grandchild.
@@ -145,7 +148,7 @@ pub enum PodEvent {
},
}
impl PodEvent {
impl WorkerEvent {
/// Whether this event should become an agent-visible notification/history item.
///
/// Control-plane-only events still travel over the same wire enum and still
@@ -153,10 +156,10 @@ impl PodEvent {
/// the notification buffer.
pub fn should_notify_agent(&self) -> bool {
match self {
PodEvent::TurnEnded { .. } | PodEvent::Errored { .. } | PodEvent::ShutDown { .. } => {
true
}
PodEvent::ScopeSubDelegated { .. } => false,
WorkerEvent::TurnEnded { .. }
| WorkerEvent::Errored { .. }
| WorkerEvent::ShutDown { .. } => true,
WorkerEvent::ScopeSubDelegated { .. } => false,
}
}
}
@@ -171,11 +174,11 @@ impl PodEvent {
/// clients (CLI piping, scripts) only need to produce a single
/// `Segment::Text`; richer clients (TUI / GUI) construct typed atoms
/// (paste chips, file refs, knowledge refs, workflow invocations) and
/// send them through directly so the Pod side never has to re-parse a
/// send them through directly so the Worker side never has to re-parse a
/// flattened string.
///
/// Forward compat: payloads with unknown `kind` deserialize to
/// `Segment::Unknown`. Pod treats this the same as known-but-unresolved
/// `Segment::Unknown`. Worker treats this the same as known-but-unresolved
/// variants — emits an alert and inserts a `[unknown input segment]`
/// placeholder into the LLM context so neither user nor LLM is blind to
/// the dropped intent.
@@ -195,7 +198,7 @@ pub enum Segment {
lines: u32,
content: String,
},
/// `@<path>` file-system reference. Pod resolves readable files to
/// `@<path>` file-system reference. Worker resolves readable files to
/// `[File: <path>]` attachments and readable normal directories to shallow
/// `[Dir: <path>]` listings; the flattened user text keeps the literal
/// `@<path>` placeholder either way.
@@ -204,7 +207,7 @@ pub enum Segment {
KnowledgeRef { slug: String },
/// `/<slug>` Workflow invocation (see `docs/plan/workflow.md`).
WorkflowInvoke { slug: String },
/// Unknown variant from a newer client. Pod treats this as an
/// Unknown variant from a newer client. Worker treats this as an
/// unresolved input — surfaces an alert and inserts a placeholder.
/// Round-trip is lossy: re-serializing yields `{"kind":"unknown"}`.
#[serde(other)]
@@ -220,7 +223,7 @@ impl Segment {
/// Flatten a segment slice into the single string the LLM receives
/// as a user message. Pure — no I/O, no alerts. Callers that need
/// to surface user-visible alerts for unresolved refs should do so
/// alongside this call (Pod does so at submit time).
/// alongside this call (Worker does so at submit time).
///
/// Sigil-prefixed variants (`FileRef` / `KnowledgeRef` / `WorkflowInvoke`)
/// flatten back to their literal sigil form (`@<path>`, `#<slug>`,
@@ -258,7 +261,7 @@ impl Segment {
impl Method {
/// Convenience: a `Run` carrying a single `Segment::Text`.
/// Used by dumb clients, inter-Pod tools, and tests that only have
/// Used by dumb clients, inter-Worker tools, and tests that only have
/// a string to forward.
pub fn run_text(s: impl Into<String>) -> Self {
Self::Run {
@@ -268,7 +271,7 @@ impl Method {
}
// ---------------------------------------------------------------------------
// Event (Pod → Client via Unix Socket broadcast)
// Event (Worker → Client via Unix Socket broadcast)
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -291,8 +294,8 @@ pub enum Event {
/// One agent-injected system item committed to history.
///
/// Carries the JSON form of `session_store::SystemItem`. Covers
/// `Method::Notify` echoes, child-Pod lifecycle events from
/// `Method::PodEvent`, `@<path>` / `#<slug>` / `/<slug>`
/// `Method::Notify` echoes, child-Worker lifecycle events from
/// `Method::WorkerEvent`, `@<path>` / `#<slug>` / `/<slug>`
/// resolution payloads, and any future agent-side injection kind.
/// Clients dispatch on the `kind` tag for typed rendering instead
/// of parsing free-text prefixes like `[Notification] …` or
@@ -309,13 +312,13 @@ pub enum Event {
/// Marker event for the start of an Invoke range; the range extends
/// implicitly until the next `InvokeStart`. Fires for every accepted
/// `Method::Run` (kind=`UserSend`), `Method::Notify` (kind=`Notify`),
/// `Method::PodEvent` re-injection (kind=`PodEvent`), and any other
/// `Method::WorkerEvent` re-injection (kind=`WorkerEvent`), and any other
/// IDLE-breaking trigger. Mid-run interrupts (e.g. hook output,
/// `<system-reminder>` injection that doesn't break IDLE) do not
/// emit `InvokeStart` — they appear as `SystemItem` only.
///
/// Carries `kind` only; the payload (user text / notify message /
/// pod event body) is delivered separately via the immediately
/// worker event body) is delivered separately via the immediately
/// following `UserMessage` / `SystemItem` event.
InvokeStart {
kind: InvokeKind,
@@ -453,7 +456,7 @@ pub enum Event {
/// derived view.
///
/// `greeting` and `status` accompany the snapshot so clients render
/// pod identity and current controller state without an extra round
/// worker identity and current controller state without an extra round
/// trip.
///
/// Live updates after the snapshot arrive through the streaming
@@ -465,7 +468,7 @@ pub enum Event {
entries: Vec<serde_json::Value>,
greeting: Greeting,
#[serde(default)]
status: PodStatus,
status: WorkerStatus,
/// Unfinished model output that has already streamed in the current
/// run but is not yet represented by committed snapshot entries.
#[serde(default, skip_serializing_if = "InFlightSnapshot::is_empty")]
@@ -483,10 +486,10 @@ pub enum Event {
#[cfg_attr(feature = "typescript", ts(type = "unknown"))]
entry: serde_json::Value,
},
/// Current Pod controller status. Broadcast on every controller-level
/// Current Worker controller status. Broadcast on every controller-level
/// transition and included in `History` snapshots for late attach.
Status {
status: PodStatus,
status: WorkerStatus,
},
/// Reply to `Method::ListCompletions`. Delivered only to the
/// requesting socket (not broadcast). `entries` is empty when no
@@ -510,15 +513,15 @@ pub enum Event {
input: Vec<Segment>,
summary: RewindSummary,
},
/// Reply to `Method::ListPods`. Payload is a stable JSON value so the Pod
/// Reply to `Method::ListWorkers`. Payload is a stable JSON value so the Worker
/// crate can evolve discovery fields without introducing a protocol
/// dependency on session-store.
PodsListed {
WorkersListed {
#[cfg_attr(feature = "typescript", ts(type = "unknown"))]
pods: serde_json::Value,
workers: serde_json::Value,
},
/// Reply to `Method::RestorePod`.
PodRestored {
/// Reply to `Method::RestoreWorker`.
WorkerRestored {
#[cfg_attr(feature = "typescript", ts(type = "unknown"))]
result: serde_json::Value,
},
@@ -533,7 +536,7 @@ pub enum Event {
/// This is not part of LLM history or prompt context; clients may display it
/// briefly as operational status.
MemoryWorker(MemoryWorkerEvent),
/// Pod has started compacting the current session.
/// Worker has started compacting the current session.
///
/// Fired immediately before a compaction run. Success is signalled by
/// `CompactDone` (with the new `SegmentId`); failure by `CompactFailed`.
@@ -554,10 +557,10 @@ pub enum Event {
Shutdown,
}
/// User-facing alert emitted from the Pod layer.
/// User-facing alert emitted from the Worker layer.
///
/// This is a separate channel from `tracing` (developer logs): entries
/// here are assembled explicitly by the Pod when a condition should be
/// here are assembled explicitly by the Worker when a condition should be
/// surfaced to the person driving the client. Keep messages short and
/// human-readable.
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -596,7 +599,7 @@ pub enum AlertLevel {
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")]
pub enum AlertSource {
Pod,
Worker,
Engine,
Compactor,
AgentsMd,
@@ -668,7 +671,7 @@ pub struct RewindSummary {
/// attach while an LLM response is still streaming.
///
/// These blocks are presentation state only: they are reconstructed from the
/// active Pod controller and must not be treated as committed assistant
/// active Worker controller and must not be treated as committed assistant
/// history. Finalized assistant items continue to come from ordinary snapshot
/// entries.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
@@ -723,21 +726,21 @@ impl InFlightToolCallState {
}
}
/// Pod self-description rendered by the TUI when a session starts empty.
/// Worker self-description rendered by the TUI when a session starts empty.
///
/// Built once in the Pod controller from the resolved manifest and
/// Built once in the Worker controller from the resolved manifest and
/// transmitted alongside `Event::Snapshot` so clients don't need
/// their own view of the manifest.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct Greeting {
pub pod_name: String,
pub worker_name: String,
pub cwd: String,
pub provider: String,
pub model: String,
pub scope_summary: String,
pub tools: Vec<String>,
/// Model context window in tokens. Always filled by the Pod greeting.
/// Model context window in tokens. Always filled by the Worker greeting.
#[serde(default)]
pub context_window: u64,
/// Estimated current session context tokens at connect time.
@@ -752,7 +755,7 @@ pub struct Greeting {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")]
pub enum PodStatus {
pub enum WorkerStatus {
#[default]
Idle,
Running,
@@ -771,7 +774,7 @@ pub enum TurnResult {
///
/// One Invoke groups all entries from this trigger up to the next
/// `Invoke` marker. The kind is the only payload — content (user text,
/// notify message, pod event body) is delivered by the immediately
/// notify message, worker event body) is delivered by the immediately
/// following Turn entry, not by the marker itself.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
@@ -781,8 +784,8 @@ pub enum InvokeKind {
UserSend,
/// `Method::Notify` — free-text notification injected into history.
Notify,
/// `Method::PodEvent` — typed lifecycle report from a child Pod.
PodEvent,
/// `Method::WorkerEvent` — typed lifecycle report from a child Worker.
WorkerEvent,
/// `<system-reminder>` etc. that crosses an IDLE boundary (mid-run
/// reminders that don't break IDLE are SystemItem-only and do not
/// open a new Invoke).
@@ -801,8 +804,8 @@ pub enum RunResult {
Paused,
LimitReached,
/// The accepted Method::Run produced no assistant/tool output before
/// user interruption, so the Pod rolled the submit-time turn state back
/// to its pre-submit snapshot. Clients should treat the Pod as Idle and
/// user interruption, so the Worker rolled the submit-time turn state back
/// to its pre-submit snapshot. Clients should treat the Worker as Idle and
/// restore the just-submitted input into the editable composer if desired.
RolledBack,
}
@@ -824,7 +827,7 @@ pub enum ErrorCode {
// Scope rule / permission (wire type)
//
// Defined here so that both `manifest` (config parsing) and `protocol`
// itself (inter-pod messaging such as `PodEvent::ScopeSubDelegated`) can
// itself (inter-worker messaging such as `WorkerEvent::ScopeSubDelegated`) can
// reference the same type without introducing a reverse dependency.
// ---------------------------------------------------------------------------
@@ -925,9 +928,9 @@ mod tests {
#[test]
fn segment_unknown_variant_decodes_as_unknown() {
// A future client sends a segment kind this Pod has never heard of.
// A future client sends a segment kind this Worker has never heard of.
// Forward compat requirement: deserialization must succeed and the
// unknown payload must surface as `Segment::Unknown` so the Pod
// unknown payload must surface as `Segment::Unknown` so the Worker
// fallback path (placeholder + alert) can fire.
let json = r#"{"kind":"image_ref","url":"https://example.com/x.png"}"#;
let seg: Segment = serde_json::from_str(json).unwrap();
@@ -1028,7 +1031,7 @@ mod tests {
for kind in [
InvokeKind::UserSend,
InvokeKind::Notify,
InvokeKind::PodEvent,
InvokeKind::WorkerEvent,
InvokeKind::SystemReminder,
InvokeKind::Wakeup,
] {
@@ -1219,7 +1222,7 @@ mod tests {
let event = Event::Snapshot {
entries: vec![serde_json::json!({"kind": "user_input", "ts": 1, "segments": []})],
greeting: Greeting {
pod_name: "test".into(),
worker_name: "test".into(),
cwd: "/tmp".into(),
provider: "anthropic".into(),
model: "claude".into(),
@@ -1228,7 +1231,7 @@ mod tests {
context_window: 200_000,
context_tokens: 42_000,
},
status: PodStatus::Paused,
status: WorkerStatus::Paused,
in_flight: InFlightSnapshot::default(),
};
let json = serde_json::to_string(&event).unwrap();
@@ -1236,7 +1239,7 @@ mod tests {
assert_eq!(parsed["event"], "snapshot");
assert!(parsed["data"]["entries"].is_array());
assert_eq!(parsed["data"]["entries"][0]["kind"], "user_input");
assert_eq!(parsed["data"]["greeting"]["pod_name"], "test");
assert_eq!(parsed["data"]["greeting"]["worker_name"], "test");
assert_eq!(parsed["data"]["greeting"]["tools"][0], "Read");
assert_eq!(parsed["data"]["greeting"]["context_window"], 200_000);
assert_eq!(parsed["data"]["greeting"]["context_tokens"], 42_000);
@@ -1245,7 +1248,7 @@ mod tests {
#[test]
fn event_snapshot_in_flight_roundtrip_and_default() {
let inbound = r#"{"event":"snapshot","data":{"entries":[],"greeting":{"pod_name":"test","cwd":"/tmp","provider":"p","model":"m","scope_summary":"s","tools":[]},"status":"running"}}"#;
let inbound = r#"{"event":"snapshot","data":{"entries":[],"greeting":{"worker_name":"test","cwd":"/tmp","provider":"p","model":"m","scope_summary":"s","tools":[]},"status":"running"}}"#;
let decoded: Event = serde_json::from_str(inbound).unwrap();
match decoded {
Event::Snapshot { in_flight, .. } => assert!(in_flight.is_empty()),
@@ -1255,7 +1258,7 @@ mod tests {
let event = Event::Snapshot {
entries: Vec::new(),
greeting: Greeting {
pod_name: "test".into(),
worker_name: "test".into(),
cwd: "/tmp".into(),
provider: "p".into(),
model: "m".into(),
@@ -1264,7 +1267,7 @@ mod tests {
context_window: 0,
context_tokens: 0,
},
status: PodStatus::Running,
status: WorkerStatus::Running,
in_flight: InFlightSnapshot {
blocks: vec![
InFlightBlock::Text {
@@ -1334,7 +1337,7 @@ mod tests {
#[test]
fn event_status_format() {
let event = Event::Status {
status: PodStatus::Running,
status: WorkerStatus::Running,
};
let json = serde_json::to_string(&event).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
@@ -1345,20 +1348,20 @@ mod tests {
assert!(matches!(
decoded,
Event::Status {
status: PodStatus::Running
status: WorkerStatus::Running
}
));
}
#[test]
fn event_snapshot_legacy_without_status_defaults_to_idle() {
let json = r#"{"event":"snapshot","data":{"entries":[],"greeting":{"pod_name":"test","cwd":"/tmp","provider":"anthropic","model":"claude","scope_summary":"","tools":[]}}}"#;
let json = r#"{"event":"snapshot","data":{"entries":[],"greeting":{"worker_name":"test","cwd":"/tmp","provider":"anthropic","model":"claude","scope_summary":"","tools":[]}}}"#;
let decoded: Event = serde_json::from_str(json).unwrap();
match decoded {
Event::Snapshot {
status, greeting, ..
} => {
assert_eq!(status, PodStatus::Idle);
assert_eq!(status, WorkerStatus::Idle);
assert_eq!(greeting.context_window, 0);
assert_eq!(greeting.context_tokens, 0);
}
@@ -1367,34 +1370,37 @@ mod tests {
}
#[test]
fn method_pod_event_turn_ended_roundtrip() {
let method = Method::PodEvent(PodEvent::TurnEnded {
pod_name: "child".into(),
fn method_worker_event_turn_ended_roundtrip() {
let method = Method::WorkerEvent(WorkerEvent::TurnEnded {
worker_name: "child".into(),
});
let json = serde_json::to_string(&method).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["method"], "pod_event");
assert_eq!(parsed["method"], "worker_event");
assert_eq!(parsed["params"]["kind"], "turn_ended");
assert_eq!(parsed["params"]["pod_name"], "child");
assert_eq!(parsed["params"]["worker_name"], "child");
let decoded: Method = serde_json::from_str(&json).unwrap();
assert!(matches!(
decoded,
Method::PodEvent(PodEvent::TurnEnded { ref pod_name }) if pod_name == "child"
Method::WorkerEvent(WorkerEvent::TurnEnded { ref worker_name }) if worker_name == "child"
));
}
#[test]
fn method_pod_event_errored_roundtrip() {
let method = Method::PodEvent(PodEvent::Errored {
pod_name: "child".into(),
fn method_worker_event_errored_roundtrip() {
let method = Method::WorkerEvent(WorkerEvent::Errored {
worker_name: "child".into(),
message: "provider 429".into(),
});
let json = serde_json::to_string(&method).unwrap();
let decoded: Method = serde_json::from_str(&json).unwrap();
match decoded {
Method::PodEvent(PodEvent::Errored { pod_name, message }) => {
assert_eq!(pod_name, "child");
Method::WorkerEvent(WorkerEvent::Errored {
worker_name,
message,
}) => {
assert_eq!(worker_name, "child");
assert_eq!(message, "provider 429");
}
other => panic!("expected Errored, got {other:?}"),
@@ -1402,43 +1408,43 @@ mod tests {
}
#[test]
fn method_pod_event_shutdown_roundtrip() {
let method = Method::PodEvent(PodEvent::ShutDown {
pod_name: "child".into(),
fn method_worker_event_shutdown_roundtrip() {
let method = Method::WorkerEvent(WorkerEvent::ShutDown {
worker_name: "child".into(),
});
let json = serde_json::to_string(&method).unwrap();
let decoded: Method = serde_json::from_str(&json).unwrap();
assert!(matches!(
decoded,
Method::PodEvent(PodEvent::ShutDown { ref pod_name }) if pod_name == "child"
Method::WorkerEvent(WorkerEvent::ShutDown { ref worker_name }) if worker_name == "child"
));
}
#[test]
fn pod_event_agent_notification_classification() {
fn worker_event_agent_notification_classification() {
assert!(
PodEvent::TurnEnded {
pod_name: "child".into()
WorkerEvent::TurnEnded {
worker_name: "child".into()
}
.should_notify_agent()
);
assert!(
PodEvent::Errored {
pod_name: "child".into(),
WorkerEvent::Errored {
worker_name: "child".into(),
message: "boom".into()
}
.should_notify_agent()
);
assert!(
PodEvent::ShutDown {
pod_name: "child".into()
WorkerEvent::ShutDown {
worker_name: "child".into()
}
.should_notify_agent()
);
assert!(
!PodEvent::ScopeSubDelegated {
parent_pod: "child".into(),
sub_pod: "grandchild".into(),
!WorkerEvent::ScopeSubDelegated {
parent_worker: "child".into(),
sub_worker: "grandchild".into(),
sub_socket: "/tmp/grandchild.sock".into(),
scope: vec![],
}
@@ -1447,10 +1453,10 @@ mod tests {
}
#[test]
fn method_pod_event_scope_sub_delegated_roundtrip() {
let method = Method::PodEvent(PodEvent::ScopeSubDelegated {
parent_pod: "child".into(),
sub_pod: "grandchild".into(),
fn method_worker_event_scope_sub_delegated_roundtrip() {
let method = Method::WorkerEvent(WorkerEvent::ScopeSubDelegated {
parent_worker: "child".into(),
sub_worker: "grandchild".into(),
sub_socket: "/run/yoi/grandchild/sock".into(),
scope: vec![ScopeRule {
target: "/tmp/work".into(),
@@ -1461,14 +1467,14 @@ mod tests {
let json = serde_json::to_string(&method).unwrap();
let decoded: Method = serde_json::from_str(&json).unwrap();
match decoded {
Method::PodEvent(PodEvent::ScopeSubDelegated {
parent_pod,
sub_pod,
Method::WorkerEvent(WorkerEvent::ScopeSubDelegated {
parent_worker,
sub_worker,
sub_socket,
scope,
}) => {
assert_eq!(parent_pod, "child");
assert_eq!(sub_pod, "grandchild");
assert_eq!(parent_worker, "child");
assert_eq!(sub_worker, "grandchild");
assert_eq!(sub_socket, PathBuf::from("/run/yoi/grandchild/sock"));
assert_eq!(scope.len(), 1);
assert_eq!(scope[0].target, PathBuf::from("/tmp/work"));
@@ -1619,7 +1625,7 @@ mod tests {
fn event_error_format() {
let event = Event::Error {
code: ErrorCode::AlreadyRunning,
message: "Pod is already executing a turn".into(),
message: "Worker is already executing a turn".into(),
};
let json = serde_json::to_string(&event).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
@@ -1654,8 +1660,8 @@ mod tests {
#[test]
fn pod_discovery_methods_roundtrip() {
let methods = [
Method::ListPods,
Method::RestorePod {
Method::ListWorkers,
Method::RestoreWorker {
name: "child".into(),
},
Method::RegisterPeer {
@@ -1666,8 +1672,8 @@ mod tests {
let json = serde_json::to_string(&method).unwrap();
let decoded: Method = serde_json::from_str(&json).unwrap();
match (decoded, method) {
(Method::ListPods, Method::ListPods)
| (Method::RestorePod { .. }, Method::RestorePod { .. })
(Method::ListWorkers, Method::ListWorkers)
| (Method::RestoreWorker { .. }, Method::RestoreWorker { .. })
| (Method::RegisterPeer { .. }, Method::RegisterPeer { .. }) => {}
(decoded, expected) => panic!("decoded {decoded:?}, expected {expected:?}"),
}
@@ -1677,10 +1683,10 @@ mod tests {
#[test]
fn pod_discovery_events_roundtrip() {
let events = [
Event::PodsListed {
pods: serde_json::json!([{ "pod_name": "child" }]),
Event::WorkersListed {
workers: serde_json::json!([{ "worker_name": "child" }]),
},
Event::PodRestored {
Event::WorkerRestored {
result: serde_json::json!({ "action": "already_live" }),
},
Event::PeerRegistered {
@@ -1691,10 +1697,10 @@ mod tests {
let json = serde_json::to_string(&event).unwrap();
let decoded: Event = serde_json::from_str(&json).unwrap();
match (decoded, event) {
(Event::PodsListed { pods }, Event::PodsListed { pods: expected }) => {
(Event::WorkersListed { workers }, Event::WorkersListed { workers: expected }) => {
assert_eq!(pods, expected)
}
(Event::PodRestored { result }, Event::PodRestored { result: expected }) => {
(Event::WorkerRestored { result }, Event::WorkerRestored { result: expected }) => {
assert_eq!(result, expected)
}
(Event::PeerRegistered { result }, Event::PeerRegistered { result: expected }) => {
+5 -5
View File
@@ -5,8 +5,8 @@ use ts_rs::{Config, TS};
use crate::{
Alert, AlertLevel, AlertSource, CompletionEntry, CompletionKind, ErrorCode, Event, Greeting,
InFlightBlock, InFlightSnapshot, InFlightToolCallState, InvokeKind, MemoryWorkerEvent, Method,
Permission, PodEvent, PodStatus, RewindSummary, RewindTarget, RewindTargetId, RunResult,
ScopeRule, Segment, TurnResult,
Permission, RewindSummary, RewindTarget, RewindTargetId, RunResult, ScopeRule, Segment,
TurnResult, WorkerEvent, WorkerStatus,
};
const GENERATED_RELATIVE_PATH: &str = "../../web/workspace/src/lib/generated/protocol.ts";
@@ -16,7 +16,7 @@ pub fn generated_typescript_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(GENERATED_RELATIVE_PATH)
}
/// Render Workspace web TypeScript bindings for the Pod wire protocol DTOs.
/// Render Workspace web TypeScript bindings for the Worker wire protocol DTOs.
///
/// Rust DTOs in this crate remain the source of truth; this function is used by
/// both the checked-in artifact generator and the stale-output drift test.
@@ -31,7 +31,7 @@ pub fn generated_protocol_types() -> String {
push_decl::<AlertLevel>(&cfg, &mut output);
push_decl::<AlertSource>(&cfg, &mut output);
push_decl::<CompletionKind>(&cfg, &mut output);
push_decl::<PodStatus>(&cfg, &mut output);
push_decl::<WorkerStatus>(&cfg, &mut output);
push_decl::<TurnResult>(&cfg, &mut output);
push_decl::<InvokeKind>(&cfg, &mut output);
push_decl::<RunResult>(&cfg, &mut output);
@@ -49,7 +49,7 @@ pub fn generated_protocol_types() -> String {
push_decl::<Alert>(&cfg, &mut output);
push_decl::<MemoryWorkerEvent>(&cfg, &mut output);
push_decl::<Segment>(&cfg, &mut output);
push_decl::<PodEvent>(&cfg, &mut output);
push_decl::<WorkerEvent>(&cfg, &mut output);
push_decl::<Method>(&cfg, &mut output);
push_decl::<Event>(&cfg, &mut output);