Merge branch 'main' of https://g1t.sh/flagon-io/g1t
53 files+261−637 files not counted0/53 viewed
| 7 | 7 | "name": "g1t", | |
| 8 | 8 | "source": "./plugins/claude-code", | |
| 9 | 9 | "description": "Work on g1t issues from Claude Code: records your sessions on g1t, loads an issue's work context, claims and hands off work, and connects g1t's MCP server.", | |
| 10 | − | "version": "0.2.2", | |
| 10 | + | "version": "0.2.3", | |
| 11 | 11 | "category": "development" | |
| 12 | 12 | } | |
| 13 | 13 | ] |
| 72 | 72 | key: cli-release-${{ runner.os }}-${{ steps.keys.outputs.zigbuild }}-${{ steps.keys.outputs.cargo }} | |
| 73 | 73 | restore-keys: cli-release-${{ runner.os }}-${{ steps.keys.outputs.zigbuild }}- | |
| 74 | 74 | - name: Build | |
| 75 | + | env: | |
| 76 | + | # Built in, so `g1t upgrade` checks what it downloads. | |
| 77 | + | G1T_RUNNER_RELEASE_KEY: ${{ vars.RUNNER_RELEASE_PUBLIC_KEY }} | |
| 75 | 78 | run: node scripts/cli-release.mjs build | |
| 76 | 79 | - uses: actions/upload-artifact@v4 | |
| 77 | 80 | with: |
| 1320 | 1320 | ||
| 1321 | 1321 | [[package]] | |
| 1322 | 1322 | name = "g1t" | |
| 1323 | − | version = "0.3.3" | |
| 1323 | + | version = "0.3.4" | |
| 1324 | 1324 | dependencies = [ | |
| 1325 | 1325 | "anstyle-query", | |
| 1326 | 1326 | "base64 0.22.1", | |
| 1327 | 1327 | "clap", | |
| 1328 | 1328 | "clap_complete", | |
| 1329 | + | "ed25519-dalek", | |
| 1329 | 1330 | "flate2", | |
| 1330 | 1331 | "hex", | |
| 1331 | 1332 | "jaq-core", |
| 506 | 506 | actor: actor(), | |
| 507 | 507 | repo, | |
| 508 | 508 | issue, | |
| 509 | + | pull: None, | |
| 510 | + | asked: None, | |
| 509 | 511 | title: text(input, "title"), | |
| 510 | 512 | body: text(input, "body"), | |
| 511 | 513 | instruction: text(input, "instruction"), |
| 359 | 359 | Op::AddComment, | |
| 360 | 360 | Op::EditComment, | |
| 361 | 361 | Op::DeleteComment, | |
| 362 | + | Op::React, | |
| 362 | 363 | Op::ListIssueLabels, | |
| 363 | 364 | Op::AddIssueLabels, | |
| 364 | 365 | Op::SetIssueLabels, | |
| ⋯ | |||
| 734 | 735 | Op::AddComment => "Add a comment", | |
| 735 | 736 | Op::EditComment => "Edit a comment", | |
| 736 | 737 | Op::DeleteComment => "Delete a comment", | |
| 738 | + | Op::React => "React with an emoji", | |
| 737 | 739 | Op::ReviewPullRequest => "Review a pull request", | |
| 738 | 740 | Op::ListPullRequests => "List pull requests", | |
| 739 | 741 | Op::GetPullRequest => "Get a pull request", | |
| 190 | 190 | AddComment, | |
| 191 | 191 | EditComment, | |
| 192 | 192 | DeleteComment, | |
| 193 | + | React, | |
| 193 | 194 | ReviewPullRequest, | |
| 194 | 195 | ListPullRequests, | |
| 195 | 196 | GetPullRequest, | |
| ⋯ | |||
| 811 | 812 | } | |
| 812 | 813 | ||
| 813 | 814 | impl Op { | |
| 814 | − | pub const ALL: [Op; 405] = [ | |
| 815 | + | pub const ALL: [Op; 406] = [ | |
| 815 | 816 | Op::Whoami, | |
| 816 | 817 | Op::GetWorkspace, | |
| 817 | 818 | Op::CreateWorkspace, | |
| ⋯ | |||
| 885 | 886 | Op::AddComment, | |
| 886 | 887 | Op::EditComment, | |
| 887 | 888 | Op::DeleteComment, | |
| 889 | + | Op::React, | |
| 888 | 890 | Op::ReviewPullRequest, | |
| 889 | 891 | Op::ListPullRequests, | |
| 890 | 892 | Op::GetPullRequest, | |
| ⋯ | |||
| 1299 | 1301 | Op::AddComment => "add_comment", | |
| 1300 | 1302 | Op::EditComment => "edit_comment", | |
| 1301 | 1303 | Op::DeleteComment => "delete_comment", | |
| 1304 | + | Op::React => "react", | |
| 1302 | 1305 | Op::ReviewPullRequest => "review_pull_request", | |
| 1303 | 1306 | Op::ListPullRequests => "list_pull_requests", | |
| 1304 | 1307 | Op::GetPullRequest => "get_pull_request", | |
| ⋯ | |||
| 1663 | 1666 | Op::DeleteComment => { | |
| 1664 | 1667 | "Delete a comment on an issue or a pull request, named by comment_id. Its author may delete it, and so may anyone with the Maintain role or higher. A review that approved or requested changes cannot be deleted, only edited, and notes of what happened cannot be deleted. This cannot be undone. Publishes comment.deleted with the comment as it was." | |
| 1665 | 1668 | } | |
| 1669 | + | Op::React => { | |
| 1670 | + | "React to an issue or a pull request with an emoji: to its description, or with comment_id to one of its comments. content is one of +1, -1, laugh, hooray, confused, heart, rocket and eyes; remove true takes your reaction back. Anyone who can read the repository may. Answers with the reactions it has now, each with its count and the usernames of who reacted (`g1t` for g1t, which reacts eyes to what it was asked to take on). get_issue and get_pull_request give each comment's reactions, and the description's as reactions." | |
| 1671 | + | } | |
| 1666 | 1672 | Op::ReviewPullRequest => { | |
| 1667 | 1673 | "Give a verdict on a pull request: approve it, or request changes and say what. Read get_pull_request_changes first. You cannot review a pull request you opened, or one g1t made for you (you are its requested_by)." | |
| 1668 | 1674 | } | |
| ⋯ | |||
| 2716 | 2722 | json!({ "repo": repo_schema(), "comment_id": comment_id_schema() }), | |
| 2717 | 2723 | &["repo", "comment_id"], | |
| 2718 | 2724 | ), | |
| 2725 | + | Op::React => object( | |
| 2726 | + | numbered(json!({ | |
| 2727 | + | "content": { | |
| 2728 | + | "type": "string", | |
| 2729 | + | "enum": ["+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes"], | |
| 2730 | + | }, | |
| 2731 | + | "comment_id": { | |
| 2732 | + | "type": "string", | |
| 2733 | + | "description": "A comment on it, to react to that comment rather than to its description.", | |
| 2734 | + | }, | |
| 2735 | + | "remove": { "type": "boolean", "description": "Take your reaction back." }, | |
| 2736 | + | })), | |
| 2737 | + | &["repo", "number", "content"], | |
| 2738 | + | ), | |
| 2719 | 2739 | Op::ReviewPullRequest => object( | |
| 2720 | 2740 | numbered(json!({ | |
| 2721 | 2741 | "verdict": { "type": "string", "enum": ["approve", "request_changes"] }, | |
| ⋯ | |||
| 5238 | 5258 | Op::ClosePullRequest => pass(work, "close_pull", &pull_action()).await, | |
| 5239 | 5259 | Op::ReopenPullRequest => pass(work, "reopen_pull", &pull_action()).await, | |
| 5240 | 5260 | Op::ConvertPullRequestToDraft => pass(work, "convert_pull_to_draft", &pull_action()).await, | |
| 5261 | + | Op::React => { | |
| 5262 | + | let Some(content) = input["content"].as_str().and_then(ReactionContent::parse) else { | |
| 5263 | + | return failed(FailureCode::Invalid, "content must be one of +1, -1, laugh, hooray, confused, heart, rocket and eyes."); | |
| 5264 | + | }; | |
| 5265 | + | let asked = ReactArgs { | |
| 5266 | + | actor: actor(), | |
| 5267 | + | repo, | |
| 5268 | + | number, | |
| 5269 | + | comment_id: optional_text(input, "comment_id"), | |
| 5270 | + | content, | |
| 5271 | + | remove: input["remove"].as_bool().unwrap_or(false), | |
| 5272 | + | }; | |
| 5273 | + | pass(work, "react", &asked).await | |
| 5274 | + | } | |
| 5241 | 5275 | Op::EditComment | Op::DeleteComment => { | |
| 5242 | 5276 | let asked = CommentActionArgs { | |
| 5243 | 5277 | actor: actor(), | |
| 1604 | 1604 | }, | |
| 1605 | 1605 | "notes": "Answers `204` with no body. Its author, or someone with the Maintain role or higher. A review that approved or requested changes answers `409`: edit it instead." | |
| 1606 | 1606 | }, | |
| 1607 | + | "react": { | |
| 1608 | + | "params": { | |
| 1609 | + | "number": 12, | |
| 1610 | + | "comment_id": "cmt_01m43sv4c8e2g6j0m4q8t2x6a1", | |
| 1611 | + | "content": "heart" | |
| 1612 | + | }, | |
| 1613 | + | "response": [ | |
| 1614 | + | { "content": "heart", "count": 1, "users": ["ana"] }, | |
| 1615 | + | { "content": "eyes", "count": 1, "users": ["g1t"] } | |
| 1616 | + | ], | |
| 1617 | + | "notes": "Reacting twice the same way is one reaction. Without comment_id it reacts to the issue's or pull request's description." | |
| 1618 | + | }, | |
| 1607 | 1619 | "list_labels": { | |
| 1608 | 1620 | "response": [ | |
| 1609 | 1621 | { |
| 1046 | 1046 | ), | |
| 1047 | 1047 | route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]), | |
| 1048 | 1048 | route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]), | |
| 1049 | + | route("POST", "/repos/:owner/:name/issues/:number/reactions", Op::React, &[]), | |
| 1049 | 1050 | route( | |
| 1050 | 1051 | "GET", | |
| 1051 | 1052 | "/repos/:owner/:name/pulls", | |
| ⋯ | |||
| 1579 | 1580 | assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" })); | |
| 1580 | 1581 | // Still an issue's comments, labels and subscription. | |
| 1581 | 1582 | assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment); | |
| 1583 | + | assert_eq!(op("POST", "/repos/acme/web/issues/7/reactions"), Op::React); | |
| 1582 | 1584 | assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels); | |
| 1583 | 1585 | assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription); | |
| 1584 | 1586 | } | |
| 165 | 165 | a("comment", Op::AddComment, "Comment on an issue or pull request; path and line for one line of a change"), | |
| 166 | 166 | a("edit_comment", Op::EditComment, "Change a comment's text: your own, or any with the Maintain role"), | |
| 167 | 167 | a("delete_comment", Op::DeleteComment, "Delete a comment: your own, or any with the Maintain role"), | |
| 168 | + | a("react", Op::React, "React with an emoji to it, or with comment_id to one of its comments; remove takes it back"), | |
| 168 | 169 | a("import", Op::ImportIssue, "Open an issue from a Jira, Linear or Sentry item"), | |
| 169 | 170 | a("work", Op::Coordination(CoordinationOp::GetWork), "Everything to pick it up: done means, claims, sessions, pull requests, blockers, decisions, recent events"), | |
| 170 | 171 | a("claim", Op::Coordination(CoordinationOp::ClaimIssue), "Say you are working on it; lists who else is, and what it is blocked by"), |
| 250 | 250 | Claude Code says so once. The plugin never installs anything itself. | |
| 251 | 251 | Install the CLI or run `g1t auth login`, and the next session is recorded. | |
| 252 | 252 | ||
| 253 | − | To update the CLI, run its installer again: | |
| 253 | + | To update the CLI, run `g1t upgrade` (0.3.4 and later). For an older CLI, | |
| 254 | + | run its installer again: | |
| 254 | 255 | ||
| 255 | 256 | ```sh | |
| 256 | 257 | curl -fsSL https://g1t.sh/install | sh | |
| ⋯ | |||
| 260 | 261 | which you have. A CLI that refuses a command the hooks send is taken as | |
| 261 | 262 | out of date, and said the same way. | |
| 262 | 263 | ||
| 264 | + | A CLI older than 0.3.3 records sessions but cannot acknowledge | |
| 265 | + | [coordination updates](#working-on-g1t). The hooks then show | |
| 266 | + | pending updates only when they change, not before every prompt, and ask | |
| 267 | + | Claude to tell you once to update the CLI. | |
| 268 | + | ||
| 263 | 269 | The hooks run on Node. Without Node 18 or later they record nothing: | |
| 264 | 270 | with an older Node, Claude Code says so once; with no `node` on your | |
| 265 | 271 | `PATH` at all, Claude Code shows each hook's failure (`node: command not | |
| 38 | 38 | release's `SHA256SUMS`, and puts `g1t` in `~/.local/bin` (Windows: | |
| 39 | 39 | `%LOCALAPPDATA%\g1t\bin`, which it adds to your `PATH`). Set | |
| 40 | 40 | `G1T_INSTALL_DIR` to put it elsewhere, or `G1T_VERSION` to install a | |
| 41 | − | version other than the latest. Run it again to update. | |
| 41 | + | version other than the latest. | |
| 42 | + | ||
| 43 | + | ### Upgrade | |
| 44 | + | ||
| 45 | + | From 0.3.4 on, `g1t upgrade` updates g1t to the newest release: | |
| 46 | + | ||
| 47 | + | ```sh | |
| 48 | + | g1t upgrade # download and install the newest release | |
| 49 | + | g1t upgrade --check # only say whether there is one | |
| 50 | + | ``` | |
| 51 | + | ||
| 52 | + | It reads the newest release's manifest, `latest.json`, and checks its | |
| 53 | + | signature (`latest.json.sig`) against g1t's release key, which is built | |
| 54 | + | into every release. It then downloads the build for your system, checks | |
| 55 | + | its SHA-256 against the manifest, and puts it where the running `g1t` is. | |
| 56 | + | If anything does not check out, nothing changes. Releases come from | |
| 57 | + | `https://g1t.sh/downloads/cli`, or from `G1T_DOWNLOAD_URL` if you set it. | |
| 58 | + | ||
| 59 | + | In a terminal, g1t looks for a newer release about once a day, in the | |
| 60 | + | background, and says so after a command when there is one. It never looks | |
| 61 | + | in CI or when its output is not a terminal. Set `G1T_NO_UPDATE_NOTIFIER=1` | |
| 62 | + | to turn it off. | |
| 63 | + | ||
| 64 | + | A g1t built from source has no release key and cannot upgrade itself. | |
| 65 | + | Neither can versions before 0.3.4. Update those by running the install | |
| 66 | + | script again. | |
| 42 | 67 | ||
| 43 | 68 | ### Without the script | |
| 44 | 69 |
| 182 | 182 | ||
| 183 | 183 | ## Sessions | |
| 184 | 184 | ||
| 185 | + | Each session that works on a pull request, or on its issue, shows in the | |
| 186 | + | conversation as a card where it started, the way a comment or a review | |
| 187 | + | does: who it is (or g1t, working for someone), where it stands, its | |
| 188 | + | branch, and its report once it ends, linked to its page. Mention `@g1t` | |
| 189 | + | in a comment to have g1t carry on with the pull request; see | |
| 190 | + | [assign g1t, or ask it in the thread](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread). | |
| 191 | + | ||
| 185 | 192 | A coding agent that works on a pull request can record how it made the | |
| 186 | 193 | change, entry by entry, with the `pull_request` tool's `record_session` | |
| 187 | 194 | action. Read it on the pull request's **Session** tab, or with | |
| ⋯ | |||
| 293 | 300 | `changes.body.from`; deleting publishes `comment.deleted`, with the comment | |
| 294 | 301 | as it was in `comment`. | |
| 295 | 302 | ||
| 303 | + | ## Reactions | |
| 304 | + | ||
| 305 | + | React to an issue's or pull request's description, or to any comment on | |
| 306 | + | it, with one of eight emoji: 👍 👎 😄 🎉 😕 ❤️ 🚀 👀. Select the smiley | |
| 307 | + | under it, or a reaction already there to add or take back yours; hovering | |
| 308 | + | one says who reacted. Anyone who can read the repository may react. g1t | |
| 309 | + | reacts 👀 to what it was asked to take on. | |
| 310 | + | ||
| 311 | + | Through the API, `POST /repos/{owner}/{name}/issues/{number}/reactions` | |
| 312 | + | with `content` (`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, | |
| 313 | + | `rocket` or `eyes`), and `comment_id` to react to a comment rather than | |
| 314 | + | the description, reacts and answers with the reactions it has now; | |
| 315 | + | `remove: true` takes yours back. On the MCP server it is the `issue` | |
| 316 | + | tool's `react` action. Each comment's `reactions`, and the description's, | |
| 317 | + | are in `GET /repos/{owner}/{name}/issues/{number}` and | |
| 318 | + | `GET /repos/{owner}/{name}/pulls/{number}`: each with its `content`, | |
| 319 | + | `count` and the `users` who reacted. | |
| 320 | + | ||
| 296 | 321 | ## Commit comments | |
| 297 | 322 | ||
| 298 | 323 | You can comment on any commit: on the whole commit, under its changes on | |
| 313 | 313 | result. Whoever started it can stop it from its page. It runs on your | |
| 314 | 314 | workspace's key and Anthropic bills the account for it; g1t bills nothing. | |
| 315 | 315 | ||
| 316 | + | ## Assign g1t, or ask it in the thread | |
| 317 | + | ||
| 318 | + | You can also hand work to g1t from the issue or pull request itself, the | |
| 319 | + | way you would ask a teammate: | |
| 320 | + | ||
| 321 | + | - **Assign the issue to `g1t`**: **Assign to g1t** in the issue's | |
| 322 | + | sidebar, `g1t` among the assignees in | |
| 323 | + | [`update_issue`](/reference/api/issues/update-issue/), or | |
| 324 | + | `g1t issue edit 12 --add-assignee g1t`. | |
| 325 | + | - **Mention `@g1t` in a comment** on an issue or a pull request: | |
| 326 | + | "@g1t the button still overlaps on a phone, fix that". On a pull | |
| 327 | + | request, the session carries on with that pull request on its branch. | |
| 328 | + | On an issue, it takes the issue on. | |
| 329 | + | ||
| 330 | + | g1t reacts 👀 to what asked it, the comment or (for an assignment) the | |
| 331 | + | issue, then starts a session on the workspace's Anthropic account, as | |
| 332 | + | the person who asked, with what they wrote as its instruction. Each | |
| 333 | + | session shows in the issue's or pull request's conversation as a card, | |
| 334 | + | where it started, the way a comment or a review does: who it works for, | |
| 335 | + | where it stands, its branch, and its report when it ends, linked to its | |
| 336 | + | page. Not happy with what a session did? Say so with `@g1t` in the same | |
| 337 | + | thread. The next session is told to fix the work on the same branch and | |
| 338 | + | pull request, not start over, and `g1t work context` shows it every | |
| 339 | + | session and pull request so far. If the session you started on that | |
| 340 | + | thread is still going, your comment goes to it instead of starting | |
| 341 | + | another. | |
| 342 | + | ||
| 343 | + | Only the workspace's members can ask g1t; anyone else's `@g1t` is an | |
| 344 | + | ordinary mention. A mention in quoted text or code (`` `@g1t` ``) asks | |
| 345 | + | nothing. g1t answers one thread at most six times an hour. When it cannot | |
| 346 | + | start a session, for instance because the workspace has no Anthropic AI | |
| 347 | + | provider, it says why in the thread. On an update pull request g1t opened, | |
| 348 | + | `@g1t rebase` and the other | |
| 349 | + | [comment commands](/guides/dependency-updates/#comment-commands) still do what | |
| 350 | + | they say. | |
| 351 | + | ||
| 316 | 352 | ## Claims | |
| 317 | 353 | ||
| 318 | 354 | A claim says you are working on an issue. It is advisory: anyone may claim |
| 1466 | 1466 | The token is the first of: --token-stdin, G1T_TOKEN, the account `g1t auth login` | |
| 1467 | 1467 | signed in for the registry's host, or what `docker login g1t.sh` stored. | |
| 1468 | 1468 | ``` | |
| 1469 | + | ||
| 1470 | + | ## g1t upgrade | |
| 1471 | + | ||
| 1472 | + | Update g1t to the newest release, checked against g1t's release key. | |
| 1473 | + | ||
| 1474 | + | ```text | |
| 1475 | + | g1t upgrade [OPTIONS] | |
| 1476 | + | ``` | |
| 1477 | + | ||
| 1478 | + | | Argument or flag | | | |
| 1479 | + | | --- | --- | | |
| 1480 | + | | `--check` | Only say whether a newer release is out. | | |
| 1481 | + | ||
| 1482 | + | ```text | |
| 1483 | + | Downloads the newest release for this machine from g1t.sh, checks its signature and SHA-256, and puts it | |
| 1484 | + | where this g1t is. In a terminal, g1t also says once a day when a newer release is out; | |
| 1485 | + | G1T_NO_UPDATE_NOTIFIER=1 turns that off. | |
| 1486 | + | ``` |
Binary or large file; its contents are not shown.
| 77 | 77 | import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; | |
| 78 | 78 | import { RadioGroup, RadioOption } from "./ui/radio-group"; | |
| 79 | 79 | import { Textarea } from "./ui/textarea"; | |
| 80 | − | import { PersonLink } from "./work"; | |
| 80 | + | import { Markdown } from "./markdown"; | |
| 81 | + | import { PersonLink, TimelineItem } from "./work"; | |
| 82 | + | import { repoAt } from "../lib/markdown-plugins"; | |
| 81 | 83 | import { IssueIcon, PullIcon } from "./work-icons"; | |
| 82 | 84 | ||
| 83 | 85 | /** Text with each `#N` linked to that issue or pull request (the issue's address leads to a pull request too). */ | |
| ⋯ | |||
| 223 | 225 | ); | |
| 224 | 226 | } | |
| 225 | 227 | ||
| 228 | + | /** | |
| 229 | + | * A session in an issue's or pull request's conversation, where it | |
| 230 | + | * started, as a card like a comment or a review: g1t working for someone | |
| 231 | + | * (a session g1t started on the workspace's Claude, when it was assigned | |
| 232 | + | * the issue or asked in the thread), or someone working in their own | |
| 233 | + | * harness. What it is about, where it stands now, its branch, and what it | |
| 234 | + | * reported once it ended, linked to its page. | |
| 235 | + | */ | |
| 236 | + | export function TimelineSession({ session, base }: { session: WorkSession; base: string }) { | |
| 237 | + | const g1t = session.harness === "claude_managed_agents"; | |
| 238 | + | const ended = session.state === "ended"; | |
| 239 | + | return ( | |
| 240 | + | <TimelineItem | |
| 241 | + | id={session.id} | |
| 242 | + | author={g1t ? "g1t" : session.actor.username} | |
| 243 | + | bot={g1t} | |
| 244 | + | at={session.startedAt} | |
| 245 | + | action={ | |
| 246 | + | <> | |
| 247 | + | {g1t ? ( | |
| 248 | + | <span> | |
| 249 | + | {ended ? "worked on this for" : "is working on this for"}{" "} | |
| 250 | + | <PersonLink name={session.actor.username} className="font-medium text-fg hover:underline"> | |
| 251 | + | @{session.actor.username} | |
| 252 | + | </PersonLink> | |
| 253 | + | </span> | |
| 254 | + | ) : ( | |
| 255 | + | <span>{ended ? "worked on this in a session" : "is working on this in a session"}</span> | |
| 256 | + | )} | |
| 257 | + | <HarnessBadge harness={session.harness} /> | |
| 258 | + | <SessionStateBadge state={session.state} /> | |
| 259 | + | </> | |
| 260 | + | } | |
| 261 | + | aside={ | |
| 262 | + | <Link | |
| 263 | + | to={`${base}/work/sessions/${session.id}`} | |
| 264 | + | prefetch="intent" | |
| 265 | + | className="inline-flex items-center gap-1 text-xs text-faint hover:text-fg" | |
| 266 | + | > | |
| 267 | + | <Activity size={12} /> | |
| 268 | + | View session | |
| 269 | + | </Link> | |
| 270 | + | } | |
| 271 | + | > | |
| 272 | + | <Link to={`${base}/work/sessions/${session.id}`} prefetch="intent" className="font-medium hover:text-accent"> | |
| 273 | + | {sessionTitle(session)} | |
| 274 | + | </Link> | |
| 275 | + | <p className="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted"> | |
| 276 | + | {ended ? ( | |
| 277 | + | <span> | |
| 278 | + | Ended <TimeAgo at={session.endedAt ?? session.lastSeenAt} /> | |
| 279 | + | </span> | |
| 280 | + | ) : ( | |
| 281 | + | <span> | |
| 282 | + | Last heard from <TimeAgo at={session.lastSeenAt} /> | |
| 283 | + | </span> | |
| 284 | + | )} | |
| 285 | + | {session.branch && ( | |
| 286 | + | <span className="inline-flex min-w-0 items-center gap-1 font-mono"> | |
| 287 | + | · <GitBranch size={11} /> | |
| 288 | + | <span className="truncate">{session.branch}</span> | |
| 289 | + | </span> | |
| 290 | + | )} | |
| 291 | + | {session.pull != null && ( | |
| 292 | + | <span> | |
| 293 | + | · on{" "} | |
| 294 | + | <Link to={`${base}/pull/${session.pull}`} className="text-fg hover:text-accent"> | |
| 295 | + | #{session.pull} | |
| 296 | + | </Link> | |
| 297 | + | </span> | |
| 298 | + | )} | |
| 299 | + | {(session.subagents ?? 0) > 0 && ( | |
| 300 | + | <span> | |
| 301 | + | · {session.subagents} {session.subagents === 1 ? "subagent" : "subagents"} | |
| 302 | + | </span> | |
| 303 | + | )} | |
| 304 | + | </p> | |
| 305 | + | {ended && session.result && ( | |
| 306 | + | <div className="mt-3 border-t border-line pt-3 text-sm"> | |
| 307 | + | <Markdown source={session.result} repo={repoAt(base)} /> | |
| 308 | + | </div> | |
| 309 | + | )} | |
| 310 | + | </TimelineItem> | |
| 311 | + | ); | |
| 312 | + | } | |
| 313 | + | ||
| 226 | 314 | /** A subagent in a line: its kind, its task, where it stands, linked to its page. */ | |
| 227 | 315 | export function SubagentLine({ session, base }: { session: WorkSession; base: string }) { | |
| 228 | 316 | return ( | |
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
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.