1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Wizard state for `Pane::NewCloudRunWizard` — fires a job
//! against a Cloud Agents *runner* (something that survives the
//! laptop close). Two runner kinds shipped in Phase 3a:
//!
//! • **Managed Agents** — Anthropic-hosted orchestration.
//! Sandbox runs either in Anthropic's cloud (default) or on a
//! self-hosted worker the user runs locally / remotely.
//! API key billing.
//! • **ECS runner** — user's own ECS task fire path (existing
//! `fire_cloud_run`). Requires `[cloud_agents]` to be
//! configured; the wizard hides this entry when the config
//! is empty.
//!
//! Steps:
//! 1. Pick runner (Managed | ECS)
//! 2a. (Managed) Configure agent + sandbox mode
//! 2b. (ECS) Ticket + flow
//! 3. Initial prompt
//! 4. Review + submit
//!
//! Submit:
//! - Managed → POST /v1/sessions (with agent_id + environment_id)
//! - ECS → `App::fire_cloud_run`
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum CloudRunner {
/// Anthropic Managed Agents — hosted orchestration + sandbox
/// (cloud or self-hosted worker). Requires ANTHROPIC_API_KEY.
/// API rates billing.
#[default]
ManagedAgents,
/// ECS runner fire path — the trigger reads
/// `[cloud_agents]` config for the region / cluster / task
/// definition / DynamoDB table. Wizard hides this entry
/// when the config is empty.
Ecs,
}
impl CloudRunner {
pub fn label(self) -> &'static str {
match self {
CloudRunner::ManagedAgents => "Managed Agents (Anthropic)",
CloudRunner::Ecs => "ECS runner",
}
}
pub fn hint(self) -> &'static str {
match self {
CloudRunner::ManagedAgents => {
"Anthropic-hosted Claude + sandbox · API key OR AWS SigV4 (SSO)"
}
CloudRunner::Ecs => "ECS task on your AWS account · configured via [cloud_agents]",
}
}
pub fn all() -> &'static [CloudRunner] {
&[CloudRunner::ManagedAgents, CloudRunner::Ecs]
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum SandboxLocation {
/// Anthropic's cloud sandbox (zero setup, default).
#[default]
AnthropicCloud,
/// `self_hosted` environment + a worker running on the user's
/// own infra. Phase 3b will wire the `ant beta:worker poll`
/// spawn for local; remote workers (Vercel/Cloudflare/Modal)
/// stay as the user's responsibility for now.
SelfHostedLocal,
/// Self-hosted environment, worker runs on a remote target
/// (deferred — wizard captures the choice, user provisions the
/// worker themselves).
SelfHostedRemote,
}
impl SandboxLocation {
pub fn label(self) -> &'static str {
match self {
SandboxLocation::AnthropicCloud => "Anthropic cloud sandbox",
SandboxLocation::SelfHostedLocal => "Self-hosted · LOCAL worker",
SandboxLocation::SelfHostedRemote => "Self-hosted · REMOTE worker",
}
}
pub fn hint(self) -> &'static str {
match self {
SandboxLocation::AnthropicCloud => "Zero setup · default · pay per session",
SandboxLocation::SelfHostedLocal => "ant beta:worker poll on this machine",
SandboxLocation::SelfHostedRemote => {
"Worker on your Vercel/Modal/AWS · survives laptop"
}
}
}
pub fn all() -> &'static [SandboxLocation] {
&[
SandboxLocation::AnthropicCloud,
SandboxLocation::SelfHostedLocal,
SandboxLocation::SelfHostedRemote,
]
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CloudRunStep {
Runner,
ManagedAgent,
ManagedSandbox,
QweTicket,
Prompt,
Review,
}
#[derive(Debug)]
pub struct NewCloudRunWizardPane {
pub step: CloudRunStep,
pub focus_row: usize,
pub runner: CloudRunner,
// ─── Managed Agents path ─────────────────────────────────
/// Existing agent id (`agent_…`) or empty if user wants the
/// wizard to create one.
pub managed_agent_id: String,
pub managed_agent_create_new: bool,
pub managed_agent_new_name: String,
pub sandbox: SandboxLocation,
/// Existing environment id (`env_…`) — when empty the wizard
/// auto-creates one matching the sandbox choice.
pub managed_env_id: String,
// ─── QWE path ────────────────────────────────────────────
pub qwe_ticket: String,
// ─── final ───────────────────────────────────────────────
pub prompt: String,
pub submitting: bool,
pub last_message: Option<String>,
}
impl NewCloudRunWizardPane {
pub fn new() -> Self {
Self {
step: CloudRunStep::Runner,
focus_row: 0,
runner: CloudRunner::default(),
managed_agent_id: String::new(),
managed_agent_create_new: true,
managed_agent_new_name: "ide-agent".to_string(),
sandbox: SandboxLocation::default(),
managed_env_id: String::new(),
qwe_ticket: String::new(),
prompt: String::new(),
submitting: false,
last_message: None,
}
}
pub fn next_step(&mut self) -> bool {
let next = match self.step {
CloudRunStep::Runner => match self.runner {
CloudRunner::ManagedAgents => CloudRunStep::ManagedAgent,
CloudRunner::Ecs => CloudRunStep::QweTicket,
},
CloudRunStep::ManagedAgent => CloudRunStep::ManagedSandbox,
CloudRunStep::ManagedSandbox => CloudRunStep::Prompt,
CloudRunStep::QweTicket => CloudRunStep::Prompt,
CloudRunStep::Prompt => CloudRunStep::Review,
CloudRunStep::Review => return false,
};
self.step = next;
self.focus_row = 0;
true
}
pub fn prev_step(&mut self) {
let prev = match self.step {
CloudRunStep::Runner => CloudRunStep::Runner,
CloudRunStep::ManagedAgent => CloudRunStep::Runner,
CloudRunStep::ManagedSandbox => CloudRunStep::ManagedAgent,
CloudRunStep::QweTicket => CloudRunStep::Runner,
CloudRunStep::Prompt => match self.runner {
CloudRunner::ManagedAgents => CloudRunStep::ManagedSandbox,
CloudRunner::Ecs => CloudRunStep::QweTicket,
},
CloudRunStep::Review => CloudRunStep::Prompt,
};
self.step = prev;
self.focus_row = 0;
}
}
impl Default for NewCloudRunWizardPane {
fn default() -> Self {
Self::new()
}
}