Skip to content

Commit

The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.

A workspace's owners can still connect, test, change and remove its AI providers (Anthropic, OpenAI, Google, Azure, an endpoint of your own and the rest), on Integrations under "Your AI providers": keys are sealed when saved, and testing one asks the provider for its models with the workspace's key. They are kept so g1t can hand work to them later; nothing routes through g1t. The top bar has an Ask AI button whose composer says it is coming soon and sends nothing. The Sessions pages for what coding agents recorded keep working.

syntaqxcommitted Parent81a8393Browse files
131 files+297−211115 files not counted0/131 viewed
+2−2
410410 { "day": "2026-10-01", "product": "other", "micros": 8_000_000 },
411411 ],
412412 "products": [
413− { "key": "other", "label": "Other", "micros": 12_000_000, "features": [],
413+ { "key": "other", "label": "Other", "micros": 12_000_000,
414414 "meters": [{ "key": "other", "label": "Other", "product": "other", "unit": "entries", "quantity": 3.0,
415415 "micros": 12_000_000, "pendingMicros": 0, "daily": [8_000_000, 4_000_000], "allowance": null,
416416 "byProject": [{ "project": "acme/web", "micros": 9_000_000, "quantity": 2.0 },
417417 { "project": "", "micros": 3_000_000, "quantity": 1.0 }] }] },
418− { "key": "sandboxes", "label": "Sandboxes", "micros": 500_000, "features": [],
418+ { "key": "sandboxes", "label": "Sandboxes", "micros": 500_000,
419419 "meters": [{ "key": "sandbox", "label": "Sandbox time", "product": "sandboxes", "unit": "seconds", "quantity": 600.0,
420420 "micros": 500_000, "pendingMicros": 0, "daily": [0, 500_000],
421421 "allowance": { "used": 600.0, "of": 3600.0, "unit": "seconds" },
+2−2
55 //! thing, brought back to the top as things happen to it.
66 //!
77 //! Every operation here is the person's own: a personal access token or a
8−//! session, never a workspace's token or g1t's agents (which act as g1t,
9−//! and g1t is never told anything).
8+//! session, never a workspace's token or a run's token (which acts as
9+//! g1t, and g1t is never told anything).
1010
1111 use g1t_contracts::inbox::*;
1212 use g1t_contracts::repos::{GetArgs, Repo, RepoPath};
+1−1
536536 ),
537537 (
538538 "Integrations",
539− "A workspace's connections to outside systems: alert sources and issue trackers.",
539+ "A workspace's connections to outside systems: AI providers, alert sources and issue trackers.",
540540 &[
541541 Op::ListIntegrations,
542542 Op::ConnectIntegration,
+6−7
14671467 "The timeline of a repository: pushes, issues, pull requests, comments and session activity, newest first."
14681468 }
14691469 Op::ListIntegrations => {
1470− "A workspace's integrations: the alert sources that open issues (Sentry, Datadog, webhooks), and the trackers whose tickets agents can read (Jira, Linear). Secrets are never returned. Members only."
1470+ "A workspace's integrations: its AI providers, the alert sources that open issues (Sentry, Datadog, webhooks), and the trackers whose tickets agents can read (Jira, Linear). Secrets are never returned. Members only."
14711471 }
14721472 Op::ConnectIntegration => {
1473− "Connect a workspace to an outside system. provider is sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token, kept encrypted and never returned (secret_hint shows its last four characters). For datadog and webhook, g1t makes the signing secret and returns it once. Owners only."
1473+ "Connect a workspace to an outside system. provider is an AI provider (anthropic, openai, gemini, xai, mistral, deepseek, azure_openai, openrouter, groq, together, fireworks, cerebras, anthropic_endpoint or openai_endpoint), which stores your AI provider credentials for the workspace, or sentry, datadog, webhook, jira or linear. config holds the settings each needs; secret is the API key or token, kept encrypted and never returned (secret_hint shows its last four characters). An AI provider's key is checked as it is saved. For datadog and webhook, g1t makes the signing secret and returns it once. Owners only."
14741474 }
14751475 Op::UpdateIntegration => {
1476− "Change an integration: its name, its config (replaced whole when given) or its secret (a new key replaces the old one, write-only). Use it to rotate a key. Fields left out are kept. Secrets are never returned. Owners only."
1476+ "Change an integration: its name, its config (replaced whole when given) or its secret (a new key replaces the old one, write-only). Use it to rotate a key, such as an AI provider's. Fields left out are kept. Secrets are never returned. Owners only."
14771477 }
14781478 Op::DisconnectIntegration => {
14791479 "Remove an integration and its secrets. Owners only."
17071707 "Choose what happens when a team is asked to review a pull request. Off, everyone in it is asked. On (`enabled`), g1t picks `count` people from it (1 to 10, never the pull request's author) and asks them, and the team stays shown as asked beside them: `round_robin` picks whoever this team asked least recently, `load_balance` whoever has the fewest pull requests waiting on their review. `skip_busy` leaves out anyone with `busy_at` or more waiting; `include_child_teams` also picks from its child teams' people; `excluded` lists usernames never picked; `notify_team` also tells the rest of the team. Fields left out keep their current value. Owners of the workspace and the team's maintainers. People only. Returns the team."
17081708 }
17091709 Op::GetUsage => {
1710− "A workspace's usage over a range of days, at price, and what paid for it. `from` and `until` are UTC days, `YYYY-MM-DD`, with `until` included and at most 400 days in all; left out, the current month so far. `products` narrows it to product families (sandboxes, deployments, git_storage, packages, security, search, other) and `projects` to repositories (\"owner/name\"). Returns `totals`: `price_micros` less `discount_micros`, `included_micros` and `credits_micros` is `charged_micros`, what is left for the workspace to pay; `pending_micros` is metered this month and charged when it closes, counted in any range that reaches into the current month, and `pending_charged_micros` is what of it will be charged then, after the discount and what g1t covers; `cost_micros` is what it cost g1t. Then `days` (each day and product with usage), `products` (every family, with its meters: quantity, unit, amount, a `daily` amount for each day of the range, any `allowance`, and the split `by_project`), `projects` (every repository with usage in the range), and the credit left now: `credit_micros`, and `ai_credit_micros` for AI credit bought earlier, which still pays toward what is owed. Usage on a meter g1t no longer has, such as agent runs and model requests from before they were retired, is kept on one meter in a last product, `other`, shown only when it has something, so nothing charged is left out. `models`, `by_agent` and `by_person` are always empty, and each product's `features` too. With `group_by` (`product`, `project` or `day`), `groups` adds up the range that way. Amounts are whole millionths of a dollar. Members of the workspace only."
1710+ "A workspace's usage over a range of days, at price, and what paid for it. `from` and `until` are UTC days, `YYYY-MM-DD`, with `until` included and at most 400 days in all; left out, the current month so far. `products` narrows it to product families (sandboxes, deployments, git_storage, packages, security, search, other) and `projects` to repositories (\"owner/name\"). Returns `totals`: `price_micros` less `discount_micros`, `included_micros` and `credits_micros` is `charged_micros`, what is left for the workspace to pay; `pending_micros` is metered this month and charged when it closes, counted in any range that reaches into the current month, and `pending_charged_micros` is what of it will be charged then, after the discount and what g1t covers; `cost_micros` is what it cost g1t. Then `days` (each day and product with usage), `products` (every family, with its meters: quantity, unit, amount, a `daily` amount for each day of the range, any `allowance`, and the split `by_project`), `projects` (every repository with usage in the range), and the credit left now: `credit_micros`, and `ai_credit_micros` for AI credit bought earlier, which still pays toward what is owed. Usage on a meter g1t no longer has, such as agent runs and model requests from before they were retired, is kept on one meter in a last product, `other`, shown only when it has something, so nothing charged is left out. With `group_by` (`product`, `project` or `day`), `groups` adds up the range that way. Amounts are whole millionths of a dollar. Members of the workspace only."
17111711 }
17121712 Op::GetBudget => {
17131713 "A workspace's budget: its monthly spend limit (`amount_micros`; `automatic` is true while the owners have not set one, and it is then $200 or twice last month's spend), what was charged this month (`spent_micros`), the most the owners may set it to themselves (`max_amount_micros`), its `alerts` (percent of the limit, each emailed to the owners once a month), whether usage pauses at the limit (`pause_at_limit`), the `webhook` told of each alert, and `state`: `ok`, `warning` or `stopped`, with a `message` when work is stopped or close to it. Members of the workspace only."
17141714 }
17151715 Op::SetBudget => {
1716− "Change a workspace's budget. Give only what you change; the rest stays as it is. `amount_micros` is the monthly spend limit, up to `max_amount_micros`, or null for the automatic one. `alerts` is some of 50, 75, 90 and 100, in percent of the limit. `pause_at_limit` false makes the limit alert only, without pausing usage; g1t's own ceiling still applies. `webhook` is an https:// address sent a JSON POST for each alert, or null for none. Owners only, as a person: signed in or with a personal access token. A workspace's own token and g1t's agents can read the budget but never change it. Returns the budget."
1716+ "Change a workspace's budget. Give only what you change; the rest stays as it is. `amount_micros` is the monthly spend limit, up to `max_amount_micros`, or null for the automatic one. `alerts` is some of 50, 75, 90 and 100, in percent of the limit. `pause_at_limit` false makes the limit alert only, without pausing usage; g1t's own ceiling still applies. `webhook` is an https:// address sent a JSON POST for each alert, or null for none. Owners only, as a person: signed in or with a personal access token. A workspace's own token and a run's token can read the budget but never change it. Returns the budget."
17171717 }
17181718 Op::ListInvoices => {
17191719 "A workspace's invoices, newest first. `invoices` is every invoice billed to it (the plan, usage and any AI credit bought earlier), each with its `status`, `total_cents`, `currency` and links to view it and its PDF. `usage_invoices` are g1t's itemised invoices for usage, one when each month closes and one each time the card is charged near the limit, with their `lines` in millionths of a dollar; `amount_micros` is the usage, and the card processing fee (`fee_micros`) and tax (`tax_micros`) are on top. Prices exclude tax: Stripe adds it where it applies. `upcoming` is what the next invoice comes to so far. `unavailable` says why `invoices` could not be read just now, when it could not. Members of the workspace only."
24452445 "name": { "type": "string", "description": "What to call it. The provider's name if left out." },
24462446 "config": {
24472447 "type": "object",
2448− "description": "Settings. repo (owner/name) is where alerts open issues; label names the label (bug). organization is the Sentry org's slug. site is Jira's address; email the account its token belongs to; keys the project or team keys it answers for. write_back (default true) tells the outside system when the work lands.",
2448+ "description": "Settings. repo (owner/name) is where alerts open issues; label names the label (bug). organization is the Sentry org's slug. site is Jira's address; email the account its token belongs to; keys the project or team keys it answers for. For an AI provider, base_url and auth_header (x-api-key or authorization) are for your own endpoint (and base_url for an Azure OpenAI resource), and model names the model or Azure deployment the team uses. write_back (default true) tells the outside system when the work lands.",
24492449 },
24502450 "secret": { "type": "string", "description": "The API key or token g1t uses to call it. Write-only: kept encrypted, never returned." },
24512451 "signing_secret": { "type": "string", "description": "For sentry: the integration's client secret." },
50615061 lead: None,
50625062 channel_id: None,
50635063 channel_name: None,
5064− budget_micros: None,
50655064 surface: Some(services.audit.surface),
50665065 };
50675066 if args.name.is_none()
+1−1
33 //! projects service keeps them; this is their public shape, in snake_case.
44 //!
55 //! A person's own: a personal access token or a session, never a
6−//! workspace's token or g1t's agents.
6+//! workspace's token or a run's token.
77
88 use g1t_contracts::{FailureCode, Outcome, PrincipalKind, Viewer};
99 use serde_json::{Value, json};
+31−124
825825 "required_approvals": 0,
826826 "count_agent_approvals": true,
827827 "allow_ignoring_checks": true,
828− "agent_review": true,
829− "max_revisions": 2,
830828 "merge_queue": false,
831− "hold_low_confidence": true,
832829 "require_code_owner_review": false,
833830 "updated_by": null,
834831 "updated_at": null
855852 "required_approvals": 1,
856853 "count_agent_approvals": false,
857854 "allow_ignoring_checks": true,
858− "agent_review": true,
859− "max_revisions": 2,
860855 "merge_queue": true,
861− "hold_low_confidence": true,
862856 "require_code_owner_review": true,
863857 "updated_by": "syntaqx",
864858 "updated_at": "2026-10-04T16:20:37.508Z"
865859 },
866− "notes": "`required_checks` replaces the whole list: at most 20 names, each a workflow's name or another status's context, as [`list_check_names`](/reference/api/repositories/list-check-names/) gives them. A pull request merges only once each passes on its head; one nothing has reported holds it too. With the merge queue on, each must also pass on the queued state, so the workflows behind them need `merge_group` in their `on:`. `required_approvals` is at most 6 and `max_revisions` at most 5. See [what a repository can ask for](/guides/working-with-g1t/#what-a-repository-can-ask-for). `hold_low_confidence` is on unless turned off: see [confidence](/guides/working-with-g1t/#how-sure-the-agent-is). With `require_code_owner_review`, a pull request into the default branch merges only once the code owners of every file it changes have approved it, as many as each section of its CODEOWNERS file asks: see [Code owners](/guides/codeowners/)."
860+ "notes": "`required_checks` replaces the whole list: at most 20 names, each a workflow's name or another status's context, as [`list_check_names`](/reference/api/repositories/list-check-names/) gives them. A pull request merges only once each passes on its head; one nothing has reported holds it too. With the merge queue on, each must also pass on the queued state, so the workflows behind them need `merge_group` in their `on:`. `required_approvals` is at most 6. See [what a repository can ask for](/guides/working-with-g1t/#what-a-repository-can-ask-for). With `require_code_owner_review`, a pull request into the default branch merges only once the code owners of every file it changes have approved it, as many as each section of its CODEOWNERS file asks: see [Code owners](/guides/codeowners/)."
867861 },
868862 "list_check_names": {
869863 "response": [
15551549 "requested_by": null,
15561550 "created_at": "2026-10-01T18:20:02.117Z",
15571551 "updated_at": "2026-10-01T18:35:44.902Z",
1558− "confidence": null,
15591552 "labels": [],
15601553 "milestone": null,
15611554 "base": "main"
16441637 "checks": null,
16451638 "overlaps": [],
16461639 "behind": false,
1647− "review_pending": false,
16481640 "lifecycle": null,
16491641 "landing": false,
16501642 "stalled": null,
1651− "messages": [
1652− {
1653− "id": "msg_01m43tx5egeh4t3f9zcnjenqvz",
1654− "author": "syntaqx",
1655− "body": "Keep \"world\" as the default when no name is given.",
1656− "created_at": "2026-10-01T18:25:00.310Z",
1657− "delivered_at": "2026-10-01T18:25:31.007Z",
1658− "kind": "message",
1659− "from_number": null,
1660− "to_number": 14,
1661− "answer": null,
1662− "declined": false
1663− }
1664− ],
16651643 "statuses": [
16661644 {
16671645 "context": "CI / pull_request",
36593637 "workspace": "flagon-io"
36603638 },
36613639 "response": [
3662− {
3663− "id": "con_01kpx3m7q2f8r9t4v6w0y1z2ab",
3664− "workspace": "flagon-io",
3665− "provider": "anthropic",
3666− "kind": "models",
3667− "name": "Anthropic",
3668− "config": {
3669− "assign": false,
3670− "write_back": true
3671− },
3672− "secret_hint": "…3f9a",
3673− "webhook_url": null,
3674− "created_by": "syntaqx",
3675− "created_at": "2026-10-04T15:42:07.318Z",
3676− "last_used_at": "2026-10-04T15:42:08.102Z",
3677− "last_error": null,
3678− "models": [
3679− "claude-haiku-4-5",
3680− "claude-sonnet-4-5"
3681− ]
3682− },
36833640 {
36843641 "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc",
36853642 "workspace": "flagon-io",
36873644 "kind": "tracker",
36883645 "name": "Jira",
36893646 "config": {
3690− "assign": false,
36913647 "write_back": true,
36923648 "site": "https://acme.atlassian.net",
36933649 "email": "syntaqx@example.com",
37043660 "models": []
37053661 }
37063662 ],
3707− "notes": "`kind` is `models`, `alerts` or `tracker`. `secret_hint` shows the end of the secret; the secret itself is never returned. `webhook_url` is where an alert source sends its alerts."
3663+ "notes": "`kind` is `alerts` or `tracker`. `secret_hint` shows the end of the secret; the secret itself is never returned. `webhook_url` is where an alert source sends its alerts."
37083664 },
37093665 "connect_integration": {
37103666 "params": {
37273683 "config": {
37283684 "repo": "flagon-io/hello",
37293685 "label": "bug",
3730− "assign": false,
37313686 "write_back": true
37323687 },
37333688 "secret_hint": null,
37403695 },
37413696 "signing_secret": "g1ts_9f2c4a7e1b0d3c6f8a2e5b7d9c1f4a6e8b0d2c4f6a8e1b3d"
37423697 },
3743− "notes": "`signing_secret` is set only when g1t made it, for `datadog` and `webhook`, and is shown only this once. A model provider is tested as it is connected. See [integrations](/guides/integrations/) and [model providers](/guides/models/)."
3698+ "notes": "`signing_secret` is set only when g1t made it, for `datadog` and `webhook`, and is shown only this once. See [integrations](/guides/integrations/)."
37443699 },
37453700 "update_integration": {
37463701 "params": {
37473702 "workspace": "flagon-io",
3748− "id": "con_01kpx5c2d8e4f6g0h2j4k6m8n0"
3703+ "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc"
37493704 },
37503705 "request": {
37513706 "config": {
3752− "base_url": "https://gpu.flagon.dev/v1",
3753− "gateway_models": [
3754− "ollama/*"
3707+ "site": "https://acme.atlassian.net",
3708+ "email": "syntaqx@example.com",
3709+ "keys": [
3710+ "TECH",
3711+ "OPS"
37553712 ]
37563713 },
3757− "secret": "sk-office-gpu-key"
3714+ "secret": "atlassian-api-token"
37583715 },
37593716 "response": {
3760− "id": "con_01kpx5c2d8e4f6g0h2j4k6m8n0",
3717+ "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc",
37613718 "workspace": "flagon-io",
3762− "provider": "openai_endpoint",
3763− "kind": "models",
3764− "name": "Office GPU",
3719+ "provider": "jira",
3720+ "kind": "tracker",
3721+ "name": "Jira",
37653722 "config": {
37663723 "write_back": true,
3767− "base_url": "https://gpu.flagon.dev/v1",
3768− "gateway_models": [
3769− "ollama/*"
3724+ "site": "https://acme.atlassian.net",
3725+ "email": "syntaqx@example.com",
3726+ "keys": [
3727+ "TECH",
3728+ "OPS"
37703729 ]
37713730 },
3772− "secret_hint": "…-key",
3731+ "secret_hint": "…oken",
37733732 "webhook_url": null,
37743733 "created_by": "syntaqx",
3775− "created_at": "2026-10-07T12:10:44.512Z",
3734+ "created_at": "2026-10-04T15:42:07.318Z",
37763735 "last_used_at": "2026-10-07T14:00:02.000Z",
37773736 "last_error": null,
3778− "models": [
3779− "llama3.3:70b",
3780− "qwen3-coder:30b"
3781− ]
3737+ "models": []
37823738 },
3783− "notes": "The secret is write-only: it is kept encrypted and only its last four characters come back, as `secret_hint`. With `gateway_models` set to `ollama/*`, an AI Gateway request for `ollama/qwen3-coder:30b` reaches this endpoint as `qwen3-coder:30b`, on the workspace's own account. See [the AI Gateway guide](/guides/ai-gateway/#your-own-providers)."
3739+ "notes": "The secret is write-only: it is kept encrypted and only its last four characters come back, as `secret_hint`. Use it to rotate a key."
37843740 },
37853741 "disconnect_integration": {
37863742 "params": {
37873743 "workspace": "flagon-io",
3788− "id": "con_01kpx3m7q2f8r9t4v6w0y1z2ab"
3744+ "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bd"
37893745 },
37903746 "response": true
37913747 },
37923748 "test_integration": {
37933749 "params": {
37943750 "workspace": "flagon-io",
3795− "id": "con_01kpx3m7q2f8r9t4v6w0y1z2ab"
3751+ "id": "con_01kpx4n8r3g9s0v5w7x1z2a3bc"
37963752 },
37973753 "response": {
37983754 "ok": true,
3799− "message": "Anthropic accepted the key and offers 9 models."
3755+ "message": "Connected to https://acme.atlassian.net as Chase Pierce."
38003756 },
38013757 "notes": "Credentials that do not work still answer `200`, with `ok` false and `message` saying what went wrong."
38023758 },
57095665 }
57105666 ]
57115667 }
5712− ],
5713− "features": []
5668+ ]
57145669 },
57155670 {
57165671 "key": "other",
57485703 }
57495704 ]
57505705 }
5751− ],
5752− "features": []
5706+ ]
57535707 }
57545708 ],
57555709 "projects": [
57565710 "flagon-io/g1t",
57575711 "flagon-io/hello"
57585712 ],
5759− "models": [],
5760− "by_agent": [],
5761− "by_person": [],
57625713 "included": {
57635714 "used": 20,
57645715 "of": 20,
58385789 "state": "ok",
58395790 "message": null
58405791 },
5841− "notes": "Fields left out keep their value. A limit above `max_amount_micros` is refused with `invalid`. Called by anyone but an owner signed in as a person, or by a workspace's own token or one of g1t's agents, it is refused with `forbidden`. Each alert is sent once a month, to the owners by email and, with `webhook`, as a JSON POST."
5792+ "notes": "Fields left out keep their value. A limit above `max_amount_micros` is refused with `invalid`. Called by anyone but an owner signed in as a person, or by a workspace's own token or a run's token, it is refused with `forbidden`. Each alert is sent once a month, to the owners by email and, with `webhook`, as a JSON POST."
58425793 },
58435794 "list_invoices": {
58445795 "params": {
83368287 },
83378288 "requested_by": null,
83388289 "created_at": "2026-10-01T18:20:02.117Z",
8339− "updated_at": "2026-10-01T18:35:44.902Z",
8340− "confidence": null
8290+ "updated_at": "2026-10-01T18:35:44.902Z"
83418291 }
83428292 ]
83438293 }
83978347 },
83988348 "requested_by": null,
83998349 "created_at": "2026-10-01T18:20:02.117Z",
8400− "updated_at": "2026-10-01T18:35:44.902Z",
8401− "confidence": null
8350+ "updated_at": "2026-10-01T18:35:44.902Z"
84028351 },
84038352 "notes": "A new base takes it out of the merge queue, and whether it is behind, merges cleanly and has the checks it needs is worked out against the new base."
84048353 },
85618510 "exceptions": []
85628511 },
85638512 "applies_to": "everyone"
8564− },
8565− {
8566− "type": "cost_cap",
8567− "parameters": {
8568− "max_usd": 25
8569− },
8570− "applies_to": "agents"
85718513 }
85728514 ],
85738515 "created_by": "syntaqx",
92279169 "exceptions": []
92289170 },
92299171 "applies_to": "everyone"
9230− },
9231− {
9232− "type": "cost_cap",
9233− "parameters": {
9234− "max_usd": 25
9235− },
9236− "applies_to": "agents"
92379172 }
92389173 ],
92399174 "created_by": "syntaqx",
93089243 "exceptions": []
93099244 },
93109245 "applies_to": "everyone"
9311− },
9312− {
9313− "type": "cost_cap",
9314− "parameters": {
9315− "max_usd": 25
9316− },
9317− "applies_to": "agents"
93189246 }
93199247 ],
93209248 "created_by": "syntaqx",
93849312 "exceptions": []
93859313 },
93869314 "applies_to": "everyone"
9387− },
9388− {
9389− "type": "cost_cap",
9390− "parameters": {
9391− "max_usd": 25
9392− },
9393− "applies_to": "agents"
93949315 }
93959316 ]
93969317 },
94549375 "exceptions": []
94559376 },
94569377 "applies_to": "everyone"
9457− },
9458− {
9459− "type": "cost_cap",
9460− "parameters": {
9461− "max_usd": 25
9462− },
9463− "applies_to": "agents"
94649378 }
94659379 ],
94669380 "created_by": "syntaqx",
95389452 "exceptions": []
95399453 },
95409454 "applies_to": "everyone"
9541− },
9542− {
9543− "type": "cost_cap",
9544− "parameters": {
9545− "max_usd": 25
9546− },
9547− "applies_to": "agents"
95489455 }
95499456 ],
95509457 "created_by": "syntaqx",
+0−1
272272 route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]),
273273 route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]),
274274 route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]),
275− // The AI Gateway's log of a workspace's requests.
276275 // Code owners: the CODEOWNERS file, checked.
277276 route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]),
278277 // Security alerts: secrets and vulnerable dependencies.
+1−1
9999 match self {
100100 RulesOp::ListRepoRulesets => "List a repository's rulesets: what may happen to its branches and tags, and what a pull request needs before it merges. With include_parents, also its workspace's rulesets that hold in it (level workspace). Each has its enforcement (active, evaluate: a dry run that records what it would have refused, or disabled), target (branch or tag), conditions (ref_name include and exclude patterns: fnmatch, ~DEFAULT_BRANCH, ~ALL), bypass_actors and rules. The one made from branch protection settings has source branch_protection.",
101101 RulesOp::GetRepoRuleset => "Get one of a repository's rulesets by id (rs_…), or one of its workspace's that holds in it.",
102− RulesOp::CreateRepoRuleset => "Create a repository ruleset: name, enforcement (active, evaluate or disabled; active by default), target (branch or tag), conditions.ref_name (include and exclude patterns), bypass_actors (each a kind: role, team, user, token or g1t, a value, and a mode: always or pull_requests; nobody bypasses unless listed, g1t included) and rules (each a type, its parameters, and applies_to: everyone, agents or people). Rule types: creation, update, deletion, non_fast_forward, required_linear_history, required_signatures, pull_request, required_status_checks, merge_queue, required_deployments, commit_message_pattern, commit_author_email_pattern, committer_email_pattern, branch_name_pattern, tag_name_pattern, file_path_restriction, file_extension_restriction, max_file_size, max_file_path_length, max_files_changed, secret_scanning, confidence_threshold, cost_cap, path_review, merge_window and agent_auto_merge. Several rulesets stack: every rule of each holds. Takes the Admin role. Returns the ruleset as saved, tidied.",
102+ RulesOp::CreateRepoRuleset => "Create a repository ruleset: name, enforcement (active, evaluate or disabled; active by default), target (branch or tag), conditions.ref_name (include and exclude patterns), bypass_actors (each a kind: role, team, user, token or g1t, a value, and a mode: always or pull_requests; nobody bypasses unless listed, g1t included) and rules (each a type, its parameters, and applies_to: everyone, agents or people). Rule types: creation, update, deletion, non_fast_forward, required_linear_history, required_signatures, pull_request, required_status_checks, merge_queue, required_deployments, commit_message_pattern, commit_author_email_pattern, committer_email_pattern, branch_name_pattern, tag_name_pattern, file_path_restriction, file_extension_restriction, max_file_size, max_file_path_length, max_files_changed, secret_scanning, path_review, merge_window and agent_auto_merge. Several rulesets stack: every rule of each holds. Takes the Admin role. Returns the ruleset as saved, tidied.",
103103 RulesOp::UpdateRepoRuleset => "Change a repository ruleset. Fields left out stay as they are; rules and bypass_actors, when given, replace the whole list. Takes the Admin role.",
104104 RulesOp::DeleteRepoRuleset => "Delete a repository ruleset. Its evaluations stay in the log. Takes the Admin role.",
105105 RulesOp::GetBranchRules => "Every rule that holds for a branch (or a tag, with target tag) of a repository, from every ruleset that targets it, the repository's and its workspace's: each with its type, parameters and applies_to, and the ruleset_id, ruleset_name, level and enforcement it comes from. Active rules come first, then those of rulesets in evaluate. rulesets lists the rulesets with who may bypass each. A branch name with slashes is URL-encoded in the path.",
+3−7
22 //! each secret is, push protection bypasses and their review, validity
33 //! checks, custom patterns), code scanning (alerts, analyses, SARIF
44 //! uploads), vulnerability alerts, the dependency graph with its SBOM and
5−//! dependency review, "Fix with g1t", settings, and the workspace's
6−//! overview.
5+//! dependency review, settings, and the workspace's overview.
76 //!
87 //! The addresses follow the common ones (`/repos/{owner}/{name}/secret-
98 //! scanning/alerts`, `/code-scanning/sarifs`, `/dependency-graph/sbom`),
12421241 }
12431242 assert_eq!(scope_for("get_sbom"), Some(Scope::SecurityRead));
12441243 assert_eq!(scope_for("upload_sarif"), Some(Scope::SecurityWrite));
1245− // Fixing an alert also opens an issue and spends agent time.
1246− let needed = g1t_contracts::scopes::needed("fix_security_alert", &json!({}));
1247− assert_eq!(needed, [Scope::SecurityWrite, Scope::IssuesWrite, Scope::AgentsRun]);
1248− // No agent decides about security.
1249− for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings", "fix_security_alert"] {
1244+ // No run decides about security.
1245+ for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings"] {
12501246 assert!(g1t_contracts::credentials::NEVER.contains(&name), "{name}");
12511247 }
12521248 }
+2−49
390390 a("list_invites", Op::ListWorkspaceInvites, "Its invites"),
391391 a("invite_member", Op::InviteMember, "Invite someone by username or email address"),
392392 a("revoke_invite", Op::RevokeWorkspaceInvite, "Revoke a pending invite"),
393− a("list_integrations", Op::ListIntegrations, "Alert sources and issue trackers"),
393+ a("list_integrations", Op::ListIntegrations, "AI providers, alert sources and issue trackers"),
394394 a("connect_integration", Op::ConnectIntegration, "Connect one"),
395395 a("update_integration", Op::UpdateIntegration, "Change one: rotate its key or its settings"),
396396 a("disconnect_integration", Op::DisconnectIntegration, "Remove one"),
812812 use super::*;
813813 use g1t_contracts::scopes::{Preset, Scope};
814814
815− /// Operations g1t no longer has, which the scopes table in
816− /// g1t-contracts still names until it is trimmed.
817− const RETIRED: &[&str] = &[
818− "message_agent",
819− "answer_message",
820− "take_messages",
821− "search_context",
822− "get_entity",
823− "assign_issue",
824− "delegate",
825− "plan_work",
826− "get_plan",
827− "apply_plan",
828− "get_model_routes",
829− "set_model_routes",
830− "get_agent_computer",
831− "wake_agent_computer",
832− "sleep_agent_computer",
833− "reset_agent_computer",
834− "list_agent_computer_commands",
835− "list_gateway_requests",
836− "fix_security_alert",
837− "list_marketplaces",
838− "add_marketplace",
839− "remove_marketplace",
840− "refresh_marketplace",
841− "discover",
842− "list_plugins",
843− "get_plugin",
844− "install_plugin",
845− "set_plugin_enabled",
846− "uninstall_plugin",
847− "update_plugin",
848− "get_ai_credit",
849− "buy_ai_credit",
850− "list_plugin_agents",
851− "list_connectors",
852− "get_connector",
853− "add_connector",
854− "set_connector_key",
855− "remove_connector",
856− "refresh_connector",
857− "grant_connector",
858− "revoke_connector",
859− "customize_bundle",
860− ];
861−
862815 fn listed(gate: &Gate) -> Vec<Value> {
863816 TOOLS.iter().filter_map(|tool| tool.listed(gate)).collect()
864817 }
905858 assert_eq!(mapped + usize::from(free), 1, "{}", op.name());
906859 }
907860 for (name, _) in OPERATIONS {
908− assert!(Op::by_name(name).is_some() || RETIRED.contains(name), "{name} is not an operation");
861+ assert!(Op::by_name(name).is_some(), "{name} is not an operation");
909862 }
910863 }
911864
+45−7
1818 | Where | **Integrations**, at `g1t.sh/<workspace>/-/integrations`, with each integration's setup page under it | **Your settings → Integrations**, at [`g1t.sh/settings/integrations`](https://g1t.sh/settings/integrations) |
1919 | Who it is for | Everyone in that workspace | Just you, in every workspace you belong to |
2020 | Who connects | Workspace owners. Members see what is connected. | You |
21−| Examples | Sentry, Jira, Linear, GitHub imports, webhooks | Signing in with GitHub, coding agents you connect through MCP, and soon your calendar |
21+| Examples | Your AI providers, Sentry, Jira, Linear, GitHub imports, webhooks | Signing in with GitHub, coding agents you connect through MCP, and soon your calendar |
2222
2323 Some tools have both sides. GitHub, for instance: a workspace owner
2424 installs g1t's GitHub App to import the team's repositories, and
3131
3232 - **Categories** down the side (along the top on a phone): Code & CI,
3333 Issues & projects, Chat & meetings, Calendar & email, Docs & knowledge,
34− Monitoring & incidents, Cloud & deploy, Data & analytics, CRM & support,
34+ Monitoring & incidents, Cloud & deploy, AI models, Data & analytics, CRM & support,
3535 Design, Security & identity, and Files & storage. Each shows how many
3636 integrations it has. A link can open on one, with `?category=code` and
3737 so on.
5656 | Code & CI | [GitHub](/guides/github/) import, [webhooks](/guides/webhooks/) | [Signing in with GitHub](/guides/github/#link-and-unlink-github); [MCP clients](/guides/authentication/#signing-in-with-oauth): your [coding agent](/guides/bring-your-own-agent/), connected to g1t as you |
5757 | Issues & projects | [Jira and Linear](#trackers) | |
5858 | Monitoring & incidents | [Sentry, Datadog, and any system that sends a webhook](#alerts) | |
59+| AI models | [Your AI providers](#your-ai-providers): Anthropic, OpenAI, Google Gemini, xAI, Mistral, DeepSeek, Azure OpenAI, OpenRouter, Groq, Together AI, Fireworks AI, Cerebras, and any Anthropic- or OpenAI-compatible endpoint | |
5960
60−Alerts and trackers each have a setup page under the directory:
61−`-/integrations/alerts` and `-/integrations/trackers`. GitHub and webhooks
61+AI providers, alerts and trackers each have a setup page under the
62+directory: `-/integrations/models`, `-/integrations/alerts` and
63+`-/integrations/trackers`. GitHub and webhooks
6264 keep their own pages; the directory links to them.
6365
6466 Secrets are sealed when they are saved and never shown again, to anyone.
8688 not listed offers the same, for whatever you searched for. Requests decide
8789 what is built next.
8890
91+## Your AI providers
92+
93+**Your AI providers**, at `g1t.sh/<workspace>/-/integrations/models`, is
94+where a workspace keeps the credentials for the AI providers its team
95+uses, so g1t can hand work to them. Only workspace owners add, test,
96+change and remove them; members see which are connected.
97+
98+- **Add one.** Choose a provider and paste its API key. Anthropic, OpenAI,
99+ Google Gemini, xAI, Mistral, DeepSeek, OpenRouter, Groq, Together AI,
100+ Fireworks AI and Cerebras need only the key. Azure OpenAI also needs
101+ your resource's endpoint and the deployment's name. An Anthropic- or
102+ OpenAI-compatible endpoint (your own gateway, LiteLLM, vLLM and the
103+ like) needs its base URL over `https://`, and takes a key if it asks for
104+ one, sent as `x-api-key` or `Authorization: Bearer`. You can name the
105+ model your team uses on each.
106+- **Test.** g1t checks a key as it is saved, and again whenever you choose
107+ **Test**, by asking the provider for its models. A key that works shows
108+ how many models it offers; one that fails says why, on the connection,
109+ until it next works. An endpoint that does not list its models is
110+ reached, but its key cannot be checked this way.
111+- **Change.** **Change its model, address or key** sets a new model or
112+ address, or replaces the key. The old key is gone once the new one is
113+ saved.
114+- **Remove.** The **×** on a connection disconnects it and deletes its key.
115+
116+Keys are sealed when they are saved and never shown again, to anyone; the
117+page shows only their last four characters.
118+
119+The **Ask AI** button in the top bar is where you will start work on one
120+of these providers. It is not on yet: it shows the composer, marked
121+coming soon.
122+
89123 ## Alerts
90124
91125 An alert source opens issues in one repository. Each problem it reports is
272306 -d '{"provider": "jira", "config": {"site": "https://acme.atlassian.net", "email": "dev@acme.com", "keys": ["TECH"]}, "secret": "<api token>"}'
273307 ```
274308
275−`provider` is `sentry`, `datadog`, `webhook`, `jira` or `linear`.
276−`config` takes `repo`, `label`, `write_back`, `organization`, `site`,
277−`email` and `keys`; each provider uses the ones above. For `datadog`
309+`provider` is an AI provider (`anthropic`, `openai`, `gemini`, `xai`,
310+`mistral`, `deepseek`, `azure_openai`, `openrouter`, `groq`, `together`,
311+`fireworks`, `cerebras`, `anthropic_endpoint` or `openai_endpoint`), or
312+`sentry`, `datadog`, `webhook`, `jira` or `linear`. `config` takes `repo`,
313+`label`, `write_back`, `organization`, `site`, `email` and `keys`, and for
314+an AI provider `base_url`, `auth_header` and `model`; each provider uses
315+the ones above. For `datadog`
278316 and `webhook`, the response's `signing_secret` is the only time the secret
279317 is shown.
+0−0

Binary or large file; its contents are not shown.

+93−0
1+import { ArrowUp, AtSign, Plus, Sparkles } from "lucide-react";
2+import type { ReactNode } from "react";
3+import { Link } from "react-router";
4+
5+import { MarkGlyph } from "./logo";
6+import { Badge } from "./ui/badge";
7+import { Button } from "./ui/button";
8+import { Card } from "./ui/card";
9+import { Hint } from "./ui/hint";
10+import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
11+
12+/**
13+ * The place to ask AI for something and hand the work to one of the
14+ * workspace's AI providers (Workspace settings → Integrations → Your AI
15+ * providers). Shown so it is clear where this will be, and not usable yet:
16+ * nothing here sends, and every control says so.
17+ */
18+export function AskComposer({ slug, children }: { slug: string; children?: ReactNode }) {
19+ return (
20+ <section aria-label="Ask AI" className="space-y-3">
21+ <Card aria-disabled="true" className="opacity-80">
22+ <p className="flex items-center gap-2 px-4 pt-3 text-sm font-medium text-fg">
23+ <Sparkles size={14} className="text-accent" aria-hidden="true" />
24+ Ask AI
25+ <Badge className="px-1.5 py-0 font-normal">Coming soon</Badge>
26+ </p>
27+ <label htmlFor="ask-ai" className="sr-only">
28+ Ask AI (coming soon)
29+ </label>
30+ <textarea
31+ id="ask-ai"
32+ disabled
33+ rows={2}
34+ placeholder="Ask anything, or describe work to hand to your AI provider."
35+ className="block w-full cursor-not-allowed resize-none bg-transparent px-4 pt-3.5 pb-2 text-sm text-fg placeholder:text-muted focus:outline-none"
36+ />
37+ <div className="flex flex-wrap items-center gap-2 px-3 pb-3">
38+ <Soon label="Add context">
39+ <Plus size={15} />
40+ </Soon>
41+ <Soon label="Mention">
42+ <AtSign size={14} />
43+ </Soon>
44+ <span className="ml-auto" />
45+ <Soon label="Send">
46+ <ArrowUp size={15} />
47+ </Soon>
48+ </div>
49+ </Card>
50+ <p className="text-xs text-muted">
51+ It will hand off to{" "}
52+ <Link to={`/${slug}/-/integrations/models`} className="text-accent hover:underline">
53+ your AI providers
54+ </Link>
55+ .
56+ </p>
57+ {children}
58+ </section>
59+ );
60+}
61+
62+/** One of the composer's controls: there, disabled, with a hint saying why. */
63+function Soon({ label, children }: { label: string; children: ReactNode }) {
64+ return (
65+ <Hint label="Coming soon" disabled>
66+ <Button type="button" variant="outline" size="icon-sm" disabled aria-label={`${label} (coming soon)`}>
67+ {children}
68+ </Button>
69+ </Hint>
70+ );
71+}
72+
73+/**
74+ * The top bar's Ask AI: the lavender pixel 1 beside its words, on a wash of
75+ * the accent. Not on yet: it opens the composer, which says so.
76+ */
77+export function AskButton({ slug }: { slug: string }) {
78+ return (
79+ <Popover>
80+ <Hint label="Ask AI · coming soon">
81+ <PopoverTrigger asChild>
82+ <Button variant="soft" size="bar" aria-label="Ask AI (coming soon)" className="gap-2 max-md:ring-0 lg:px-3">
83+ <MarkGlyph className="h-3.5" />
84+ <span className="max-lg:hidden">Ask AI</span>
85+ </Button>
86+ </PopoverTrigger>
87+ </Hint>
88+ <PopoverContent align="end" className="w-[26rem] p-3">
89+ <AskComposer slug={slug} />
90+ </PopoverContent>
91+ </Popover>
92+ );
93+}
+7−5
2424 import { type Provider, PROVIDERS } from "@g1t/contracts";
2525 import { type ConnectorScope, type ConnectorView, connectorPath } from "@g1t/contracts/connectors";
2626
27+import { AI_PROVIDERS } from "../lib/ai-providers";
2728 import { CONTACT } from "../lib/legal";
2829 import {
2930 type CategoryFilter,
7071 linear: "#8b8ff0",
7172 };
7273
73−/** A provider's mark: its initial in its colour, or an icon for the generic ones. */
74+/** A provider's mark: its initials in its colour, or an icon for the generic ones. */
7475 export function ProviderMark({ provider, size = 32 }: { provider: Provider; size?: number }) {
75− const color = PROVIDER_COLORS[provider] ?? "#a1a1aa";
76+ const entry = AI_PROVIDERS[provider];
77+ const color = entry?.color ?? PROVIDER_COLORS[provider] ?? "#a1a1aa";
7678 const content =
7779 provider === "webhook" ? (
7880 <Webhook size={size * 0.5} />
7981 ) : provider.endsWith("_endpoint") ? (
8082 <Bot size={size * 0.5} />
8183 ) : (
82− PROVIDERS[provider].label[0]
84+ entry?.mark || PROVIDERS[provider].label[0]
8385 );
8486 return (
8587 <span
8991 width: size,
9092 height: size,
9193 borderRadius: size * 0.28,
92− fontSize: size * 0.42,
94+ fontSize: size * (String(content).length > 1 ? 0.34 : 0.42),
9395 background: `color-mix(in srgb, ${color} 16%, transparent)`,
94− // Toward the text colour, so the letter reads on the tile in both themes.
96+ // Toward the text colour, so the letters read on the tile in both themes.
9597 color: `color-mix(in srgb, ${color} 60%, var(--color-fg))`,
9698 boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${color} 30%, transparent)`,
9799 }}
+99−0
1+/**
2+ * A workspace's AI providers: the catalogue people choose from, and the
3+ * fields each one asks for when it is added. The marks and tiles are the
4+ * integrations' own (components/connectors).
5+ */
6+import type { ReactNode } from "react";
7+
8+import type { Provider } from "@g1t/contracts";
9+
10+import { AI_PROVIDER_GROUPS, AI_PROVIDERS, aiProvidersIn } from "../lib/ai-providers";
11+import { ProviderTiles } from "./connectors";
12+import { Field, Input } from "./ui";
13+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
14+
15+/** The AI providers to choose from, grouped. */
16+export function ModelCatalog({ slug, adding }: { slug: string; adding: Provider | null }) {
17+ return (
18+ <div className="mt-4 space-y-4">
19+ {AI_PROVIDER_GROUPS.map(({ group, title }) => (
20+ <div key={group}>
21+ <p className="mb-2 text-xs font-medium tracking-wide text-faint uppercase">{title}</p>
22+ <ProviderTiles
23+ providers={aiProvidersIn(group)}
24+ slug={slug}
25+ adding={adding}
26+ blurb={(provider) => AI_PROVIDERS[provider]?.blurb ?? ""}
27+ />
28+ </div>
29+ ))}
30+ </div>
31+ );
32+}
33+
34+/** What adding an AI provider asks for. */
35+export function ModelProviderFields({ provider }: { provider: Provider }): ReactNode {
36+ const entry = AI_PROVIDERS[provider];
37+ if (provider === "azure_openai") {
38+ return (
39+ <>
40+ <Field label="Endpoint" hint="Your resource's endpoint, from the Azure portal: Keys and Endpoint.">
41+ <Input name="baseUrl" type="url" required placeholder="https://acme.openai.azure.com" />
42+ </Field>
43+ <Field label="Key" hint="KEY 1 or KEY 2 of the resource. Sealed when saved: nobody sees it again.">
44+ <Input name="secret" type="password" required autoComplete="new-password" />
45+ </Field>
46+ <Field label="Deployment" hint="The name of the model deployment. Add one provider for each deployment your team uses.">
47+ <Input name="model" required placeholder="gpt-5" />
48+ </Field>
49+ </>
50+ );
51+ }
52+ if (provider === "anthropic_endpoint" || provider === "openai_endpoint") {
53+ const anthropic = provider === "anthropic_endpoint";
54+ return (
55+ <>
56+ <Field
57+ label="Base URL"
58+ hint={anthropic ? "Without /v1, such as https://llm.acme.dev." : "Up to and including the version, such as https://llm.acme.dev/v1."}
59+ >
60+ <Input name="baseUrl" type="url" required placeholder={anthropic ? "https://llm.acme.dev" : "https://llm.acme.dev/v1"} />
61+ </Field>
62+ <Field label="Key" hint="Optional, if the endpoint needs one. Sealed when saved: nobody sees it again.">
63+ <Input name="secret" type="password" autoComplete="new-password" />
64+ </Field>
65+ <Field label="Send the key as">
66+ <Select name="authHeader" defaultValue={anthropic ? "x-api-key" : "authorization"}>
67+ <SelectTrigger aria-label="Send the key as" className="font-mono text-[0.8125rem]">
68+ <SelectValue />
69+ </SelectTrigger>
70+ <SelectContent>
71+ <SelectItem value="x-api-key" description="Anthropic's header" className="font-mono text-[0.8125rem]">
72+ x-api-key
73+ </SelectItem>
74+ <SelectItem value="authorization" description="OpenAI's header, and most others" className="font-mono text-[0.8125rem]">
75+ Authorization: Bearer
76+ </SelectItem>
77+ </SelectContent>
78+ </Select>
79+ </Field>
80+ <Field label="Model" hint="Optional. The model your team uses on it.">
81+ <Input name="model" placeholder={anthropic ? "claude-sonnet-5-5" : "qwen3-coder"} />
82+ </Field>
83+ </>
84+ );
85+ }
86+ return (
87+ <>
88+ <Field
89+ label="API key"
90+ hint={`${entry?.keyUrl ? `Get one at ${new URL(entry.keyUrl).host}. ` : ""}Sealed when saved: nobody sees it again.`}
91+ >
92+ <Input name="secret" type="password" required placeholder={entry?.keyPlaceholder} autoComplete="new-password" />
93+ </Field>
94+ <Field label="Model" hint="Optional. The model your team uses on it. g1t lists the key's models when it checks the key.">
95+ <Input name="model" placeholder={entry?.model} />
96+ </Field>
97+ </>
98+ );
99+}
+4−4
1414 import { PAUSE_FOR, type PauseFor, dndOn, pauseUntil, untilLabel } from "../../lib/presence";
1515 import { applySoundSettings, preview, saveSoundSettings, useSoundSettings } from "../../lib/sounds";
1616
17−const CUE: Partial<Record<Exclude<SoundCue, "call">, { label: string; detail: string }>> = {
17+const CUE: Record<Exclude<SoundCue, "call">, { label: string; detail: string }> = {
1818 message: { label: "New message", detail: "In a conversation you have open, while you're looking elsewhere." },
1919 direct: { label: "Direct message", detail: "Someone messages you directly." },
2020 mention: { label: "Mention", detail: "You were @mentioned." },
2121 sent: { label: "Sent", detail: "A soft tick as your message goes." },
2222 };
2323
24−/** The cues offered: every one that still plays (an agent's `agent_done` no longer does). */
25−const CUES = SOUND_CUES.filter((cue) => CUE[cue] != null);
24+/** The cues offered, in the settings' order. */
25+const CUES = SOUND_CUES;
2626
2727 const SETS: { set: SoundSet; label: string; detail: string }[] = [
2828 { set: "soft", label: "Soft", detail: "Warm and low." },
190190
191191 <ul className={off ? "opacity-60" : ""} aria-label="Sounds">
192192 {CUES.map((cue) => {
193− const { label, detail } = CUE[cue]!;
193+ const { label, detail } = CUE[cue];
194194 return (
195195 <li key={cue} className="flex items-center gap-3 px-4 py-2.5">
196196 <Hint label={`Play ${label.toLowerCase()}`}>
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…
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…
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…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…