Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

185 linesCodeBlameRaw
1//! Starting work on an issue in a session of its own, on the person's own
2//! Claude (the integrations service, `src/agent_sessions.rs`).
3//!
4//! g1t runs no model. Work on an issue happens in someone's own harness:
5//! their Claude Code with the g1t plugin, or a Claude Managed Agents
6//! session that g1t starts with the workspace's own Anthropic key. Either
7//! way the session is recorded on g1t (`work::Session`) and tied to the
8//! issue and the pull requests it makes. `start_agent_session` is what
9//! Ask, chat and the issue page call to hand work off.
10
11use serde::{Deserialize, Serialize};
12
13use crate::repos::RepoPath;
14use crate::work::Session;
15use crate::{User, Viewer};
16
17/// What kind of work a session is asked for. A hint in its prompt.
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum AgentMode {
21 /// Work out how, and say so on the issue; change nothing.
22 Plan,
23 /// Make the change and open a pull request.
24 #[default]
25 Implement,
26 /// Review the issue's pull request.
27 Review,
28}
29
30impl AgentMode {
31 pub fn as_str(self) -> &'static str {
32 match self {
33 AgentMode::Plan => "plan",
34 AgentMode::Implement => "implement",
35 AgentMode::Review => "review",
36 }
37 }
38
39 pub fn parse(name: &str) -> Option<AgentMode> {
40 match name.trim().to_ascii_lowercase().as_str() {
41 "plan" => Some(AgentMode::Plan),
42 "implement" => Some(AgentMode::Implement),
43 "review" => Some(AgentMode::Review),
44 _ => None,
45 }
46 }
47}
48
49/// The most characters of an instruction kept.
50pub const MAX_INSTRUCTION_CHARS: usize = 4_000;
51
52/// Who asked g1t in an issue or a pull request, rather than from a
53/// button: by assigning it the issue, or by mentioning `@g1t` in a comment.
54#[derive(Clone, Debug, Default, Serialize, Deserialize)]
55pub struct Asked {
56 /// Their username.
57 pub by: String,
58 /// What their comment said; none for an assignment.
59 #[serde(default)]
60 pub comment: Option<String>,
61 /// Where they asked: the comment, or the issue.
62 pub url: String,
63}
64
65/// `start_agent_session`: work on `issue` (or a new issue from `title` and
66/// `body`), or carry on with pull request `pull`, as `actor`. Returns
67/// `Outcome<AgentSessionStarted>`.
68#[derive(Debug, Serialize, Deserialize)]
69pub struct StartAgentSessionArgs {
70 pub actor: User,
71 pub repo: RepoPath,
72 #[serde(default)]
73 pub issue: Option<u32>,
74 /// A pull request to carry on with, on its branch: in place of `issue`.
75 #[serde(default)]
76 pub pull: Option<u32>,
77 /// Set when g1t was asked in the thread (integrations' summon.rs).
78 #[serde(default)]
79 pub asked: Option<Asked>,
80 /// Without `issue`: a new issue's title, opened first.
81 #[serde(default)]
82 pub title: Option<String>,
83 #[serde(default)]
84 pub body: Option<String>,
85 /// What to do beyond what the issue says.
86 #[serde(default)]
87 pub instruction: Option<String>,
88 #[serde(default)]
89 pub mode: Option<AgentMode>,
90 /// `true`: a hand-off to run in Claude Code, never a session started
91 /// here. `false`: a session here or a refusal. None: a session here
92 /// when the workspace has an Anthropic connection, else the hand-off.
93 #[serde(default)]
94 pub handoff: Option<bool>,
95}
96
97/// What `start_agent_session` started, or the hand-off to run.
98#[derive(Clone, Debug, Default, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct AgentSessionStarted {
101 /// The issue it works on: for a pull request with none, its number.
102 pub issue: u32,
103 /// The pull request it carries on with, when it was started on one.
104 #[serde(default)]
105 pub pull: Option<u32>,
106 /// The session on g1t, when one was started here.
107 #[serde(default)]
108 pub session: Option<Session>,
109 /// Its page on g1t.
110 #[serde(default)]
111 pub session_url: Option<String>,
112 /// Who runs it: `anthropic`, or none for a hand-off.
113 #[serde(default)]
114 pub provider: Option<String>,
115 /// Its own id with the provider (`sesn_…`).
116 #[serde(default)]
117 pub provider_session_id: Option<String>,
118 /// Where the provider shows it, when it says.
119 #[serde(default)]
120 pub provider_session_url: Option<String>,
121 /// Whether this is a hand-off: nothing was started.
122 pub handoff: bool,
123 /// For a hand-off: the Claude Code command line that starts it.
124 #[serde(default)]
125 pub command: Option<String>,
126 /// What the session is asked: the prompt sent, or to paste.
127 pub prompt: String,
128}
129
130/// `agent_sessions_setup`: whether `viewer` can start sessions on the
131/// workspace's Anthropic key. Returns `Outcome<AgentSessionsSetup>`.
132#[derive(Debug, Serialize, Deserialize)]
133pub struct AgentSessionsSetupArgs {
134 pub viewer: Viewer,
135 pub workspace: String,
136}
137
138#[derive(Clone, Debug, Default, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct AgentSessionsSetup {
141 /// A session can be started with the workspace's Anthropic key.
142 pub available: bool,
143 /// The Anthropic connection used.
144 #[serde(default)]
145 pub connection_name: Option<String>,
146 /// The model sessions run on.
147 #[serde(default)]
148 pub model: Option<String>,
149 /// Why not, when not.
150 #[serde(default)]
151 pub problem: Option<String>,
152}
153
154/// `sync_agent_session`: brings a Managed Agents session's record on g1t
155/// up to date now (it is otherwise followed every minute). Returns
156/// `Outcome<bool>`: whether it is still going.
157#[derive(Debug, Serialize, Deserialize)]
158pub struct SyncAgentSessionArgs {
159 pub viewer: Viewer,
160 pub repo: RepoPath,
161 /// The g1t session (`ses_…`).
162 pub id: String,
163}
164
165/// `stop_agent_session`: interrupts a Managed Agents session and ends its
166/// record. Only whoever started it. Returns `Outcome<bool>`.
167#[derive(Debug, Serialize, Deserialize)]
168pub struct StopAgentSessionArgs {
169 pub actor: User,
170 pub repo: RepoPath,
171 pub id: String,
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn modes_are_named_one_way() {
180 assert_eq!(AgentMode::parse("Review"), Some(AgentMode::Review));
181 assert_eq!(AgentMode::parse("nap"), None);
182 assert_eq!(serde_json::to_value(AgentMode::Plan).unwrap(), "plan");
183 assert_eq!(AgentMode::default(), AgentMode::Implement);
184 }
185}