@team-agent/installer 0.5.49 → 0.5.51
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 +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +6 -4
- package/crates/team-agent/src/cli/emit.rs +91 -39
- package/crates/team-agent/src/cli/mod.rs +33 -15
- package/crates/team-agent/src/cli/named_address.rs +82 -53
- package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
- package/crates/team-agent/src/cli/send/persist.rs +154 -0
- package/crates/team-agent/src/cli/send/presentation.rs +333 -0
- package/crates/team-agent/src/cli/send/resolve.rs +361 -0
- package/crates/team-agent/src/cli/send.rs +103 -1308
- package/crates/team-agent/src/cli/spec.rs +2 -2
- package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
- package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
- package/crates/team-agent/src/cli/status_port/format.rs +145 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
- package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
- package/crates/team-agent/src/cli/status_port/store.rs +412 -0
- package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
- package/crates/team-agent/src/cli/status_port.rs +47 -1548
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +9 -7
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +17 -33
- package/crates/team-agent/src/cli/types.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
- package/crates/team-agent/src/coordinator/tick.rs +37 -0
- package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
- package/crates/team-agent/src/db/message_store.rs +154 -44
- package/crates/team-agent/src/event_log.rs +73 -0
- package/crates/team-agent/src/leader/start.rs +28 -4
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
- package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
- package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
- package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -26
- package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +67 -26
- package/crates/team-agent/src/lifecycle/restart/remove.rs +435 -72
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +575 -17
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +55 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
- package/crates/team-agent/src/mcp_server/mod.rs +2 -2
- package/crates/team-agent/src/mcp_server/tests/send.rs +22 -15
- package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -0
- package/crates/team-agent/src/mcp_server/tools.rs +26 -15
- package/crates/team-agent/src/mcp_server/wire.rs +2 -18
- package/crates/team-agent/src/messaging/activity.rs +4 -2
- package/crates/team-agent/src/messaging/address.rs +86 -0
- package/crates/team-agent/src/messaging/delivery.rs +165 -39
- package/crates/team-agent/src/messaging/helpers.rs +17 -13
- package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
- package/crates/team-agent/src/messaging/mod.rs +11 -2
- package/crates/team-agent/src/messaging/persist.rs +309 -0
- package/crates/team-agent/src/messaging/results.rs +16 -24
- package/crates/team-agent/src/messaging/scheduler.rs +4 -2
- package/crates/team-agent/src/messaging/selftest.rs +19 -12
- package/crates/team-agent/src/messaging/send.rs +133 -58
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
- package/crates/team-agent/src/messaging/watchers.rs +13 -3
- package/crates/team-agent/src/redaction.rs +72 -2
- package/crates/team-agent/src/state/persist.rs +2 -1
- package/crates/team-agent/src/state/repository/tests.rs +47 -0
- package/crates/team-agent/src/state/repository.rs +59 -16
- package/package.json +4 -4
|
@@ -13,13 +13,14 @@ use std::path::Path;
|
|
|
13
13
|
|
|
14
14
|
use crate::model::ids::AgentId;
|
|
15
15
|
|
|
16
|
-
#[derive(Clone, Debug)]
|
|
16
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
17
17
|
pub struct CapturedHealth {
|
|
18
18
|
pub owner_team_id: Option<String>,
|
|
19
19
|
pub status: Option<String>,
|
|
20
20
|
pub last_output_at: Option<String>,
|
|
21
21
|
pub context_usage_pct: Option<i64>,
|
|
22
22
|
pub current_task_id: Option<String>,
|
|
23
|
+
pub updated_at: String,
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/// golden agents.py:185 `copy.deepcopy(store.agent_health().get(agent_id))` — read the row BEFORE
|
|
@@ -27,6 +28,7 @@ pub struct CapturedHealth {
|
|
|
27
28
|
/// absent.
|
|
28
29
|
pub fn select_agent_health(
|
|
29
30
|
workspace: &Path,
|
|
31
|
+
owner_team_id: &str,
|
|
30
32
|
agent_id: &AgentId,
|
|
31
33
|
) -> Result<Option<CapturedHealth>, crate::db::DbError> {
|
|
32
34
|
let store = crate::message_store::MessageStore::open(workspace)
|
|
@@ -34,9 +36,9 @@ pub fn select_agent_health(
|
|
|
34
36
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
35
37
|
let row = conn
|
|
36
38
|
.query_row(
|
|
37
|
-
"select owner_team_id, status, last_output_at, context_usage_pct, current_task_id \
|
|
38
|
-
from agent_health where agent_id = ?
|
|
39
|
-
[agent_id.as_str()],
|
|
39
|
+
"select owner_team_id, status, last_output_at, context_usage_pct, current_task_id, updated_at \
|
|
40
|
+
from agent_health where owner_team_id = ?1 and agent_id = ?2",
|
|
41
|
+
rusqlite::params![owner_team_id, agent_id.as_str()],
|
|
40
42
|
|r| {
|
|
41
43
|
Ok(CapturedHealth {
|
|
42
44
|
owner_team_id: r.get::<_, Option<String>>(0)?,
|
|
@@ -44,6 +46,7 @@ pub fn select_agent_health(
|
|
|
44
46
|
last_output_at: r.get::<_, Option<String>>(2)?,
|
|
45
47
|
context_usage_pct: r.get::<_, Option<i64>>(3)?,
|
|
46
48
|
current_task_id: r.get::<_, Option<String>>(4)?,
|
|
49
|
+
updated_at: r.get::<_, String>(5)?,
|
|
47
50
|
})
|
|
48
51
|
},
|
|
49
52
|
)
|
|
@@ -55,42 +58,44 @@ pub fn select_agent_health(
|
|
|
55
58
|
/// or delete the row when there was nothing to restore.
|
|
56
59
|
pub fn restore_agent_health(
|
|
57
60
|
workspace: &Path,
|
|
61
|
+
owner_team_id: &str,
|
|
58
62
|
agent_id: &AgentId,
|
|
59
63
|
row: &Option<CapturedHealth>,
|
|
60
64
|
) -> Result<(), crate::db::DbError> {
|
|
61
65
|
let Some(row) = row else {
|
|
62
|
-
return delete_agent_health(workspace, agent_id);
|
|
66
|
+
return delete_agent_health(workspace, owner_team_id, agent_id);
|
|
63
67
|
};
|
|
64
68
|
let store = crate::message_store::MessageStore::open(workspace)
|
|
65
69
|
.map_err(|e| crate::db::DbError::Schema(e.to_string()))?;
|
|
66
70
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
67
71
|
let status = row.status.clone().unwrap_or_else(|| "IDLE".to_string());
|
|
68
|
-
let now = chrono::Utc::now()
|
|
69
|
-
.format("%Y-%m-%dT%H:%M:%S%.6f+00:00")
|
|
70
|
-
.to_string();
|
|
71
72
|
conn.execute(
|
|
72
73
|
"insert into agent_health (owner_team_id, agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at) \
|
|
73
74
|
values (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
|
74
75
|
rusqlite::params![
|
|
75
|
-
|
|
76
|
+
owner_team_id,
|
|
76
77
|
agent_id.as_str(),
|
|
77
78
|
status,
|
|
78
79
|
row.last_output_at,
|
|
79
80
|
row.context_usage_pct,
|
|
80
81
|
row.current_task_id,
|
|
81
|
-
|
|
82
|
+
row.updated_at,
|
|
82
83
|
],
|
|
83
84
|
)?;
|
|
84
85
|
Ok(())
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
fn delete_agent_health(
|
|
88
|
+
fn delete_agent_health(
|
|
89
|
+
workspace: &Path,
|
|
90
|
+
owner_team_id: &str,
|
|
91
|
+
agent_id: &AgentId,
|
|
92
|
+
) -> Result<(), crate::db::DbError> {
|
|
88
93
|
let store = crate::message_store::MessageStore::open(workspace)
|
|
89
94
|
.map_err(|e| crate::db::DbError::Schema(e.to_string()))?;
|
|
90
95
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
91
96
|
conn.execute(
|
|
92
|
-
"delete from agent_health where agent_id = ?
|
|
93
|
-
[agent_id.as_str()],
|
|
97
|
+
"delete from agent_health where owner_team_id = ?1 and agent_id = ?2",
|
|
98
|
+
rusqlite::params![owner_team_id, agent_id.as_str()],
|
|
94
99
|
)?;
|
|
95
100
|
Ok(())
|
|
96
101
|
}
|
|
@@ -52,6 +52,8 @@ pub enum MessageStoreError {
|
|
|
52
52
|
Sqlite(#[from] rusqlite::Error),
|
|
53
53
|
#[error("io: {0}")]
|
|
54
54
|
Io(#[from] std::io::Error),
|
|
55
|
+
#[error("delivery receipt missing for message: {0}")]
|
|
56
|
+
DeliveryReceiptMissing(String),
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
/// Outcome of [`MessageStore::claim_leader_notification_delivery`]
|
|
@@ -78,6 +80,40 @@ pub struct NotificationClaimParams<'a> {
|
|
|
78
80
|
pub pane_id: Option<&'a str>,
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
/// Canonical initial message-row statuses shared by persistence, presentation,
|
|
84
|
+
/// claiming and recovery. New durable dispositions must be added here first.
|
|
85
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
86
|
+
pub enum MessageRowStatus {
|
|
87
|
+
Accepted,
|
|
88
|
+
QueuedUntilLeaderAttach,
|
|
89
|
+
QueuedCoordinatorUnavailable,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl MessageRowStatus {
|
|
93
|
+
pub const fn as_str(self) -> &'static str {
|
|
94
|
+
match self {
|
|
95
|
+
Self::Accepted => "accepted",
|
|
96
|
+
Self::QueuedUntilLeaderAttach => "queued_until_leader_attach",
|
|
97
|
+
Self::QueuedCoordinatorUnavailable => "queued_coordinator_unavailable",
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Fully resolved durable-message insert. Grammar, scope and transport data do
|
|
103
|
+
/// not belong here; callers must resolve those before crossing this boundary.
|
|
104
|
+
pub struct PersistMessageInput<'a> {
|
|
105
|
+
pub message_id: Option<&'a str>,
|
|
106
|
+
pub owner_team_id: Option<&'a str>,
|
|
107
|
+
pub task_id: Option<&'a str>,
|
|
108
|
+
pub sender: &'a str,
|
|
109
|
+
pub recipient: &'a str,
|
|
110
|
+
pub reply_to: Option<&'a str>,
|
|
111
|
+
pub requires_ack: bool,
|
|
112
|
+
pub status: MessageRowStatus,
|
|
113
|
+
pub content: &'a str,
|
|
114
|
+
pub error: Option<&'a str>,
|
|
115
|
+
}
|
|
116
|
+
|
|
81
117
|
/// `leader_notification_log._legacy_epoch_from_uuid` (line 145-147):
|
|
82
118
|
/// `int(zlib.crc32(str(uuid or "").encode("utf-8")) & 0x7FFFFFFF)`.
|
|
83
119
|
pub fn legacy_epoch_from_uuid(leader_session_uuid: Option<&str>) -> i64 {
|
|
@@ -133,44 +169,67 @@ impl MessageStore {
|
|
|
133
169
|
&self.path
|
|
134
170
|
}
|
|
135
171
|
|
|
136
|
-
|
|
137
|
-
/// a row with `status='accepted'`, `requires_ack` as 0/1 int, `artifact_refs`
|
|
138
|
-
/// defaulting to `'[]'`, `delivery_attempts=0`, timestamps = now.
|
|
139
|
-
#[allow(clippy::too_many_arguments)]
|
|
140
|
-
pub fn create_message(
|
|
172
|
+
pub fn persist_message(
|
|
141
173
|
&self,
|
|
142
|
-
|
|
143
|
-
sender: &str,
|
|
144
|
-
recipient: &str,
|
|
145
|
-
content: &str,
|
|
146
|
-
reply_to: Option<&str>,
|
|
147
|
-
requires_ack: bool,
|
|
148
|
-
owner_team_id: Option<&str>,
|
|
174
|
+
input: PersistMessageInput<'_>,
|
|
149
175
|
) -> Result<String, MessageStoreError> {
|
|
150
176
|
let conn = crate::db::schema::open_db(&self.path)?;
|
|
151
|
-
let message_id =
|
|
177
|
+
let message_id = input
|
|
178
|
+
.message_id
|
|
179
|
+
.map(ToOwned::to_owned)
|
|
180
|
+
.unwrap_or_else(next_message_id);
|
|
152
181
|
let now = now_ts();
|
|
153
182
|
conn.execute(
|
|
154
183
|
"insert into messages(
|
|
155
184
|
message_id, owner_team_id, task_id, sender, recipient, reply_to, requires_ack,
|
|
156
185
|
status, content, artifact_refs, created_at, updated_at, delivered_at,
|
|
157
186
|
acknowledged_at, error, delivery_attempts
|
|
158
|
-
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7,
|
|
187
|
+
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, '[]', ?10, ?10, null, null, ?11, 0)",
|
|
159
188
|
params![
|
|
160
189
|
message_id,
|
|
161
|
-
owner_team_id,
|
|
162
|
-
task_id,
|
|
163
|
-
sender,
|
|
164
|
-
recipient,
|
|
165
|
-
reply_to,
|
|
166
|
-
if requires_ack { 1 } else { 0 },
|
|
167
|
-
|
|
190
|
+
input.owner_team_id,
|
|
191
|
+
input.task_id,
|
|
192
|
+
input.sender,
|
|
193
|
+
input.recipient,
|
|
194
|
+
input.reply_to,
|
|
195
|
+
if input.requires_ack { 1 } else { 0 },
|
|
196
|
+
input.status.as_str(),
|
|
197
|
+
input.content,
|
|
168
198
|
now,
|
|
199
|
+
input.error,
|
|
169
200
|
],
|
|
170
201
|
)?;
|
|
171
202
|
Ok(message_id)
|
|
172
203
|
}
|
|
173
204
|
|
|
205
|
+
/// `create_message` (`core.py:71-114`). Returns `msg_<uuid4 hex[:12]>`; inserts
|
|
206
|
+
/// a row with `status='accepted'`, `requires_ack` as 0/1 int, `artifact_refs`
|
|
207
|
+
/// defaulting to `'[]'`, `delivery_attempts=0`, timestamps = now.
|
|
208
|
+
#[allow(clippy::too_many_arguments)]
|
|
209
|
+
pub fn create_message(
|
|
210
|
+
&self,
|
|
211
|
+
task_id: Option<&str>,
|
|
212
|
+
sender: &str,
|
|
213
|
+
recipient: &str,
|
|
214
|
+
content: &str,
|
|
215
|
+
reply_to: Option<&str>,
|
|
216
|
+
requires_ack: bool,
|
|
217
|
+
owner_team_id: Option<&str>,
|
|
218
|
+
) -> Result<String, MessageStoreError> {
|
|
219
|
+
self.persist_message(PersistMessageInput {
|
|
220
|
+
message_id: None,
|
|
221
|
+
owner_team_id,
|
|
222
|
+
task_id,
|
|
223
|
+
sender,
|
|
224
|
+
recipient,
|
|
225
|
+
reply_to,
|
|
226
|
+
requires_ack,
|
|
227
|
+
status: MessageRowStatus::Accepted,
|
|
228
|
+
content,
|
|
229
|
+
error: None,
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
174
233
|
/// Caller-supplied-id variant of [`create_message`] (CR-015/054 — `--message-id`).
|
|
175
234
|
/// Inserts exactly the given `message_id` instead of generating one. The store
|
|
176
235
|
/// PK is `message_id`, so a repeat with the same id is rejected by SQLite; the
|
|
@@ -190,27 +249,18 @@ impl MessageStore {
|
|
|
190
249
|
requires_ack: bool,
|
|
191
250
|
owner_team_id: Option<&str>,
|
|
192
251
|
) -> Result<String, MessageStoreError> {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
sender,
|
|
206
|
-
recipient,
|
|
207
|
-
reply_to,
|
|
208
|
-
if requires_ack { 1 } else { 0 },
|
|
209
|
-
content,
|
|
210
|
-
now,
|
|
211
|
-
],
|
|
212
|
-
)?;
|
|
213
|
-
Ok(message_id.to_string())
|
|
252
|
+
self.persist_message(PersistMessageInput {
|
|
253
|
+
message_id: Some(message_id),
|
|
254
|
+
owner_team_id,
|
|
255
|
+
task_id,
|
|
256
|
+
sender,
|
|
257
|
+
recipient,
|
|
258
|
+
reply_to,
|
|
259
|
+
requires_ack,
|
|
260
|
+
status: MessageRowStatus::Accepted,
|
|
261
|
+
content,
|
|
262
|
+
error: None,
|
|
263
|
+
})
|
|
214
264
|
}
|
|
215
265
|
|
|
216
266
|
/// `true` iff a `messages` row with this `message_id` already exists. Used by
|
|
@@ -260,6 +310,62 @@ impl MessageStore {
|
|
|
260
310
|
Ok(())
|
|
261
311
|
}
|
|
262
312
|
|
|
313
|
+
/// Record that the transport submitted a message without claiming that the
|
|
314
|
+
/// provider accepted it. The stable message id is also the receipt token.
|
|
315
|
+
pub fn record_delivery_submission(
|
|
316
|
+
&self,
|
|
317
|
+
message_id: &str,
|
|
318
|
+
visible: bool,
|
|
319
|
+
) -> Result<(), MessageStoreError> {
|
|
320
|
+
let conn = crate::db::schema::open_db(&self.path)?;
|
|
321
|
+
let now = now_ts();
|
|
322
|
+
conn.execute(
|
|
323
|
+
"insert into delivery_tokens(
|
|
324
|
+
message_id, unique_token, injected_at, visible_at,
|
|
325
|
+
consumed_at, failed_at, failure_reason
|
|
326
|
+
) values (?1, ?1, ?2, ?3, null, null, null)
|
|
327
|
+
on conflict(message_id) do update set
|
|
328
|
+
visible_at = coalesce(delivery_tokens.visible_at, excluded.visible_at),
|
|
329
|
+
failed_at = null,
|
|
330
|
+
failure_reason = null",
|
|
331
|
+
params![message_id, now, visible.then_some(now_ts())],
|
|
332
|
+
)?;
|
|
333
|
+
Ok(())
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/// Atomically persist the provider-side receipt and advance the message to
|
|
337
|
+
/// delivered. A transport-only caller cannot use this without first
|
|
338
|
+
/// recording the submission row above.
|
|
339
|
+
pub fn mark_delivered_with_receipt(&self, message_id: &str) -> Result<(), MessageStoreError> {
|
|
340
|
+
let mut conn = crate::db::schema::open_db(&self.path)?;
|
|
341
|
+
let tx = conn.transaction()?;
|
|
342
|
+
let now = now_ts();
|
|
343
|
+
let receipts = tx.execute(
|
|
344
|
+
"update delivery_tokens
|
|
345
|
+
set consumed_at = coalesce(consumed_at, ?2),
|
|
346
|
+
failed_at = null,
|
|
347
|
+
failure_reason = null
|
|
348
|
+
where message_id = ?1",
|
|
349
|
+
params![message_id, now],
|
|
350
|
+
)?;
|
|
351
|
+
if receipts != 1 {
|
|
352
|
+
return Err(MessageStoreError::DeliveryReceiptMissing(
|
|
353
|
+
message_id.to_string(),
|
|
354
|
+
));
|
|
355
|
+
}
|
|
356
|
+
tx.execute(
|
|
357
|
+
"update messages
|
|
358
|
+
set status = case when status = 'acknowledged' then status else 'delivered' end,
|
|
359
|
+
updated_at = ?2,
|
|
360
|
+
delivered_at = ?2,
|
|
361
|
+
error = null
|
|
362
|
+
where message_id = ?1",
|
|
363
|
+
params![message_id, now_ts()],
|
|
364
|
+
)?;
|
|
365
|
+
tx.commit()?;
|
|
366
|
+
Ok(())
|
|
367
|
+
}
|
|
368
|
+
|
|
263
369
|
/// `claim_for_delivery` (`core.py:190-205`): atomic single-winner claim. Flips an
|
|
264
370
|
/// eligible row (status ∈ pending/accepted/queued_until_idle/queued_until_start/
|
|
265
371
|
/// queued_stopped/queued_pane_missing) to `target_resolved`, `delivery_attempts +=
|
|
@@ -274,9 +380,13 @@ impl MessageStore {
|
|
|
274
380
|
where message_id = ?1
|
|
275
381
|
and status in (
|
|
276
382
|
'pending', 'accepted', 'queued_until_idle', 'queued_until_start',
|
|
277
|
-
'queued_stopped', 'queued_pane_missing'
|
|
383
|
+
'queued_stopped', 'queued_pane_missing', ?3
|
|
278
384
|
)",
|
|
279
|
-
params![
|
|
385
|
+
params![
|
|
386
|
+
message_id,
|
|
387
|
+
now_ts(),
|
|
388
|
+
MessageRowStatus::QueuedCoordinatorUnavailable.as_str()
|
|
389
|
+
],
|
|
280
390
|
)?;
|
|
281
391
|
Ok(rows == 1)
|
|
282
392
|
}
|
|
@@ -35,6 +35,38 @@ use crate::model::paths::logs_dir;
|
|
|
35
35
|
pub const EVENT_LOG_ROTATE_BYTES: u64 = 5 * 1024 * 1024;
|
|
36
36
|
/// `events.py:18`:保留 5 个 archive。
|
|
37
37
|
pub const EVENT_LOG_ARCHIVE_KEEP: u32 = 5;
|
|
38
|
+
pub(crate) const PROVIDER_SESSION_CONVERGING: &str = "provider.session.converging";
|
|
39
|
+
pub(crate) const PROVIDER_WORKER_SPAWN_ARGV: &str = "provider.worker.spawn_argv";
|
|
40
|
+
|
|
41
|
+
pub(crate) fn provider_worker_spawn_argv_fields(fields: Value) -> Value {
|
|
42
|
+
let object = fields.as_object();
|
|
43
|
+
let field = |key: &str| {
|
|
44
|
+
object
|
|
45
|
+
.and_then(|object| object.get(key))
|
|
46
|
+
.cloned()
|
|
47
|
+
.unwrap_or(Value::Null)
|
|
48
|
+
};
|
|
49
|
+
let array = |key: &str| match field(key) {
|
|
50
|
+
value @ Value::Array(_) => value,
|
|
51
|
+
_ => Value::Array(Vec::new()),
|
|
52
|
+
};
|
|
53
|
+
serde_json::json!({
|
|
54
|
+
"agent_id": field("agent_id"),
|
|
55
|
+
"provider": field("provider"),
|
|
56
|
+
"argv": array("argv"),
|
|
57
|
+
"session_id_in_argv": field("session_id_in_argv"),
|
|
58
|
+
"expected_session_id": field("expected_session_id"),
|
|
59
|
+
"spawn_cwd": field("spawn_cwd"),
|
|
60
|
+
"spawned_at": field("spawned_at"),
|
|
61
|
+
"source": field("source"),
|
|
62
|
+
"spawn_epoch": field("spawn_epoch"),
|
|
63
|
+
"env_overlay_keys": array("env_overlay_keys"),
|
|
64
|
+
"env_unset": array("env_unset"),
|
|
65
|
+
"tmux_start_mode": field("tmux_start_mode"),
|
|
66
|
+
"tmux_endpoint": field("tmux_endpoint"),
|
|
67
|
+
"tmux_endpoint_source": field("tmux_endpoint_source"),
|
|
68
|
+
})
|
|
69
|
+
}
|
|
38
70
|
|
|
39
71
|
#[derive(Debug, Error)]
|
|
40
72
|
pub enum EventLogError {
|
|
@@ -277,6 +309,47 @@ mod tests {
|
|
|
277
309
|
assert!(ts.ends_with("+00:00"));
|
|
278
310
|
}
|
|
279
311
|
|
|
312
|
+
#[test]
|
|
313
|
+
fn provider_spawn_fields_have_one_canonical_shape() {
|
|
314
|
+
let fields = provider_worker_spawn_argv_fields(json!({
|
|
315
|
+
"agent_id": "worker",
|
|
316
|
+
"provider": "fake",
|
|
317
|
+
"source": "restart",
|
|
318
|
+
"ignored": "not part of the schema",
|
|
319
|
+
}));
|
|
320
|
+
let object = fields.as_object().unwrap();
|
|
321
|
+
let keys = object
|
|
322
|
+
.keys()
|
|
323
|
+
.map(String::as_str)
|
|
324
|
+
.collect::<std::collections::BTreeSet<_>>();
|
|
325
|
+
assert_eq!(
|
|
326
|
+
keys,
|
|
327
|
+
[
|
|
328
|
+
"agent_id",
|
|
329
|
+
"argv",
|
|
330
|
+
"env_overlay_keys",
|
|
331
|
+
"env_unset",
|
|
332
|
+
"expected_session_id",
|
|
333
|
+
"provider",
|
|
334
|
+
"session_id_in_argv",
|
|
335
|
+
"source",
|
|
336
|
+
"spawn_cwd",
|
|
337
|
+
"spawn_epoch",
|
|
338
|
+
"spawned_at",
|
|
339
|
+
"tmux_endpoint",
|
|
340
|
+
"tmux_endpoint_source",
|
|
341
|
+
"tmux_start_mode",
|
|
342
|
+
]
|
|
343
|
+
.into_iter()
|
|
344
|
+
.collect::<std::collections::BTreeSet<_>>()
|
|
345
|
+
);
|
|
346
|
+
assert_eq!(fields["argv"], json!([]));
|
|
347
|
+
assert_eq!(fields["env_overlay_keys"], json!([]));
|
|
348
|
+
assert_eq!(fields["env_unset"], json!([]));
|
|
349
|
+
assert!(fields["spawn_cwd"].is_null());
|
|
350
|
+
assert!(fields.get("ignored").is_none());
|
|
351
|
+
}
|
|
352
|
+
|
|
280
353
|
#[test]
|
|
281
354
|
fn tail_returns_last_n_and_raw_on_bad_line() {
|
|
282
355
|
let ws = temp_ws();
|
|
@@ -816,7 +816,13 @@ fn persist_managed_leader_binding(
|
|
|
816
816
|
.with_team_owner(owner)
|
|
817
817
|
.with_owner_epoch(owner_epoch);
|
|
818
818
|
crate::state::ownership::write_owner(&mut state, identity.team_id.as_str(), record);
|
|
819
|
-
crate::state::
|
|
819
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
820
|
+
crate::state::repository::StateWriteIntent::LeaderStartBinding {
|
|
821
|
+
team_key: identity.team_id.as_str(),
|
|
822
|
+
transport_kind: "managed",
|
|
823
|
+
},
|
|
824
|
+
&state,
|
|
825
|
+
)?;
|
|
820
826
|
Ok(())
|
|
821
827
|
}
|
|
822
828
|
|
|
@@ -933,7 +939,13 @@ fn persist_exec_provider_leader_binding(
|
|
|
933
939
|
.with_team_owner(owner)
|
|
934
940
|
.with_owner_epoch(owner_epoch);
|
|
935
941
|
crate::state::ownership::write_owner(&mut state, identity.team_id.as_str(), record);
|
|
936
|
-
crate::state::
|
|
942
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
943
|
+
crate::state::repository::StateWriteIntent::LeaderStartBinding {
|
|
944
|
+
team_key: identity.team_id.as_str(),
|
|
945
|
+
transport_kind: "exec_provider",
|
|
946
|
+
},
|
|
947
|
+
&state,
|
|
948
|
+
)?;
|
|
937
949
|
Ok(())
|
|
938
950
|
}
|
|
939
951
|
|
|
@@ -1124,7 +1136,13 @@ fn refresh_managed_leader_provider_binding(
|
|
|
1124
1136
|
.with_team_owner(owner)
|
|
1125
1137
|
.with_owner_epoch(existing_epoch);
|
|
1126
1138
|
crate::state::ownership::write_owner(&mut state, team_key, record);
|
|
1127
|
-
crate::state::
|
|
1139
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
1140
|
+
crate::state::repository::StateWriteIntent::LeaderStartBinding {
|
|
1141
|
+
team_key,
|
|
1142
|
+
transport_kind: "managed_reentry",
|
|
1143
|
+
},
|
|
1144
|
+
&state,
|
|
1145
|
+
)?;
|
|
1128
1146
|
Ok(())
|
|
1129
1147
|
}
|
|
1130
1148
|
|
|
@@ -1169,7 +1187,13 @@ fn persist_external_leader_topology_marker(
|
|
|
1169
1187
|
teams.insert(identity.team_id.as_str().to_string(), entry);
|
|
1170
1188
|
}
|
|
1171
1189
|
}
|
|
1172
|
-
crate::state::
|
|
1190
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
1191
|
+
crate::state::repository::StateWriteIntent::LeaderStartBinding {
|
|
1192
|
+
team_key: identity.team_id.as_str(),
|
|
1193
|
+
transport_kind: "external",
|
|
1194
|
+
},
|
|
1195
|
+
&state,
|
|
1196
|
+
)?;
|
|
1173
1197
|
Ok(())
|
|
1174
1198
|
}
|
|
1175
1199
|
|