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
//! The flags that take each harness past its routine startup prompts, in one table
//! (`startup_prompts.json`, which the TypeScript SDK reads as its own copy) for every launch path:
//! `supercode open --new`, a resume, a native start. `startup` pairs answer the prompts every launch
//! meets (Codex's new-version check, its trust-this-folder question); `unattended` flags answer the
//! approval prompts of a launch nobody watches, unless the caller chose its own
//! (`unattended_chosen_by`). `{cwd}` in a flag is the launch's directory as a JSON string, the form a
//! Codex `-c projects={…}` table takes it in; a pair that needs it is left out without one.
use std::path::Path;
use serde::Deserialize;
#[derive(Deserialize)]
struct Prompts {
startup: Vec<Vec<String>>,
unattended: Vec<String>,
unattended_chosen_by: Vec<String>,
}
fn table() -> std::collections::BTreeMap<String, Prompts> {
serde_json::from_str(include_str!("startup_prompts.json"))
.expect("startup_prompts.json is the harness crate's own table")
}
/// The arguments `harness` is launched with to open past its routine prompts, ahead of the
/// caller's own `given` arguments. A startup flag the caller already sets (its key, the text before
/// `=`, appears in `given`) is left to the caller.
pub fn startup_arguments(
harness: &str,
cwd: Option<&Path>,
given: &[String],
unattended: bool,
) -> Vec<String> {
let table = table();
let Some(prompts) = table.get(harness) else {
return Vec::new();
};
let cwd = cwd.map(|cwd| {
serde_json::to_string(cwd.to_string_lossy().as_ref())
.expect("a filesystem path always serializes as JSON text")
});
let mut arguments = Vec::new();
for pair in &prompts.startup {
let needs_cwd = pair.iter().any(|flag| flag.contains("{cwd}"));
if needs_cwd && cwd.is_none() {
continue;
}
let key = pair
.last()
.and_then(|flag| flag.split('=').next())
.unwrap_or_default();
if !key.is_empty() && given.iter().any(|argument| argument.contains(key)) {
continue;
}
arguments.extend(pair.iter().map(|flag| match &cwd {
Some(cwd) => flag.replace("{cwd}", cwd),
None => flag.clone(),
}));
}
let chosen = given.iter().any(|argument| {
prompts
.unattended_chosen_by
.iter()
.any(|flag| argument == flag || argument.starts_with(&format!("{flag}=")))
});
if unattended && !chosen {
arguments.extend(prompts.unattended.iter().cloned());
}
arguments
}