pub struct CCCCClient { /* private fields */ }Expand description
Blocking CCCC Daemon IPC v1 client.
Implementations§
Source§impl CCCCClient
impl CCCCClient
Sourcepub fn discover() -> Result<Self>
pub fn discover() -> Result<Self>
Discover the currently running daemon.
Examples found in repository?
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let client = CCCCClient::discover()?;
5 let requirements = CompatibilityRequirements {
6 minimum_ipc_version: 1,
7 operations: vec![
8 "groups",
9 "group_show",
10 "send",
11 "tracked_send",
12 "reply",
13 "inbox_read",
14 "message_history",
15 "context_get",
16 "context_sync",
17 "group_preamble_get",
18 "send_files",
19 "terminal_history",
20 "term_resize",
21 "web_model_delivery_preferences_get",
22 "web_model_delivery_preferences_update",
23 "web_model_runtime_recover_turn",
24 "assistant_voice_document_library",
25 "assistant_voice_document_library_update",
26 "assistant_voice_document_delete",
27 ],
28 ..Default::default()
29 };
30 let daemon = client.assert_compatible(&requirements)?;
31 println!(
32 "CCCC {} ({}) is compatible with IPC v{}",
33 daemon.version, daemon.implementation, daemon.ipc_v
34 );
35 Ok(())
36}More examples
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}Sourcepub fn discover_in(cccc_home: Option<&Path>) -> Result<Self>
pub fn discover_in(cccc_home: Option<&Path>) -> Result<Self>
Discover a daemon under an explicit CCCC_HOME.
pub fn new(endpoint: DaemonEndpoint) -> Self
pub fn with_timeout(self, timeout: Duration) -> Self
Sourcepub fn endpoint(&self) -> &DaemonEndpoint
pub fn endpoint(&self) -> &DaemonEndpoint
The endpoint supplied to new or found during initial discovery.
Sourcepub fn current_endpoint(&self) -> DaemonEndpoint
pub fn current_endpoint(&self) -> DaemonEndpoint
The endpoint currently used for calls, including a successful rediscovery after daemon restart.
Sourcepub fn call(
&self,
op: &str,
args: Map<String, Value>,
) -> Result<Map<String, Value>>
pub fn call( &self, op: &str, args: Map<String, Value>, ) -> Result<Map<String, Value>>
Call any non-streaming daemon operation and return its result object.
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}Sourcepub fn call_raw(
&self,
op: &str,
args: Map<String, Value>,
) -> Result<DaemonResponse>
pub fn call_raw( &self, op: &str, args: Map<String, Value>, ) -> Result<DaemonResponse>
Call an operation and retain the full response envelope.
pub fn ping(&self) -> Result<PingResult>
pub fn groups(&self) -> Result<Map<String, Value>>
Sourcepub fn group_show(&self, group_id: &str) -> Result<Map<String, Value>>
pub fn group_show(&self, group_id: &str) -> Result<Map<String, Value>>
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}pub fn send( &self, group_id: &str, text: &str, message_mode: MessageMode, by: &str, ) -> Result<Map<String, Value>>
pub fn reply( &self, group_id: &str, reply_to: &str, text: &str, by: &str, ) -> Result<Map<String, Value>>
pub fn reply_with_mode( &self, group_id: &str, reply_to: &str, text: &str, message_mode: ReplyMessageMode, by: &str, ) -> Result<Map<String, Value>>
pub fn reply_request_cancel( &self, group_id: &str, source_event_id: &str, by: &str, ) -> Result<Map<String, Value>>
pub fn message_deliver( &self, group_id: &str, source_event_id: &str, actor_ids: &[&str], by: &str, force_ambiguous: bool, ) -> Result<Map<String, Value>>
pub fn inbox_peek( &self, group_id: &str, actor_id: &str, by: &str, limit: Option<u32>, ) -> Result<Map<String, Value>>
pub fn inbox_read( &self, group_id: &str, actor_id: &str, by: &str, limit: Option<u32>, ) -> Result<Map<String, Value>>
Sourcepub fn inbox_summary(
&self,
group_id: &str,
cutoff: &str,
by: &str,
) -> Result<InboxSummaryResult>
pub fn inbox_summary( &self, group_id: &str, cutoff: &str, by: &str, ) -> Result<InboxSummaryResult>
Summarize current awaiting Mail without advancing cursors.
The inclusive RFC3339 cutoff is an age threshold, not a historical snapshot. The daemon validates the cutoff and caller permissions.
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}Sourcepub fn message_history(
&self,
group_id: &str,
actor_id: &str,
by: &str,
mode: MessageHistoryMode,
query: Option<&str>,
before_event_id: Option<&str>,
limit: Option<u32>,
) -> Result<Map<String, Value>>
pub fn message_history( &self, group_id: &str, actor_id: &str, by: &str, mode: MessageHistoryMode, query: Option<&str>, before_event_id: Option<&str>, limit: Option<u32>, ) -> Result<Map<String, Value>>
Inspect actor-visible chat history without consuming Mail.
Sourcepub fn message_history_exact(
&self,
group_id: &str,
actor_id: &str,
event_id: &str,
by: &str,
) -> Result<Map<String, Value>>
pub fn message_history_exact( &self, group_id: &str, actor_id: &str, event_id: &str, by: &str, ) -> Result<Map<String, Value>>
Read one visible event and its rendered delivery text without consuming
Mail. Older daemons silently ignore event_id; their list response is
rejected instead of being mistaken for an exact read.
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}pub fn context_get(&self, group_id: &str) -> Result<Map<String, Value>>
pub fn context_get_with_detail( &self, group_id: &str, detail: ContextDetail, ) -> Result<Map<String, Value>>
pub fn context_sync( &self, group_id: &str, by: &str, operations: Vec<Value>, ) -> Result<Map<String, Value>>
pub fn context_sync_checked( &self, group_id: &str, by: &str, operations: Vec<Value>, if_version: Option<&str>, dry_run: bool, ) -> Result<Map<String, Value>>
pub fn terminal_history( &self, group_id: &str, actor_id: &str, options: &TerminalHistoryOptions, ) -> Result<TerminalHistoryResult>
pub fn terminal_since( &self, group_id: &str, actor_id: &str, after: u64, options: &TerminalSinceOptions, ) -> Result<TerminalSinceResult>
pub fn terminal_snapshot( &self, group_id: &str, actor_id: &str, options: &TerminalSnapshotOptions, ) -> Result<TerminalSnapshotResult>
Sourcepub fn term_resize(
&self,
group_id: &str,
actor_id: &str,
cols: u32,
rows: u32,
) -> Result<TerminalResizeResult>
pub fn term_resize( &self, group_id: &str, actor_id: &str, cols: u32, rows: u32, ) -> Result<TerminalResizeResult>
Resize a PTY using the normative op, with a compatibility fallback for
Rust daemon builds that temporarily exposed terminal_resize instead.
Sourcepub fn web_model_delivery_preferences_get(
&self,
group_id: &str,
actor_id: &str,
) -> Result<WebModelDeliveryPreferencesResult>
pub fn web_model_delivery_preferences_get( &self, group_id: &str, actor_id: &str, ) -> Result<WebModelDeliveryPreferencesResult>
Read delivery preferences for a ChatGPT browser Actor.
Sourcepub fn web_model_delivery_preferences_update(
&self,
group_id: &str,
actor_id: &str,
mode: WebModelDeliveryMode,
by: &str,
) -> Result<WebModelDeliveryPreferencesResult>
pub fn web_model_delivery_preferences_update( &self, group_id: &str, actor_id: &str, mode: WebModelDeliveryMode, by: &str, ) -> Result<WebModelDeliveryPreferencesResult>
Update delivery preferences for a ChatGPT browser Actor.
Sourcepub fn web_model_runtime_recover_turn(
&self,
group_id: &str,
actor_id: &str,
event_ids: &[String],
) -> Result<WebModelRuntimeRecoverTurnResult>
pub fn web_model_runtime_recover_turn( &self, group_id: &str, actor_id: &str, event_ids: &[String], ) -> Result<WebModelRuntimeRecoverTurnResult>
Recover a ChatGPT browser turn. Grok uses its own event path.
Sourcepub fn assert_compatible(
&self,
requirements: &CompatibilityRequirements<'_>,
) -> Result<PingResult>
pub fn assert_compatible( &self, requirements: &CompatibilityRequirements<'_>, ) -> Result<PingResult>
Validate protocol, advertised capabilities, and actual op recognition.
Examples found in repository?
3fn main() -> Result<(), Box<dyn std::error::Error>> {
4 let client = CCCCClient::discover()?;
5 let requirements = CompatibilityRequirements {
6 minimum_ipc_version: 1,
7 operations: vec![
8 "groups",
9 "group_show",
10 "send",
11 "tracked_send",
12 "reply",
13 "inbox_read",
14 "message_history",
15 "context_get",
16 "context_sync",
17 "group_preamble_get",
18 "send_files",
19 "terminal_history",
20 "term_resize",
21 "web_model_delivery_preferences_get",
22 "web_model_delivery_preferences_update",
23 "web_model_runtime_recover_turn",
24 "assistant_voice_document_library",
25 "assistant_voice_document_library_update",
26 "assistant_voice_document_delete",
27 ],
28 ..Default::default()
29 };
30 let daemon = client.assert_compatible(&requirements)?;
31 println!(
32 "CCCC {} ({}) is compatible with IPC v{}",
33 daemon.version, daemon.implementation, daemon.ipc_v
34 );
35 Ok(())
36}Source§impl CCCCClient
impl CCCCClient
pub fn connect_catalog( &self, options: &ConnectCatalogOptions, ) -> Result<Map<String, Value>>
pub fn connect_send( &self, options: &ConnectSendOptions, ) -> Result<Map<String, Value>>
Sourcepub fn connect_send_files(
&self,
options: &ConnectSendOptions,
paths: &[String],
) -> Result<Map<String, Value>>
pub fn connect_send_files( &self, options: &ConnectSendOptions, paths: &[String], ) -> Result<Map<String, Value>>
Paths are read by the daemon within the source Group’s active scope.
Source§impl CCCCClient
impl CCCCClient
Sourcepub fn assistant_voice_document_library(
&self,
group_id: &str,
) -> Result<VoiceDocumentLibrary>
pub fn assistant_voice_document_library( &self, group_id: &str, ) -> Result<VoiceDocumentLibrary>
Read the stored library without workspace discovery or content reconciliation.
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}Sourcepub fn assistant_voice_document_library_update(
&self,
group_id: &str,
action: &VoiceDocumentLibraryAction,
by: Option<&str>,
) -> Result<VoiceDocumentLibrary>
pub fn assistant_voice_document_library_update( &self, group_id: &str, action: &VoiceDocumentLibraryAction, by: Option<&str>, ) -> Result<VoiceDocumentLibrary>
The daemon enforces writer permission; omitted by means the local user.
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}Sourcepub fn assistant_voice_document_delete(
&self,
group_id: &str,
document_path: &str,
by: Option<&str>,
) -> Result<Map<String, Value>>
pub fn assistant_voice_document_delete( &self, group_id: &str, document_path: &str, by: Option<&str>, ) -> Result<Map<String, Value>>
Permanently delete the Markdown file. Archive instead to retain the file.
Examples found in repository?
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6 let home = std::path::PathBuf::from(
7 std::env::var_os("CCCC_HOME").expect("isolated CCCC_HOME required"),
8 );
9 let client = CCCCClient::discover()?;
10 let call = |op: &str, args: Value| client.call(op, args.as_object().expect("object").clone());
11 let group = call("group_create", json!({"title":"Rust SDK release fixture"}))?;
12 let group_id = group["group"]["group_id"].as_str().expect("group id");
13 call(
14 "group_set_state",
15 json!({"group_id":group_id,"state":"paused"}),
16 )?;
17 call(
18 "actor_add",
19 json!({"group_id":group_id,"actor_id":"fixture","runtime":"custom",
20 "command":[std::env::current_exe()?.to_string_lossy()]}),
21 )?;
22 let event = call(
23 "send",
24 json!({"group_id":group_id,"text":"release mail","message_mode":"mail",
25 "to":["fixture"],"by":"user"}),
26 )?;
27 let event_id = event["event"]["id"].as_str().expect("event id");
28 let exact = client.message_history_exact(group_id, "fixture", event_id, "user")?;
29 assert_eq!(exact["messages"][0]["id"], event_id);
30 let summary = client.inbox_summary(group_id, "2020-01-01T00:00:00Z", "user")?;
31 assert_eq!(
32 summary
33 .actors
34 .iter()
35 .find(|row| row.actor_id == "fixture")
36 .unwrap()
37 .awaiting_count,
38 1
39 );
40 let path = "notes/sdk.md";
41 call(
42 "assistant_voice_document_save",
43 json!({"group_id":group_id,"document_path":path,"content":"# SDK fixture"}),
44 )?;
45 let library = client.assistant_voice_document_library_update(
46 group_id,
47 &Action::CreateFolder {
48 name: "Meetings".into(),
49 },
50 None,
51 )?;
52 let folder_id = library.folders[0].folder_id.clone();
53 for action in [
54 Action::RenameFolder {
55 folder_id: folder_id.clone(),
56 name: "Notes".into(),
57 },
58 Action::Rename {
59 document_path: path.into(),
60 name: "Summary".into(),
61 },
62 Action::Move {
63 document_path: path.into(),
64 folder_id: folder_id.clone(),
65 },
66 ] {
67 client.assistant_voice_document_library_update(group_id, &action, None)?;
68 }
69 call(
70 "assistant_voice_document_archive",
71 json!({"group_id":group_id,"document_path":path}),
72 )?;
73 let library = client.assistant_voice_document_library(group_id)?;
74 assert_eq!(library.documents[0]["status"], "archived");
75 let file = home
76 .join("voice-secretary")
77 .join(group_id)
78 .join("documents")
79 .join(path);
80 assert_eq!(std::fs::read_to_string(&file)?, "# SDK fixture");
81 let library = client.assistant_voice_document_library_update(
82 group_id,
83 &Action::Restore {
84 document_path: path.into(),
85 },
86 None,
87 )?;
88 assert_eq!(library.documents[0]["status"], "active");
89 assert_eq!(library.documents[0]["folder_id"], folder_id);
90 client.assistant_voice_document_library_update(
91 group_id,
92 &Action::Move {
93 document_path: path.into(),
94 folder_id: "".into(),
95 },
96 None,
97 )?;
98 let order = vec![format!("folder:{folder_id}"), format!("document:{path}")];
99 let library = client.assistant_voice_document_library_update(
100 group_id,
101 &Action::ReorderRoot {
102 root_order: order.clone(),
103 },
104 None,
105 )?;
106 assert_eq!(library.root_order, order);
107 let library = client.assistant_voice_document_library_update(
108 group_id,
109 &Action::ReorderRoot { root_order: vec![] },
110 None,
111 )?;
112 assert!(library.root_order.is_empty());
113 client.assistant_voice_document_library_update(
114 group_id,
115 &Action::RemoveFolder { folder_id },
116 None,
117 )?;
118 let deleted = client.assistant_voice_document_delete(group_id, path, None)?;
119 assert_eq!(deleted["event"]["data"]["action"], "deleted");
120 assert!(!file.exists());
121 assert!(client
122 .assistant_voice_document_library(group_id)?
123 .documents
124 .is_empty());
125 assert!(client
126 .assistant_voice_document_library_update(
127 group_id,
128 &Action::Restore {
129 document_path: path.into()
130 },
131 None
132 )
133 .is_err());
134
135 // Persist configuration only. No browser/provider starts in this paused Group.
136 for (id, runtime) in [
137 ("chat1", "web_model"),
138 ("chat2", "web_model"),
139 ("bot", "grok_web_model"),
140 ] {
141 let actor = call(
142 "actor_add",
143 json!({"group_id":group_id,"actor_id":id,"runtime":runtime}),
144 )?;
145 assert_eq!(actor["actor"]["runtime"], runtime);
146 assert_eq!(actor["actor"]["runner"], "headless");
147 }
148 let group: Map<String, Value> = client.group_show(group_id)?;
149 assert_eq!(group["group"]["state"], "paused");
150 println!("Installed Rust artifact: Voice library lifecycle and Web Model configuration passed");
151 Ok(())
152}