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.

Commit

Sessions across a workspace: one list of every kind

list_workspace_sessions and get_workspace_session read every repository of a workspace the viewer can read and answer one shape for each kind of session: someone's own harness (with its subagents), one g1t started on the workspace's provider, g1t's own agent, and what an agent recorded on a pull request with no session of its own. They come with the merge queues that have something waiting and what landed last. GET /workspaces/{workspace}/sessions[/{id}], the session tool's list and get with workspace, and g1t session list --workspace.

G1T-Session:
ses_01m560bz6eeqc8v1hrrha03pap
syntaqxcommitted Parent1cb8c98Browse files
24 files+622−158 files not counted0/24 viewed
+133−10
3535 ListClaims,
3636 GetWork,
3737 StartAgentSession,
38+ ListWorkspaceSessions,
39+ GetWorkspaceSession,
3840 }
3941
4042 impl CoordinationOp {
4143 /// Every one: `Op::ALL` lists each as `Op::Coordination(…)`, which a
4244 /// test checks against this.
43− pub const ALL: [CoordinationOp; 12] = [
45+ pub const ALL: [CoordinationOp; 14] = [
4446 CoordinationOp::StartSession,
4547 CoordinationOp::UpdateSession,
4648 CoordinationOp::EndSession,
5355 CoordinationOp::ListClaims,
5456 CoordinationOp::GetWork,
5557 CoordinationOp::StartAgentSession,
58+ CoordinationOp::ListWorkspaceSessions,
59+ CoordinationOp::GetWorkspaceSession,
5660 ];
5761
5862 pub fn name(self) -> &'static str {
6973 CoordinationOp::ListClaims => "list_claims",
7074 CoordinationOp::GetWork => "get_work",
7175 CoordinationOp::StartAgentSession => "start_agent_session",
76+ CoordinationOp::ListWorkspaceSessions => "list_workspace_sessions",
77+ CoordinationOp::GetWorkspaceSession => "get_workspace_session",
7278 }
7379 }
7480
8793 CoordinationOp::ListClaims => "List claims",
8894 CoordinationOp::GetWork => "Get a work item's work context",
8995 CoordinationOp::StartAgentSession => "Start an agent session on a work item",
96+ CoordinationOp::ListWorkspaceSessions => "List a workspace's sessions",
97+ CoordinationOp::GetWorkspaceSession => "Get a session of a workspace",
9098 }
9199 }
92100
93101 /// Whether it is about one repository: sessions are where they run;
94102 /// claims and work contexts are of a workspace's work items, and take a
95103 /// repository only to name its workspace (or, for a claim, where it is
96− /// made from).
104+ /// made from). Sessions are read across a workspace too: listing or
105+ /// getting them takes a repository or a workspace.
97106 pub fn needs_repo(self) -> bool {
98107 !matches!(
99108 self,
100− CoordinationOp::ClaimWorkItem
109+ CoordinationOp::GetSession
110+ | CoordinationOp::ListSessions
111+ | CoordinationOp::ListWorkspaceSessions
112+ | CoordinationOp::GetWorkspaceSession
113+ | CoordinationOp::ClaimWorkItem
101114 | CoordinationOp::ReleaseClaim
102115 | CoordinationOp::HandOffWorkItem
103116 | CoordinationOp::ListClaims
109122 pub fn reads(self) -> bool {
110123 matches!(
111124 self,
112− CoordinationOp::GetSession | CoordinationOp::ListSessions | CoordinationOp::ListClaims | CoordinationOp::GetWork
125+ CoordinationOp::GetSession
126+ | CoordinationOp::ListSessions
127+ | CoordinationOp::ListClaims
128+ | CoordinationOp::GetWork
129+ | CoordinationOp::ListWorkspaceSessions
130+ | CoordinationOp::GetWorkspaceSession
113131 )
114132 }
115133
118136 CoordinationOp::StartSession => "Start a session: one run of your own harness (a Claude Code session, a Codex run) on a repository, so others can see who is working on what. `harness` is claude_code, codex, cursor, copilot or other. Give `harness_session_id`, the harness's own id for the session, and starting again with it returns the session already started (refreshing the work item, pull request, branch, title and head given) instead of a new one, so a hook that fires twice is harmless. `item` and `pull` attach it to a work item of the repository's workspace and a pull request of the repository; a pull request for a work item attaches that work item too. A subagent is a session of its own: give `parent_id` (one of your sessions here), its `agent_type`, the `task` and `prompt` its parent gave it, and as `harness_session_id` the parent's harness id, `/agent/` and the subagent's id; it starts on the parent's work item, pull request and branch unless it says otherwise, sees and is seen by its siblings in get_work and list_sessions, and once ended stays ended when started again. Returns the session (`id` `ses_…`, `state` active, idle or ended). A new session, or one started again after it ended, is a session.started event. Needs a confirmed email address and a role that may open work items.",
119137 CoordinationOp::UpdateSession => "Update one of your sessions: attach it to a work item or pull request, or change its branch, head commit or title. Only the fields given change. An empty body is a heartbeat: every call keeps the session active and renews the claims made from it. A session reads as idle after 15 minutes without one and ends (`end_reason` timed_out) after 30 minutes; a heartbeat brings a timed-out one back. Attaching a pull request moves the entries recorded so far into that pull request's session. A subagent's `task`, `prompt`, `model` and `result` (what it reported, up to 8,000 characters) can be set too; a subagent that ended takes them and stays ended. Returns the session; one ended with end_session is refused with `409`.",
120138 CoordinationOp::EndSession => "End one of your sessions, with an optional `reason` and `result` (what it reported when it finished: a subagent's final message, up to 8,000 characters, never a tool's output). A session.ended event. Ending one already ended returns it as it is, taking `result` if it had none. Its subagents still going end with it (`parent ended`). Its claims are not released: release them, hand the work item off, or let them expire. Anyone with write access to the repository may also end someone else's session, one left behind by a harness that was killed or never ended it: it ends as `ended by hand by @you`, without their `reason` or `result`.",
121− CoordinationOp::GetSession => "A session and its entries, oldest first, numbered within the session (`seq`); `after` gives the entries after that number, 500 at a time. With them: `children`, its subagents and theirs in the order they started (each names its `parent_id`); `touched`, the files, commits, branches, pull requests and work items it and they touched; `links`, the work items and pull requests they worked on and how each was tied (`via`: attached, claim, branch, commit, trailer, opened or ref); `skills`, the skills they used by name; and for a subagent, `root`, the session at the top of its tree. `updates` contains up to 100 oldest pending coordination events that concern this session (id, kind, data, at): those about the work items and pull requests it is on or tied to, or every one while it is on nothing yet; never its own start, end or claims. Reading never acknowledges them; use record_session_entries with acknowledge IDs after considering them. To check for updates without the transcript, give `after` 4294967295. Anyone who can read the repository may.",
122− CoordinationOp::ListSessions => "A repository's sessions, most recently heard from first, up to 100: `state` active (heard from in the last 15 minutes), idle or ended; `item` (a work item's number) or `pull` for those on one, attached or tied to it any other way; `path` for those that touched a file (or any under a folder ending in `/`). Subagents are left out unless `subagents` is true, which lists each tree whole. Each says how many `subagents` it started. Anyone who can read the repository may.",
139+ CoordinationOp::GetSession => "A session and its entries, oldest first, numbered within the session (`seq`); `after` gives the entries after that number, 500 at a time. With them: `children`, its subagents and theirs in the order they started (each names its `parent_id`); `touched`, the files, commits, branches, pull requests and work items it and they touched; `links`, the work items and pull requests they worked on and how each was tied (`via`: attached, claim, branch, commit, trailer, opened or ref); `skills`, the skills they used by name; and for a subagent, `root`, the session at the top of its tree. `updates` contains up to 100 oldest pending coordination events that concern this session (id, kind, data, at): those about the work items and pull requests it is on or tied to, or every one while it is on nothing yet; never its own start, end or claims. Reading never acknowledges them; use record_session_entries with acknowledge IDs after considering them. To check for updates without the transcript, give `after` 4294967295. Anyone who can read the repository may. With `workspace` instead of `repo`, the session is found in whichever repository of the workspace it runs in, and `repo` says which.",
140+ CoordinationOp::ListSessions => "A repository's sessions, most recently heard from first, up to 100: `state` active (heard from in the last 15 minutes), idle or ended; `item` (a work item's number) or `pull` for those on one, attached or tied to it any other way; `path` for those that touched a file (or any under a folder ending in `/`). Subagents are left out unless `subagents` is true, which lists each tree whole. Each says how many `subagents` it started. Anyone who can read the repository may. With `workspace` instead of `repo`, every repository of the workspace you may read: see list_workspace_sessions.",
141+ CoordinationOp::ListWorkspaceSessions => "What is cooking across a workspace: the sessions of every repository of it you may read, every kind in one shape, most recently heard from first (up to 200, 60 if `limit` is left out). `kind` is harness (someone's own harness: Claude Code, Codex, Cursor, Copilot, another), hosted (started on g1t on the workspace's Anthropic provider), agent (g1t's own agent on a pull request; members only) or recorded (what a coding agent recorded on a pull request with no session behind it). Each gives `repo`, `title`, `actor`, `harness` or `agent`, `branch`, `item` and its title, `pull` and its title and status, `state` (active, idle or ended: one quiet for 30 minutes reads as ended), `last_activity_at`, `subagents`, and for harness and hosted ones the `session` itself. `state` live (going now), recent (the default) or ended; `harness`, `person` (a username), `repo` (a repository's name in the workspace) and `item` narrow it; `subagents` true gives each tree's subagents in `children`. With them: `queue`, the merge queues with something waiting, `landed`, the pull requests merged most recently, and `repos`, the repositories looked in. Only what you may read is listed; outside the workspace, sessions show who runs them and what they are tied to, not what they recorded.",
142+ CoordinationOp::GetWorkspaceSession => "One session of a workspace by its id (ses_…), in whichever of its repositories it runs: as get_session, with `repo` naming the repository. Anyone who can read that repository may.",
123143 CoordinationOp::RecordSessionEntries => "Record entries in one of your sessions: up to 200 at a time, each with `kind` (prompt, message, tool_call, tool_result, note, or skill for a skill it used, by name), `text` (up to 64,000 characters), and for tools `tool`. Give each a `key` and recording it again does nothing, so a retried hook records it once. `touched` says what it touched, up to 200: `{ kind, value }` with kind file (a path from the repository's root), commit, branch, pull (one it opened) or item (a work item it named); each is kept once, ties the session to the pull requests and work items it names, and a file another active session touched too (outside its own parents and subagents, so siblings yes) is an overlap.detected event for both, once per pair and again when a file it was not announced for joins. Entries of a session attached to a pull request show in that pull request's session too, where reviewers read why the change is what it is. `acknowledge` accepts up to 100 exact pending update IDs this session has considered; reading alone never acknowledges. Recording is a heartbeat. Returns how many were `recorded`, how many were `duplicates`, and how many of the `acknowledge` IDs were `acknowledged` (fewer than given means some were not this session's updates).",
124144 CoordinationOp::ClaimWorkItem => "Claim an open work item: say you are working on it. A claim is advisory, never a lock: anyone may claim a work item someone else has, and `others` lists the claims already on it, so you can coordinate or pick something else. It lasts `ttl_minutes` (30 unless you say, up to 240) and is renewed by claiming again or, when you give the `session_id` of one of your sessions, by that session's heartbeats. Name the work item by `workspace` and `number`; `repo`, the repository of your checkout, names the workspace when `workspace` is left out and says where the claim is made from. Returns your `claim`, the `others`, and `blocked_by`: each work item this one waits on, with its claims and pull requests. A new claim is a work.claimed event; one taking over a hand-off says which in `took_over`. For work that has no work item yet, leave out `number` and give `title` (and `body`): the work item is opened first (filed from `repo` when given; a work_item.opened event), then claimed, the session named by `session_id` is attached to it, and `item` is the work item opened. The session is checked first, so a claim it would refuse opens nothing.",
125145 CoordinationOp::ReleaseClaim => "Release your claim on a work item, with an optional `reason`: you stopped working on it. A work.released event. Claims also end when the work item closes (reason item_closed), and expire when not renewed. Refused with `404` when you hold no active claim on it.",
198218 ),
199219 CoordinationOp::GetSession => (
200220 json!({
201− "repo": repo,
221+ "repo": { "type": "string", "description": "Repository as \"owner/name\". Left out, give workspace." },
222+ "workspace": { "type": "string", "description": "A workspace's slug, instead of repo: the session is found in whichever repository of it it runs in." },
202223 "id": id,
203224 "after": { "type": "integer", "description": "Only entries after this sequence number, for the next page. To read only pending `updates`, give 4294967295: no entries come back." },
204225 }),
205− &["repo", "id"],
226+ &["id"],
206227 ),
207228 CoordinationOp::ListSessions => (
208229 json!({
209− "repo": repo,
230+ "repo": { "type": "string", "description": "Repository as \"owner/name\". Left out, give workspace for every repository of it you may read." },
231+ "workspace": { "type": "string", "description": "A workspace's slug, instead of repo: its sessions across every repository of it you may read (see list_workspace_sessions for what else it takes)." },
210232 "state": { "type": "string", "enum": ["active", "idle", "ended"], "description": "Only sessions in this state." },
211233 "item": { "type": "integer", "description": "Only sessions on this work item: attached, or tied to it any other way." },
212234 "pull": { "type": "integer", "description": "Only sessions on this pull request: attached, or tied to it any other way." },
213235 "path": { "type": "string", "description": "Only sessions that touched this file, or any under it when it ends in `/`." },
214236 "subagents": { "type": "boolean", "description": "List subagents too, each tree whole. Left out, only the sessions at the top." },
215237 }),
216− &["repo"],
238+ &[],
239+ ),
240+ CoordinationOp::ListWorkspaceSessions => (
241+ json!({
242+ "workspace": { "type": "string", "description": "The workspace's slug." },
243+ "state": { "type": "string", "enum": ["live", "recent", "ended"], "description": "live: going now (active or idle, a run queued or running); recent: every one (the default); ended." },
244+ "kind": { "type": "string", "enum": SessionKind::ALL.map(SessionKind::as_str), "description": "Only sessions of this kind." },
245+ "harness": { "type": "string", "enum": Harness::ALL.map(Harness::as_str), "description": "Only sessions in this harness." },
246+ "person": { "type": "string", "description": "Only sessions this person runs, by username." },
247+ "repo": { "type": "string", "description": "Only sessions in this repository of the workspace, by its name (or \"owner/name\")." },
248+ "item": { "type": "integer", "description": "Only sessions on this work item of the workspace." },
249+ "subagents": { "type": "boolean", "description": "Give each session's subagents too, in its `children`." },
250+ "limit": { "type": "integer", "description": "At most this many, up to 200; 60 if left out." },
251+ }),
252+ &["workspace"],
253+ ),
254+ CoordinationOp::GetWorkspaceSession => (
255+ json!({
256+ "workspace": { "type": "string", "description": "The workspace's slug." },
257+ "id": id,
258+ "after": { "type": "integer", "description": "Only entries after this sequence number, for the next page." },
259+ }),
260+ &["workspace", "id"],
217261 ),
218262 CoordinationOp::RecordSessionEntries => (
219263 json!({
364408 // A claim or a work context names its work item's workspace, or a
365409 // repository of it.
366410 let workspace = item_workspace(input);
411+ // Sessions across a workspace: listing or getting them without a repository.
412+ if given.is_none() && !workspace.is_empty() {
413+ match op {
414+ CoordinationOp::ListSessions => return list_workspace_sessions(services, viewer, input, workspace).await,
415+ CoordinationOp::GetSession => return get_workspace_session(services, viewer, input, workspace).await,
416+ _ => {}
417+ }
418+ }
419+ if matches!(op, CoordinationOp::ListWorkspaceSessions | CoordinationOp::GetWorkspaceSession) {
420+ if workspace.is_empty() {
421+ return invalid("Give the workspace by its slug.".to_owned());
422+ }
423+ return match op {
424+ CoordinationOp::ListWorkspaceSessions => list_workspace_sessions(services, viewer, input, workspace).await,
425+ _ => get_workspace_session(services, viewer, input, workspace).await,
426+ };
427+ }
428+ if matches!(op, CoordinationOp::ListSessions | CoordinationOp::GetSession) && given.is_none() {
429+ return invalid("Give the repository as \"owner/name\", or the workspace by its slug.".to_owned());
430+ }
367431 if !op.needs_repo() && workspace.is_empty() {
368432 return invalid("Give the workspace by its slug, or a repository of it as \"owner/name\".".to_owned());
369433 }
552616 };
553617 g1t_kit::call(&services.integrations, "start_agent_session", &args).await
554618 }
619+ // Answered above, before a repository is asked for.
620+ CoordinationOp::ListWorkspaceSessions | CoordinationOp::GetWorkspaceSession => invalid("Give the workspace by its slug.".to_owned()),
555621 }
556622 }
557623
624+/// `list_workspace_sessions`, from a `workspace` and its filters.
625+async fn list_workspace_sessions(services: &Services, viewer: &Viewer, input: &Value, workspace: String) -> Result<Outcome<Value>> {
626+ let invalid = |message: String| Ok(Outcome::fail(FailureCode::Invalid, message));
627+ let shown = match text(input, "state") {
628+ None => SessionsShown::Recent,
629+ Some(state) => match SessionsShown::parse(&state) {
630+ Some(shown) => shown,
631+ None => return invalid(format!("state is live, recent or ended, not {state}.")),
632+ },
633+ };
634+ let kind = match text(input, "kind") {
635+ None => None,
636+ Some(kind) => match SessionKind::parse(&kind) {
637+ Some(kind) => Some(kind),
638+ None => return invalid(format!("kind is harness, hosted, agent or recorded, not {kind}.")),
639+ },
640+ };
641+ let harness = match text(input, "harness") {
642+ None => None,
643+ Some(name) => match Harness::parse(&name) {
644+ Some(harness) => Some(harness),
645+ None => {
646+ let names: Vec<&str> = Harness::ALL.iter().map(|harness| harness.as_str()).collect();
647+ return invalid(format!("harness is one of {}.", names.join(", ")));
648+ }
649+ },
650+ };
651+ let item = match optional_number(input, "item") {
652+ Ok(item) => item,
653+ Err(message) => return invalid(message),
654+ };
655+ let subagents = matches!(&input["subagents"], Value::Bool(true)) || input["subagents"].as_str() == Some("true");
656+ let args = ListWorkspaceSessionsArgs {
657+ viewer: viewer.clone(),
658+ workspace,
659+ shown,
660+ kind,
661+ harness,
662+ person: text(input, "person"),
663+ repo: text(input, "repo").or_else(|| text(input, "repository")),
664+ item,
665+ subagents,
666+ sessions_only: false,
667+ limit: number(input, "limit"),
668+ };
669+ g1t_kit::call(&services.work, "list_workspace_sessions", &args).await
670+}
671+
672+/// `get_workspace_session`, from a `workspace` and the session's `id`.
673+async fn get_workspace_session(services: &Services, viewer: &Viewer, input: &Value, workspace: String) -> Result<Outcome<Value>> {
674+ let Some(id) = text(input, "id") else {
675+ return Ok(Outcome::fail(FailureCode::Invalid, "Name the session by its id (ses_…)."));
676+ };
677+ let args = GetWorkspaceSessionArgs { viewer: viewer.clone(), workspace, id, after_seq: number(input, "after").unwrap_or_default() };
678+ g1t_kit::call(&services.work, "get_workspace_session", &args).await
679+}
680+
558681 #[cfg(test)]
559682 mod tests {
560683 use super::*;
+2−0
450450 Op::Coordination(CoordinationOp::EndSession),
451451 Op::Coordination(CoordinationOp::GetSession),
452452 Op::Coordination(CoordinationOp::ListSessions),
453+ Op::Coordination(CoordinationOp::ListWorkspaceSessions),
454+ Op::Coordination(CoordinationOp::GetWorkspaceSession),
453455 Op::Coordination(CoordinationOp::RecordSessionEntries),
454456 Op::Coordination(CoordinationOp::StartAgentSession),
455457 Op::ReadSession,
+3−1
868868 }
869869
870870 impl Op {
871− pub const ALL: [Op; 428] = [
871+ pub const ALL: [Op; 430] = [
872872 Op::Whoami,
873873 Op::GetWorkspace,
874874 Op::CreateWorkspace,
12971297 Op::Coordination(CoordinationOp::ListClaims),
12981298 Op::Coordination(CoordinationOp::GetWork),
12991299 Op::Coordination(CoordinationOp::StartAgentSession),
1300+ Op::Coordination(CoordinationOp::ListWorkspaceSessions),
1301+ Op::Coordination(CoordinationOp::GetWorkspaceSession),
13001302 ];
13011303
13021304 pub fn by_name(name: &str) -> Option<Op> {
+0−0

Binary or large file; its contents are not shown.

+2−0
268268 }
269269 Op::Coordination(CoordinationOp::GetSession) => through::<work::SessionDetail>(op, sent),
270270 Op::Coordination(CoordinationOp::ListSessions) => through::<Vec<work::Session>>(op, sent),
271+ Op::Coordination(CoordinationOp::ListWorkspaceSessions) => through::<work::WorkspaceSessions>(op, sent),
272+ Op::Coordination(CoordinationOp::GetWorkspaceSession) => through::<work::WorkspaceSessionDetail>(op, sent),
271273 Op::Coordination(CoordinationOp::RecordSessionEntries) => through::<work::Recorded>(op, sent),
272274 Op::Coordination(CoordinationOp::ClaimWorkItem) => through::<work::Claimed>(op, sent),
273275 Op::Coordination(CoordinationOp::ReleaseClaim | CoordinationOp::HandOffWorkItem) => through::<work::Claim>(op, sent),
+16−0
645645 Op::Coordination(CoordinationOp::ListClaims),
646646 &[("state", "state"), ("item", "item"), ("repo", "repo")],
647647 ),
648+ route(
649+ "GET",
650+ "/workspaces/:workspace/sessions",
651+ Op::Coordination(CoordinationOp::ListWorkspaceSessions),
652+ &[
653+ ("state", "state"),
654+ ("kind", "kind"),
655+ ("harness", "harness"),
656+ ("person", "person"),
657+ ("repo", "repo"),
658+ ("item", "item"),
659+ ("subagents", "subagents"),
660+ ("limit", "limit"),
661+ ],
662+ ),
663+ route("GET", "/workspaces/:workspace/sessions/:id", Op::Coordination(CoordinationOp::GetWorkspaceSession), &[("after", "after")]),
648664 route("POST", "/repos/:owner/:name/sessions", Op::Coordination(CoordinationOp::StartSession), &[]),
649665 route(
650666 "GET",
+6−2
212212 Tool {
213213 name: "session",
214214 title: "Sessions",
215− description: "Your harness session on a repository, so others see who is working on what: `start` once (idempotent on harness_session_id; a subagent starts its own with parent_id), `update` as a heartbeat or to attach a work item, pull request, branch or head commit, `record` user-facing decisions, tool calls, skills used and what it touched (idempotent on key), `end` when done with what it reported. `list` with an item, or a path, shows who else (sibling subagents too) is on the same work. `dispatch` starts work on a work item in a session on the person's own Claude. A session goes idle after 15 minutes without a heartbeat and ends after 30 minutes; a heartbeat brings it back.",
215+ description: "Your harness session on a repository, so others see who is working on what: `start` once (idempotent on harness_session_id; a subagent starts its own with parent_id), `update` as a heartbeat or to attach a work item, pull request, branch or head commit, `record` user-facing decisions, tool calls, skills used and what it touched (idempotent on key), `end` when done with what it reported. `list` with an item, or a path, shows who else (sibling subagents too) is on the same work; `list` with a `workspace` shows everything cooking across it: people's harnesses, sessions started on g1t, g1t's agent and what was recorded on pull requests. `dispatch` starts work on a work item in a session on the person's own Claude. A session goes idle after 15 minutes without a heartbeat and ends after 30 minutes; a heartbeat brings it back.",
216216 default_action: None,
217217 actions: &[
218218 a("start", Op::Coordination(CoordinationOp::StartSession), "Start one, or get the one started with this harness_session_id"),
220220 a("record", Op::Coordination(CoordinationOp::RecordSessionEntries), "Record entries and acknowledge exact coordination update IDs after considering them; entry keys make retries idempotent"),
221221 a("end", Op::Coordination(CoordinationOp::EndSession), "End it"),
222222 a("get", Op::Coordination(CoordinationOp::GetSession), "One session, entries and pending coordination updates (reading never acknowledges)"),
223− a("list", Op::Coordination(CoordinationOp::ListSessions), "A repository's sessions: active, idle or ended, by work item, pull request or file touched; with subagents, each tree whole"),
223+ a("list", Op::Coordination(CoordinationOp::ListSessions), "A repository's sessions: active, idle or ended, by work item, pull request or file touched; with subagents, each tree whole. With workspace instead: what is cooking across it, every kind of session, with its merge queues and what landed"),
224224 a("dispatch", Op::Coordination(CoordinationOp::StartAgentSession), "Start work on a work item in a session on the person's own Claude (Managed Agents with the workspace's Anthropic key), or get a Claude Code hand-off"),
225225 ],
226226 },
646646 // A person's own Getting started is about them at a page, not for agents.
647647 Op::Onboarding(OnboardingOp::GetOnboarding),
648648 Op::Onboarding(OnboardingOp::UpdateOnboarding),
649+ // Sessions across a workspace are the `session` tool's `list` and
650+ // `get` with a `workspace`.
651+ Op::Coordination(CoordinationOp::ListWorkspaceSessions),
652+ Op::Coordination(CoordinationOp::GetWorkspaceSession),
649653 ];
650654
651655 /// Operations that cannot be undone, or reach beyond g1t's own records:
+5−1
10071007 | `-L, --limit <LIMIT>` | Default: `20`. |
10081008 | `--path <PATH>` | Only sessions that touched this file, or any under it when it ends in /. |
10091009 | `--subagents` | List subagents too, under the sessions that started them. |
1010+| `--workspace [SLUG]` | Every repository of a workspace you may read, every kind of session: this repository's workspace, or the one named. |
1011+| `--harness <HARNESS>` | With --workspace: only sessions in this harness (claude_code, codex, …). |
1012+| `--person <PERSON>` | With --workspace: only sessions this person runs, by username. |
1013+| `--in <NAME>` | With --workspace: only sessions in this repository of it, by name. |
10101014
1011−Calls [`list_sessions`](/reference/api/sessions/list-sessions/).
1015+Calls [`list_sessions`](/reference/api/sessions/list-sessions/), [`list_workspace_sessions`](/reference/api/sessions/list-workspace-sessions/).
10121016
10131017 ### g1t session view
10141018
+0−0

Binary or large file; its contents are not shown.

+2−0
12221222 // Harness sessions, which record how pull requests are made.
12231223 ("get_session", Scope::PullRequestsRead),
12241224 ("list_sessions", Scope::PullRequestsRead),
1225+ ("list_workspace_sessions", Scope::PullRequestsRead),
1226+ ("get_workspace_session", Scope::PullRequestsRead),
12251227 ("start_session", Scope::PullRequestsWrite),
12261228 ("update_session", Scope::PullRequestsWrite),
12271229 ("end_session", Scope::PullRequestsWrite),
+3−0
2525 mod items;
2626 pub use items::*;
2727
28+mod workspace_sessions;
29+pub use workspace_sessions::*;
30+
2831 pub mod refs;
2932
3033 /// The labels a new repository starts with, and that "Add the default
+323−0
1+//! Sessions across a workspace: what is cooking in every repository of it
2+//! the viewer may read, every kind of session in one shape, with the merge
3+//! queues and what just landed. Sessions' own page in the web, `GET
4+//! /workspaces/{workspace}/sessions`, the `session` tool's `list` with a
5+//! `workspace`, and `g1t session list --workspace`.
6+//!
7+//! Kinds of session:
8+//! - **harness**: someone's own harness (Claude Code, Codex, Cursor,
9+//! Copilot, another) recording itself with `start_session`.
10+//! - **hosted**: a session g1t started for someone, from the web or a work
11+//! item, on the workspace's Anthropic provider (`claude_managed_agents`).
12+//! - **agent**: a run of g1t's own agent on a pull request (`agent_runs`).
13+//! - **recorded**: what a coding agent recorded on a pull request with
14+//! `record_session`, with no session of its own behind it.
15+
16+use serde::{Deserialize, Serialize};
17+
18+use super::{Harness, Pull, PullStatus, QueueView, Session, SessionDetail, SessionState, SESSION_IDLE_MS, SESSION_TIMEOUT_MS};
19+use crate::Viewer;
20+
21+/// What kind of session one in a workspace's list is.
22+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
23+#[serde(rename_all = "snake_case")]
24+pub enum SessionKind {
25+ Harness,
26+ Hosted,
27+ Agent,
28+ Recorded,
29+}
30+
31+impl SessionKind {
32+ pub const ALL: [SessionKind; 4] = [SessionKind::Harness, SessionKind::Hosted, SessionKind::Agent, SessionKind::Recorded];
33+
34+ pub fn as_str(self) -> &'static str {
35+ match self {
36+ SessionKind::Harness => "harness",
37+ SessionKind::Hosted => "hosted",
38+ SessionKind::Agent => "agent",
39+ SessionKind::Recorded => "recorded",
40+ }
41+ }
42+
43+ pub fn parse(name: &str) -> Option<SessionKind> {
44+ let name = name.trim().to_lowercase();
45+ SessionKind::ALL.into_iter().find(|kind| kind.as_str() == name)
46+ }
47+
48+ /// The kind of a session recorded with `start_session`: g1t started
49+ /// those in Claude Managed Agents, the rest are someone's own harness.
50+ pub fn of(harness: Harness) -> SessionKind {
51+ match harness {
52+ Harness::ClaudeManagedAgents => SessionKind::Hosted,
53+ _ => SessionKind::Harness,
54+ }
55+ }
56+}
57+
58+/// Which of a workspace's sessions to list.
59+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
60+#[serde(rename_all = "snake_case")]
61+pub enum SessionsShown {
62+ /// Those going now: active or idle, a run queued or running.
63+ Live,
64+ /// Every one, most recently heard from first.
65+ #[default]
66+ Recent,
67+ /// Those that ended, or went quiet long enough to have.
68+ Ended,
69+}
70+
71+impl SessionsShown {
72+ pub fn parse(name: &str) -> Option<SessionsShown> {
73+ match name.trim().to_lowercase().as_str() {
74+ "live" => Some(SessionsShown::Live),
75+ "recent" | "all" => Some(SessionsShown::Recent),
76+ "ended" => Some(SessionsShown::Ended),
77+ _ => None,
78+ }
79+ }
80+
81+ pub fn admits(self, state: SessionState) -> bool {
82+ match self {
83+ SessionsShown::Live => state != SessionState::Ended,
84+ SessionsShown::Recent => true,
85+ SessionsShown::Ended => state == SessionState::Ended,
86+ }
87+ }
88+}
89+
90+/// The state of a session as it is now: one that went quiet past the
91+/// timeout reads as ended, as the next sweep of its repository will make
92+/// it, so a list across a workspace never shows one as live.
93+pub fn settled_state(state: SessionState, last_seen_ms: u64, now_ms: u64) -> SessionState {
94+ if state != SessionState::Ended && now_ms.saturating_sub(last_seen_ms) >= SESSION_TIMEOUT_MS {
95+ SessionState::Ended
96+ } else {
97+ state
98+ }
99+}
100+
101+/// The state of something heard from at `last_ms` with no state of its own
102+/// (a pull request's recorded session): active, idle or ended by how long
103+/// it has been quiet, unless something of it is still running.
104+pub fn quiet_state(running: bool, last_ms: u64, now_ms: u64) -> SessionState {
105+ let quiet = now_ms.saturating_sub(last_ms);
106+ if running || quiet < SESSION_IDLE_MS {
107+ SessionState::Active
108+ } else if quiet < SESSION_TIMEOUT_MS {
109+ SessionState::Idle
110+ } else {
111+ SessionState::Ended
112+ }
113+}
114+
115+/// One session of a workspace, whatever its kind.
116+#[derive(Clone, Debug, Serialize, Deserialize)]
117+#[serde(rename_all = "camelCase")]
118+pub struct WorkspaceSession {
119+ pub kind: SessionKind,
120+ /// `ses_…` for a harness or hosted session, `run_…` for g1t's agent,
121+ /// the pull request's id for one recorded on it.
122+ pub id: String,
123+ /// The repository it is in, `owner/name`.
124+ pub repo: String,
125+ pub repo_id: String,
126+ pub title: Option<String>,
127+ /// Who runs it: whose token started it, who put g1t's agent to work, or
128+ /// who opened the pull request. `g1t` for g1t's own.
129+ pub actor: Option<String>,
130+ /// For a harness or hosted session.
131+ pub harness: Option<Harness>,
132+ /// For g1t's agent and a recorded one: the agent label (`g1t`, `claude-code`).
133+ pub agent: Option<String>,
134+ pub branch: Option<String>,
135+ /// The work item it is on, by number in the workspace.
136+ pub item: Option<u32>,
137+ pub item_title: Option<String>,
138+ pub pull: Option<u32>,
139+ pub pull_title: Option<String>,
140+ pub pull_status: Option<PullStatus>,
141+ pub state: SessionState,
142+ /// RFC 3339.
143+ pub started_at: String,
144+ /// RFC 3339: its last heartbeat, step or entry.
145+ pub last_activity_at: String,
146+ /// How many subagents it started.
147+ pub subagents: u32,
148+ /// For g1t's agent: what it is doing now, in one line.
149+ pub step: Option<String>,
150+ /// For a harness or hosted session: the session itself.
151+ pub session: Option<Session>,
152+ /// With `subagents`: its subagents and theirs, each naming its parent.
153+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
154+ pub children: Vec<Session>,
155+}
156+
157+/// A repository of the workspace the viewer may read.
158+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
159+#[serde(rename_all = "camelCase")]
160+pub struct SessionsRepo {
161+ pub id: String,
162+ /// `owner/name`.
163+ pub path: String,
164+ pub default_branch: String,
165+}
166+
167+/// One repository's merge queue, when something is waiting in it.
168+#[derive(Clone, Debug, Serialize, Deserialize)]
169+#[serde(rename_all = "camelCase")]
170+pub struct RepoQueue {
171+ /// `owner/name`.
172+ pub repo: String,
173+ pub default_branch: String,
174+ #[serde(flatten)]
175+ pub queue: QueueView,
176+}
177+
178+/// A pull request with the repository it is in.
179+#[derive(Clone, Debug, Serialize, Deserialize)]
180+#[serde(rename_all = "camelCase")]
181+pub struct RepoPull {
182+ /// `owner/name`.
183+ pub repo: String,
184+ #[serde(flatten)]
185+ pub pull: Pull,
186+}
187+
188+/// `list_workspace_sessions`'s answer.
189+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
190+#[serde(rename_all = "camelCase")]
191+pub struct WorkspaceSessions {
192+ /// Most recently heard from first; live ones first with `Live`.
193+ pub sessions: Vec<WorkspaceSession>,
194+ /// The merge queues with something waiting, busiest first.
195+ #[serde(default)]
196+ pub queue: Vec<RepoQueue>,
197+ /// The pull requests merged most recently, newest first.
198+ #[serde(default)]
199+ pub landed: Vec<RepoPull>,
200+ /// The repositories looked in: every one of the workspace the viewer
201+ /// may read, or the one asked for.
202+ #[serde(default)]
203+ pub repos: Vec<SessionsRepo>,
204+}
205+
206+/// The most sessions one list gives.
207+pub const MAX_WORKSPACE_SESSIONS: u32 = 200;
208+/// The sessions a list gives when it does not say.
209+pub const DEFAULT_WORKSPACE_SESSIONS: u32 = 60;
210+
211+/// `list_workspace_sessions`: the sessions of every repository of a
212+/// workspace the viewer may read. Returns `Outcome<WorkspaceSessions>`.
213+#[derive(Debug, Default, Serialize, Deserialize)]
214+pub struct ListWorkspaceSessionsArgs {
215+ pub viewer: Viewer,
216+ pub workspace: String,
217+ #[serde(default)]
218+ pub shown: SessionsShown,
219+ #[serde(default)]
220+ pub kind: Option<SessionKind>,
221+ #[serde(default)]
222+ pub harness: Option<Harness>,
223+ /// Only those this person runs, by username.
224+ #[serde(default)]
225+ pub person: Option<String>,
226+ /// Only those in this repository of the workspace, by name.
227+ #[serde(default)]
228+ pub repo: Option<String>,
229+ /// Only those on this work item, by number.
230+ #[serde(default)]
231+ pub item: Option<u32>,
232+ /// Each session's subagents too, in its `children`.
233+ #[serde(default)]
234+ pub subagents: bool,
235+ /// Leave out the merge queues and what landed.
236+ #[serde(default)]
237+ pub sessions_only: bool,
238+ #[serde(default)]
239+ pub limit: Option<u32>,
240+}
241+
242+/// `get_workspace_session`: one harness or hosted session of a workspace by
243+/// its id, in whichever of its repositories it runs. Returns
244+/// `Outcome<WorkspaceSessionDetail>`.
245+#[derive(Debug, Serialize, Deserialize)]
246+pub struct GetWorkspaceSessionArgs {
247+ pub viewer: Viewer,
248+ pub workspace: String,
249+ pub id: String,
250+ #[serde(default)]
251+ pub after_seq: u32,
252+}
253+
254+/// A session and the repository it runs in.
255+#[derive(Clone, Debug, Serialize, Deserialize)]
256+pub struct WorkspaceSessionDetail {
257+ /// `owner/name`.
258+ pub repo: String,
259+ #[serde(flatten)]
260+ pub detail: SessionDetail,
261+}
262+
263+#[cfg(test)]
264+mod tests {
265+ use super::*;
266+
267+ const NOW: u64 = 1_791_800_000_000;
268+
269+ #[test]
270+ fn a_session_started_by_g1t_is_hosted_and_the_rest_are_harnesses() {
271+ assert_eq!(SessionKind::of(Harness::ClaudeManagedAgents), SessionKind::Hosted);
272+ for harness in [Harness::ClaudeCode, Harness::Codex, Harness::Cursor, Harness::Copilot, Harness::Other] {
273+ assert_eq!(SessionKind::of(harness), SessionKind::Harness);
274+ }
275+ for kind in SessionKind::ALL {
276+ assert_eq!(SessionKind::parse(kind.as_str()), Some(kind));
277+ }
278+ assert_eq!(SessionKind::parse("Agent"), Some(SessionKind::Agent));
279+ assert_eq!(SessionKind::parse("bot"), None);
280+ }
281+
282+ #[test]
283+ fn a_quiet_session_settles_as_ended_and_live_shows_only_those_going() {
284+ assert_eq!(settled_state(SessionState::Active, NOW - 60_000, NOW), SessionState::Active);
285+ assert_eq!(settled_state(SessionState::Idle, NOW - SESSION_IDLE_MS, NOW), SessionState::Idle);
286+ assert_eq!(settled_state(SessionState::Idle, NOW - SESSION_TIMEOUT_MS, NOW), SessionState::Ended);
287+ assert!(SessionsShown::Live.admits(SessionState::Idle));
288+ assert!(!SessionsShown::Live.admits(SessionState::Ended));
289+ assert!(SessionsShown::Ended.admits(SessionState::Ended));
290+ assert!(!SessionsShown::Ended.admits(SessionState::Active));
291+ assert!(SessionsShown::Recent.admits(SessionState::Ended));
292+ assert_eq!(SessionsShown::parse("live"), Some(SessionsShown::Live));
293+ assert_eq!(SessionsShown::parse("all"), Some(SessionsShown::Recent));
294+ assert_eq!(SessionsShown::default(), SessionsShown::Recent);
295+ }
296+
297+ #[test]
298+ fn a_recorded_session_reads_its_state_from_how_long_it_has_been_quiet() {
299+ assert_eq!(quiet_state(false, NOW - 1_000, NOW), SessionState::Active);
300+ assert_eq!(quiet_state(false, NOW - SESSION_IDLE_MS, NOW), SessionState::Idle);
301+ assert_eq!(quiet_state(false, NOW - SESSION_TIMEOUT_MS, NOW), SessionState::Ended);
302+ assert_eq!(quiet_state(true, NOW - SESSION_TIMEOUT_MS * 4, NOW), SessionState::Active);
303+ }
304+
305+ #[test]
306+ fn a_detail_carries_its_repository_beside_the_session() {
307+ let detail: WorkspaceSessionDetail = serde_json::from_value(serde_json::json!({
308+ "repo": "acme/web",
309+ "session": {
310+ "id": "ses_1", "repoId": "rep_1", "item": 4, "pull": null, "branch": "main",
311+ "actor": { "id": "usr_1", "username": "ana" }, "harness": "codex", "harnessSessionId": null,
312+ "title": "Greet", "state": "active", "headCommit": null, "startedAt": "t", "lastSeenAt": "t",
313+ "endedAt": null, "endReason": null
314+ },
315+ "entries": []
316+ }))
317+ .unwrap();
318+ assert_eq!(detail.repo, "acme/web");
319+ let back = serde_json::to_value(&detail).unwrap();
320+ assert_eq!(back["repo"], "acme/web");
321+ assert_eq!(back["session"]["id"], "ses_1");
322+ }
323+}
+13−0
11861186 /// List subagents too, under the sessions that started them.
11871187 #[arg(long)]
11881188 pub subagents: bool,
1189+ /// Every repository of a workspace you may read, every kind of session:
1190+ /// this repository's workspace, or the one named.
1191+ #[arg(long, value_name = "SLUG", num_args = 0..=1, default_missing_value = "")]
1192+ pub workspace: Option<String>,
1193+ /// With --workspace: only sessions in this harness (claude_code, codex, …).
1194+ #[arg(long, requires = "workspace")]
1195+ pub harness: Option<String>,
1196+ /// With --workspace: only sessions this person runs, by username.
1197+ #[arg(long, requires = "workspace")]
1198+ pub person: Option<String>,
1199+ /// With --workspace: only sessions in this repository of it, by name.
1200+ #[arg(long = "in", value_name = "NAME", requires = "workspace")]
1201+ pub in_repo: Option<String>,
11891202 }
11901203
11911204 #[derive(Debug, Args)]
+79−0
313313 }
314314
315315 fn list(ctx: &Ctx, args: SessionListArgs) -> Result<()> {
316+ if args.workspace.is_some() {
317+ return list_workspace(ctx, args);
318+ }
316319 let (repo, client) = super::coordination_client(ctx, super::QUICK)?;
317320 let state = args.state.map(|state| match state {
318321 SessionState::Active => "active",
331334 print_list(&ctx.out, &sessions, current().as_deref())
332335 }
333336
337+/// `g1t session list --workspace`: what is cooking across a workspace.
338+fn list_workspace(ctx: &Ctx, args: SessionListArgs) -> Result<()> {
339+ let place = super::work::place(ctx, args.workspace.as_deref())?;
340+ let state = match args.state {
341+ None => "recent",
342+ Some(SessionState::Active | SessionState::Idle) => "live",
343+ Some(SessionState::Ended) => "ended",
344+ };
345+ let mut call = place.client.op("list_workspace_sessions").arg("workspace", &place.workspace).query("state", state).query("limit", args.limit.clamp(1, 200));
346+ if let Some(item) = args.item {
347+ call = call.query("item", item);
348+ }
349+ if let Some(harness) = &args.harness {
350+ call = call.query("harness", harness);
351+ }
352+ if let Some(person) = &args.person {
353+ call = call.query("person", person.trim_start_matches('@'));
354+ }
355+ if let Some(name) = &args.in_repo {
356+ call = call.query("repo", name);
357+ }
358+ if args.subagents {
359+ call = call.query("subagents", true);
360+ }
361+ let found = call.send()?;
362+ if ctx.out.structured() {
363+ return ctx.out.emit(&found);
364+ }
365+ let sessions = found["sessions"].as_array().cloned().unwrap_or_default();
366+ if sessions.is_empty() {
367+ eprintln!("No {} sessions in the {} workspace.", if state == "recent" { "matching" } else { state }, place.workspace);
368+ return Ok(());
369+ }
370+ let mine = current();
371+ let mut rows: Vec<Vec<Cell>> = Vec::new();
372+ for session in &sessions {
373+ let id = s(session, "id");
374+ let marked = if Some(id) == mine.as_deref() { format!("{id} *") } else { id.to_owned() };
375+ let by = match session["harness"].as_str() {
376+ Some(harness) => harness.to_owned(),
377+ None => format!("{} {}", s(session, "kind"), s(session, "agent")).trim().to_owned(),
378+ };
379+ let mut on = s(session, "repo").to_owned();
380+ if let Some(item) = session["item"].as_u64() {
381+ on.push_str(&format!(" #{item}"));
382+ }
383+ if let Some(pull) = session["pull"].as_u64() {
384+ on.push_str(&format!(" PR #{pull}"));
385+ }
386+ rows.push(vec![
387+ Cell::new(marked),
388+ Cell::styled(s(session, "state"), ctx.out.state(s(session, "state"))),
389+ Cell::new(s(session, "actor")),
390+ Cell::new(by),
391+ Cell::new(on),
392+ Cell::styled(ago(s(session, "last_activity_at")), ctx.out.dim(&ago(s(session, "last_activity_at")))),
393+ ]);
394+ for child in session["children"].as_array().into_iter().flatten() {
395+ rows.push(vec![
396+ Cell::new(format!("↳ {} {}", s(child, "id"), s(child, "agent_type")).trim_end().to_owned()),
397+ Cell::styled(s(child, "state"), ctx.out.state(s(child, "state"))),
398+ Cell::new(super::login_of(child)),
399+ Cell::new(s(child, "harness")),
400+ Cell::new(String::new()),
401+ Cell::styled(ago(s(child, "last_seen_at")), ctx.out.dim(&ago(s(child, "last_seen_at")))),
402+ ]);
403+ }
404+ }
405+ ctx.out.table(&["session", "state", "who", "harness", "on", "last seen"], &rows)?;
406+ let waiting: u64 = found["queue"].as_array().into_iter().flatten().map(|queue| queue["active"].as_array().map_or(0, |active| active.len() as u64)).sum();
407+ if waiting > 0 {
408+ eprintln!("{waiting} waiting in merge queues.");
409+ }
410+ Ok(())
411+}
412+
334413 pub fn print_list(out: &Output, sessions: &[Value], mine: Option<&str>) -> Result<()> {
335414 let rows: Vec<Vec<Cell>> = sessions
336415 .iter()
+1−1
255255 Mapping { command: "session log", ops: &["record_session_entries"] },
256256 Mapping { command: "session attach", ops: &["update_session"] },
257257 Mapping { command: "session end", ops: &["end_session"] },
258− Mapping { command: "session list", ops: &["list_sessions"] },
258+ Mapping { command: "session list", ops: &["list_sessions", "list_workspace_sessions"] },
259259 Mapping { command: "session view", ops: &["get_session"] },
260260 Mapping { command: "session updates", ops: &["get_session"] },
261261 Mapping { command: "session acknowledge", ops: &["record_session_entries"] },
+34−0
59695969 body: &[],
59705970 },
59715971 Operation {
5972+ id: "get_workspace_session",
5973+ method: "GET",
5974+ path: "/workspaces/{workspace}/sessions/{id}",
5975+ tag: "Sessions",
5976+ summary: "Get a session of a workspace",
5977+ scope: Some("pull_requests:read"),
5978+ params: &[
5979+ Param { name: "workspace", location: In::Path, kind: "string", required: true, values: &[] },
5980+ Param { name: "id", location: In::Path, kind: "string", required: true, values: &[] },
5981+ Param { name: "after", location: In::Query, kind: "integer", required: false, values: &[] },
5982+ ],
5983+ body: &[],
5984+ },
5985+ Operation {
59725986 id: "list_sessions",
59735987 method: "GET",
59745988 path: "/repos/{owner}/{name}/sessions",
59876001 body: &[],
59886002 },
59896003 Operation {
6004+ id: "list_workspace_sessions",
6005+ method: "GET",
6006+ path: "/workspaces/{workspace}/sessions",
6007+ tag: "Sessions",
6008+ summary: "List a workspace's sessions",
6009+ scope: Some("pull_requests:read"),
6010+ params: &[
6011+ Param { name: "workspace", location: In::Path, kind: "string", required: true, values: &[] },
6012+ Param { name: "state", location: In::Query, kind: "string", required: false, values: &["live", "recent", "ended"] },
6013+ Param { name: "kind", location: In::Query, kind: "string", required: false, values: &["harness", "hosted", "agent", "recorded"] },
6014+ Param { name: "harness", location: In::Query, kind: "string", required: false, values: &["claude_code", "claude_managed_agents", "codex", "cursor", "copilot", "other"] },
6015+ Param { name: "person", location: In::Query, kind: "string", required: false, values: &[] },
6016+ Param { name: "repo", location: In::Query, kind: "string", required: false, values: &[] },
6017+ Param { name: "item", location: In::Query, kind: "integer", required: false, values: &[] },
6018+ Param { name: "subagents", location: In::Query, kind: "boolean", required: false, values: &[] },
6019+ Param { name: "limit", location: In::Query, kind: "integer", required: false, values: &[] },
6020+ ],
6021+ body: &[],
6022+ },
6023+ Operation {
59906024 id: "read_session",
59916025 method: "GET",
59926026 path: "/repos/{owner}/{name}/pulls/{number}/session",
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…

Comments

Sign in to comment.