@team-agent/installer 0.5.114 → 0.5.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/emit.rs +2 -2
  4. package/crates/team-agent/src/cli/mod.rs +155 -19
  5. package/crates/team-agent/src/cli/models.rs +52 -1
  6. package/crates/team-agent/src/cli/shutdown_socket.rs +288 -0
  7. package/crates/team-agent/src/cli/spec.rs +1 -1
  8. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +11 -4
  9. package/crates/team-agent/src/contract_runtime/backend.rs +490 -97
  10. package/crates/team-agent/src/contract_runtime/commands.rs +326 -0
  11. package/crates/team-agent/src/contract_runtime/config.rs +39 -8
  12. package/crates/team-agent/src/contract_runtime/config_tests.rs +56 -0
  13. package/crates/team-agent/src/contract_runtime/discovery.rs +218 -40
  14. package/crates/team-agent/src/contract_runtime/framework.rs +38 -5
  15. package/crates/team-agent/src/contract_runtime/mcp.rs +3 -0
  16. package/crates/team-agent/src/contract_runtime/mod.rs +2 -0
  17. package/crates/team-agent/src/contract_runtime/pump.rs +97 -33
  18. package/crates/team-agent/src/contract_runtime/registry.rs +248 -4
  19. package/crates/team-agent/src/contract_runtime/workspace.rs +228 -0
  20. package/crates/team-agent/src/leader/start.rs +93 -5
  21. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +99 -1
  22. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +3 -5
  23. package/crates/team-agent/src/lifecycle/launch.rs +6 -0
  24. package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -0
  25. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +109 -58
  26. package/crates/team-agent/src/lifecycle/restart/selection.rs +63 -7
  27. package/crates/team-agent/src/lifecycle/role_config.rs +17 -1
  28. package/crates/team-agent/src/provider/session/capture.rs +24 -1
  29. package/crates/team-agent/src/state/persist.rs +72 -2
  30. package/crates/team-agent/src/state/projection.rs +29 -0
  31. package/crates/team-agent/src/state/repository/tests.rs +70 -0
  32. package/crates/team-agent/src/state/repository.rs +5 -1
  33. package/package.json +4 -4
@@ -2,7 +2,7 @@
2
2
  //! implicit business retry, queue flush, or replay of uncertain messages.
3
3
  use super::{
4
4
  backend::{self, Backend, BackendError},
5
- forward, framework,
5
+ forward, framework, registry,
6
6
  };
7
7
  use serde_json::Value;
8
8
  use std::path::Path;
@@ -12,11 +12,10 @@ use team_agent_contract::host::{
12
12
  clock::{Clock, RealClock},
13
13
  process::verify_native_ancestry,
14
14
  };
15
- use team_agent_contract::kiro::{interaction, KIRO_DESCRIPTOR};
16
15
  use team_agent_contract::orchestration::{physical::*, store::*, supervisor::*};
17
16
  use team_agent_contract::runtime::probes::*;
18
17
 
19
- fn protocol(
18
+ pub(super) fn protocol(
20
19
  backend: &mut Backend,
21
20
  seat: &SeatRecord,
22
21
  clock: &RealClock,
@@ -28,6 +27,9 @@ fn protocol(
28
27
  .into_iter()
29
28
  .filter(|record| {
30
29
  !record.closed
30
+ && record.identity == seat.identity
31
+ && record.server_key == seat.server_key
32
+ && record.binding_key == seat.binding_key
31
33
  && record.native_process == target.process
32
34
  && verify_native_ancestry(&record.process, &record.native_process).is_ok()
33
35
  })
@@ -75,6 +77,35 @@ fn protocol(
75
77
  });
76
78
  }
77
79
  }
80
+ if registry::provider(seat.provider.as_str())?.uses_tool_roundtrips() {
81
+ let session = seat.session.as_ref().ok_or(BackendError::Metadata)?;
82
+ if target.identity() != &seat.identity || target.binding != seat.binding_key
83
+ || target.provider != seat.provider || target.native != seat.native
84
+ || target.native_session.as_ref() != Some(&session.native_session)
85
+ || session.source != seat.identity || session.provider != seat.provider
86
+ || session.native != seat.native || session.cwd != target.cwd
87
+ || target.candidate_sha256 != backend.binding.candidate_sha256
88
+ {
89
+ return Err(ContractError::Mismatch("native roundtrip current binding").into());
90
+ }
91
+ // The core returns only per-tool invocation + actual successful
92
+ // write/flush pairs for THIS captured connection and generation.
93
+ // Server list writes or another connection's facts cannot qualify.
94
+ let tools = backend.store.completed_connection_tools(&seat.identity, &connection.connection)?;
95
+ if !tools.is_empty() {
96
+ events.push(ProtocolEvent {
97
+ scope: target.scope(events.len() as u64, clock.now(), Duration::from_secs(2),
98
+ "live-native-tool-roundtrip-ledger"),
99
+ fact: ProtocolFact::ClientBound {
100
+ server_instance: connection.connection.clone(),
101
+ evidence: ClientBindingEvidence {
102
+ server_key: seat.server_key.clone(), tools,
103
+ source: ClientBindingSource::NativeToolRoundtrip,
104
+ },
105
+ },
106
+ });
107
+ }
108
+ }
78
109
  Ok(collect_protocol(
79
110
  target,
80
111
  &connection.connection,
@@ -91,8 +122,18 @@ fn deliver_one(backend: &mut Backend, seat: &SeatRecord) -> Result<(), BackendEr
91
122
  {
92
123
  return Ok(());
93
124
  }
125
+ let selected = registry::provider(seat.provider.as_str())?;
126
+ let bootstrap_allowed = selected.first_business_bootstrap() && !seat.bootstrap_used;
127
+ let profiles = selected.descriptor().input.profiles.require("native input profiles")?;
128
+ let profile = profiles.iter().find(|profile| profile.id == seat.profile && profile.matches_native(&seat.native))
129
+ .ok_or(ContractError::ProfileUnavailable)?;
130
+ profile.policy.require("native submit policy")?;
131
+ let role = backend::read_role(&backend.binding, &seat.identity)?;
132
+ if role.provider != seat.provider {
133
+ return Err(BackendError::Metadata);
134
+ }
94
135
  let clock = RealClock::new();
95
- let adapter = backend::adapter(&backend.binding, &seat.identity)?;
136
+ let adapter = backend::adapter(&backend.binding, &seat.identity, &seat.provider)?;
96
137
  let hooks = adapter.hooks();
97
138
  let operation = OperationId::new(format!(
98
139
  "deliver-{}",
@@ -104,15 +145,19 @@ fn deliver_one(backend: &mut Backend, seat: &SeatRecord) -> Result<(), BackendEr
104
145
  ))?;
105
146
  let mut physical = PhysicalRuntime::new(
106
147
  Registration {
107
- descriptor: &KIRO_DESCRIPTOR,
148
+ descriptor: selected.descriptor(),
108
149
  hooks: &hooks,
109
- controls: interaction::CONTROLS,
150
+ controls: selected.controls(),
151
+ startup: selected.startup(),
152
+ session_panel: selected.session_panel(),
110
153
  },
111
- backend::settings(&backend.binding),
154
+ backend::settings(&backend.binding, selected),
112
155
  operation.clone(),
113
156
  &clock,
114
157
  )?;
115
- physical.evidence = backend::policy_evidence(&seat.native, backend.binding.candidate_sha256);
158
+ physical.evidence = backend::policy_evidence(
159
+ &seat.provider, &seat.native, backend.binding.candidate_sha256,
160
+ )?;
116
161
  physical.protocol = Some(protocol(backend, seat, &clock)?);
117
162
  let sample = physical.readiness(seat)?;
118
163
  if !matches!(sample.pane.outcome, ProbeOutcome::Observed(ref pane) if pane.surface == InputSurface::ComposerReady)
@@ -120,28 +165,39 @@ fn deliver_one(backend: &mut Backend, seat: &SeatRecord) -> Result<(), BackendEr
120
165
  {
121
166
  return Ok(());
122
167
  }
123
- backend
124
- .store
125
- .begin_observation(&seat.identity, &operation)?;
126
- // A completed-but-uncertain inspector quarantines the seat, without an
127
- // orphan lease that would prevent explicit scoped shutdown. No replay or
128
- // blind Escape to clear an unrelated approval/composer is permitted.
129
- let binding = match physical.client_binding(seat, &interaction::TOOL_PANEL) {
130
- Ok(binding) => binding,
131
- Err(error) => {
132
- backend.store.fail_observation(&seat.identity, &operation)?;
133
- return Err(error.into());
168
+ // A roundtrip provider consumes only its captured protocol evidence. An
169
+ // Unknown/partial binding is left to the typed readiness gate; it never
170
+ // triggers a guessed TUI /tools command or a Kiro inspector fallback.
171
+ if !bootstrap_allowed && !selected.uses_tool_roundtrips() {
172
+ let panel = selected.tool_panel()?;
173
+ if panel.profile_id != seat.profile || panel.server_key != seat.server_key
174
+ || panel.policy_sha256 != profile.policy_sha256
175
+ {
176
+ return Err(ContractError::Mismatch("native inspector seat binding").into());
134
177
  }
135
- };
136
- backend
137
- .store
138
- .finish_observation(&seat.identity, &operation)?;
139
- if !matches!(binding.outcome, ProbeOutcome::Observed(_)) {
140
- return Ok(());
178
+ backend.store.begin_observation(&seat.identity, &operation)?;
179
+ // Uncertain inspection quarantines the seat without an orphan lease.
180
+ // No replay or blind Escape clears an unrelated composer/approval.
181
+ let binding = match physical.client_binding(seat, panel) {
182
+ Ok(binding) => binding,
183
+ Err(error) => {
184
+ backend.store.fail_observation(&seat.identity, &operation)?;
185
+ return Err(error.into());
186
+ }
187
+ };
188
+ backend.store.finish_observation(&seat.identity, &operation)?;
189
+ if !matches!(binding.outcome, ProbeOutcome::Observed(_)) {
190
+ return Ok(());
191
+ }
192
+ let mut current_protocol = protocol(backend, seat, &clock)?;
193
+ current_protocol.binding = binding;
194
+ physical.protocol = Some(current_protocol);
195
+ }
196
+ // H3's owned prompt receipt is independently checked by PhysicalRuntime;
197
+ // only the one durable first-business claim can carry this instruction.
198
+ if bootstrap_allowed {
199
+ physical.initial_prompt = Some(role.prompt.text.clone());
141
200
  }
142
- let mut current_protocol = protocol(backend, seat, &clock)?;
143
- current_protocol.binding = binding;
144
- physical.protocol = Some(current_protocol);
145
201
  let mut bootstrap = OutboxBootstrap::new(ContractStore::open(
146
202
  &backend.binding.transport_root,
147
203
  backend.binding.scope.clone(),
@@ -150,10 +206,10 @@ fn deliver_one(backend: &mut Backend, seat: &SeatRecord) -> Result<(), BackendEr
150
206
  physical.bootstrap = Some(&mut bootstrap);
151
207
  let sample = physical.readiness(seat)?;
152
208
  let authorization = DeliveryAuthorization {
153
- allow_first_business_bootstrap: false,
209
+ allow_first_business_bootstrap: bootstrap_allowed,
154
210
  require_server_handshake: true,
155
211
  channel: Channel::Tmux,
156
- policy_sha256: interaction::POLICY,
212
+ policy_sha256: profile.policy_sha256,
157
213
  };
158
214
  let _ = team_agent_contract::orchestration::supervisor::tick(
159
215
  &mut backend.store,
@@ -170,15 +226,18 @@ pub fn tick(workspace: &Path, state: &mut Value) -> Result<(), BackendError> {
170
226
  return Ok(());
171
227
  }
172
228
  let team = crate::state::projection::team_state_key(state);
173
- let mut backend = Backend::open(workspace, &team)?;
174
229
  let ids: Vec<_> = state
175
230
  .get("agents")
176
231
  .and_then(Value::as_object)
177
232
  .into_iter()
178
233
  .flat_map(|agents| agents.iter())
179
- .filter(|(_, agent)| framework::is_contract(agent))
234
+ .filter(|(_, agent)| framework::is_contract(agent) && !framework::is_paused_unstarted(agent))
180
235
  .map(|(id, _)| id.clone())
181
236
  .collect();
237
+ if ids.is_empty() {
238
+ return Ok(());
239
+ }
240
+ let mut backend = Backend::open(workspace, &team)?;
182
241
  let mut failure = None;
183
242
  for id in ids {
184
243
  let Some(seat) = backend.store.seat(&SeatId::new(&id)?)? else {
@@ -192,7 +251,12 @@ pub fn tick(workspace: &Path, state: &mut Value) -> Result<(), BackendError> {
192
251
  .and_then(|agents| agents.get_mut(&id))
193
252
  {
194
253
  match framework::project_seat(workspace, &team, &id) {
195
- Ok(projected) => *agent = projected,
254
+ Ok(projected) => {
255
+ let row = agent.as_object_mut().ok_or(BackendError::Metadata)?;
256
+ for (key, value) in projected.as_object().ok_or(BackendError::Metadata)? {
257
+ row.insert(key.clone(), value.clone());
258
+ }
259
+ },
196
260
  Err(error) => failure = Some(error),
197
261
  }
198
262
  }
@@ -1,9 +1,195 @@
1
- //! The sole new-runtime provider registration. This is not an admission claim:
1
+ //! The sole new-runtime provider registry. This is not an admission claim:
2
2
  //! the descriptor's Unverified gates still apply to each requested operation.
3
- use team_agent_contract::contract::descriptor::ProviderDescriptor;
4
- use team_agent_contract::kiro::KIRO_DESCRIPTOR;
3
+ use std::collections::BTreeMap;
4
+ use std::path::PathBuf;
5
+ use team_agent_contract::contract::{
6
+ descriptor::{PromptCarrier, ProviderDescriptor, ToolNaming},
7
+ types::InstanceIdentity,
8
+ hooks::{PlanHook, ProviderHooks},
9
+ plan::{LaunchPlan, ResolvedLaunch},
10
+ types::{ContractError, Support},
11
+ };
12
+ use team_agent_contract::kiro::{self, KiroAdapter, KIRO_DESCRIPTOR};
13
+ use team_agent_contract::providers::cursor::{self, CursorAdapter, CURSOR_DESCRIPTOR};
14
+ use team_agent_contract::providers::grok_build::{self, GrokAdapter, GROK_BUILD_DESCRIPTOR};
15
+ use team_agent_contract::runtime::{
16
+ delivery::{ControlDefinition, StartupDefinition},
17
+ native_panel::{NativePanelPolicy, SessionPanelPolicy},
18
+ };
5
19
 
6
- const DESCRIPTORS: &[&ProviderDescriptor] = &[&KIRO_DESCRIPTOR];
20
+ const DESCRIPTORS: &[&ProviderDescriptor] = &[&KIRO_DESCRIPTOR, &CURSOR_DESCRIPTOR, &GROK_BUILD_DESCRIPTOR];
21
+
22
+ #[derive(Clone)]
23
+ pub enum NativeRuntime {
24
+ Cursor(cursor::native::RuntimeInstallation),
25
+ GrokBuild(grok_build::native::RuntimeInstallation),
26
+ }
27
+
28
+ /// Leaf selection stays here; the framework, lifecycle and outbox remain shared.
29
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
30
+ pub(super) enum NativeProvider {
31
+ Kiro,
32
+ Cursor,
33
+ GrokBuild,
34
+ }
35
+
36
+ impl NativeProvider {
37
+ pub fn descriptor(self) -> &'static ProviderDescriptor {
38
+ match self {
39
+ Self::Kiro => &KIRO_DESCRIPTOR,
40
+ Self::Cursor => &CURSOR_DESCRIPTOR,
41
+ Self::GrokBuild => &GROK_BUILD_DESCRIPTOR,
42
+ }
43
+ }
44
+
45
+ pub fn action_budget(self) -> std::time::Duration {
46
+ // Kiro's existing budget is unchanged; Cursor's measured cold-ready
47
+ // path needs a larger finite bound, not an unbounded readiness wait.
48
+ std::time::Duration::from_secs(match self { Self::Kiro => 30, Self::Cursor | Self::GrokBuild => 90 })
49
+ }
50
+
51
+ pub fn controls(self) -> &'static [ControlDefinition] {
52
+ match self {
53
+ Self::Kiro => kiro::interaction::CONTROLS,
54
+ Self::Cursor => cursor::interaction::CONTROLS,
55
+ Self::GrokBuild => grok_build::interaction::CONTROLS,
56
+ }
57
+ }
58
+
59
+ /// A terminal instruction bridge needs one durable, authorized first packet
60
+ /// before the native client can demonstrate its Team-tool registry.
61
+ pub fn first_business_bootstrap(self) -> bool {
62
+ matches!(self.descriptor().prompt.carrier, Support::Supported(PromptCarrier::RuntimeBridge))
63
+ }
64
+
65
+ /// These providers use the separately fenced native tool-roundtrip ledger,
66
+ /// never a guessed neutral tools panel or another provider's inspector.
67
+ pub fn uses_tool_roundtrips(self) -> bool {
68
+ matches!(self, Self::Cursor | Self::GrokBuild)
69
+ }
70
+
71
+ pub fn startup(self) -> Option<&'static StartupDefinition> {
72
+ match self {
73
+ Self::GrokBuild => Some(&grok_build::interaction::STARTUP),
74
+ Self::Kiro | Self::Cursor => None,
75
+ }
76
+ }
77
+
78
+ pub fn session_panel(self) -> Option<&'static SessionPanelPolicy> {
79
+ match self {
80
+ Self::GrokBuild => Some(&grok_build::interaction::SESSION_PANEL),
81
+ Self::Kiro | Self::Cursor => None,
82
+ }
83
+ }
84
+
85
+ pub fn tool_panel(self) -> Result<&'static NativePanelPolicy, ContractError> {
86
+ match self {
87
+ Self::Kiro => Ok(&kiro::interaction::TOOL_PANEL),
88
+ Self::Cursor => Err(ContractError::Unverified {
89
+ field: "Cursor native Team-tool inspector",
90
+ reason: cursor::UNVERIFIED,
91
+ }),
92
+ Self::GrokBuild => Err(ContractError::Unverified {
93
+ field: "Grok Build native Team-tool inspector",
94
+ reason: grok_build::UNVERIFIED,
95
+ }),
96
+ }
97
+ }
98
+
99
+ pub fn server_key_for(self, identity: &InstanceIdentity) -> Result<String, ContractError> {
100
+ if self == Self::GrokBuild {
101
+ return Ok(grok_build::server_key(identity));
102
+ }
103
+ Ok(self.server_key()?.into())
104
+ }
105
+
106
+ pub fn server_key(self) -> Result<&'static str, ContractError> {
107
+ match self.descriptor().tool_names.naming.require("tool naming")? {
108
+ ToolNaming::StaticServer { key } => Ok(key),
109
+ ToolNaming::RuntimeBound => Err(ContractError::Invalid("runtime server-key receipt required")),
110
+ }
111
+ }
112
+
113
+ pub fn adapter(
114
+ self,
115
+ executable: PathBuf,
116
+ arguments: Vec<String>,
117
+ environment: BTreeMap<String, String>,
118
+ ) -> Result<NativeAdapter, ContractError> {
119
+ match self {
120
+ Self::Kiro => KiroAdapter::new(kiro::McpStdio {
121
+ executable, arguments, environment,
122
+ }).map(NativeAdapter::Kiro),
123
+ Self::Cursor => CursorAdapter::new(cursor::McpStdio {
124
+ executable, arguments, environment,
125
+ }).map(NativeAdapter::Cursor),
126
+ Self::GrokBuild => GrokAdapter::new(grok_build::McpStdio {
127
+ executable, arguments, environment,
128
+ }).map(NativeAdapter::GrokBuild),
129
+ }
130
+ }
131
+ }
132
+
133
+ pub(super) enum NativeAdapter {
134
+ Kiro(KiroAdapter),
135
+ Cursor(CursorAdapter),
136
+ GrokBuild(GrokAdapter),
137
+ }
138
+
139
+ impl NativeAdapter {
140
+ pub fn with_launch_context(
141
+ self,
142
+ project: PathBuf,
143
+ runtime: Option<NativeRuntime>,
144
+ ) -> Result<Self, ContractError> {
145
+ match self {
146
+ Self::Kiro(adapter) if runtime.is_none() => Ok(Self::Kiro(adapter)),
147
+ Self::Kiro(_) => Err(ContractError::Mismatch("native runtime provider")),
148
+ Self::Cursor(adapter) => {
149
+ let adapter = adapter.with_project(project)?;
150
+ Ok(Self::Cursor(match runtime {
151
+ Some(NativeRuntime::Cursor(runtime)) => adapter.with_runtime(runtime),
152
+ Some(_) => return Err(ContractError::Mismatch("native runtime provider")),
153
+ None => adapter,
154
+ }))
155
+ }
156
+ Self::GrokBuild(adapter) => {
157
+ let adapter = adapter.with_project(project)?;
158
+ Ok(Self::GrokBuild(match runtime {
159
+ Some(NativeRuntime::GrokBuild(runtime)) => adapter.with_runtime(runtime),
160
+ Some(_) => return Err(ContractError::Mismatch("native runtime provider")),
161
+ None => adapter,
162
+ }))
163
+ }
164
+ }
165
+ }
166
+
167
+ pub fn hooks(&self) -> ProviderHooks<'_> {
168
+ match self {
169
+ Self::Kiro(adapter) => adapter.hooks(),
170
+ Self::Cursor(adapter) => adapter.hooks(),
171
+ Self::GrokBuild(adapter) => adapter.hooks(),
172
+ }
173
+ }
174
+
175
+ pub fn plan(&self, request: &ResolvedLaunch) -> Result<LaunchPlan, ContractError> {
176
+ match self {
177
+ Self::Kiro(adapter) => adapter.plan(request),
178
+ Self::Cursor(adapter) => adapter.plan(request),
179
+ Self::GrokBuild(adapter) => adapter.plan(request),
180
+ }
181
+ }
182
+ }
183
+
184
+ pub(super) fn provider(key: &str) -> Result<NativeProvider, ContractError> {
185
+ let descriptor = descriptor(key).ok_or(ContractError::Invalid("unregistered contract provider"))?;
186
+ match descriptor.identity.id {
187
+ "kiro" => Ok(NativeProvider::Kiro),
188
+ "cursor" => Ok(NativeProvider::Cursor),
189
+ "grok_build" => Ok(NativeProvider::GrokBuild),
190
+ _ => Err(ContractError::Invalid("contract provider dispatcher")),
191
+ }
192
+ }
7
193
 
8
194
  pub(crate) fn descriptors() -> &'static [&'static ProviderDescriptor] {
9
195
  DESCRIPTORS
@@ -28,3 +214,61 @@ pub fn recognizes(key: &str) -> bool {
28
214
  .any(|alias| alias.eq_ignore_ascii_case(key))
29
215
  })
30
216
  }
217
+
218
+ #[cfg(test)]
219
+ mod tests {
220
+ use super::*;
221
+
222
+ #[test]
223
+ fn canonical_cursor_does_not_reclassify_legacy_names() {
224
+ assert_eq!(provider("cursor").unwrap(), NativeProvider::Cursor);
225
+ assert_eq!(provider("kiro").unwrap(), NativeProvider::Kiro);
226
+ for legacy in ["cursor_agent", "agent", "pi", "codex", "grok"] {
227
+ assert!(descriptor(legacy).is_none());
228
+ assert!(!recognizes(legacy));
229
+ assert!(provider(legacy).is_err());
230
+ }
231
+ assert_eq!(provider("grok_build").unwrap(), NativeProvider::GrokBuild);
232
+ assert!(recognizes(" Grok_Build "));
233
+ assert!(descriptor("Grok_Build").is_none());
234
+ assert!(GROK_BUILD_DESCRIPTOR.identity.aliases.is_empty());
235
+ assert!(recognizes(" Cursor "));
236
+ assert!(descriptor("Cursor").is_none());
237
+ for descriptor in descriptors() {
238
+ assert_eq!(provider(descriptor.identity.id).unwrap().descriptor(), *descriptor);
239
+ }
240
+ }
241
+
242
+ #[test]
243
+ fn grok_server_key_is_generation_bound_and_has_no_inspector_fallback() {
244
+ use team_agent_contract::contract::types::{Generation, InstanceId, ScopeId, SeatId};
245
+ let mut identity = InstanceIdentity {
246
+ scope: ScopeId::new("scope").unwrap(), seat: SeatId::new("worker").unwrap(),
247
+ instance: InstanceId::new("instance").unwrap(), generation: Generation(1),
248
+ };
249
+ let first = NativeProvider::GrokBuild.server_key_for(&identity).unwrap();
250
+ assert_eq!(first, grok_build::server_key(&identity));
251
+ identity.generation = Generation(2);
252
+ assert_ne!(first, NativeProvider::GrokBuild.server_key_for(&identity).unwrap());
253
+ assert!(NativeProvider::GrokBuild.server_key().is_err());
254
+ assert!(NativeProvider::GrokBuild.tool_panel().is_err());
255
+ assert!(NativeProvider::GrokBuild.startup().is_some());
256
+ assert!(NativeProvider::GrokBuild.session_panel().is_some());
257
+ assert!(NativeProvider::GrokBuild.uses_tool_roundtrips());
258
+ }
259
+
260
+ #[test]
261
+ fn cursor_has_no_kiro_control_or_tool_inspector_fallback() {
262
+ assert!(NativeProvider::Cursor.controls().is_empty());
263
+ assert!(matches!(NativeProvider::Cursor.tool_panel(), Err(ContractError::Unverified { .. })));
264
+ assert_eq!(NativeProvider::Cursor.server_key().unwrap(), "team");
265
+ assert!(NativeProvider::Cursor.first_business_bootstrap());
266
+ assert!(NativeProvider::Cursor.uses_tool_roundtrips());
267
+ assert!(!NativeProvider::Kiro.first_business_bootstrap());
268
+ assert!(!NativeProvider::Kiro.uses_tool_roundtrips());
269
+ let kiro_panel = NativeProvider::Kiro.tool_panel().unwrap();
270
+ assert_eq!(kiro_panel.server_key, NativeProvider::Kiro.server_key().unwrap());
271
+ assert_eq!(kiro_panel.policy_sha256, kiro::interaction::POLICY);
272
+ assert!(!NativeProvider::Kiro.controls().is_empty());
273
+ }
274
+ }