first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+284
View File
@@ -0,0 +1,284 @@
---
name: changelog-draft
description: Generate a reviewable changelog draft from PRs merged in a release range. Extracts explicit CHANGELOG markers, classifies unmarked PRs, adds external contributor attribution, and outputs markdown + JSON artifacts. Does NOT mutate channel_versions.json.
---
# Changelog Draft Generator
## Inputs
| Parameter | Required | Description |
|-----------|----------|-------------|
| `channel` | yes | Release channel: `stable`, `preview`, or `dev` |
| `release_tag` | yes | The release tag to generate the changelog for (e.g. `v0.2026.05.06.09.12.stable_00`) |
| `output_dir` | no | Directory to write output files. Defaults to `$RUNNER_TEMP` or `/tmp/changelog-draft` |
| `attribution` | no | Attribution mode: `external-only` (default), `all`, or `none` |
## Workflow
### Step 1 — Determine the release range
Infer the previous release **cut** for comparison. Release tags follow the pattern `v0.YYYY.MM.DD.HH.MM.<channel>_NN`, where `_NN` is the RC/hotfix number within that release cut. Multiple tags can share the same date prefix (e.g. `_00`, `_01`, `_02` are all part of one release cut).
The base tag must be the `_00` tag of the **previous** release cut (i.e. a different date), not just the previous tag. For example, if generating a changelog for `v0.2026.04.29.08.57.stable_01`, the base should be `v0.2026.04.22.08.57.stable_00`, not `v0.2026.04.29.08.57.stable_00`.
```bash
# 1. Extract the date prefix from the release_tag (everything before _NN)
release_date_prefix="${release_tag%_*}"
# 2. List all _00 tags for the channel (these are release cut points), sorted descending
git tag --list "v0.*.${channel}_00" --sort=-version:refname
# 3. Pick the first _00 tag whose date prefix differs from release_date_prefix
```
Record the range as `previous_cut_tag..release_tag`.
### Step 2 — Fetch PR data
Run the `fetch_prs.py` script to collect all public-release PRs merged in the release range and extract explicit changelog markers. Pass the repository that the workflow checked out, not necessarily the public repository. Release workflows run from `warpdotdev/warp-internal`, and the script deterministically resolves `warp-repo-sync[bot]` PRs back to their original public `warpdotdev/warp` PR metadata before emitting JSON. When running from `warpdotdev/warp-internal`, the script intentionally omits PRs that were not authored by the repo-sync bot, because those are private internal changes that must not be exposed to the changelog agent or generated artifacts.
```bash
python3 .agents/skills/changelog-draft/scripts/fetch_prs.py \
--repo "${GITHUB_REPOSITORY:-warpdotdev/warp}" \
--base-ref <previous_tag> \
--head-ref <release_tag>
```
The script outputs JSON to stdout with this structure:
```json
{
"range": { "base": "<previous_tag>", "head": "<release_tag>" },
"prs": [
{
"number": 1234,
"url": "https://github.com/warpdotdev/warp/pull/1234",
"title": "...",
"author": "username",
"body": "...",
"labels": ["..."],
"merged_at": "2026-05-01T...",
"explicit_entries": [
{ "category": "NEW-FEATURE", "text": "Added dark mode" }
],
"linked_issues": [5678],
"changed_files": ["app/src/ai/agent.rs", "crates/warp_features/src/lib.rs"],
"source_repo": "warpdotdev/warp",
"internal_pr": {
"number": 25712,
"url": "https://github.com/warpdotdev/warp-internal/pull/25712",
"author": "warp-repo-sync[bot]",
"title": "...",
"repo": "warpdotdev/warp-internal"
}
}
]
}
```
Use the top-level `number`, `url`, `author`, `body`, `labels`, `changed_files`, and `source_repo` fields as the source of truth. `internal_pr` is audit-only and must never be used for contributor attribution or user-facing changelog links. If `url` is empty, omit the PR link from user-facing markdown rather than synthesizing one.
### Step 3 — Classify contributors
Run the `classify_contributors.py` script with the unique author logins from Step 2:
```bash
python3 .agents/skills/changelog-draft/scripts/classify_contributors.py \
--org warpdotdev \
--authors author1,author2,author3
```
Output JSON:
```json
{
"internal": ["author1"],
"external": ["author3"],
"bot": ["author2"],
"unknown": []
}
```
### Step 4 — Extract feature flags
Run the `extract_feature_flags.py` script to get the current flag gate lists:
```bash
python3 .agents/skills/changelog-draft/scripts/extract_feature_flags.py \
--file crates/warp_features/src/lib.rs
```
Output JSON:
```json
{
"release_flags": ["Autoupdate", "Changelog", ...],
"preview_flags": ["Orchestration", ...],
"dogfood_flags": ["LogExpensiveFramesInSentry", ...]
}
```
### Step 5 — Fetch issue reporters
Collect all unique `linked_issues` from Step 2 and fetch the original reporter for each. Pass `--org` so the script checks org membership and filters out internal reporters automatically:
```bash
python3 .agents/skills/changelog-draft/scripts/fetch_issue_reporters.py \
--repo warpdotdev/warp \
--org warpdotdev \
--issues 5678,9012
```
Output JSON (only external reporters are included):
```json
{
"issue_reporters": [
{
"issue_number": 5678,
"title": "Crash when opening large file",
"reporter": "community-user",
"reporter_url": "https://github.com/community-user",
"url": "https://github.com/warpdotdev/warp/issues/5678"
}
]
}
```
The `--org` flag checks each reporter's org membership via the GitHub API, filtering out internal members so they aren't misattributed as external community reporters. These reporters will be credited in the "Community" section of the changelog.
Whenever the markdown draft credits a PR author, contributor, or issue reporter, render the username as a GitHub profile link such as `[@username](https://github.com/username)`.
### Step 6 — Classify unmarked PRs
For each PR that has no explicit `CHANGELOG-*` entries, decide whether to include it and under which category.
Follow the classification guidance in `.agents/skills/classify-changelog-pr/SKILL.md`.
For each unmarked PR, produce a classification:
```json
{
"pr_number": 1234,
"include": true,
"category": "IMPROVEMENT",
"text": "Proposed changelog line",
"confidence": "high",
"rationale": "...",
"feature_flag": null,
"needs_review": false
}
```
**Key rules:**
- PRs that only touch CI, tests, docs, or internal tooling → `include: false`
- PRs behind dogfood-only feature flags → `include: false` for stable channel
- PRs behind preview flags → `include: false` for stable, `include: true` for preview
- When in doubt, set `needs_review: true` and `confidence: "low"`
- Bot PRs (dependabot, renovate, etc.) → `include: false`
**Feature-flag detection:** Use the `changed_files` list from Step 2 to check if any PR touches `crates/warp_features/src/lib.rs` or references a `FeatureFlag` variant in its title/body. Cross-reference with the flag lists from Step 4 to determine channel visibility.
**Unknown contributors:** Authors in the `unknown` bucket (org membership check failed due to auth) should be treated conservatively — do not attribute them as external. Note them in the output for manual verification.
### Step 7 — Assemble the draft
Combine explicit entries (Step 2) and inferred entries (Step 6) into the final report. Group by category in this order:
1. `NEW-FEATURE` — New Features
2. `IMPROVEMENT` — Improvements
3. `BUG-FIX` — Bug Fixes
4. `OZ` — Oz Updates
PRs marked with `CHANGELOG-NONE` are explicitly opted out and must never appear in the changelog markdown.
When creating entries, copy `pr_number`, `url`, `author`, `source_repo`, and `internal_pr` from the normalized PR record. The release JSON converter uses `url` directly; do not invent public PR URLs from PR numbers.
### Step 8 — Write output files
Write two files to `output_dir`:
**`changelog-draft.md`** — Human-reviewable markdown, ready for Slack/Notion:
```markdown
# Changelog Draft
**Channel:** stable
**Range:** v0.2026.05.01... → v0.2026.05.06...
**Generated:** 2026-05-06T15:00:00Z
## New Features
- Added dark mode ([#1234](https://github.com/warpdotdev/warp/pull/1234)) — [@external-contributor](https://github.com/external-contributor) ✨
## Improvements
- Faster tab switching ([#1235](https://github.com/warpdotdev/warp/pull/1235))
## Bug Fixes
- Fixed crash on startup ([#1236](https://github.com/warpdotdev/warp/pull/1236))
## Oz Updates
- Improved agent memory ([#1237](https://github.com/warpdotdev/warp/pull/1237))
## Community
### Contributors
- [@contributor1](https://github.com/contributor1) — [#1234](https://github.com/warpdotdev/warp/pull/1234) ✨
### Issue Reporters
Thanks to the community members who reported issues fixed in this release:
- [@reporter1](https://github.com/reporter1) — [#5678](https://github.com/warpdotdev/warp/issues/5678) "Crash when opening large file"
```
The markdown draft must **not** include "Needs Review" or "Skipped PRs" sections — those are internal details that belong only in the JSON audit artifact.
**`changelog-draft.json`** — Machine-readable audit artifact (internal only):
```json
{
"channel": "stable",
"range": { "base": "v0...", "head": "v0..." },
"generated_at": "2026-05-06T15:00:00Z",
"entries": [
{
"pr_number": 1234,
"url": "https://github.com/warpdotdev/warp/pull/1234",
"category": "NEW-FEATURE",
"text": "Added dark mode",
"source": "explicit",
"author": "external-contributor",
"is_external": true,
"confidence": "high",
"rationale": null,
"feature_flag": null,
"source_repo": "warpdotdev/warp",
"internal_pr": null
}
],
"skipped": [...],
"needs_review": [...],
"issue_reporters": [...]
}
```
The JSON artifact retains `skipped`, `needs_review`, and `issue_reporters` for audit purposes — every PR in the range must appear in either `entries`, `skipped`, or `needs_review`.
### Step 9 — Generate release-pipeline JSON
Run the conversion script to deterministically produce `changelog-release.json` from the audit artifact:
```bash
python3 .agents/skills/changelog-draft/scripts/convert_to_release_json.py \
--input <output_dir>/changelog-draft.json \
--output <output_dir>/changelog-release.json
```
This produces the flat JSON structure consumed by the `create_release` workflow for Slack and the in-app "What's New" dialog. Do **not** generate this file manually — always use the script so the output is deterministic and consistent.
## Constraints
- **Never** write to `channel_versions.json` or any production config file.
- **Never** push commits, create branches, or open PRs.
- All output goes to `output_dir` only.
- The markdown draft should be copy-pasteable into Slack or Notion for review.
- Keep the JSON artifact complete enough for audit: every PR in the range should appear in either `entries`, `skipped`, or `needs_review`.
## Validation
After generating output, verify:
1. Every PR in the range is accounted for (entries + skipped + needs_review = total PRs).
2. Explicit marker entries match what `fetch_prs.py` extracted (no dropped markers).
3. No duplicate PR numbers across sections.
4. The markdown renders cleanly (no broken links or formatting).
@@ -0,0 +1,96 @@
# Changelog Draft
**Channel:** stable
**Range:** v0.2026.04.29.08.56.stable_00 → v0.2026.05.06.09.12.stable_00
**Generated:** 2026-05-06T19:00:00Z
**Total PRs in range:** 211 | **Explicit markers:** 57 | **Unmarked:** 154
---
## New Features
- You can now drag tabs out of a window into their own window, or between windows, similar to Chrome. ([#9275](https://github.com/warpdotdev/warp/pull/9275))
- Added a `/set-tab-color` slash command for setting or clearing the current tab's color from the input bar. ([#9305](https://github.com/warpdotdev/warp/pull/9305))
## Improvements
- Added tab context menu actions to copy visible tab and pane metadata when available. ([#10120](https://github.com/warpdotdev/warp/pull/10120))
- The conversation details panel can now be opened and closed with a configurable keyboard shortcut. ([#9837](https://github.com/warpdotdev/warp/pull/9837))
- Conversation details side panel is now available for local Warp Agent conversations, not just cloud Oz runs. Click the info button in the pane header to open it for any active AI conversation. ([#9493](https://github.com/warpdotdev/warp/pull/9493))
- Reduced memory usage and CPU work in the agent runs management view while a conversation is streaming. ([#9866](https://github.com/warpdotdev/warp/pull/9866))
- Added support for drag-and-drop of image files into an active CLI agent session (e.g. Claude Code). ([#9553](https://github.com/warpdotdev/warp/pull/9553))
- Warp now renders inline local images and Mermaid diagrams in agent block output. ([#9993](https://github.com/warpdotdev/warp/pull/9993))
- Warp now silently falls back to a regular SSH session on remote hosts where the prebuilt remote-server binary is incompatible (e.g. glibc < 2.31), instead of attempting an install that would fail at runtime. ([#9681](https://github.com/warpdotdev/warp/pull/9681))
- HTML files using the .htm extension now open with HTML syntax highlighting in Warp's editor. ([#9360](https://github.com/warpdotdev/warp/pull/9360))
- Recognize Block's `goose` CLI agent — running `goose` now activates the CLI-agent toolbar, status, brand color, and icon like other recognized third-party agents. ([#9497](https://github.com/warpdotdev/warp/pull/9497))
- Added a `/continue-locally` slash command to continue cloud conversations locally. ([#9500](https://github.com/warpdotdev/warp/pull/9500))
- Added a "Show in Finder" (macOS) / "Show containing folder" (Linux/Windows) option to the tooltip that appears when clicking a detected file link. ([#9475](https://github.com/warpdotdev/warp/pull/9475))
- Tighten orchestration event subscription scope so SSE runs only for active parent and child agent runs. ([#9273](https://github.com/warpdotdev/warp/pull/9273))
- Fix macOS IME candidate popup positioning in code editor panes so it anchors to the editor caret instead of stale terminal/input positions. ([#9555](https://github.com/warpdotdev/warp/pull/9555))
## Bug Fixes
- Fixed /feedback recording "Unknown" instead of the installed Warp version on packaged builds. ([#10219](https://github.com/warpdotdev/warp/pull/10219))
- Fixed find (cmd+f) selection jumping to a different match when new output streams into the active block. ([#10057](https://github.com/warpdotdev/warp/pull/10057))
- Fix Japanese IME losing the last character of a phrase that ends right before a punctuation mark on macOS. ([#9730](https://github.com/warpdotdev/warp/pull/9730))
- Fixed local file tree blinking/reshuffling when connected to an SSH session ([#10184](https://github.com/warpdotdev/warp/pull/10184))
- Fixed terminal text selection not auto-scrolling when dragging beyond bounds ([#9448](https://github.com/warpdotdev/warp/pull/9448))
- Fixed Ctrl-G not closing CLI agent rich input on linux when editor is focused ([#10030](https://github.com/warpdotdev/warp/pull/10030))
- Pressing backspace in the agent view when the buffer is empty no longer resets the conversation. ([#10114](https://github.com/warpdotdev/warp/pull/10114))
- Fixed unnecessary reconnect attempts for remote SSH sessions after system sleep, reducing error noise ([#10096](https://github.com/warpdotdev/warp/pull/10096))
- Fixes issue with repeated TUI redraws for CLI agents on terminal pane resize. ([#9877](https://github.com/warpdotdev/warp/pull/9877))
- Fix new-session "+" dropdown alignment when the Tabs Panel is placed on the right side of the header toolbar. ([#9492](https://github.com/warpdotdev/warp/pull/9492))
- Copy keybinding now prioritizes selected text in the input over a selected block when both are active. ([#9491](https://github.com/warpdotdev/warp/pull/9491))
- [Windows] Fix hotkey window. ([#9891](https://github.com/warpdotdev/warp/pull/9891))
- [Windows] Symlink traversal fixed. ([#9863](https://github.com/warpdotdev/warp/pull/9863))
- Fixed a crash on Windows when handing off a Web conversation to the native client. ([#9987](https://github.com/warpdotdev/warp/pull/9987))
- Fixed a bug where multiple 'open skill' buttons shared hover state. ([#9437](https://github.com/warpdotdev/warp/pull/9437))
- Fixed the OSS Linux desktop entry so WarpOss launches through the packaged `warp-terminal-oss` command. ([#9424](https://github.com/warpdotdev/warp/pull/9424))
- Fixed Ctrl/Cmd shortcuts (e.g. copy, paste) failing on Windows when a non-Latin keyboard layout was active. ([#9476](https://github.com/warpdotdev/warp/pull/9476))
- Fixed background colour bleeding in alt screen programs (e.g. delta, diff-so-fancy). ([#9852](https://github.com/warpdotdev/warp/pull/9852))
- Clip the warping indicator's action chips onto a new line on narrow panes instead of overflowing. ([#9297](https://github.com/warpdotdev/warp/pull/9297))
- Inline `.bmp`, `.tiff` / `.tif`, and `.ico` images in agent block output now render correctly. ([#9397](https://github.com/warpdotdev/warp/pull/9397))
- If user attaches an image in block input we should lock in agent mode, without running the NLD classifier. ([#9366](https://github.com/warpdotdev/warp/pull/9366))
- Remote-server installs no longer fail when the staging-directory cleanup hits a race. ([#9681](https://github.com/warpdotdev/warp/pull/9681))
- `.command` shell scripts now open with shell syntax highlighting in Warp's editor. ([#9345](https://github.com/warpdotdev/warp/pull/9345))
- Fix git diff chip flickering between tracked-only and all-files count when untracked files are present ([#9244](https://github.com/warpdotdev/warp/pull/9244))
- `Open File → Default App` now opens files in the running Warp channel instead of routing to a different installed Warp. ([#9285](https://github.com/warpdotdev/warp/pull/9285))
- Fixed vertical tabs settings popup items being unclickable ([#9540](https://github.com/warpdotdev/warp/pull/9540))
- Fixed a macOS memory leak that occurred when Warp enumerated system fonts or built a font fallback chain. ([#9665](https://github.com/warpdotdev/warp/pull/9665))
- Executable shell scripts opened from a `file://` URL now run in the terminal instead of opening in the editor. ([#9503](https://github.com/warpdotdev/warp/pull/9503))
- Fixed Option+Enter, Option+Tab, and Option+Escape sending literal key names instead of correct escape sequences ([#9514](https://github.com/warpdotdev/warp/pull/9514))
- Fixed read_files tool showing an empty box when the LLM requests line ranges beyond the end of a file. ([#9326](https://github.com/warpdotdev/warp/pull/9326))
- Prevent Warp from consuming too much memory when identifying filepaths in long block outputs. ([#9617](https://github.com/warpdotdev/warp/pull/9617))
- Don't trigger the agent onboarding tutorial when Warp is running in headless SDK/CLI mode. ([#9590](https://github.com/warpdotdev/warp/pull/9590))
- Added `--version` flag support in the Oz CLI ([#9252](https://github.com/warpdotdev/warp/pull/9252))
- Fixed file tree flickering when transitioning to an SSH remote session ([#9320](https://github.com/warpdotdev/warp/pull/9320))
- Fixed scroll-to-start/end of selected block keybinding not working when the input is focused. ([#9332](https://github.com/warpdotdev/warp/pull/9332))
- Fix the terminal pane background appearing darker in horizontal tabs mode with background image or custom opacity. ([#9474](https://github.com/warpdotdev/warp/pull/9474))
- AI code blocks tagged `vue`, `xml`, `dockerfile`, `jsx`, `tsx`, etc. now render with syntax highlighting. ([#9471](https://github.com/warpdotdev/warp/pull/9471))
- Reopen Closed Session is now reachable from the new-session menu on Linux and Windows. ([#9347](https://github.com/warpdotdev/warp/pull/9347))
- Fixed missing syntax highlighting for C++ header files using `.hpp`, `.hxx`, or `.H` extensions. ([#9388](https://github.com/warpdotdev/warp/pull/9388))
- Fixed `/open-file` handling for relative WSL paths so Unix separators are preserved. ([#9322](https://github.com/warpdotdev/warp/pull/9322))
## Oz Updates
- Add Codex as a supported harness for local child agents. ([#10176](https://github.com/warpdotdev/warp/pull/10176))
- Configurable max context window per profile. ([#9352](https://github.com/warpdotdev/warp/pull/9352))
---
## Community
### Contributors
- @Abdalla-Eldoumani ✨
- @Akeuuh — [#9655](https://github.com/warpdotdev/warp/pull/9655) ✨
- @AntonVishal
- @BennyWaitWhat
- @Faizanq
- @JamieMcMillan
- @R3flector
- @amriksingh0786
- @princepal9120
- @webdevtodayjason
- @zerone0x
### Issue Reporters
Thanks to the community members who reported issues fixed in this release:
- @user123 — [#5678](https://github.com/warpdotdev/warp/issues/5678) "Crash when opening large file"
---
*This draft was generated by the `changelog-draft` Oz skill. Needs Review and Skipped PRs are available in the JSON audit artifact.*
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""Build a Slack Block Kit payload from release-pipeline changelog JSON."""
from __future__ import annotations
import argparse
import html
import json
import re
import sys
from pathlib import Path
from typing import Iterable
from urllib.parse import quote
MAX_SECTION_TEXT_LENGTH = 3000
MAX_MESSAGE_BLOCKS = 50
SECTION_ORDER = (
("newFeatures", "New Features"),
("improvements", "Improvements"),
("bugFixes", "Bug Fixes"),
("images", "Image"),
# Keep the existing label stable for compatibility with recent Slack posts.
("oz_updates", "oz_updates"),
)
MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\((https?://[^)\s]+)\)")
SLACK_LINK_URL_SAFE_CHARS = "/:?&=#%+~@!$'()*;,[]"
def escape_slack_text(text: str) -> str:
"""Escape Slack control characters in ordinary mrkdwn text."""
return html.escape(text, quote=False)
def escape_slack_link_url(url: str) -> str:
"""Percent-encode Slack link delimiters before embedding a URL in mrkdwn."""
return quote(url, safe=SLACK_LINK_URL_SAFE_CHARS)
def slack_link(url: str, label: str) -> str:
"""Build a Slack mrkdwn link from an already-validated URL and label."""
return f"<{escape_slack_link_url(url)}|{escape_slack_text(label)}>"
def markdown_links_to_slack(text: str) -> str:
"""Convert standard Markdown links to Slack mrkdwn links."""
parts: list[str] = []
last_end = 0
for match in MARKDOWN_LINK_RE.finditer(text):
parts.append(escape_slack_text(text[last_end : match.start()]))
label, url = match.groups()
parts.append(slack_link(url, label))
last_end = match.end()
parts.append(escape_slack_text(text[last_end:]))
return "".join(parts)
def slack_lines(changelog: dict) -> list[str]:
"""Render non-empty changelog sections as Slack mrkdwn lines."""
lines: list[str] = []
for key, title in SECTION_ORDER:
values = changelog.get(key, [])
if not isinstance(values, list) or not values:
continue
lines.append(f"*{title}*")
for value in values:
lines.append(f"{markdown_links_to_slack(str(value))}")
return lines
def split_overlong_line(line: str) -> Iterable[str]:
"""Split a pathological line so every Slack section remains valid."""
while len(line) > MAX_SECTION_TEXT_LENGTH - 1:
yield line[: MAX_SECTION_TEXT_LENGTH - 1]
line = line[MAX_SECTION_TEXT_LENGTH - 1 :]
yield line
def chunk_lines(lines: list[str]) -> list[str]:
"""Split text into section-sized chunks while retaining copy boundaries."""
chunks: list[str] = []
buffer = ""
for raw_line in lines:
for line in split_overlong_line(raw_line):
candidate = line if not buffer else f"{buffer}\n{line}"
# Reserve a character for a trailing newline. Keeping it in each
# section preserves a separator when Slack copies adjacent blocks.
if len(candidate) + 1 > MAX_SECTION_TEXT_LENGTH:
chunks.append(f"{buffer}\n")
buffer = line
else:
buffer = candidate
if buffer:
chunks.append(f"{buffer}\n")
return chunks
def artifact_link_block(markdown_artifact_url: str) -> dict | None:
"""Build a Slack block linking to the downloadable Markdown artifact."""
if not markdown_artifact_url:
return None
return {
"type": "section",
"text": {
"type": "mrkdwn",
"text": (
"Raw Markdown changelog: "
f"{slack_link(markdown_artifact_url, 'Download raw Markdown changelog artifact')}"
),
},
}
def build_payload(
changelog: dict, release_tag: str, markdown_artifact_url: str = ""
) -> dict:
"""Build a Block Kit message and reject payloads Slack cannot accept."""
chunks = chunk_lines(slack_lines(changelog))
if not chunks:
return {"blocks": []}
blocks = [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"Changelog for {release_tag}",
},
}
]
artifact_block = artifact_link_block(markdown_artifact_url)
if artifact_block is not None:
blocks.append(artifact_block)
blocks.extend(
{
"type": "section",
"expand": True,
"text": {"type": "mrkdwn", "text": chunk},
}
for chunk in chunks
)
if len(blocks) > MAX_MESSAGE_BLOCKS:
raise ValueError(
"Slack payload would require "
f"{len(blocks)} blocks, exceeding Slack's {MAX_MESSAGE_BLOCKS}-block limit"
)
return {"blocks": blocks}
def main() -> None:
parser = argparse.ArgumentParser(
description="Build Slack payload JSON from changelog release JSON"
)
parser.add_argument("--input", required=True, help="Path to changelog JSON")
parser.add_argument("--release-tag", required=True, help="Release tag header text")
parser.add_argument(
"--markdown-artifact-url",
default="",
help="Download URL for the raw Markdown changelog artifact",
)
parser.add_argument("--output", required=True, help="Payload JSON output path")
args = parser.parse_args()
with open(args.input) as f:
changelog = json.load(f)
payload = build_payload(changelog, args.release_tag, args.markdown_artifact_url)
Path(args.output).write_text(json.dumps(payload, separators=(",", ":")) + "\n")
print(f"Built Slack payload with {len(payload['blocks'])} blocks", file=sys.stderr)
if __name__ == "__main__":
main()
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Classify GitHub usernames as internal, external, or bot.
Uses `gh api` to check org membership — stdlib only, no pip deps.
Usage:
python3 classify_contributors.py --org warpdotdev --authors user1,user2,user3
Outputs JSON to stdout.
"""
import argparse
import json
import subprocess
import sys
KNOWN_BOTS = frozenset(
{
"dependabot",
"dependabot[bot]",
"renovate",
"renovate[bot]",
"github-actions",
"github-actions[bot]",
"codecov",
"codecov[bot]",
"warp-bot",
"warp-bot[bot]",
}
)
def run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(cmd, capture_output=True, text=True, check=check)
def check_org_membership(org: str, username: str) -> str:
"""Check if a user is a member of the given GitHub org via gh api.
Returns:
'internal' if the user is an org member (HTTP 204),
'external' if the user is confirmed not a member (HTTP 404),
'unknown' if the check failed due to auth/permission issues.
"""
result = run(
["gh", "api", f"orgs/{org}/members/{username}", "--silent"],
check=False,
)
if result.returncode == 0:
return "internal"
# Distinguish auth failures from genuine "not a member" responses.
# gh api exits non-zero for both 404 (not a member) and 403/401 (no
# read:org scope). Only treat an explicit 404 as "external";
# everything else (network errors, rate limits, auth issues) is "unknown"
# to avoid publicly crediting internal or unverified users.
stderr = result.stderr.lower()
if "404" in stderr:
return "external"
return "unknown"
def main() -> None:
parser = argparse.ArgumentParser(description="Classify contributor types")
parser.add_argument("--org", required=True, help="GitHub org to check membership")
parser.add_argument(
"--authors",
required=True,
help="Comma-separated list of GitHub usernames",
)
args = parser.parse_args()
authors = [a.strip() for a in args.authors.split(",") if a.strip()]
internal: list[str] = []
external: list[str] = []
bot: list[str] = []
unknown: list[str] = []
for author in authors:
if author.lower() in KNOWN_BOTS or author.endswith("[bot]"):
bot.append(author)
else:
status = check_org_membership(args.org, author)
if status == "internal":
internal.append(author)
elif status == "unknown":
unknown.append(author)
else:
external.append(author)
output = {"internal": internal, "external": external, "bot": bot, "unknown": unknown}
json.dump(output, sys.stdout, indent=2)
print()
if __name__ == "__main__":
main()
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Convert changelog-draft.json to the release-pipeline-compatible changelog-release.json.
Reads the audit artifact produced by the changelog-draft skill and emits the
flat JSON structure consumed by the create_release workflow (Slack payload
builder + in-app changelog.json step).
Usage:
python3 convert_to_release_json.py --input <changelog-draft.json> --output <changelog-release.json>
The output schema:
{
"newFeatures": ["..."],
"improvements": ["..."],
"bugFixes": ["..."],
"images": ["..."],
"oz_updates": ["..."]
}
"""
import argparse
import json
import sys
# Map from changelog-draft.json category names to release JSON keys.
CATEGORY_MAP = {
"NEW-FEATURE": "newFeatures",
"IMPROVEMENT": "improvements",
"BUG-FIX": "bugFixes",
"OZ": "oz_updates",
"IMAGE": "images",
}
def github_profile_link(username: str) -> str:
"""Format a GitHub username as a markdown profile link."""
return f"[@{username}](https://github.com/{username})"
def format_entry(entry: dict) -> str:
"""Format a single changelog entry as a text line with a PR link.
Includes external contributor attribution when applicable.
"""
text = entry["text"]
pr_number = entry.get("pr_number") or entry.get("number")
url = entry.get("url") or entry.get("pr_url")
link = ""
if url and pr_number:
link = f" ([#{pr_number}]({url}))"
attribution = ""
if entry.get("is_external") and entry.get("author"):
attribution = f"{github_profile_link(entry['author'])}"
return f"{text}{link}{attribution}"
def convert(draft: dict) -> dict:
"""Convert a changelog-draft.json dict to changelog-release.json dict."""
release: dict[str, list[str]] = {
"newFeatures": [],
"improvements": [],
"bugFixes": [],
"images": [],
"oz_updates": [],
}
for entry in draft.get("entries", []):
category = entry.get("category", "")
release_key = CATEGORY_MAP.get(category)
if release_key is None:
continue
if category == "IMAGE":
# IMAGE entries store a URL in "text" — pass through directly.
release["images"].append(entry["text"])
else:
release[release_key].append(format_entry(entry))
return release
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert changelog-draft.json to changelog-release.json"
)
parser.add_argument(
"--input",
required=True,
help="Path to changelog-draft.json",
)
parser.add_argument(
"--output",
required=True,
help="Path to write changelog-release.json",
)
args = parser.parse_args()
with open(args.input) as f:
draft = json.load(f)
release = convert(draft)
with open(args.output, "w") as f:
json.dump(release, f, indent=2)
f.write("\n")
# Summary to stdout for CI logs
for key, items in release.items():
print(f" {key}: {len(items)} entries")
if __name__ == "__main__":
main()
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Extract RELEASE_FLAGS, PREVIEW_FLAGS, and DOGFOOD_FLAGS from warp_features.
Parses crates/warp_features/src/lib.rs to find the const arrays and extracts
the FeatureFlag variant names. Stdlib only, no pip deps.
Usage:
python3 extract_feature_flags.py --file crates/warp_features/src/lib.rs
Outputs JSON to stdout.
"""
import argparse
import json
import re
import sys
def extract_flag_list(source: str, const_name: str) -> list[str]:
"""Extract FeatureFlag variant names from a const array definition."""
# Match: pub const CONST_NAME: &[FeatureFlag] = &[ ... ];
pattern = rf"pub\s+const\s+{re.escape(const_name)}\s*:\s*&\[FeatureFlag\]\s*=\s*&\[(.*?)\];"
m = re.search(pattern, source, re.DOTALL)
if not m:
return []
block = m.group(1)
# Extract FeatureFlag::VariantName entries, ignoring #[cfg(...)] attributes
variants = re.findall(r"FeatureFlag::(\w+)", block)
return variants
def main() -> None:
parser = argparse.ArgumentParser(description="Extract feature flag gate lists")
parser.add_argument(
"--file",
required=True,
help="Path to warp_features lib.rs",
)
args = parser.parse_args()
with open(args.file) as f:
source = f.read()
output = {
"release_flags": extract_flag_list(source, "RELEASE_FLAGS"),
"preview_flags": extract_flag_list(source, "PREVIEW_FLAGS"),
"dogfood_flags": extract_flag_list(source, "DOGFOOD_FLAGS"),
}
json.dump(output, sys.stdout, indent=2)
print()
if __name__ == "__main__":
main()
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""Fetch the original reporters for GitHub issues linked to PRs in a release.
Uses `gh` CLI (must be authenticated) — stdlib only, no pip deps.
Usage:
python3 fetch_issue_reporters.py --repo warpdotdev/warp --issues 1234,5678,9012
Outputs JSON to stdout mapping issue numbers to reporter info.
"""
import argparse
import json
import subprocess
import sys
def run(cmd: list[str], *, check: bool = True) -> str:
result = subprocess.run(cmd, capture_output=True, text=True, check=check)
return result.stdout.strip()
def run_full(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(cmd, capture_output=True, text=True, check=check)
def is_org_member(org: str, username: str) -> bool:
"""Check if a user is a member of the given GitHub org.
Returns True for members (HTTP 204), False for non-members (HTTP 404),
and True (conservative) for auth failures so internal users aren't
misattributed as external.
"""
result = run_full(
["gh", "api", f"orgs/{org}/members/{username}", "--silent"],
check=False,
)
if result.returncode == 0:
return True
stderr = result.stderr.lower()
# Auth failure — be conservative, treat as internal
if "403" in stderr or "401" in stderr or "saml" in stderr:
return True
return False
def fetch_issue_reporter(repo: str, issue_number: int) -> dict | None:
"""Fetch the reporter (author) of a GitHub issue via gh CLI."""
raw = run(
[
"gh",
"issue",
"view",
str(issue_number),
"--repo",
repo,
"--json",
"number,title,author,url",
],
check=False,
)
if not raw:
return None
try:
data = json.loads(raw)
except json.JSONDecodeError:
return None
author = ""
if isinstance(data.get("author"), dict):
author = data["author"].get("login", "")
elif isinstance(data.get("author"), str):
author = data["author"]
return {
"issue_number": data.get("number", issue_number),
"title": data.get("title", ""),
"reporter": author,
"reporter_url": f"https://github.com/{author}" if author else "",
"url": data.get("url", ""),
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Fetch issue reporters for linked issues"
)
parser.add_argument("--repo", required=True, help="GitHub repo (owner/name)")
parser.add_argument(
"--org",
required=False,
default="",
help="GitHub org to filter out internal reporters (e.g. warpdotdev)",
)
parser.add_argument(
"--issues",
required=True,
help="Comma-separated issue numbers",
)
args = parser.parse_args()
issue_numbers = [
int(n.strip()) for n in args.issues.split(",") if n.strip().isdigit()
]
org = args.org
reporters: list[dict] = []
seen_reporters: set[str] = set()
for num in issue_numbers:
info = fetch_issue_reporter(args.repo, num)
if not info or not info["reporter"]:
continue
username = info["reporter"]
# Skip internal org members when --org is provided
if org and username not in seen_reporters and is_org_member(org, username):
seen_reporters.add(username)
continue
if username not in seen_reporters:
seen_reporters.add(username)
reporters.append(info)
json.dump({"issue_reporters": reporters}, sys.stdout, indent=2)
print() # trailing newline
if __name__ == "__main__":
main()
@@ -0,0 +1,358 @@
#!/usr/bin/env python3
"""Fetch PRs merged in a release range and extract explicit CHANGELOG markers.
Uses `gh` CLI (must be authenticated) and `git` — stdlib only, no pip deps.
Usage:
python3 fetch_prs.py --repo warpdotdev/warp --base-ref <prev_tag> --head-ref <release_tag>
Outputs JSON to stdout.
"""
import argparse
import json
import re
import subprocess
import sys
# Matches lines like: CHANGELOG-NEW-FEATURE: Added dark mode
MARKER_RE = re.compile(
r"^CHANGELOG-(NEW-FEATURE|IMPROVEMENT|BUG-FIX|IMAGE|OZ|NONE)\s*:?\s*(.*)$",
re.MULTILINE,
)
# Matches issue-closing keywords: Fixes #123, Closes #456, Resolves #789
LINKED_ISSUE_RE = re.compile(
r"(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)",
re.IGNORECASE,
)
PUBLIC_REPO = "warpdotdev/warp"
INTERNAL_REPO = "warpdotdev/warp-internal"
REPO_SYNC_AUTHORS = frozenset(
{
"app/warp-repo-sync",
"warp-repo-sync",
"warp-repo-sync[bot]",
}
)
PUBLIC_PR_URL_RE = re.compile(r"https://github\.com/warpdotdev/warp/pull/(\d+)")
def run(cmd: list[str], *, check: bool = True) -> str:
result = subprocess.run(cmd, capture_output=True, text=True, check=check)
return result.stdout.strip()
def get_commits(base_ref: str, head_ref: str) -> list[str]:
"""Return SHAs of first-parent commits between base and head."""
log = run(
[
"git",
"log",
"--first-parent",
"--format=%H",
f"{base_ref}..{head_ref}",
]
)
if not log:
return []
return log.splitlines()
def extract_pr_number(sha: str) -> int | None:
"""Extract PR number from a squash-merge commit subject line.
Expects the GitHub squash format: 'feat: something (#1234)'.
Matches the trailing parenthesized (#N) to avoid grabbing issue
numbers from titles like 'Fixes #123 (#456)'.
"""
msg = run(["git", "log", "-1", "--format=%s", sha])
# Match the last (#N) in the subject — GitHub always appends the PR number
m = re.search(r"\(#(\d+)\)\s*$", msg)
if m:
return int(m.group(1))
# Fallback: first bare #N (for non-standard subjects)
m = re.search(r"#(\d+)", msg)
if m:
return int(m.group(1))
return None
def get_merged_commits(sha: str) -> list[str]:
"""For a merge commit, return the SHAs brought in by the merge.
A merge commit has two parents: the first parent is the mainline, the
second parent is the tip of the merged branch. The commits unique to
the merge are those reachable from the second parent but not the first.
Returns an empty list for non-merge commits.
"""
parents = run(["git", "log", "-1", "--format=%P", sha]).split()
if len(parents) < 2:
return []
log = run(
["git", "log", "--format=%H", f"{parents[0]}..{parents[1]}"],
check=False,
)
if not log:
return []
return log.splitlines()
def fetch_pr_data(repo: str, pr_number: int) -> dict | None:
"""Fetch PR metadata and changed file paths via gh CLI."""
fields = "number,title,author,body,labels,mergedAt,files,url"
raw = run(
["gh", "pr", "view", str(pr_number), "--repo", repo, "--json", fields],
check=False,
)
if not raw:
return None
try:
return json.loads(raw)
except json.JSONDecodeError:
return None
def fetch_pr_commit_messages(repo: str, pr_number: int) -> list[str]:
"""Fetch commit messages for a PR via the GitHub API."""
raw = run(
["gh", "api", f"repos/{repo}/pulls/{pr_number}/commits"],
check=False,
)
if not raw:
return []
try:
commits = json.loads(raw)
except json.JSONDecodeError:
return []
messages = []
for commit in commits:
if not isinstance(commit, dict):
continue
commit_data = commit.get("commit")
if isinstance(commit_data, dict):
message = commit_data.get("message")
if message:
messages.append(message)
return messages
def get_author_login(data: dict) -> str:
"""Extract a GitHub login from a gh PR JSON object."""
if isinstance(data.get("author"), dict):
return data["author"].get("login", "")
if isinstance(data.get("author"), str):
return data["author"]
return ""
def get_label_names(data: dict) -> list[str]:
"""Extract label names from a gh PR JSON object."""
label_names = []
for lbl in data.get("labels", []) or []:
if isinstance(lbl, dict):
label_names.append(lbl.get("name", ""))
else:
label_names.append(str(lbl))
return label_names
def get_file_paths(data: dict) -> list[str]:
"""Extract changed file paths from a gh PR JSON object."""
file_paths = []
for f in data.get("files", []) or []:
if isinstance(f, dict):
file_paths.append(f.get("path", ""))
return file_paths
def is_repo_sync_pr(data: dict) -> bool:
"""Return whether this PR was created by the public-to-internal repo sync bot."""
return get_author_login(data) in REPO_SYNC_AUTHORS
def should_include_pr(repo: str, data: dict) -> bool:
"""Return whether a PR should be exposed to changelog generation.
Releases are cut from warp-internal, but non-sync-bot PRs merged there are
private/internal changes. Do not expose them to the Oz changelog agent or to
generated artifacts.
"""
return repo != INTERNAL_REPO or is_repo_sync_pr(data)
def extract_public_pr_number(text: str) -> int | None:
"""Extract a public warpdotdev/warp PR number from text."""
if not text:
return None
m = PUBLIC_PR_URL_RE.search(text)
if m:
return int(m.group(1))
# Repo-sync commits commonly preserve the original public squash-merge
# subject, such as "feat: add thing (#1234)".
m = re.search(r"\(#(\d+)\)\s*$", text.splitlines()[0] if text else "")
if m:
return int(m.group(1))
return None
def resolve_public_pr_number(repo: str, pr_number: int, data: dict) -> int | None:
"""Resolve a repo-sync PR back to its original public warpdotdev/warp PR."""
public_pr_number = extract_public_pr_number(data.get("body", "") or "")
if public_pr_number is not None:
return public_pr_number
for message in fetch_pr_commit_messages(repo, pr_number):
public_pr_number = extract_public_pr_number(message)
if public_pr_number is not None:
return public_pr_number
return None
def pr_reference(repo: str, pr_number: int, data: dict) -> dict:
"""Build a compact audit reference to a PR."""
return {
"number": data.get("number", pr_number),
"url": data.get("url", ""),
"author": get_author_login(data),
"title": data.get("title", ""),
"repo": repo,
}
def normalize_pr_data(repo: str, pr_number: int, data: dict) -> tuple[str, dict, dict | None]:
"""Resolve repo-sync PRs to public PR metadata.
The release workflow runs from warp-internal, where public PRs are mirrored
as warp-repo-sync[bot] PRs with different PR numbers. For changelog output
and contributor attribution, use the original public PR metadata when it can
be resolved, and keep the internal PR under `internal_pr` for audit only.
"""
internal_pr = pr_reference(repo, pr_number, data) if repo != PUBLIC_REPO else None
if repo == PUBLIC_REPO or not is_repo_sync_pr(data):
return repo, data, internal_pr
public_pr_number = resolve_public_pr_number(repo, pr_number, data)
if public_pr_number is None:
return repo, data, internal_pr
public_data = fetch_pr_data(PUBLIC_REPO, public_pr_number)
if public_data is None:
return repo, data, internal_pr
return PUBLIC_REPO, public_data, internal_pr
def extract_linked_issues(body: str) -> list[int]:
"""Extract issue numbers from closing keywords in a PR body."""
if not body:
return []
return sorted(set(int(m.group(1)) for m in LINKED_ISSUE_RE.finditer(body)))
def strip_html_comments(text: str) -> str:
"""Remove HTML comment blocks (<!-- ... -->) from text.
This prevents template placeholders inside HTML comments from being
parsed as real CHANGELOG markers.
"""
return re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
def extract_markers(body: str) -> list[dict]:
"""Extract CHANGELOG-* markers from a PR body."""
if not body:
return []
# Strip HTML comments so template placeholders aren't treated as real markers
cleaned = strip_html_comments(body)
entries = []
has_opt_out = False
for m in MARKER_RE.finditer(cleaned):
category = m.group(1)
text = m.group(2).strip()
# CHANGELOG-NONE is an explicit opt-out — skip all other markers
if category == "NONE":
has_opt_out = True
continue
# Skip template placeholders
if text.startswith("{{") or text.startswith("{text") or not text:
continue
entries.append({"category": category, "text": text})
# If the PR explicitly opted out, return a special marker
if has_opt_out:
return [{"category": "NONE", "text": ""}]
return entries
def main() -> None:
parser = argparse.ArgumentParser(description="Fetch PRs in a release range")
parser.add_argument("--repo", required=True, help="GitHub repo (owner/name)")
parser.add_argument("--base-ref", required=True, help="Previous release tag")
parser.add_argument("--head-ref", required=True, help="Current release tag")
args = parser.parse_args()
commit_shas = get_commits(args.base_ref, args.head_ref)
seen_prs: set[int] = set()
prs: list[dict] = []
def process_pr(pr_num: int) -> None:
"""Fetch and record a single PR by number."""
data = fetch_pr_data(args.repo, pr_num)
if data is None:
return
if not should_include_pr(args.repo, data):
return
source_repo, data, internal_pr = normalize_pr_data(args.repo, pr_num, data)
author_login = get_author_login(data)
label_names = get_label_names(data)
body = data.get("body", "") or ""
explicit_entries = extract_markers(body)
linked_issues = extract_linked_issues(body)
file_paths = get_file_paths(data)
pr = {
"number": data.get("number", pr_num),
"url": data.get("url", "") if source_repo == PUBLIC_REPO else "",
"title": data.get("title", ""),
"author": author_login,
"body": body,
"labels": label_names,
"merged_at": data.get("mergedAt", ""),
"explicit_entries": explicit_entries,
"linked_issues": linked_issues,
"changed_files": file_paths,
"source_repo": source_repo,
}
if internal_pr is not None:
pr["internal_pr"] = internal_pr
prs.append(pr)
for sha in commit_shas:
pr_num = extract_pr_number(sha)
if pr_num is not None and pr_num not in seen_prs:
# Normal squash-merge commit
seen_prs.add(pr_num)
process_pr(pr_num)
else:
# Merge commit fallback: walk the merged-in commits for PR numbers.
# This handles branches merged via merge commit (e.g. security-patches)
# rather than the usual squash merge.
for merged_sha in get_merged_commits(sha):
inner_pr = extract_pr_number(merged_sha)
if inner_pr is not None and inner_pr not in seen_prs:
seen_prs.add(inner_pr)
process_pr(inner_pr)
output = {
"range": {"base": args.base_ref, "head": args.head_ref},
"prs": prs,
}
json.dump(output, sys.stdout, indent=2)
print() # trailing newline
if __name__ == "__main__":
main()
@@ -0,0 +1,55 @@
---
name: classify-changelog-pr
description: Reference guidance for classifying whether an unmarked PR should appear in the changelog and under which category. Used inline by the changelog-draft skill — not dispatched as a separate agent.
---
# Classify Changelog PR
This document provides classification rules for PRs that lack explicit `CHANGELOG-*` markers. The changelog-draft agent follows these rules inline when deciding whether to include an unmarked PR.
## Categories
- **NEW-FEATURE** — A substantial new user-facing capability. Reserve for features that would warrant docs, marketing, or social media attention.
- **IMPROVEMENT** — Enhances an existing feature in a way users would notice (performance, UX, new options).
- **BUG-FIX** — Fixes a user-visible bug or regression.
- **OZ** — Changes to Oz / AI agent capabilities. At most 4 per release in the stable changelog.
- **NONE** — Explicitly opt out of changelog inclusion. Handled upstream by `fetch_prs.py` marker extraction.
## Decision rules
### Always exclude
- PRs with an explicit `CHANGELOG-NONE` marker (contributor opted out)
- PRs authored by known bots (dependabot, renovate, github-actions, codecov)
- PRs that exclusively modify CI workflows (`.github/workflows/`), test files, or dev tooling
- PRs that only update internal docs, comments, or README files
- Dependency bumps with no user-facing behavior change
- Refactors with no observable behavior change (code moves, renames, formatting)
### Always include
- PRs with explicit `CHANGELOG-*` markers (handled before this guidance applies)
- PRs that fix a crash, data loss, or security issue — even without a marker
### Conditional on channel
- **Stable channel:** Only include changes that are live for all users. Exclude PRs gated behind `DOGFOOD_FLAGS` or `PREVIEW_FLAGS`.
- **Preview channel:** Include PRs gated behind `PREVIEW_FLAGS`. Still exclude `DOGFOOD_FLAGS`-only changes.
- **Dev channel:** Include everything that's user-visible, regardless of flag gates.
### Feature-flagged PRs
If a PR mentions a `FeatureFlag` variant in its diff or title:
1. Check which flag list it belongs to (`RELEASE_FLAGS`, `PREVIEW_FLAGS`, `DOGFOOD_FLAGS`).
2. Apply the channel rules above.
3. If the flag is in `RELEASE_FLAGS` or enabled by default in `app/Cargo.toml`, treat it as live.
4. Set `feature_flag` in the classification output to the flag name.
### Confidence levels
- **high** — Clear user-visible change with obvious category.
- **medium** — Likely user-visible but category or scope is somewhat ambiguous.
- **low** — Unclear whether users would notice; or the PR touches both internal and user-facing code. Set `needs_review: true`.
## Writing changelog text
- Write from the user's perspective: "Added X", "Fixed Y", "Improved Z".
- Keep it to one sentence, ≤ 120 characters.
- Don't reference internal implementation details, file paths, or function names.
- Don't start with "PR" or the PR number — those are added as metadata.
- Use active voice and present tense for new features ("Adds dark mode"), past tense for fixes ("Fixed crash on startup").
+406
View File
@@ -0,0 +1,406 @@
---
name: create-launch-modal
description: Create a one-time launch modal in the Warp client (feature announcement, onboarding, etc.). Use when adding a new modal that should appear exactly once per user on startup, gated by a feature flag, with colors sourced from Warp theme tokens and terminal theme colors.
---
# create-launch-modal
Create a one-time launch modal — the feature-announcement design used for launches like "Orchestrate any agent, anywhere" or "Warp is now open-source."
## Reference implementation
`app/src/workspace/view/orchestration_launch_modal/` — the canonical, most up-to-date example of this pattern.
## Checklist
- [ ] Feature flag in `warp_features/src/lib.rs`
- [ ] Settings field in `app/src/settings/ai.rs`
- [ ] Trigger logic in `app/src/workspace/one_time_modal_model.rs`
- [ ] View files under `app/src/workspace/view/<name>_launch_modal/`
- [ ] Workspace wiring in `app/src/workspace/view.rs` and `app/src/workspace/mod.rs`
- [ ] Debug actions in `app/src/workspace/action.rs`
- [ ] Hero image at `app/assets/async/png/onboarding/<name>_launch_banner.png`
- [ ] Any custom icons added to `crates/warp_core/src/ui/icons.rs` + SVG in `app/assets/bundled/svg/`
---
## Step 0 Custom icons (if needed)
If the modal uses icons not yet in the `Icon` enum, add them before writing the view.
In `crates/warp_core/src/ui/icons.rs`:
```rust
// Add to enum
YourIconName,
// Add to From<Icon> for &'static str match
Icon::YourIconName => "bundled/svg/your-icon-name.svg",
```
Drop the SVG file at `app/assets/bundled/svg/your-icon-name.svg`. Use the same 24×24 viewBox format as existing icons.
---
## Step 1 Feature flag
Add to `crates/warp_features/src/lib.rs`:
```rust
/// Enables the <name> launch modal.
<YourModalName>LaunchModal,
```
Enable for dogfood:
```rust
pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[
FeatureFlag::<YourModalName>LaunchModal,
// ...
];
```
---
## Step 2 Settings field
Add to `app/src/settings/ai.rs` inside `define_settings_group!(AISettings, ...)`.
Pattern: one boolean field per modal, globally synced (not respecting user sync), private.
```rust
// This is not a user-visible setting - it's merely a one-time flag to track if the
// <name> launch modal has been shown to the user.
//
// We model it as a setting so it's only shown once to a given user regardless of the number of
// devices they use.
did_check_to_trigger_<name>_launch_modal: DidShow<Name>LaunchModal {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
private: true,
}
```
---
## Step 3 OneTimeModalModel
File: `app/src/workspace/one_time_modal_model.rs`
### 3a. Add field to struct
```rust
is_<name>_launch_modal_open: bool,
```
### 3b. Initialize to false in `new()`
```rust
is_<name>_launch_modal_open: false,
```
### 3c. Pre-dismiss for new users (critical)
In the `AuthComplete``!is_existing_user` branch, add to the `AISettings::handle` update block alongside the other pre-dismissals. **Without this, new users see the modal on their second startup after onboarding.**
```rust
if let Err(e) = settings
.did_check_to_trigger_<name>_launch_modal
.set_value(true, ctx)
{
log::warn!("Failed to mark <name> launch modal as dismissed: {e}");
}
```
### 3d. Public API methods
```rust
pub fn is_<name>_launch_modal_open(&self) -> bool {
self.is_<name>_launch_modal_open && self.target_window_id.is_some()
}
pub fn mark_<name>_launch_modal_dismissed(&mut self, ctx: &mut ModelContext<Self>) {
self.set_<name>_launch_modal_open(false, ctx);
}
#[cfg(debug_assertions)]
pub fn force_open_<name>_launch_modal(&mut self, ctx: &mut ModelContext<Self>) {
self.set_<name>_launch_modal_open(true, ctx);
}
```
### 3e. Private setter
```rust
fn set_<name>_launch_modal_open(&mut self, is_open: bool, ctx: &mut ModelContext<Self>) -> bool {
if self.is_<name>_launch_modal_open != is_open {
self.is_<name>_launch_modal_open = is_open;
ctx.emit(OneTimeModalEvent::VisibilityChanged { is_open });
return true;
}
false
}
```
### 3f. Add to `is_any_modal_open`
```rust
|| self.is_<name>_launch_modal_open
```
### 3g. Trigger function
```rust
fn check_and_trigger_<name>_launch_modal(&mut self, ctx: &mut ModelContext<Self>) -> bool {
if !FeatureFlag::<Name>LaunchModal.is_enabled() {
return false;
}
let ai_settings = AISettings::as_ref(ctx);
if *ai_settings.did_check_to_trigger_<name>_launch_modal {
return false;
}
AISettings::handle(ctx).update(ctx, |settings, ctx| {
if let Err(e) = settings
.did_check_to_trigger_<name>_launch_modal
.set_value(true, ctx)
{
log::warn!("Failed to mark <name> launch modal as dismissed: {e}");
}
});
let should_show = !matches!(ChannelState::channel(), Channel::Integration);
self.set_<name>_launch_modal_open(should_show, ctx);
should_show
}
```
### 3h. Call from `check_and_trigger_all_modals`
Insert before `check_and_trigger_hoa_onboarding`:
```rust
if self.check_and_trigger_<name>_launch_modal(ctx) {
return;
}
```
---
## Step 4 View
Create `app/src/workspace/view/<name>_launch_modal/mod.rs`:
```rust
mod view;
pub use view::{init, <Name>LaunchModal, <Name>LaunchModalEvent};
```
Create `app/src/workspace/view/<name>_launch_modal/view.rs`. Copy from `orchestration_launch_modal/view.rs` and adapt. Key details:
### Color sources (important)
- Prefer Warp theme tokens for modal backgrounds, text, overlays, and borders:
- background surfaces: `appearance.theme().surface_3()` (or another `surface_*` token when needed)
- primary/subtext: `appearance.theme().main_text_color(...)` and `appearance.theme().sub_text_color(...)`
- overlays/hover fills: `appearance.theme().surface_overlay_1()` / `surface_overlay_2()`
- subtle borders: `appearance.theme().outline()`
- Use terminal theme colors for terminal-color accents (for example, magenta launch badge accents):
- `appearance.theme().terminal_colors().normal.magenta`
- `appearance.theme().ansi_overlay_1(magenta)` for low-alpha backgrounds
- Avoid hardcoded hex colors.
### Hero image
- Store at `app/assets/async/png/onboarding/<name>_launch_banner.png`
- **Aspect ratio matters**: if the image is wider than `MODAL_WIDTH/HERO_HEIGHT` (420/92 ≈ 4.57), wrap the hero `ConstrainedBox` in `Clipped::new(...)` to prevent horizontal bleed when `cover()` scales it
- Images pre-sized to exactly 420×92 need no `Clipped`; images only taller (aspect ratio < 4.57) are fine without it
```rust
const MODAL_WIDTH: f32 = 420.;
const HERO_HEIGHT: f32 = 92.;
const HERO_IMAGE_PATH: &str = "async/png/onboarding/<name>_launch_banner.png";
fn render_hero(&self) -> Box<dyn Element> {
let hero = Clipped::new( // only needed if image ratio > 4.57
ConstrainedBox::new(
Image::new(AssetSource::Bundled { path: HERO_IMAGE_PATH }, CacheOption::Original)
.with_corner_radius(CornerRadius::with_top(Radius::Pixels(8.)))
.cover()
.top_aligned()
.finish(),
)
.with_width(MODAL_WIDTH)
.with_height(HERO_HEIGHT)
.finish(),
)
.finish();
// ... close button overlay via Stack + add_positioned_child
}
```
### "New" badge
Use the standard badge — 24 px tall, 8 px horizontal padding, 14 px font, pill corners, with magenta sourced from terminal theme colors:
```rust
fn render_badge(appearance: &Appearance) -> Box<dyn Element> {
let magenta = appearance.theme().terminal_colors().normal.magenta;
let text = Text::new_inline("New".to_string(), appearance.ui_font_family(), 14.)
.with_color(magenta.into())
.finish();
ConstrainedBox::new(
Container::new(
Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_main_axis_size(MainAxisSize::Min)
.with_child(text)
.finish(),
)
.with_horizontal_padding(8.)
.with_background(Fill::Solid(appearance.theme().ansi_overlay_1(magenta)))
.with_corner_radius(CornerRadius::with_all(Radius::Percentage(50.)))
.finish(),
)
.with_height(24.)
.finish()
}
```
### URLs
Always use `https://`, not `http://`:
```rust
const LEARN_MORE_URL: &str = "https://warp.dev/your-blog-link";
```
---
## Step 5 Workspace wiring
### `app/src/workspace/view.rs`
```rust
// Module declaration (top)
pub(crate) mod <name>_launch_modal;
// Import
use crate::workspace::view::<name>_launch_modal::{<Name>LaunchModal, <Name>LaunchModalEvent};
// Struct field
<name>_launch_modal: ViewHandle<<Name>LaunchModal>,
// In Workspace::new()
let <name>_launch_view = ctx.add_typed_action_view(<Name>LaunchModal::new);
ctx.subscribe_to_view(&<name>_launch_view, |me, _, event, ctx| {
me.handle_<name>_launch_modal_event(event, ctx);
});
// In struct initialization
<name>_launch_modal: <name>_launch_view,
// In OneTimeModalModel subscription handler
} else if model_ref.is_<name>_launch_modal_open() {
me.focus_<name>_launch_modal(ctx);
// In View::render (inside the should_show_modal block)
if should_show_modal && one_time_modal_model.is_<name>_launch_modal_open() {
stack.add_child(ChildView::new(&self.<name>_launch_modal).finish());
}
```
Add event handler and focus helper:
```rust
fn handle_<name>_launch_modal_event(&mut self, event: &<Name>LaunchModalEvent, ctx: &mut ViewContext<Self>) {
match event {
<Name>LaunchModalEvent::Close => {
OneTimeModalModel::handle(ctx).update(ctx, |model, ctx| {
model.mark_<name>_launch_modal_dismissed(ctx);
});
self.focus_active_tab(ctx);
ctx.notify();
}
}
}
fn focus_<name>_launch_modal(&mut self, ctx: &mut ViewContext<Self>) {
ctx.focus(&self.<name>_launch_modal);
}
```
### `app/src/workspace/mod.rs`
```rust
// In pub fn init()
view::<name>_launch_modal::init(app);
// In debug bindings block
EditableBinding::new(
"workspace:open_<name>_launch_modal",
"[Debug] Open <Name> Launch Modal",
WorkspaceAction::Open<Name>LaunchModal,
)
.with_context_predicate(id!("Workspace")),
EditableBinding::new(
"workspace:reset_<name>_launch_modal_state",
"[Debug] Reset <Name> Launch Modal State",
WorkspaceAction::Reset<Name>LaunchModalState,
)
.with_context_predicate(id!("Workspace")),
```
---
## Step 6 Debug actions
In `app/src/workspace/action.rs`:
```rust
/// Open the <Name> Launch Modal (for debugging)
#[cfg(debug_assertions)]
Open<Name>LaunchModal,
/// Reset the <name> launch modal dismissed state (for debugging)
#[cfg(debug_assertions)]
Reset<Name>LaunchModalState,
```
Add both variants to the `is_visible_in_command_palette` `false` arm.
In `app/src/workspace/view.rs` `TypedActionView::handle_action`:
```rust
#[cfg(debug_assertions)]
Open<Name>LaunchModal => {
OneTimeModalModel::handle(ctx).update(ctx, |model, ctx| {
model.force_open_<name>_launch_modal(ctx);
});
ctx.notify();
}
#[cfg(debug_assertions)]
Reset<Name>LaunchModalState => {
AISettings::handle(ctx).update(ctx, |settings, ctx| {
if let Err(e) = settings
.did_check_to_trigger_<name>_launch_modal
.set_value(false, ctx)
{
log::warn!("Failed to reset <name> launch modal state: {e}");
}
});
}
```
---
## Behavior summary
| User type | Sees modal? |
|---|---|
| New signup | No — pre-dismissed in `AuthComplete` new-user branch |
| Not signed in | No — trigger never fires without `AuthComplete` |
| Existing user, flag enabled | Yes — on first startup after cloud prefs load |
| Integration channel | No — suppressed by `Channel::Integration` check |
| Already seen it | No — setting persists globally across devices |
-231
View File
@@ -1,231 +0,0 @@
---
name: create-pr
description: Create a pull request in the warp repository for the current branch. Use when the user mentions opening a PR, creating a pull request, submitting changes for review, or preparing code for merge.
---
# create-pr
## Overview
This guide covers best practices for creating pull requests in the warp repository, including merging master, running presubmit checks, linking Linear tasks, ensuring appropriate test coverage, and structuring your PR for effective review.
## Related Skills
- `fix-errors` - Fix presubmit failures (formatting, linting, tests) before opening PR
- `rust-unit-tests` - Write unit tests for your changes, if applicable (see "Testing Requirements" below)
- `warp-integration-test` - Add or update integration coverage for user-visible flows, regressions, and P0 use cases
- `add-feature-flag` - Gate changes behind feature flags
## Pre-PR Checklist
### 1. Merge master into your feature branch
**Always merge master into your feature branch before starting the review process.**
```bash
git fetch origin
git merge origin/master
```
Resolve any merge conflicts locally before opening the PR.
### 2. Run presubmit checks for code changes
If the PR includes code changes, run the relevant presubmit checks before opening or updating it:
```bash
./script/presubmit
```
`./script/presubmit` runs:
- `cargo fmt` - Code formatting
- `cargo clippy` - Linting with all warnings as errors
- All tests (unit, doc, and integration)
If the PR is documentation-only (for example, skills, markdown, or other non-code content), you do not need to run `cargo fmt` or `cargo clippy` just to open or update the PR.
If presubmit fails for a code-changing PR, use the `fix-errors` skill to resolve issues.
**You must run `cargo fmt` and `cargo clippy` before:**
- Opening a new PR that includes code changes
- Pushing new commits that include code changes to an existing PR branch
- Any reviewed branch update that changes code
### 3. Review your changes
Before creating a PR, review what changes you're about to submit:
```bash
# View commits in your branch (comparing against base branch)
git --no-pager log <base-branch>..HEAD --oneline
# View file statistics for changes
git --no-pager diff <base-branch>...HEAD --stat
# View full diff
git --no-pager diff <base-branch>...HEAD
```
This helps you:
- Verify all intended changes are included
- Catch unintended changes before review
- Write an accurate PR description
- Ensure you're comparing against the correct base branch
- **Tests:** Include tests when required—bug fixes (regression test), algorithmic code (unit tests), UI components (layout test), P0 use cases (integration test). See Testing Requirements below.
### 4. Link to Linear task
When possible, PRs should be associated with a Linear task. Use the Linear MCP tool (if available) to find corresponding issues.
**Branch naming convention:**
Remote branches should be prefixed with your name (e.g., `zheng/feature`, `alice/fix-bug`).
**How to link PRs to Linear:**
Include the issue ID in the PR title (e.g., `[WARP-1234] Add new feature`). Do this **before** creating the PR for automatic linking.
### 5. Open the PR
Use the PR template at `.github/pull_request_template.md` when opening PRs.
Add changelog entries when appropriate using the format at the bottom of the PR template. Some examples:
- Feature: "Global search in files across your current directories. Use CMD-F/CTRL-SHIFT-F to open."
- Improvement: "Added horizontal autoscrolling when jumping to line/column."
- Bug fix: "Fixed session viewer input being cleared when agent runs commands.
**CLI workflow:**
- **Check if PR exists** for current branch:
```bash
gh pr view --json number,url
```
Exit code 0 if PR exists, 1 if not.
- **Create a new PR:**
```bash
# With title and body
gh pr create --title "Title" --body "Description" --draft
# Auto-fill from commits
gh pr create --fill --draft
# Use PR template file
gh pr create --body-file .github/pull_request_template.md --title "Title" --draft
```
Key flags: `--draft` / `-d`, `--fill` / `-f`, `--body-file` / `-F`, `--web` / `-w`
- **Update an existing PR:**
```bash
gh pr edit --title "New title" --body "New body"
gh pr edit --add-reviewer username --add-label bug
```
- **Mark PR ready for review:**
```bash
gh pr ready
```
### 6. Include co-author attribution
When committing changes or creating a PR, include attribution at the end of every commit message or PR description:
```
Co-Authored-By: Warp <agent@warp.dev>
```
## Testing Requirements
### Bug fixes require regression tests
**All bug fixes should be accompanied by a regression test.** This helps prevent re-breaking something that was already broken once.
The test should:
- Reproduce the original bug (would fail before the fix)
- Pass after the fix is applied
- Be clearly named to indicate what bug it's preventing
### Algorithmic code requires unit tests
Code with non-trivial logic should have unit tests to validate functionality:
**Examples of what needs unit tests:**
- Custom data structures (e.g., `SumTree`)
- Search-related APIs that should return expected results for a given query
- Core layout code in the UI framework
- Any algorithmic or computational logic
**Not required for:**
- Sufficiently-simple functions
- Trivial getters/setters
See the `rust-unit-tests` skill for guidance on writing unit tests.
### UI components need layout validation tests
**All UI components (implementations of `View`) should have a simple unit test** to validate that they can be laid out without a panic.
This provides high-level coverage over rendering "safety" (though not "correctness"):
```rust
#[test]
fn test_component_can_layout() {
use warpui::App;
use warp::test_util::{terminal::initialize_app_for_terminal_view, add_window_with_terminal};
App::test((), |mut app| async move {
initialize_app_for_terminal_view(&mut app);
let term = add_window_with_terminal(&mut app, None);
// Render the component - should not panic
term.update(&mut app, |view, ctx| {
// Create and layout your component
});
})
}
```
### Ask before skipping integration coverage
If the PR changes a user-visible flow, fixes an end-to-end regression, or otherwise looks like it would benefit from integration coverage, use the `ask_user_question` tool before creating or updating the PR to ask whether the user wants an integration test added as part of the work.
Prefer a direct choice such as:
- `Yes, add an integration test before creating the PR`
- `No, continue without an integration test`
If the user chooses to add one, use the `warp-integration-test` skill.
### P0 use cases require integration tests
**All "P0 use cases" require an integration test** that covers the behavior/flow in question.
**A "P0 use case" is defined as:** Any behavior of the application that, if broken, warrants an out-of-band release.
Integration tests should:
- Exercise the full user-facing flow
- Validate end-to-end functionality
- Be placed in the `integration/` directory
Use the `warp-integration-test` skill for implementation details, test registration steps, and validation workflow.
## PR Description Guidelines
Your PR summary under the "Description" section should include:
1. **What** - What changes are being made
2. **Why** - Why these changes are necessary (link to Linear task if applicable)
3. **How** - Brief explanation of the approach taken
## After Opening the PR
1. **Monitor CI checks** - Ensure all automated checks pass
2. **Respond to review comments** - Address feedback promptly
3. **Keep the PR up to date** - Merge master if conflicts arise
4. **Re-run relevant validation** - After making changes based on review feedback. For code changes, re-run `cargo fmt`/`cargo clippy` (and other relevant checks); for documentation-only changes, this is not required.
## Best Practices
- **Keep PRs focused** - One logical change per PR when possible
- **Write clear commit messages** - Explain what and why, not just what
- **Self-review first** - Review your own diff before requesting review
- **Update tests** - Ensure test coverage reflects your changes
- **Document breaking changes** - Call out any API changes or breaking modifications
- **Use feature flags** - Gate risky changes behind feature flags when appropriate (see the `add-feature-flag` skill)
+7 -2
View File
@@ -1,10 +1,15 @@
---
name: dedupe-issue-local
specializes: dedupe-issue
description: Repo-specific dedupe guidance for warp-external. Only the categories declared overridable by the core dedupe-issue skill may be specialized here.
specializes_source: warpdotdev/oz-for-oss:.agents/skills/dedupe-issue
description: Repo-specific dedupe guidance for warp. Only the categories declared overridable by the core dedupe-issue skill may be specialized here.
---
# Repo-specific dedupe guidance for `warp-external`
# Repo-specific dedupe guidance for `warp`
## Prerequisite: install the parent skill
This skill specializes the core `dedupe-issue` skill (named in the `specializes` frontmatter field) and is not functional on its own. Before applying its guidance, confirm the parent skill is installed and resolvable at `.agents/skills/dedupe-issue/SKILL.md`. If it is missing, install it first by copying the skill directory from the source declared in the `specializes_source` frontmatter field (`warpdotdev/oz-for-oss:.agents/skills/dedupe-issue`). Then continue with the guidance below.
This file is a companion to the core `dedupe-issue` skill. It does not
redefine the duplicate-detection algorithm, the similarity thresholds,
@@ -1,112 +0,0 @@
---
name: diagnose-ci-failures
description: Diagnose CI failures for a PR using the GitHub CLI, extract error logs, and generate a plan to fix them. Use when the user asks to check CI status, pull CI issues, triage test failures, or investigate PR build failures.
---
# diagnose-ci-failures
Programmatically diagnose CI failures for a PR and generate a plan to fix them.
## Overview
This skill provides a deterministic workflow to check CI status for a PR, extract failure logs, analyze errors, and create a plan (not code changes) to resolve issues. The output is always a plan document that can be reviewed before execution.
## Workflow
### 1. Verify PR exists for current branch
Get the current branch and check if a PR exists:
```bash
# Get current branch
git branch --show-current
# Check for PR
gh --no-pager pr view <branch-name> --json number,title,url,state
```
If no PR exists, inform the user and offer to create one using the `create-pr` skill.
### 2. Check CI status
Fetch the status of all CI checks:
```bash
gh pr view <branch-name> --json statusCheckRollup
```
Parse the output to identify:
- Completed checks vs. in-progress checks
- Successful checks
- Failed checks with their names and details URLs
If CI is still running, inform the user which checks have already failed or passed, highlight the checks that are still running, and suggest waiting for completion before diagnosis.
### 3. Extract failure logs
For each failed check, pull the logs using the run ID from the status check:
```bash
gh run view <run-id> --log-failed
```
Focus on extracting:
- Error messages and their locations (file paths, line numbers)
- Compilation errors (unused imports, type mismatches, etc.)
- Linting/clippy errors with specific lint names
- Test failure messages and stack traces
- Build failures and their root causes
### 4. Categorize errors
Group errors by type:
- **Formatting issues**: `cargo fmt` failures
- **Linting issues**: `cargo clippy` warnings/errors
- **Compilation errors**: Type errors, missing imports, signature mismatches
- **Test failures**: Failing tests with their names and failure reasons
- **Platform-specific issues**: WASM, Linux, macOS, Windows-specific failures
### 5. Generate fix plan
Create a plan document (using `create_plan` tool) with:
- **Problem Statement**: Summary of failing checks
- **Current State**: What errors were found and where
- **Proposed Changes**: Specific fixes needed for each error category
- **Validation Steps**: Commands to verify fixes (fmt, clippy, tests, presubmit)
The plan should reference the `fix-errors` skill for detailed guidance on resolving specific error types.
## Important Notes
- **Always create a plan first**: Never make code changes directly. Generate a plan for user review
- **Check test status in CI**: Even if tests fail locally, verify they passed in CI before flagging as issues
- **Unrelated test failures**: If tests passed in CI but fail locally, they may be environment-specific or flaky
- **Multiple error types**: Fix one category at a time (e.g., all clippy errors before tests)
- **Cross-reference fix-errors skill**: For detailed error resolution strategies, use the `fix-errors` skill
## Common CI Check Names
- `Formatting + Clippy (MacOS)`
- `Formatting + Clippy (Linux)`
- `Run MacOS tests`
- `Run Linux tests`
- `Run Windows tests`
- `Check CI results` (summary check)
- `WASM build`
## Example Commands
**Get PR status with details:**
```bash
gh --no-pager pr view --json number,title,state,statusCheckRollup
```
**Get logs from specific failed run:**
```bash
gh run view 12345678 --log-failed
```
**Check for specific error in logs:**
```bash
gh run view 12345678 --log-failed 2>&1 | grep -A 5 "error:"
```
-180
View File
@@ -1,180 +0,0 @@
---
name: fix-errors
description: Fix compilation errors, linting issues, and test failures in the warp Rust codebase. Covers presubmit checks, WASM-specific errors, and running specific tests. Use when the user hits build errors, clippy or fmt failures, test failures, or needs to run or interpret presubmit before a PR.
---
# fix-errors
Fix compilation errors, linting issues, and test failures in the warp Rust codebase.
## Overview
This skill helps resolve common issues encountered during development, including:
- Compilation errors (unused imports, type mismatches, etc.)
- Linting failures (clippy warnings)
- Formatting violations
- WASM-specific errors
- Test failures
Before opening or updating a pull request, all presubmit checks must pass.
## Presubmit Checks
Run all presubmit checks at once:
```bash
./script/presubmit
```
This runs formatting, linting, and all tests. If it passes, you're ready to open a PR.
### Individual Checks
Run checks separately when debugging specific issues:
**Rust formatting:**
```bash
cargo fmt -- --check
```
**Clippy (full workspace):**
```bash
cargo clippy --workspace --exclude warp_completer --all-targets --all-features --tests -- -D warnings
cargo clippy -p warp_completer --all-targets --tests -- -D warnings
```
**WASM Clippy:**
```bash
cargo clippy --target wasm32-unknown-unknown --profile release-wasm-debug_assertions --no-deps
```
**Objective-C/C/C++ formatting:**
```bash
./script/run-clang-format.py -r --extensions 'c,h,cpp,m' ./crates/warpui/src/ ./app/src/
```
**All tests:**
```bash
cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
cargo nextest run -p warp_completer --features v2
```
**Doc tests:**
```bash
cargo test --doc
```
## Running Specific Tests
**Single package:**
```bash
cargo nextest run -p <package_name>
```
**Filter by test name:**
```bash
cargo nextest run -E 'test(<substring>)'
```
**Specific package with filter:**
```bash
cargo nextest run -p <package_name> -E 'test(<substring>)'
```
**With output (no capture):**
```bash
cargo nextest run -p <package> --nocapture
```
## Common Error Types
### Unused Imports
Remove unused `use` statements identified by the compiler.
### Unused Constants
Remove constants that are defined but never used.
### Unknown Imports
Add the correct `use` statement for undefined types. Search the codebase to find the correct module path.
### Type Mismatches
Update function calls to pass arguments of the correct type. Common fixes:
- Use `.as_str()` instead of `.clone()` when a `&str` is expected
- Use `&value` when a reference is needed
- Use `.to_string()` when `String` is expected but `&str` is provided
### Struct Field Changes
When a struct adds/removes fields, update all places where it's constructed or destructured:
- Struct initialization
- Pattern matching (`match`, `if let`)
- Destructuring assignments
### Function Signature Changes
When a function adds a new parameter, update all call sites to provide the new argument:
- For `bool` params: pass `true` or `false` based on context
- For `Option<T>` params: pass `None` as default or `Some(value)` if needed
### Enum Variant Changes
When adding a new enum variant, update exhaustive `match` statements:
- Add a new match arm with appropriate handling
- Mirror the implementation pattern of similar variants
### Incorrect Trait Implementation
Fix trait implementations that return the wrong type or don't satisfy trait bounds.
### WASM-Specific Errors
WASM builds (`wasm32-unknown-unknown` target) don't support filesystem operations. Code that uses filesystem APIs must be gated behind the `local_fs` feature flag.
**Common WASM errors:**
- Dead code warnings for code only used in non-WASM builds
- Unused code that's only relevant when `local_fs` is available
- Tests that require filesystem access
**Fixes:**
**Gate tests behind `local_fs`:**
```rust
#[test]
#[cfg(feature = "local_fs")]
fn test_find_git_repo_with_worktree() {
// Test that uses filesystem operations
}
```
**Conditionally allow dead code for types only used when `local_fs` is enabled:**
```rust
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
#[derive(Clone, EnumDiscriminants, Serialize)]
pub enum ExampleType {
// Variants only used when local_fs is enabled
Variant1,
Variant2,
Variant3,
}
```
WASM errors are discovered by running:
```bash
cargo clippy --target wasm32-unknown-unknown --profile release-wasm-debug_assertions --no-deps
```
## Best Practices
**Before fixing:**
- Read the full error message to understand the root cause
- Check if multiple errors are related (fixing one may resolve others)
- For trait/type errors, verify you understand the expected vs actual types
- For WASM errors, check if code needs to be gated behind `local_fs`
**When fixing:**
- Fix one error type at a time when there are multiple issues
- Run `cargo check` frequently to verify fixes
- For WASM errors, run WASM clippy to verify the fix
- For complex changes, run relevant tests after fixing
**After fixing:**
- Always run `cargo fmt` and `cargo clippy` before pushing
- Run the full presubmit script before opening or updating a PR. Use the `create-pr` skill for more detailed instructions
- Verify tests pass in the areas you modified
-91
View File
@@ -1,91 +0,0 @@
---
name: implement-specs
description: Implement an approved feature from PRODUCT.md and TECH.md, keeping specs and code aligned in the same PR as implementation evolves. Use after the product and tech specs are approved and the next step is building the feature.
---
# implement-specs
Implement an approved feature from `PRODUCT.md` and `TECH.md`.
## Overview
Use this skill after the product and tech specs are approved. The goal is to build the feature described by the specs while keeping the checked-in specs and the implementation aligned as the work evolves.
Approved specs should live directly under a ticket-named directory in `specs/`, for example `specs/APP-1234/PRODUCT.md` and `specs/APP-1234/TECH.md`.
In many cases, the implementation should be pushed in the same PR as the product and tech specs. As the engineer iterates, changes to `PRODUCT.md`, `TECH.md`, and the code should all be pushed in that same PR so review stays anchored to the feature that will actually ship.
## Prerequisites
Before using this skill:
- confirm that `PRODUCT.md` exists
- confirm that `TECH.md` exists when the feature warranted one
- confirm that the relevant specs have been reviewed and approved enough to start implementation
## Workflow
### 1. Read the approved specs first
Treat:
- `PRODUCT.md` as the source of truth for user-facing behavior
- `TECH.md` as the source of truth for architecture, sequencing, and implementation shape
Make sure you understand the expected behavior, constraints, risks, and validation plan before writing code.
### 2. Offer optional implementation aids for large features
For large or long-running features, optionally offer one of these aids to the user before implementation begins:
- `PROJECT_LOG.md` to track checkpoints, explored paths, partial findings, and current implementation state
- `DECISIONS.md` to capture concrete product and technical decisions made during the PRD and tech design process
These are optional aids, not required deliverables. Offer them when they would reduce confusion or help future agents avoid re-exploring the same paths.
### 3. Plan and implement against the specs
Break the work into concrete implementation steps, then implement the feature against the approved specs.
During implementation:
- keep behavior aligned with `PRODUCT.md`
- keep architecture and sequencing aligned with `TECH.md`
- add or update tests and verification artifacts as the work lands
Use the same PR for the specs and implementation when practical so the full feature evolution is reviewable in one place.
### 4. Update specs as the implementation evolves
If implementation reveals that the intended behavior or design should change, update the checked-in specs rather than letting them go stale.
In particular:
- update `PRODUCT.md` when user-facing behavior, UX, edge cases, or success criteria change
- update `TECH.md` when architecture, sequencing, module boundaries, or validation strategy change
- keep those updates in the same PR as the corresponding code changes
The PR should describe the feature that actually ships, not just the initial draft of the specs.
### 5. Verify against the specs
Before considering the work complete, verify that the code matches the current specs.
Prefer:
- `rust-unit-tests` for unit tests and regression coverage
- integration or end-to-end tests for important user flows
## Best Practices
- Keep specs and code synchronized throughout implementation.
- Prefer updating the spec immediately when decisions change rather than batching spec cleanup until the end.
- Use optional tracking documents only when they add real value for a complex feature.
- Keep the same PR coherent: spec updates, code changes, tests, and optional tracking docs should all support the same feature narrative.
## Related Skills
- `spec-driven-implementation`
- `write-product-spec`
- `write-tech-spec`
- `rust-unit-tests`
@@ -0,0 +1,300 @@
---
name: onboarding-verification-skill
description: Launch two parallel Oz cloud agents with computer use to download and install the latest stable Linux Warp build, capture screenshots while walking through first-time onboarding in both logged-out and logged-in states, then selectively fan out follow-up cloud agents for distinct onboarding branches proposed by those initial explorers. Use this whenever the user asks to test, document, screenshot, or walk through the Warp first-time install/onboarding experience in a cloud Linux environment.
---
# Onboarding verification skill
Use this skill to verify the first-time Warp install and onboarding flow on Linux with broader branch coverage than a single linear walkthrough.
The parent agent should not perform the walkthrough locally. Launch two parallel Oz cloud agents with computer use. Both initial children install the latest stable Warp Linux package appropriate for their platform and capture screenshots at every visible onboarding step until Warp reaches a usable terminal session. One child verifies the login-free flow. The other child verifies the logged-in flow using the managed secret `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN`.
Those two baseline explorers are also responsible for noticing meaningful alternate onboarding branches and returning concrete plans for follow-up cloud agents. The parent agent should synthesize those plans, deduplicate overlapping suggestions, and launch a bounded second wave of targeted follow-up agents to improve coverage of paths a real user might encounter.
## Parent workflow
1. Launch exactly two remote Oz cloud agents in a single parallel `run_agents` batch with computer use enabled.
2. Use no environment-specific assumptions unless the user provided an environment. If no environment was provided, omit the environment ID and let Warp choose the default remote environment.
3. Give both baseline child agents the shared child prompt below, plus the appropriate flow-specific prompt.
4. Wait for both baseline agents' reports. Each report must include:
- The completed baseline walkthrough result and artifacts.
- A concise list of observed UI quality issues, suspected bugs, error states, or rough edges, with screenshots when visible.
- A prioritized follow-up coverage plan describing distinct onboarding paths worth exploring with additional cloud agents.
5. Treat the authenticated baseline child as blocked if `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` is missing or does not authenticate successfully.
6. Build a combined coverage map from the two baseline reports. Deduplicate suggestions that reach the same visible state or exercise the same decision surface.
7. Launch a second `run_agents` batch with computer use enabled for the most valuable follow-up onboarding branches:
- Prefer branches that materially change visible UI, available controls, downstream screens, auth state, or setup outcomes.
- Favor paths likely to expose correctness, polish, layout, truncation, loading, or validation problems.
- Default to at most four follow-up agents total unless the user explicitly asked for exhaustive coverage or the baseline reports show more than four clearly distinct high-value branches.
- Do not launch speculative follow-ups when the baseline agents did not observe a concrete branch point; report that coverage stopped after the baseline pass instead.
8. Give each follow-up child the shared child prompt, the follow-up flow prompt below, the logged-out or logged-in flow prompt that matches its assigned auth state, and one synthesized branch assignment from the baseline reports.
9. Wait for all follow-up reports before summarizing coverage, issues, artifacts, and any still-unexplored branches worth a later run.
## Managed FTUE auth secret
- `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` is an internal-team managed secret for cloud agents, not a repo file or prompt literal.
- The secret should authenticate as a dedicated non-employee, non-`warp.dev` FTUE test user.
- Rotate the secret with `oz-dev secret update --team --value-file <private-token-file> ONBOARDING_AGENT_FTUE_REFRESH_TOKEN`.
- Treat the private token file as local scratch material only. Do not read it into chat, print it, stage it, commit it, upload it, or include it in artifacts. Delete it after the managed secret is updated.
- Children should receive the secret only through the managed environment variable injected into the remote run.
Use the initial `run_agents` call shaped like this:
```text
summary: Launching two baseline cloud agents with computer use to compare logged-out and logged-in Warp onboarding screenshots and propose follow-up coverage branches.
remote.computer_use_enabled: true
agent_run_configs:
- name: "warp-onboarding-logged-out"
prompt: the logged-out flow prompt below
- name: "warp-onboarding-logged-in"
prompt: the logged-in flow prompt below
base_prompt: the shared child prompt below
```
When the baseline reports identify concrete follow-up branches, use a second `run_agents` call shaped like this:
```text
summary: Launching targeted cloud follow-up agents to explore distinct onboarding branches identified by the baseline onboarding explorers.
remote.computer_use_enabled: true
agent_run_configs:
- name: "warp-onboarding-followup-theme-choice"
prompt: the follow-up flow prompt below, the logged-out flow prompt below, and one synthesized logged-out branch assignment
- name: "warp-onboarding-followup-model-choice"
prompt: the follow-up flow prompt below, the logged-in flow prompt below, and one synthesized logged-in branch assignment
base_prompt: the shared child prompt below
```
## Shared child prompt
Give both cloud agents these shared instructions:
```text
You are verifying the first-time Warp install and onboarding experience on Linux.
Goal:
- Download and install the latest stable Warp Linux build appropriate for this cloud environment's distro and CPU architecture.
- Launch Warp in a fresh first-run state.
- Take a screenshot at every visible onboarding step.
- Continue until Warp reaches a usable terminal session, or stop and report a blocker if the assigned flow cannot proceed.
- Notice alternate onboarding decisions that lead to meaningfully different screens, states, or outcomes, and return concrete follow-up cloud-agent plans for the parent orchestrator.
- Treat visual polish, missing assets, misalignment, overlapping content, clipped text, poor contrast, broken loading states, unexpected errors, and confusing controls as verification findings rather than ignoring them.
Install requirements:
- Use official stable Warp downloads only.
- Do not use Warp Preview, Alpha, source builds, or a repository development build.
- Detect CPU architecture with `uname -m`.
- Detect the package manager or distro before choosing the package format.
- Prefer native packages over AppImage because they install dependencies and register the app normally.
Stable Linux package mapping:
- Debian/Ubuntu with amd64 or x86_64: https://app.warp.dev/download?package=deb
- Debian/Ubuntu with arm64 or aarch64: https://app.warp.dev/download?package=deb_arm64
- Fedora/RHEL/CentOS/openSUSE with amd64 or x86_64: https://app.warp.dev/download?package=rpm
- Fedora/RHEL/CentOS/openSUSE with arm64 or aarch64: https://app.warp.dev/download?package=rpm_arm64
- Arch with amd64 or x86_64: https://app.warp.dev/download?package=pacman
- Arch with arm64 or aarch64: https://app.warp.dev/download?package=pacman_arm64
- If no native package path is available, use the AppImage fallback:
- amd64 or x86_64: https://app.warp.dev/download?package=appimage
- arm64 or aarch64: https://app.warp.dev/download?package=appimage_arm64
Before launch:
- Create a flow-specific artifact directory such as `~/warp-onboarding-logged-out` or `~/warp-onboarding-logged-in`.
- Ensure the run starts from a fresh Warp first-run state by removing only Warp-specific config/data/cache/state directories for the test user, such as `~/.config/warp-terminal`, `~/.local/share/warp-terminal`, `~/.local/state/warp-terminal`, and `~/.cache/warp-terminal` if they exist.
- Do not delete unrelated user files or system directories.
Screenshot workflow:
- Take the first screenshot before interacting with the first visible Warp window.
- Take one screenshot before every user action.
- Take another screenshot after each action if the UI changes.
- Use sequential filenames with a flow prefix, such as `01-logged-out-initial-window.png` or `01-logged-in-initial-window.png`.
- If anything looks wrong, take an additional issue-focused screenshot that captures the problematic state as clearly as possible.
- Maintain a manifest file in the artifact directory with, for each screenshot:
- filename
- timestamp
- what was visible
- what action was about to happen or just happened
- For issue-focused screenshots, add the suspected issue category and the screen or step where it appeared.
- Do not include secret values, refresh tokens, ID tokens, auth redirect URLs, or Authorization headers in the manifest, logs, shell history, screenshots, or final report.
Onboarding behavior:
- Baseline children choose the default or most conservative option at each step unless the flow-specific prompt says otherwise, while recording branch points that deserve separate follow-up coverage.
- Follow-up children take the specifically assigned alternate branch, then use the default or most conservative option for unrelated decisions unless the branch assignment says otherwise.
- If telemetry, shell, theme, editor-import, or agent integration choices appear, use the default path and document the choice in the manifest.
- Continue until a normal terminal prompt is visible and usable.
UI quality review:
- Watch for screens that are visually broken, obviously unfinished, misaligned, truncated, clipped, crowded, low-contrast, unexpectedly blank, stuck loading, or inconsistent with adjacent steps.
- Watch for actionable errors or validation states that appear during normal flow exploration, including auth failures, failed button transitions, controls that do not respond, duplicated overlays, missing images, or broken post-selection states.
- For every suspicious state:
- Capture a screenshot.
- Record the screen, the action that led to it, what looked wrong, and whether it blocked progress.
- Describe the issue factually. If expected behavior is uncertain, say it appears suspicious rather than claiming a confirmed bug.
Terminal verification:
- Once a terminal session is visible, run a harmless flow-specific command:
- logged-out flow: `echo warp-onboarding-logged-out-ready`
- logged-in flow: `echo warp-onboarding-logged-in-ready`
- Capture a final screenshot showing the usable terminal and command output.
Report back:
- Whether you were a baseline explorer or a follow-up branch explorer.
- Which flow you ran: logged-out or logged-in.
- OS and distro detected.
- CPU architecture detected.
- Package URL and install method used.
- Launch command used.
- Whether the walkthrough reached a usable terminal session.
- Ordered screenshot list with short descriptions.
- Artifact directory path.
- Any built-in artifact IDs or attachment names if the harness supports artifact upload.
- Any visual polish concern, suspected bug, error state, or unpolished/misaligned screen, including:
- screenshot filename
- screen or step
- action taken immediately before it appeared
- concise observed behavior
- whether it blocked progress
- Any blocker, crash, missing dependency, display problem, auth failure, or step that required judgment.
- For baseline explorers, include a `Follow-up coverage plan` section with zero or more proposed child-agent branches. Each proposal must include:
- suggested agent name
- logged-out or logged-in flow
- onboarding screen or decision point where the alternate branch begins
- exact alternate choice or action sequence to explore
- why it is materially distinct from the baseline path
- what user-visible state, setup outcome, or failure mode it could reveal
- any secret, auth, or environment dependency
- priority: high, medium, or low
- For follow-up explorers, include whether the assigned branch was reachable and completed. If a new branch point appears while following the assigned path, record it as a later-run suggestion instead of recursively expanding the run yourself.
Do not upload screenshots or logs to public external services. If the harness provides a built-in artifact or screenshot attachment mechanism, use that. Otherwise, leave the files in the artifact directory and report their paths.
```
## Logged-out flow prompt
Append this prompt to the shared child prompt for the logged-out child:
```text
You own the logged-out onboarding flow.
Flow-specific goal:
- Do not create an account, log in, or use a real user identity.
- Continue only through login-free or account-free paths until Warp reaches a usable terminal session.
- Stop and report a blocker if the flow requires login or account creation with no skip/continue-without-account option.
Flow-specific onboarding behavior:
- If there is a skip, "continue without account", "not now", "login later", or equivalent option, use it.
- Do not enter an email address, connect OAuth, paste an auth token, or create credentials.
- Be especially alert for logged-out branch points around choosing terminal-only versus agentic experiences, customization/layout options, third-party integration toggles, and terminal theme selection. If they appear, propose follow-up branches that exercise materially different choices rather than trying all alternates inline.
- Use the artifact directory `~/warp-onboarding-logged-out`.
```
## Logged-in flow prompt
Append this prompt to the shared child prompt for the logged-in child:
```text
You own the logged-in onboarding flow.
Flow-specific goal:
- Use the managed secret environment variable `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` to authenticate as the dedicated non-employee, non-`warp.dev` FTUE test user.
- Exercise onboarding screens that are available to an already-authenticated user.
- Continue through the authenticated onboarding path until Warp reaches a usable terminal session.
Secret handling requirements:
- Before doing auth work, verify that `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` exists and is non-empty without printing it.
- Never echo, log, screenshot, upload, or report the secret value.
- Avoid shell tracing (`set -x`) and avoid writing commands that place the raw token in shell history or process lists.
- Treat every auth redirect URL containing the refresh token as secret-bearing material, even after URL-encoding.
- Do not pass a token-bearing redirect URL to a shell command, desktop URI handler, browser address bar, process argument, log, artifact, or report. In particular, do not use commands such as `xdg-open`, `gio open`, `open`, or equivalent with the redirect URL.
- If you need to construct an auth redirect URL, keep it only in a clipboard value or a private temporary file with user-only permissions, paste it through Warp's visible Paste Auth Token flow, then delete the temporary file immediately after use.
Secure Paste Auth Token process:
1. Verify `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` exists and is non-empty without printing it.
2. Start Warp's normal login flow and derive the current-run `state` from Warp's generated login URL.
3. Normalize the managed secret privately:
- Trim surrounding whitespace and one pair of surrounding single or double quotes if present.
- If the secret parses as a URL with a `refresh_token` query parameter, extract that `refresh_token` value and ignore any stale `state` in the secret.
- Otherwise, treat the trimmed secret as the raw refresh token.
4. URL-encode the extracted refresh token and current-run `state` separately as query parameter values.
5. Construct the redirect URL only in a clipboard value or private temporary file with user-only permissions.
6. Return to Warp and use the visible Paste Auth Token path:
- Click the `Click here to paste your token from the browser` link, `Paste Auth Token` button, or equivalent pasted-token control shown by Warp.
- Focus the auth token text input that appears.
- Paste the prepared redirect URL into that input and submit it through Warp's UI so Warp parses and validates it.
7. Delete any private temporary files immediately after use and clear the clipboard if the environment supports doing so safely.
8. If the Paste Auth Token UI cannot be reached or automated safely, stop and report an auth blocker instead of parsing the redirect in place of Warp, using a desktop URI handler, browser address bar, or shell command with the token-bearing URL.
Preferred authenticated path:
- Launch Warp in a fresh first-run state and choose the login/sign-in path from onboarding.
- Use Warp's built-in Paste Auth Token flow rather than visiting real OAuth providers, invoking a desktop URI handler, or asking the agent to parse/validate the redirect URI itself.
- Derive `<state>` from the login URL generated by Warp if the UI exposes a copied login URL or opens the browser. If the UI does not expose the state after reasonable effort, report that as an auth blocker rather than bypassing state validation.
- Do not preflight the token with Firebase Secure Token before handing it to Warp. Warp's desktop redirect handler only requires `refresh_token` and `state`; `user_uid` is optional, and `deleted_anonymous_user=true` handles the anonymous-user override case.
- Treat `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` as either of these secret shapes:
- a raw Firebase refresh token, or
- a complete Warp desktop auth redirect URL containing a `refresh_token` query parameter.
- Normalize the secret into a current-run redirect URL without printing it:
- Trim surrounding whitespace and one pair of surrounding single or double quotes if present.
- If the secret parses as a URL with a `refresh_token` query parameter, extract that `refresh_token` value and ignore any stale `state` in the secret.
- Otherwise, treat the trimmed secret as the raw refresh token.
- URL-encode the extracted refresh token and the current-run `state` separately as query parameter values.
- Build `warp://auth/desktop_redirect?refresh_token=<url-encoded-normalized-refresh-token>&deleted_anonymous_user=true&state=<url-encoded-current-state>`.
- Do not include `user_uid` unless it is already present in a provided desktop redirect URL; it is not required for this flow.
- Construct the normalized redirect URL in a clipboard value or private temporary file only, then hand it to Warp through the Paste Auth Token UI. Do not parse, validate, or route the redirect outside of Warp.
- If the Paste Auth Token flow cannot be reached or automated safely, stop and report an auth blocker instead of using a desktop URI handler or any shell command that contains the token-bearing URL.
Fallback authenticated path:
- If Warp rejects the normalized redirect, report the non-sensitive user-visible error and classify whether the secret appeared to be a raw token or a desktop redirect URL, without reporting any token contents.
- If the Paste Auth Token flow is blocked by UI automation issues, report the blocker and include the exact non-sensitive step where automation failed.
- Do not switch to a logged-out path for this child.
Flow-specific onboarding behavior:
- Choose login/sign-in rather than skip/login-later when presented with an auth choice.
- After auth succeeds, continue through the remaining onboarding screens with default or conservative options.
- Be especially alert for logged-in branch points around model selection, account-aware onboarding screens, AI/agent setup, workspace or project setup, and any decision that changes available product capability. If they appear, propose follow-up branches that exercise materially different choices rather than trying all alternates inline.
- After the terminal verification succeeds, click the upper-right avatar/account control, open Settings from that menu, and capture an additional screenshot that clearly shows the logged-in user's email address in Warp settings or account/profile settings.
- Include the account/settings email screenshot in the manifest and final report. The email address itself may be visible in the screenshot, but do not copy the email into logs, shell output, or the final text report unless the user explicitly asks for it.
- Use the artifact directory `~/warp-onboarding-logged-in`.
```
## Follow-up flow prompt
Append this prompt to the shared child prompt for every second-wave child, followed by the matching logged-out or logged-in flow prompt and one branch assignment synthesized from the baseline reports:
```text
You own one follow-up onboarding branch selected by the parent orchestrator from an earlier baseline exploration report.
Follow-up branch behavior:
- Start from a fresh first-run Warp state and install the same latest stable Linux build using the shared instructions.
- Respect the assigned auth state: remain logged out for logged-out assignments, or use the managed authenticated flow for logged-in assignments.
- Follow the exact alternate onboarding choice or action sequence in the branch assignment.
- Capture screenshots before and after each assigned branch decision, then continue to a usable terminal session if the path allows it.
- Apply the same UI quality review standard as the baseline explorers and call out anything that looks broken, rough, misaligned, confusing, or unexpectedly error-prone.
- If the assigned branch is not reachable, capture the closest relevant screen, report why it was unreachable, and do not silently substitute a different branch.
- If the assigned branch reveals another interesting alternate path, record it as a later-run suggestion rather than recursively launching more agents yourself.
Final report additions:
- Repeat the exact branch assignment you attempted in concise non-sensitive terms.
- State whether it was reachable, completed, blocked, or not applicable.
- Compare the branch outcome against the likely baseline behavior when that comparison is visible from the UI.
```
## Success criteria
The run is successful when:
- Warp stable was installed from an official Linux package or AppImage for the detected architecture.
- Screenshots were captured for each onboarding screen and the final usable terminal.
- The logged-out child reached a usable terminal without login, account creation, or a real user identity.
- The logged-in child authenticated using `ONBOARDING_AGENT_FTUE_REFRESH_TOKEN` and reached a usable terminal in the authenticated FTUE path.
- The logged-in child captured an additional post-login screenshot from the avatar/settings flow showing the logged-in user's email address.
- Each terminal session was usable enough to run its flow-specific `echo` command.
- Both baseline explorers returned either concrete follow-up coverage proposals or an explicit explanation that they did not observe meaningful additional branch points.
- The parent orchestrator launched targeted second-wave agents for the highest-value concrete branch proposals, unless there were no such proposals or a prerequisite blocker made them infeasible.
- Every reported visual polish concern, suspected bug, or error state includes a screenshot reference whenever the issue was visible on screen.
## Common failure handling
- If the package manager prompts for confirmation, use the non-interactive confirmation flag supported by that package manager.
- If launching `warp-terminal` fails because of display setup, inspect the cloud environment's display variables and try launching from the desktop/app launcher if computer use provides one.
- If the logged-out flow blocks on login with no skip path, stop at that screen, capture a screenshot, and report that as the terminal point for the logged-out flow.
- If the logged-in flow cannot authenticate because the secret is missing, invalid, expired, revoked, or cannot be routed through Warp's auth redirect flow, stop at that screen, capture a screenshot, and report the non-sensitive blocker.
- If the native package cannot be installed because dependencies are unavailable, fall back to the matching AppImage and clearly report the fallback.
+1 -1
View File
@@ -81,7 +81,7 @@ pub const PREVIEW_FLAGS: &[FeatureFlag] = &[
### Validate
```bash
cargo fmt
./script/format
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
```
+1 -1
View File
@@ -116,7 +116,7 @@ After removing the flag:
```bash
# Format and lint
cargo fmt
./script/format
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
# Run tests
@@ -0,0 +1,58 @@
---
name: reproduce-bug-report-local
specializes: reproduce-bug-report
specializes_source: warpdotdev/common-skills:.agents/skills/reproduce-bug-report
description: Repo-specific bug reproduction guidance for Warp. Specializes the core reproduce-bug-report skill for logged-out Warp UI repros, exact reporter-version installs, and login-free onboarding.
---
# Repo-specific bug reproduction guidance for `warp`
## Prerequisite: install the parent skill
This skill specializes the core `reproduce-bug-report` skill (named in the `specializes` frontmatter field) and is not functional on its own. Before applying its guidance, confirm the parent skill is installed and resolvable at `.agents/skills/reproduce-bug-report/SKILL.md`. If it is missing, install it first by copying the skill directory from the source declared in the `specializes_source` frontmatter field (`warpdotdev/common-skills:.agents/skills/reproduce-bug-report`). Then continue with the guidance below.
This file is a companion to the core `reproduce-bug-report` skill. It does not redefine the shared Oz computer-use orchestration, artifact handling, safety rules, or reporting format. It specializes scope and setup for Warp bug reports.
## Scope
- Use this workflow only for Warp bugs that can be exercised while the app remains logged out.
- Apply it to UI-visible Warp bugs, interaction bugs, rendering/layout bugs, logged-out onboarding bugs, settings bugs, editor/display bugs, terminal-display bugs, and other visual or interactive issues where screenshots or recordings would help.
- Do not use it for authenticated-user flows, account-specific state, cloud-synced state, logged-in onboarding, or AI behaviors that require login.
- If a report requires authentication, account state, cloud sync, or another logged-in-only capability, do not launch a repro agent with this local specialization; report that it is out of scope for the current logged-out Warp workflow.
## Warp version and install strategy
- Prefer reproducing against the exact Warp version/build and channel reported by the user.
- Do not build Warp from source by default. Install the matching Linux package or binary release for the reporter's version/channel instead.
- If the bug report names a macOS or Windows build, use the corresponding Linux build from the same version/channel when a matching Linux artifact exists, and state that this is a Linux proxy for the reporter's platform.
- Use the repository's or Warp release tooling/docs available in the environment to find and install the exact versioned Linux artifact. Do not silently substitute the latest stable build when an exact matching version can be installed.
- If the exact version/build cannot be found or installed, report that clearly, explain what was attempted, and use the closest justified fallback only when it is useful for continuing the investigation.
- Record the requested reporter Warp version, the installed Linux version, the source of the installed artifact, and any fallback decision in the manifest and final report.
## Logged-out Warp baseline
- Keep Warp logged out for the entire repro attempt. Do not create an account, sign in, paste auth tokens, or use real user credentials.
- Launch Warp and complete the login-free / continue-without-account onboarding path until a normal logged-out terminal session is usable.
- Capture a post-onboarding baseline screenshot before attempting the bug-specific reproduction.
- If the assigned bug cannot be exercised after entering a normal logged-out Warp session, stop and report the blocker instead of improvising an authenticated flow.
## Local prompt additions
When applying the core skill to Warp, ensure the parent prompt and child prompts include:
- Reporter Warp version/build/channel: the exact value from the report, or `unknown`.
- Build/app target: the exact versioned Linux Warp package/binary to install, or the justified fallback if an exact artifact is unavailable.
- Assigned Warp state: first-run logged-out state, completed logged-out onboarding, terminal/session/layout/settings state, or the targeted code-path hypothesis.
- A reminder that Warp must remain logged out and that logged-in-only reports are blocked for this specialization.
## Local reproduction priorities
- Match the reporter's Warp version/build/channel before broadening the search space.
- Follow the issue's exact steps first, then test at most two targeted variations supported by the issue or by a code-path hypothesis.
- Prefer targeted hypotheses derived from Warp UI strings, settings names, feature names, telemetry names, route names, and relevant components over broad exploratory clicking.
- In the final report, include:
- the reporter-requested Warp version/build/channel
- the installed Linux Warp version/build/channel
- the package or binary source
- whether a fallback was used
- whether the test was a Linux proxy for a macOS or Windows report
@@ -1,89 +0,0 @@
---
name: resolve-merge-conflicts
description: Resolve Git merge conflicts by extracting only unresolved paths, conflict hunks, and compact diffs instead of loading whole files into context. Use when a merge, rebase, cherry-pick, or stash pop stops on conflicts, when `git status` shows unmerged paths, or when files contain conflict markers.
---
# Resolve Merge Conflicts
## Overview
Resolve conflicts without opening full files unless the compact view is insufficient. Start with a summary, then inspect one conflicted file at a time.
## Workflow
1. Start with a summary.
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py
```
Use the summary to identify which files are unresolved, which index stages exist, and how many text hunks each file contains.
2. Drill into one file.
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --file path/to/file
```
Prefer this over reading the whole file. The script prints only nearby context, the `ours` / `base` / `theirs` sections for each hunk, and a compact unified diff between `ours` and `theirs`.
3. Resolve the file.
- Take one side wholesale with `git checkout --ours -- path/to/file` or `git checkout --theirs -- path/to/file` when appropriate.
- Otherwise edit the file directly and remove the conflict markers.
- Read more of the file only if the compact output is not enough to decide the correct merge.
4. Re-check unresolved files.
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py
git diff --name-only --diff-filter=U
```
5. Validate the resolution.
- Ensure no unmerged paths remain.
- Ensure no `<<<<<<<`, `=======`, or `>>>>>>>` markers remain in the resolved files.
- Run targeted tests, builds, or linters for the touched area.
- Stage the resolved files.
## Commands
### Summary only
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py
```
### Detailed view for one file
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --file path/to/file
```
### Detailed view for all conflicted files
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --all
```
### JSON output
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --file path/to/file --json
```
### Tune output size
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py \
--file path/to/file \
--context 3 \
--max-lines 60
```
## Notes
- Use the script before opening conflicted files directly.
- Resolve one file at a time to keep context small.
- Expect marker-based text conflicts and index-only conflicts such as add/add or modify/delete. The script summarizes both, and it falls back to index-stage previews when the worktree file has no conflict markers.
@@ -1,468 +0,0 @@
#!/usr/bin/env python3
"""Summarize and extract compact merge-conflict context from a Git repository."""
from __future__ import annotations
import argparse
import difflib
import json
import re
import subprocess
import sys
from pathlib import Path
START_RE = re.compile(r"^<<<<<<<(?: (.*))?$")
BASE_RE = re.compile(r"^\|\|\|\|\|\|\|(?: (.*))?$")
END_RE = re.compile(r"^>>>>>>>(?: (.*))?$")
def run_git(repo_root: Path, *args: str) -> str:
result = subprocess.run(
["git", "-C", str(repo_root), *args],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
message = result.stderr.strip() or result.stdout.strip() or "unknown git error"
raise RuntimeError(f"git {' '.join(args)} failed: {message}")
return result.stdout
def find_repo_root(start: Path) -> Path:
result = subprocess.run(
["git", "-C", str(start), "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
message = result.stderr.strip() or result.stdout.strip() or "not a git repository"
raise RuntimeError(message)
return Path(result.stdout.strip()).resolve()
def get_unmerged_entries(repo_root: Path) -> dict[str, dict[int, dict[str, str]]]:
entries: dict[str, dict[int, dict[str, str]]] = {}
output = run_git(repo_root, "ls-files", "-u", "-z")
for record in output.split("\0"):
if not record:
continue
metadata, path = record.split("\t", 1)
mode, object_id, stage_text = metadata.split()
file_entry = entries.setdefault(path, {})
file_entry[int(stage_text)] = {"mode": mode, "object_id": object_id}
return entries
def read_text_file(path: Path) -> list[str] | None:
if not path.exists() or path.is_dir():
return None
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return None
if "\x00" in text:
return None
return text.splitlines()
def read_stage_text(repo_root: Path, path: str, stage: int) -> list[str] | None:
result = subprocess.run(
["git", "-C", str(repo_root), "show", f":{stage}:{path}"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return None
if "\x00" in result.stdout:
return None
return result.stdout.splitlines()
def truncate_lines(lines: list[str], max_lines: int) -> list[str]:
if len(lines) <= max_lines:
return lines
omitted = len(lines) - max_lines
return [*lines[:max_lines], f"... ({omitted} more lines omitted)"]
def build_diff(
left_lines: list[str],
right_lines: list[str],
left_label: str,
right_label: str,
max_lines: int,
) -> list[str]:
diff = list(
difflib.unified_diff(
left_lines,
right_lines,
fromfile=left_label,
tofile=right_label,
lineterm="",
)
)
if not diff:
diff = ["(no textual diff)"]
return truncate_lines(diff, max_lines)
def classify_conflict(stages: list[int], marker_hunks: int) -> str:
if marker_hunks:
return "text"
stage_set = set(stages)
if stage_set == {2, 3}:
return "add/add"
if stage_set == {1, 2}:
return "deleted-by-them"
if stage_set == {1, 3}:
return "deleted-by-us"
if stage_set == {1, 2, 3}:
return "index-only"
return "unmerged"
def normalize_requested_path(repo_root: Path, raw_path: str) -> str:
path = Path(raw_path)
candidate = path.resolve() if path.is_absolute() else (repo_root / path).resolve()
try:
return str(candidate.relative_to(repo_root))
except ValueError as error:
raise RuntimeError(f"path is outside repository: {raw_path}") from error
def parse_conflict_hunks(lines: list[str], context: int) -> tuple[list[dict[str, object]], str | None]:
hunks: list[dict[str, object]] = []
index = 0
while index < len(lines):
start_match = START_RE.match(lines[index])
if not start_match:
index += 1
continue
start_index = index
ours_label = start_match.group(1) or "ours"
index += 1
ours: list[str] = []
base: list[str] = []
theirs: list[str] = []
base_label: str | None = None
theirs_label = "theirs"
while index < len(lines):
base_match = BASE_RE.match(lines[index])
if base_match:
base_label = base_match.group(1) or "base"
index += 1
while index < len(lines) and lines[index] != "=======":
base.append(lines[index])
index += 1
break
if lines[index] == "=======":
break
ours.append(lines[index])
index += 1
if index >= len(lines) or lines[index] != "=======":
return hunks, f"unterminated conflict starting at line {start_index + 1}"
index += 1
end_index = index
while index < len(lines):
end_match = END_RE.match(lines[index])
if end_match:
theirs_label = end_match.group(1) or "theirs"
end_index = index
index += 1
break
theirs.append(lines[index])
index += 1
else:
return hunks, f"unterminated conflict starting at line {start_index + 1}"
hunks.append(
{
"start_line": start_index + 1,
"end_line": end_index + 1,
"before_context": lines[max(0, start_index - context):start_index],
"ours": ours,
"ours_label": ours_label,
"base": base or None,
"base_label": base_label,
"theirs": theirs,
"theirs_label": theirs_label,
"after_context": lines[index:index + context],
}
)
return hunks, None
def build_summary_report(repo_root: Path, path: str, stage_entries: dict[int, dict[str, str]], context: int) -> dict[str, object]:
worktree_lines = read_text_file(repo_root / path)
hunks: list[dict[str, object]] = []
parse_error = None
if worktree_lines is not None:
hunks, parse_error = parse_conflict_hunks(worktree_lines, context)
stages = sorted(stage_entries)
return {
"path": path,
"stages": stages,
"conflict_type": classify_conflict(stages, len(hunks)),
"marker_hunks": len(hunks),
"parse_error": parse_error,
"worktree_present": worktree_lines is not None,
"hunks": hunks,
}
def build_index_preview(repo_root: Path, report: dict[str, object], max_lines: int) -> dict[str, object]:
path = str(report["path"])
ours = read_stage_text(repo_root, path, 2)
theirs = read_stage_text(repo_root, path, 3)
base = read_stage_text(repo_root, path, 1)
preview: dict[str, object] = {
"ours": truncate_lines(ours, max_lines) if ours else None,
"theirs": truncate_lines(theirs, max_lines) if theirs else None,
"base": truncate_lines(base, max_lines) if base else None,
}
if ours and theirs:
preview["ours_vs_theirs_diff"] = build_diff(ours, theirs, "ours", "theirs", max_lines)
return preview
def section_lines(title: str, lines: list[str] | None) -> list[str]:
if lines is None:
return [f"{title}:", " (not present)"]
if not lines:
return [f"{title}:", " (empty)"]
return [f"{title}:", *[f" {line}" for line in lines]]
def render_summary_text(repo_root: Path, reports: list[dict[str, object]]) -> str:
lines = [f"repo: {repo_root}", f"conflicted files: {len(reports)}"]
for report in reports:
stages = ",".join(str(stage) for stage in report["stages"])
lines.append(
f"- {report['path']} | type={report['conflict_type']} | stages={stages} | hunks={report['marker_hunks']}"
)
if report["parse_error"]:
lines.append(f" parse-error: {report['parse_error']}")
lines.append("use --file <path> for compact hunk details or --all for every file")
return "\n".join(lines)
def render_detail_text(
repo_root: Path,
report: dict[str, object],
max_lines: int,
) -> str:
lines = [
f"== {report['path']} ==",
f"type: {report['conflict_type']}",
f"stages: {', '.join(str(stage) for stage in report['stages'])}",
]
parse_error = report["parse_error"]
if parse_error:
lines.append(f"parse-error: {parse_error}")
hunks = report["hunks"]
if hunks:
lines.append(f"hunks: {len(hunks)}")
for index, hunk in enumerate(hunks, start=1):
ours = list(hunk["ours"])
theirs = list(hunk["theirs"])
diff = build_diff(
ours,
theirs,
str(hunk["ours_label"]),
str(hunk["theirs_label"]),
max_lines,
)
lines.extend(
[
"",
f"[hunk {index}] current lines {hunk['start_line']}-{hunk['end_line']}",
*section_lines("before", truncate_lines(list(hunk["before_context"]), max_lines)),
*section_lines(
f"ours ({hunk['ours_label']})",
truncate_lines(ours, max_lines),
),
]
)
if hunk["base"] is not None:
lines.extend(
section_lines(
f"base ({hunk['base_label'] or 'base'})",
truncate_lines(list(hunk["base"]), max_lines),
)
)
lines.extend(
[
*section_lines(
f"theirs ({hunk['theirs_label']})",
truncate_lines(theirs, max_lines),
),
*section_lines("ours vs theirs diff", diff),
*section_lines("after", truncate_lines(list(hunk["after_context"]), max_lines)),
]
)
return "\n".join(lines)
preview = build_index_preview(repo_root, report, max_lines)
lines.append("hunks: 0")
lines.append("index preview:")
lines.extend(section_lines("ours", preview["ours"]))
lines.extend(section_lines("base", preview["base"]))
lines.extend(section_lines("theirs", preview["theirs"]))
if "ours_vs_theirs_diff" in preview:
lines.extend(section_lines("ours vs theirs diff", preview["ours_vs_theirs_diff"]))
return "\n".join(lines)
def render_json(
repo_root: Path,
reports: list[dict[str, object]],
include_details: bool,
max_lines: int,
) -> str:
files: list[dict[str, object]] = []
for report in reports:
file_entry: dict[str, object] = {
"path": report["path"],
"conflict_type": report["conflict_type"],
"stages": report["stages"],
"marker_hunks": report["marker_hunks"],
"parse_error": report["parse_error"],
}
if include_details:
if report["hunks"]:
file_entry["hunks"] = [
{
"start_line": hunk["start_line"],
"end_line": hunk["end_line"],
"before_context": truncate_lines(list(hunk["before_context"]), max_lines),
"ours_label": hunk["ours_label"],
"ours": truncate_lines(list(hunk["ours"]), max_lines),
"base_label": hunk["base_label"],
"base": truncate_lines(list(hunk["base"]), max_lines) if hunk["base"] else None,
"theirs_label": hunk["theirs_label"],
"theirs": truncate_lines(list(hunk["theirs"]), max_lines),
"after_context": truncate_lines(list(hunk["after_context"]), max_lines),
"ours_vs_theirs_diff": build_diff(
list(hunk["ours"]),
list(hunk["theirs"]),
str(hunk["ours_label"]),
str(hunk["theirs_label"]),
max_lines,
),
}
for hunk in report["hunks"]
]
else:
file_entry["index_preview"] = build_index_preview(repo_root, report, max_lines)
files.append(file_entry)
return json.dumps(
{
"repo_root": str(repo_root),
"conflicted_files": files,
},
indent=2,
)
def main() -> int:
parser = argparse.ArgumentParser(
description="Summarize and extract compact merge-conflict context."
)
parser.add_argument("--repo", default=".", help="Path inside the target repository.")
parser.add_argument(
"--file",
action="append",
default=[],
help="Conflicted file to inspect in detail. Repeat to inspect multiple files.",
)
parser.add_argument(
"--all",
action="store_true",
help="Print detailed output for every conflicted file.",
)
parser.add_argument(
"--json",
action="store_true",
help="Emit JSON instead of text.",
)
parser.add_argument(
"--context",
type=int,
default=2,
help="Lines of surrounding context to include around each conflict hunk.",
)
parser.add_argument(
"--max-lines",
type=int,
default=40,
help="Maximum lines to print for each section before truncating.",
)
args = parser.parse_args()
if args.all and args.file:
parser.error("--all cannot be combined with --file")
if args.context < 0:
parser.error("--context must be non-negative")
if args.max_lines <= 0:
parser.error("--max-lines must be positive")
try:
repo_root = find_repo_root(Path(args.repo).resolve())
entries = get_unmerged_entries(repo_root)
except RuntimeError as error:
print(f"error: {error}", file=sys.stderr)
return 2
reports = [
build_summary_report(repo_root, path, entries[path], args.context)
for path in sorted(entries)
]
if not reports:
message = json.dumps({"repo_root": str(repo_root), "conflicted_files": []}, indent=2) if args.json else f"repo: {repo_root}\nconflicted files: 0"
print(message)
return 0
if args.all:
selected_reports = reports
elif args.file:
try:
requested_paths = {normalize_requested_path(repo_root, path) for path in args.file}
except RuntimeError as error:
print(f"error: {error}", file=sys.stderr)
return 2
known_paths = {str(report["path"]) for report in reports}
missing = sorted(requested_paths - known_paths)
if missing:
for path in missing:
print(f"error: conflicted file not found: {path}", file=sys.stderr)
return 2
selected_reports = [report for report in reports if report["path"] in requested_paths]
else:
selected_reports = []
if args.json:
print(render_json(repo_root, selected_reports or reports, bool(selected_reports), args.max_lines))
return 0
if not selected_reports:
print(render_summary_text(repo_root, reports))
return 0
print("\n\n".join(render_detail_text(repo_root, report, args.max_lines) for report in selected_reports))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+20 -3
View File
@@ -1,10 +1,15 @@
---
name: review-pr-local
specializes: review-pr
description: Repo-specific review guidance for warp-external. Only the categories declared overridable by the core review-pr skill may be specialized here.
specializes_source: warpdotdev/common-skills:.agents/skills/review-pr
description: Repo-specific review guidance for warp. Only the categories declared overridable by the core review-pr skill may be specialized here.
---
# Repo-specific review guidance for `warp-external`
# Repo-specific review guidance for `warp`
## Prerequisite: install the parent skill
This skill specializes the core `review-pr` skill (named in the `specializes` frontmatter field) and is not functional on its own. Before applying its guidance, confirm the parent skill is installed and resolvable at `.agents/skills/review-pr/SKILL.md`. If it is missing, install it first by copying the skill directory from the source declared in the `specializes_source` frontmatter field (`warpdotdev/common-skills:.agents/skills/review-pr`). Then continue with the guidance below.
This file is a companion to the core `review-pr` skill. It does not
redefine the review output schema, severity labels, safety rules, or
@@ -15,13 +20,25 @@ skill marks as overridable.
- Do not suggest adding test cases that only vary constructor inputs or struct fields when an existing test already covers the meaningful behavior. Only suggest new tests when they exercise a distinct code path or edge case.
- When a PR is clearly a V0 or initial implementation, frame robustness suggestions such as timeouts, retries, and lifecycle management as optional future work rather than blocking concerns, unless they risk correctness, security, data loss, or a persistent UI hang.
- For Rust changes, apply the repository conventions from `WARP.md`: avoid unnecessary type annotations, prefer imports over long path qualifiers, name context parameters `ctx` and place them last, remove unused parameters instead of prefixing them with `_`, and prefer inline format arguments in macros.
- For Rust changes, apply the repository conventions from `AGENTS.md`: avoid unnecessary type annotations, prefer imports over long path qualifiers, name context parameters `ctx` and place them last, remove unused parameters instead of prefixing them with `_`, and prefer inline format arguments in macros.
- Avoid wildcard `_` match arms when an enum can reasonably be matched exhaustively; exhaustive matches are preferred so future variants are surfaced during review.
- For new or changed feature flags, prefer high-level runtime checks with `FeatureFlag::YourFlag.is_enabled()` over `#[cfg(...)]` unless the code cannot compile without a compile-time gate.
- Flag nested or redundant `TerminalModel` locking when the call stack may already hold the model lock. Prefer passing locked references down the stack and keeping lock scopes short.
- In WarpUI code, flag inline `MouseStateHandle::default()` usage during render or event handling. Mouse state handles should be created during construction and then cloned/referenced where needed.
- For user-facing UI changes, mention missing validation only when it is tied to a concrete risk or when the PR changes behavior that should be verified visually.
## Behavioral or UI-impacting changes require visual evidence
- If the PR changes anything user-visible (UI components, layout, styling, copy in surfaces users see, terminal/Warp app visuals, or other behavior a user can perceive), analyze both `pr_description.txt` and any PR comments available in the workflow context for attached screenshots, GIFs, or videos demonstrating the change end to end.
- Treat markdown image/video embeds (`![...](...)`, `<img ...>`, `<video ...>`), GitHub user-attachment links (e.g. `https://github.com/user-attachments/...`, `https://user-images.githubusercontent.com/...`), Loom links, and similar hosted media as valid evidence.
- The `Screenshots / Videos` section from `.github/pull_request_template.md` being present but empty does not count as evidence.
- Unit tests, integration tests, `git diff --check`, code-path descriptions, and other textual explanations may supplement visual evidence but do not replace it for user-visible behavior.
- If the change is behavioral or UI-impacting and no screenshots or videos are attached in the description or comments, add an inline or summary-level comment requesting them. Use wording such as: "For this user-facing change, please include screenshots or a screen recording demonstrating it working end to end."
- When required visual evidence is missing for a behavioral or UI-impacting change that can be manually tested, set the final recommendation in the top-level `body` `## Verdict` section to `Request changes`, even if no other blocking issues were found. The top-level `verdict` field must be `"REJECT"` to match.
- Author environment limitations (e.g., headless runner, no desktop, environment can't capture) do not exempt UI-impacting changes from visual evidence. Suggest capturing the recording from a local desktop run or from a remote environment with desktop/computer-use support (for example, a coding agent such as Oz with [computer use](https://docs.warp.dev/agent-platform/warps-agent/capabilities-overview/computer-use) enabled). Reply with something like: _"This change is user-facing, so a screenshot or short recording is still required. If a local desktop isn't available, you can capture it from a coding agent that supports computer use (Oz is one option — see [Warp's computer use docs](https://docs.warp.dev/agent-platform/warps-agent/capabilities-overview/computer-use)) and attach it here."_ Set the verdict to `Request changes`.
- Exempt visual evidence only when the user-visible behavior truly cannot be meaningfully shown visually (for example, changes affecting only screen readers or non-visual side effects). If so, briefly state why screenshots or recordings would not be meaningful. Never exempt based on limitations of the author's environment.
- If the PR is not user-visible at all (e.g. pure refactor, internal tools, build scripts, backend-only code, tests, or documentation), do not request screenshots or videos.
## User-facing strings
- Flag interpolated text that would read unnaturally at runtime or combine sentence fragments with the wrong casing.
-109
View File
@@ -1,109 +0,0 @@
---
name: review-pr
description: Review a pull request diff and write structured feedback to review.json for the workflow to publish. Use when reviewing a checked-out PR from local artifacts like pr_diff.txt and pr_description.txt and producing machine-readable review output instead of posting directly to GitHub.
---
# Review PR Skill
Review the current pull request and write the output to `review.json`.
## Context
- The working directory is the PR branch checkout.
- The workflow provides an annotated diff in `pr_diff.txt`.
- The workflow provides the PR description in `pr_description.txt`.
- Focus on files and lines changed by this PR.
- Do not post comments or reviews to GitHub directly.
## Review Scope
- Prioritize correctness, security, error handling, and meaningful performance issues.
- Include style or nit comments only when you can provide a concrete suggestion block.
- If a concern involves untouched code, mention it in the summary instead of an inline comment.
- Do not suggest adding test cases that only vary constructor inputs or struct fields when the existing test already covers the meaningful behavior. Only suggest new tests when they exercise a distinct code path or edge case.
- When a PR is clearly a V0 or initial implementation, frame robustness suggestions (timeouts, retries, lifecycle management) as optional future work rather than blocking concerns, unless they risk correctness, security, or data loss.
## Diff Line Annotations
The diff file uses these prefixes:
- `[OLD:n]` for deleted lines on the old side. Use `"LEFT"`.
- `[NEW:n]` for added lines on the new side. Use `"RIGHT"`.
- `[OLD:n,NEW:m]` for unchanged context. Use `"RIGHT"` with line `m`.
## Comment Requirements
Every comment body must start with one of these labels:
- `🚨 [CRITICAL]` for bugs, security issues, crashes, or data loss.
- `⚠️ [IMPORTANT]` for logic problems, edge cases, or missing error handling.
- `💡 [SUGGESTION]` for worthwhile improvements or better patterns.
- `🧹 [NIT]` for cleanup only when the comment includes a suggestion block.
Write comments with these constraints:
- Be concise, direct, and actionable.
- Do not add compliments or hedging.
- Prefer single-line comments.
- Keep ranges to at most 10 lines.
- Restrict inline comments to valid changed lines in this PR.
## Suggestion Blocks
When proposing a code change, use:
```suggestion
<replacement code here>
```
Rules:
- Match the exact indentation of the original file.
- Include only replacement code.
- For multi-line suggestions, set `start_line` to the first line and `line` to the last line.
## Output Format
Create `review.json` with this shape:
```json
{
"summary": "## Overview\n...\n\n## Concerns\n- ...\n\n## Verdict\nFound: 1 critical, 2 important, 3 suggestions\n\n**Request changes**",
"comments": [
{
"path": "path/to/file",
"line": 42,
"side": "RIGHT",
"start_line": 40,
"body": "⚠️ [IMPORTANT] Short explanation\n\n```suggestion\nreplacement\n```"
}
]
}
```
Field rules:
- `path` must be relative to the repository root.
- `line` is required and must target the correct side.
- `start_line` is optional and only for multi-line ranges.
- `side` must be `"LEFT"` or `"RIGHT"`.
## Summary Requirements
The `summary` must include:
- A high-level overview of the PR.
- Important concerns and any untouched-code concerns that could not be commented inline.
- Issue counts in the format `Found: X critical, Y important, Z suggestions`.
- A final recommendation of `Approve`, `Approve with nits`, or `Request changes`.
## Final Checks
Before finishing:
- Validate `review.json` with `jq`.
- Fix invalid JSON if validation fails.
- Confirm line numbers match the annotated diff.
- Do not run `gh pr review`, `gh pr comment`, `gh api`, or any other command that posts to GitHub.
Your only output is the final `review.json`.
+1 -1
View File
@@ -106,7 +106,7 @@ cargo test --doc
## Linting and formatting
Run before submitting changes:
```bash
cargo fmt
./script/format
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
```
@@ -1,142 +0,0 @@
---
name: spec-driven-implementation
description: Drive a spec-first workflow for substantial features by writing PRODUCT.md before implementation, writing TECH.md when warranted, and keeping both specs updated as implementation evolves. Use when starting a significant feature, planning agent-driven implementation, or when the user wants product and tech specs checked into source control.
---
# spec-driven-implementation
Drive a spec-first workflow for substantial features in Warp.
## Overview
Use this skill for significant features where a written spec will improve implementation quality, reduce ambiguity, or make review easier. Be pragmatic: not every change needs specs.
Specs should usually live in:
- `specs/<linear-ticket-number>/PRODUCT.md`
- `specs/<linear-ticket-number>/TECH.md`
For example:
- `specs/APP-1234/PRODUCT.md`
- `specs/APP-1234/TECH.md`
`specs/` should contain only ticket-named directories as direct children. Do not create engineer-named subdirectories or feature-slug directories there.
If a relevant Linear issue does not already exist, create one before writing specs. Use the Linear MCP tools directly:
- `list_teams` to find the appropriate team
- `list_issue_labels` to inspect the expected labels/tags
- `save_issue` to create the issue with the appropriate team and labels
If the correct team or labels are not obvious from the request and surrounding context, use `ask_user_question` to clarify rather than guessing.
These specs should largely be written by agents, not by hand, and should be checked into source control so they can be reviewed and kept current with the code.
## When specs are required
Strongly prefer specs when the change is substantial, such as:
- product or architectural ambiguity
- expected implementation size around 1k+ LOC
- deep or cross-cutting stack changes
- risky behavior changes where regressions would be expensive
- work where agent quality will improve materially from clearer inputs
Specs are often unnecessary for:
- small, local bug fixes
- straightforward refactors
- narrow UI tweaks with little ambiguity
For pure UI changes, the product spec is often useful while the tech spec may be unnecessary.
## Workflow
### 1. Decide whether the feature needs specs
Evaluate the size, ambiguity, and risk of the feature. If specs will not meaningfully improve execution or review, skip them and focus on verification instead.
### 2. Write the product spec first
Before implementation, create `PRODUCT.md` describing the desired user-facing behavior.
Use the `write-product-spec` skill to produce it. The product spec should define:
- what problem is being solved
- the desired user experience
- invariants and edge cases
- success criteria
- how the behavior will be validated
If the feature has UI or interaction design, ask for a Figma mock if one exists. If there is no mock, continue but call that out explicitly in the product spec.
Reference the Linear issue in the spec when one exists. Because specs live under `specs/<linear-ticket-number>/...`, this should usually be straightforward.
### 3. Write the tech spec when warranted
Use the `write-tech-spec` skill for substantial or ambiguous implementation work.
Prefer a tech spec when:
- the implementation spans multiple subsystems
- architecture or extensibility matters
- there are meaningful tradeoffs to document
- reviewers will benefit more from reviewing the plan than the raw code
It is acceptable to write the tech spec after an e2e prototype if that leads to a more accurate implementation plan. Do not force a premature tech spec when the implementation details are still too uncertain.
### 4. Implement approved specs
After the specs are approved, use the `implement-specs` skill to build from the approved `PRODUCT.md` and `TECH.md`.
The implementation can often be pushed in the same PR as the product and tech specs. As the engineer iterates, keep `PRODUCT.md`, `TECH.md`, code changes, and tests in that same PR so the review reflects the feature that will actually ship.
For large features, the implementer may optionally offer:
- `PROJECT_LOG.md` to track explored paths, checkpoints, and current implementation state
- `DECISIONS.md` to capture concrete product and technical decisions made during design and implementation
These are optional aids, not required outputs.
### 5. Keep specs current during implementation
If implementation changes from the spec, update the spec rather than leaving it stale.
Update `PRODUCT.md` when:
- user-facing behavior changes
- success criteria change
- UX details or edge cases change
Update `TECH.md` when:
- the implementation approach changes
- architectural boundaries move
- risks, dependencies, or rollout details change
- the testing or validation plan changes
The checked-in specs should describe the feature that actually ships, not just the initial intent. Keep those spec updates in the same PR as the related code changes whenever practical.
### 6. Verify behavior against the spec
Before considering the work complete, make sure verification maps back to the specs. Prefer tests and artifacts that validate the product behavior directly:
- use the `rust-unit-tests` skill for crate-level unit tests and regression coverage
- integration tests for critical user flows
- loom walkthroughs or equivalent feature demonstrations when appropriate
- screenshots or videos when useful for UI-heavy work
## Best Practices
- Be pragmatic above all else.
- Write specs to improve input quality for agents, not as ceremony.
- Keep product specs behavior-oriented and implementation-light.
- Keep tech specs implementation-oriented and grounded in current codebase patterns.
- Use review time to validate specs and behavior, not to over-index on code style nits.
## Related Skills
- `implement-specs`
- `write-product-spec`
- `write-tech-spec`
+10 -3
View File
@@ -1,10 +1,15 @@
---
name: triage-issue-local
specializes: triage-issue
description: Repo-specific triage guidance for warp-external. Only the categories declared overridable by the core triage-issue skill may be specialized here.
specializes_source: warpdotdev/oz-for-oss:.agents/skills/triage-issue
description: Repo-specific triage guidance for warp. Only the categories declared overridable by the core triage-issue skill may be specialized here.
---
# Repo-specific triage guidance for `warp-external`
# Repo-specific triage guidance for `warp`
## Prerequisite: install the parent skill
This skill specializes the core `triage-issue` skill (named in the `specializes` frontmatter field) and is not functional on its own. Before applying its guidance, confirm the parent skill is installed and resolvable at `.agents/skills/triage-issue/SKILL.md`. If it is missing, install it first by copying the skill directory from the source declared in the `specializes_source` frontmatter field (`warpdotdev/oz-for-oss:.agents/skills/triage-issue`). Then continue with the guidance below.
This file is a companion to the core `triage-issue` skill. It does not
redefine the triage output schema, safety rules, or follow-up-question
@@ -13,7 +18,7 @@ marks as overridable.
## Heuristics
- `warp-external` is the public-facing Warp desktop client repository. Treat public issue reports as potentially incomplete and avoid asking for secrets, tokens, private workspace names, private repository names, or account identifiers in the public issue thread.
- `warp` is the public-facing Warp desktop client repository. Treat public issue reports as potentially incomplete and avoid asking for secrets, tokens, private workspace names, private repository names, or account identifiers in the public issue thread.
- Distinguish the user's observed Warp behavior from their guesses about Rust modules, UI components, server behavior, feature flags, or product intent.
- For issue reports that mention another terminal, editor, shell, or CLI tool, identify whether the problem is Warp-specific or generally reproducible outside Warp before assigning Warp ownership.
- When the issue includes screenshots, videos, logs, stack traces, or command output, use them as primary evidence and ask follow-up questions only for missing details that cannot be inferred from that evidence.
@@ -28,6 +33,8 @@ Ask **at most 2 follow-up questions** per triage response. Each question must be
The label taxonomy for this repository is managed in `.github/issue-triage/config.json`. Prefer labels from that configuration, especially the `area:*`, `os:*`, `repro:*`, `accessibility`, `needs-info`, `duplicate`, and primary issue-type labels. Do not invent new labels unless the prompt explicitly allows it.
Evaluate `ready-to-implement` during triage instead of relying on issue-template defaults. For bug reports, apply `ready-to-implement` only when the issue is reproducible from the provided evidence or straightforward local verification and the likely fix appears narrow enough to implement without a product spec, design mocks, or substantial investigation. If the bug is not reproducible, lacks a clear fix path, requires product/design decisions, or needs deeper technical discovery, omit `ready-to-implement` and prefer `needs-info`, `ready-to-spec`, `needs-mocks`, or the appropriate `repro:*` label.
Use area labels based on the user's reported surface:
- `area:shell-terminal` for terminal output, block rendering, shell integration, prompt rendering, command execution display, and terminal-emulation behavior.
-114
View File
@@ -1,114 +0,0 @@
---
name: update-skill
description: Create or update skills by generating, editing, or refining SKILL.md files in this repository. Use when authoring new skills or revising the structure, frontmatter, or guidance for existing ones.
---
# update-skill
This guide provides instructions for creating or updating skills in this repository. It covers the required structure, frontmatter, and best practices for skills.
## Quick Start
Every skill is a directory containing a `SKILL.md` file with YAML frontmatter and markdown body:
```markdown
---
name: pdf-processing
description: Extract text and tables from PDF files, fill forms, merge documents.
---
# PDF Processing
## When to use this skill
Use this skill when the user needs to work with PDF files...
## How to extract text
1. Use pdfplumber for text extraction...
## How to fill forms
...
```
## Requirements
### Frontmatter (Required)
Every SKILL.md must start with YAML frontmatter containing:
- **name**: Kebab-case identifier (lowercase letters, numbers, hyphens only)
- Example: `add-feature-flag`, `rust-unit-tests`, `update-skill`
- **description**: Specific description of what the skill does and when to use it
- Must be non-empty
- Should include key terms for skill discovery
- Begin with an action verb to clearly state what the skill accomplishes (e.g., "Adds feature flags..." instead of "Helps with features..."), and immediately follow with a specific use case or context (e.g., "Use when working with feature flags")
- Write in third person (e.g., "Adds feature flags..." not "I can help you add...")
### Writing Effective Descriptions
The description field is critical for skill discovery. Include both **what** the skill does and **when** to use it. Some good examples:
- `git-commit`: "Generate descriptive commit messages by analyzing git diffs. Use when the user asks for help writing commit messages or reviewing staged changes."
- `pdf-processing`: "Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction."
Avoid vague descriptions like "Helps with code" or "Does development tasks". For more context, see "Description Best Practices" in [references/best-practices.md](references/best-practices.md).
### Skill Structure
Typical sections in Warp skills:
1. **Title and brief summary** Clear title and a concise overview of the skill's purpose and primary use cases. Link to sections, reference files or related skills if useful
2. **Overview** - Context about the skill's purpose (optional but common), extends the summary with more details and context
3. **Main content** - Steps, usage instructions, or workflow guidance
4. **Best Practices** - Guidelines and recommendations (optional)
5. **Examples / Reference PRs** - Links to real examples (optional)
Keep the structure flexible based on the skill's needs. Simple skills can omit the optional sections.
### Validation
Optionally, use the [skills-ref](https://github.com/agentskills/agentskills/tree/main/skills-ref) reference library to validate your skills:
```bash
skills-ref validate ./my-skill
```
This checks that your SKILL.md frontmatter is valid and follows all naming conventions. If not installed, use the WebSearch tool to get context around this package.
### Main Content Best Practices
- For guidance on what qualifies as good main content, see "Conciseness Principles" in [references/best-practices.md](references/best-practices.md)
- When formatting code examples, see "Code Example Formatting" in [references/best-practices.md](references/best-practices.md).
### File Organization
- **Simple skills** (<=200 lines): Keep everything in SKILL.md
- **Complex skills** (>200 lines): Split detailed content into `references/` subdirectory
- Reference files from SKILL.md with clear links
- Example: "See [references/best-practices.md](references/best-practices.md) for detailed guidance"
## When to Split Content
Create `references/` subdirectory when:
- SKILL.md approaches 200+ lines
- Skill covers multiple domains or workflows that can be loaded independently
- Detailed reference material would clutter the main instructions
Keep only essential workflow and procedural instructions in SKILL.md. Move detailed reference material, schemas, and extensive examples to `references/` files.
## Examples from Existing Skills
For reference on structure and style:
- `.agents/skills/add-feature-flag/SKILL.md` - Multi-step workflow with clear sequential steps
- `.agents/skills/rust-unit-tests/SKILL.md` - Comprehensive guide with code examples and helper utilities
- `.agents/skills/remove-feature-flag/SKILL.md` - Cleanup workflow with search commands
## Best Practices
See [references/best-practices.md](references/best-practices.md) for detailed authoring guidance including:
- Progressive disclosure patterns
- Writing concise, effective instructions
- Code example formatting
- Common anti-patterns to avoid
@@ -1,310 +0,0 @@
# Best Practices for Warp Skills
Detailed authoring guidance for creating effective skills in `.agents/skills/`.
## Progressive Disclosure
Skills use a loading system to manage context efficiently:
1. **Metadata (name + description)** - Always loaded at startup
2. **SKILL.md body** - Loaded when skill triggers
3. **Reference files** - Loaded only when needed
### When to Use References
Keep SKILL.md under 150-200 lines. When content grows beyond this:
**Pattern 1: High-level guide with references**
SKILL.md contains the core workflow and points to detailed references:
```markdown
## Advanced Features
- **Detailed configuration**: See [references/config.md](references/config.md)
- **API reference**: See [references/api.md](references/api.md)
- **Examples**: See [references/examples.md](references/examples.md)
```
**Pattern 2: Domain-specific organization**
For skills with multiple independent domains, organize by domain:
```
skill-name/
├── SKILL.md (overview and navigation)
└── references/
├── domain-a.md
├── domain-b.md
└── domain-c.md
```
When the user works with domain-a, the agent only loads domain-a.md, not the others.
**Pattern 3: Conditional details**
Show basic content inline, link to advanced content:
```markdown
## Basic Usage
[Core instructions here]
**For advanced configuration**: See [references/advanced.md](references/advanced.md)
```
### Important Guidelines
- **Keep references one level deep** - All reference files should link directly from SKILL.md
- **Avoid nested references** - Don't create references that reference other files
- **Add table of contents** - For reference files >100 lines, include TOC at the top
## Writing Effective Descriptions
The description field enables skill discovery. the agent uses it to decide when to load the skill.
### Description Best Practices
1. **Be specific and include key terms**
- Good: "Add a new feature flag to gate code changes in the Warp codebase."
- Avoid: "Helps with features."
2. **Include both what and when**
- What the skill does: "Write, improve, and run Rust unit tests"
- When to use it: "in the warp Rust codebase"
3. **Write in third person**
- Good: "Adds feature flags to gate code changes"
- Avoid: "I can help you add feature flags"
- Avoid: "You can use this to add feature flags"
4. **Include trigger terms**
- Mention specific files, commands, or concepts
- Example: "Use when working with PDF files, forms, or document extraction"
## Conciseness Principles
Context window is shared across all skills, conversation history, and the system prompt. Every token matters.
### Default Assumption: Agent is Already Smart
Only add context the agent doesn't already have. Challenge each piece:
- "Does the agent really need this explanation?"
- "Can I assume the agent knows this?"
- "Does this paragraph justify its token cost?"
**Good (concise):**
```markdown
## Extract PDF text
Use pdfplumber for text extraction:
\`\`\`python
import pdfplumber
with pdfplumber.open("file.pdf") as pdf:
text = pdf.pages[0].extract_text()
\`\`\`
```
**Bad (verbose):**
```markdown
## Extract PDF text
PDF (Portable Document Format) files are a common file format that contains
text, images, and other content. To extract text from a PDF, you'll need to
use a library. There are many libraries available for PDF processing, but we
recommend pdfplumber because it's easy to use and handles most cases well.
First, you'll need to install it using pip. Then you can use the code below...
```
The concise version assumes the agent knows what PDFs are and how libraries work.
## Code Example Formatting
### Syntax Highlighting
Always specify the language for code blocks:
```rust
pub fn example() {
println!("Always specify language");
}
```
```bash
cargo nextest run --workspace
```
### Example Structure
For workflow-based skills, show before/after or step-by-step:
```markdown
### Before:
\`\`\`rust
if FeatureFlag::YourFeature.is_enabled() {
// new behavior
} else {
// old behavior (dead code)
}
\`\`\`
### After:
\`\`\`rust
// new behavior (unconditionally enabled)
\`\`\`
```
### Inline Commands
For shell commands, show the complete command with flags:
```bash
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
```
Explain non-obvious flags if necessary, but prefer self-documenting commands.
## Workflows vs Simple Instructions
### When to Use Workflows
Use numbered steps for multi-step processes where order matters:
```markdown
## Workflow
1. Analyze the form structure
2. Create field mapping
3. Validate mapping
4. Fill the form
5. Verify output
```
Include a checklist for complex workflows:
```markdown
Copy this checklist and track progress:
\`\`\`
Task Progress:
- [ ] Step 1: Analyze form
- [ ] Step 2: Create mapping
- [ ] Step 3: Validate
- [ ] Step 4: Fill form
- [ ] Step 5: Verify
\`\`\`
```
### When to Use Simple Instructions
For straightforward tasks, skip the workflow structure:
```markdown
## Adding a Feature Flag
Add the feature to `app/Cargo.toml`:
\`\`\`toml
[features]
your_feature_name = []
\`\`\`
Then gate code with the runtime check:
\`\`\`rust
if FeatureFlag::YourFeatureName.is_enabled() {
// feature-gated behavior
}
\`\`\`
```
## Common Anti-Patterns
### ❌ Windows-Style Paths
Always use forward slashes:
- ✓ Good: `scripts/helper.py`, `references/guide.md`
- ✗ Avoid: `scripts\helper.py`, `references\guide.md`
### ❌ Vague Descriptions
Be specific:
- ✗ Avoid: "Helps with documents"
- ✓ Good: "Extract text and tables from PDF files"
### ❌ Too Many Options
Don't present multiple approaches unless necessary:
- ✗ Avoid: "You can use pypdf, or pdfplumber, or PyMuPDF, or..."
- ✓ Good: "Use pdfplumber for text extraction. For scanned PDFs requiring OCR, use pdf2image with pytesseract instead."
### ❌ Time-Sensitive Information
Don't include dates or version-specific guidance:
- ✗ Avoid: "If you're doing this before August 2025, use the old API."
- ✓ Good: Use a "Current method" and "Old patterns" section with deprecation notes
### ❌ Inconsistent Terminology
Choose one term and use it throughout:
- ✗ Avoid: Mix "API endpoint", "URL", "API route", "path"
- ✓ Good: Always "API endpoint"
### ❌ Explaining the Obvious
Skip explanations for concepts the agent already knows:
- ✗ Avoid: "Git is a version control system that tracks changes in files..."
- ✓ Good: "Use `git --no-pager diff` to see changes without pagination"
### ❌ Over-Structuring Simple Skills
Not every skill needs an Overview, Best Practices, and Examples section. Use only what adds value:
- Simple skills: Title + instructions
- Medium skills: Title + Overview + instructions
- Complex skills: Full structure with multiple sections
## Naming Conventions
Use consistent naming patterns for skills:
**Recommended: Gerund form (verb + -ing)**
- `processing-pdfs`
- `analyzing-spreadsheets`
- `managing-databases`
- `testing-code`
**Acceptable alternatives:**
- Noun phrases: `pdf-processing`, `spreadsheet-analysis`
- Action-oriented: `process-pdfs`, `analyze-spreadsheets`
**Avoid:**
- Vague names: `helper`, `utils`, `tools`
- Overly generic: `documents`, `data`, `files`
Consistent naming makes skills easier to reference, understand at a glance, and organize.
## Skill Iteration
Skills improve through usage. When updating a skill:
1. **Observe usage** - Note where the agent struggles or succeeds
2. **Identify gaps** - What information was missing or unclear?
3. **Update targeted sections** - Fix specific issues without over-explaining
4. **Test changes** - Use the skill on similar tasks to verify improvements
Keep iterations focused. Don't add content preemptively—only add what's proven necessary through real usage.
-164
View File
@@ -1,164 +0,0 @@
---
name: write-product-spec
description: Write a PRODUCT.md spec for a significant user-facing feature in Warp, focused on detailed behavior and validation. Use when the user asks for a product spec, desired behavior doc, or PRD, wants to define feature behavior before implementation, or when the feature is substantial or behaviorally ambiguous enough that a written spec would improve implementation or review.
---
# write-product-spec
Write a `PRODUCT.md` spec for a significant feature in Warp.
## Overview
The product spec should make the desired behavior unambiguous enough that an agent can implement it correctly and avoid regressions. Describe the feature purely from the user's perspective — what the user sees, does, and experiences, and the invariants that must hold for them. Do not include implementation details (internal types, state layout, module boundaries, data flow, algorithms).
"User" is not limited to the end user of the Warp app. It means whoever consumes the surface being designed:
- For UI / UX features: the human using Warp.
- For a data model: the code that reads and writes that model.
- For an API, protocol, or library: the callers of that API — other services, client code, plugins, or agents.
- For a CLI tool or developer-facing surface: the developer invoking it.
The spec should describe behavior from that consumer's perspective: the shape of the surface, the operations they can perform, what they see back, invariants they can rely on, and edge cases they must handle — without prescribing how the surface is implemented underneath.
Implementation details, validation, and test planning live in a companion `TECH.md`, produced by the `write-tech-spec` skill. Writing the product spec is usually the first step of a two-step process: once `PRODUCT.md` is agreed on, invoke `write-tech-spec` to produce `TECH.md` for the same feature (or let the user know that's the expected next step). The product spec should be written so the tech spec can be written directly from it.
Write specs to `specs/<id>/PRODUCT.md`, where `<id>` is one of:
- a Linear ticket number (e.g. `specs/APP-1234/PRODUCT.md`)
- a GitHub issue id, prefixed with `gh-` (e.g. `specs/gh-4567/PRODUCT.md`)
- a short kebab-case feature name (e.g. `specs/vertical-tabs-hover-sidecar/PRODUCT.md`)
`specs/` should contain only id-named directories as direct children — no engineer-named subdirectories.
Ticket / issue references are optional. If the user has a Linear ticket or GitHub issue, use its id. If they don't, ask them for a feature name to use as the directory. Only create a new Linear ticket or GitHub issue when the user explicitly asks for one; in that case use the Linear MCP tools or `gh` CLI respectively (and `ask_user_question` if team, labels, or repo are unclear).
## Before writing
Gather only the context you need: directory id (Linear ticket, GitHub issue, or feature name), feature summary, target users, key behaviors, edge cases, and how the feature will be validated. Use `ask_user_question` for missing context rather than guessing.
### Figma mocks
If the feature has any UI or interaction design, ask the user whether a Figma mock exists before drafting the Behavior section, and include the link in the spec when one is provided. A mock is often the most reliable source of truth for visual states, spacing, and edge-case layouts — not asking can cause the Behavior section to guess at intent the designer already settled.
- If the user provides a link, include it under a short `## Figma` section (or inline near the top of Behavior) as `Figma: <link>`.
- If the user confirms no mock exists, note `Figma: none provided` so the absence is explicit rather than ambiguous.
- If the feature is purely backend (data model, API, CLI with no visual surface), skip the question and omit the section.
Do not silently drop design context; an explicit "none" is preferable to no mention at all on features where design would normally be expected.
## Structure
Required sections:
1. **Summary** — 13 sentences describing the feature and desired outcome.
2. **Behavior** — The meat of the spec. An exhaustive English description of how the feature works, written as numbered, testable invariants. See "The Behavior section" below — this is where the spec earns its length, and everything else should stay thin to avoid duplicating it.
Optional sections — include only when they add signal beyond the core. Omit the heading entirely if empty; do not write "None" as a placeholder.
- **Problem** — Include only when the motivation isn't obvious from Summary.
- **Goals / Non-goals** — Include when scope is ambiguous or has been contested.
- **Figma** — Include with a link when one exists, or an explicit `Figma: none provided` note when design matters but no mock exists. Omit entirely for non-visual features. See "Figma mocks" above.
- **Open questions** — Prefer inline `**Open question:** …` next to the relevant behavior. Include a dedicated section only if there are multiple unresolved questions worth collecting.
Do not include Validation, Success criteria, or Testing sections. Validation and test planning live in the companion `TECH.md` (produced by `write-tech-spec`). Write Behavior as numbered invariants that are testable on their own — the tech spec can reference them directly.
## The Behavior section
Behavior is the spec. Everything else is framing.
The goal of Behavior is a complete English description of how the feature works, detailed enough that a tech spec can be written directly from it without the author having to guess or re-derive product intent. If a reader finishes Behavior with questions about what the feature does in some situation, the section is not done.
Describe, at minimum:
- Default behavior and the happy-path user flow.
- Every user-visible state and the transitions between them.
- All inputs the user can provide and how the feature responds.
- Empty states, error states, loading / pending states, and cancellation.
- Edge cases a reasonable implementer would not think to ask about — permission denied, offline, timeouts, races between state changes, multiple concurrent instances, stale or missing data, focus loss mid-interaction, interactions with adjacent features.
- Keyboard, accessibility, and focus expectations where relevant.
- Invariants that must hold at all times and behaviors that must not regress.
Length Behavior to match the feature. Trivial features may need a handful of invariants; complex features may need many, with sub-sections per flow or state. The rest of the spec should stay thin so Behavior can be as exhaustive as the feature requires without producing a bloated document overall. Err toward enumerating one more edge case rather than one fewer.
## Length heuristic
Behavior should be as long as the feature requires — do not truncate edge cases to hit a line target. The heuristic below applies to everything around Behavior (Summary, optional sections): keep that framing thin so the spec's total length reflects the feature's actual complexity, not structural overhead.
- Trivial fix or narrow UI tweak: no spec.
- Small feature (single module, few edge cases): framing plus Behavior typically ~3060 lines total.
- Medium feature (cross-module, multiple states): typically ~80150 lines total.
- Large or behaviorally rich feature: longer is fine, and most of the length should live in Behavior.
If you find yourself writing the same idea in Summary, Problem, Goals, and Behavior, collapse the framing — not the Behavior content.
## Writing guidance
- Prefer concrete, observable behavior over aspirational wording.
- Write Behavior as a list of invariants rather than prose when possible.
- Capture invariants that must not regress and edge cases that are easy to miss.
- Avoid implementation details unless unavoidable for the UX.
- Each section should earn its place — if a section would repeat another or contain only boilerplate, omit it.
## Keep the spec current
Approved specs may ship in the same PR as the implementation. As implementation evolves, update `PRODUCT.md` in the same PR when user-facing behavior or UX details change. The checked-in spec should describe the feature that actually ships.
For large features, the implementer may optionally keep a `DECISIONS.md` file summarizing concrete decisions made during design and implementation. Offer it when it would help future agents; otherwise skip it.
## Related Skills
- `implement-specs`
- `write-tech-spec`
- `spec-driven-implementation`
## Example Behavior section
A sample Behavior section for a hypothetical feature: rendering GitHub-flavored Markdown tables in the Warp block list. It demonstrates the expected shape — numbered, testable, user-perspective invariants that enumerate defaults, edge cases, malformed input, streaming, selection/copy, search, sharing, theming, and cross-surface consistency, with one inline open question.
````markdown
## Behavior
1. When a terminal output block contains a GitHub-flavored Markdown table (a header row, a separator row of one or more `---` segments, and one or more body rows, all delimited by `|`), that table renders as a visually formatted table in the block — not as raw pipe-delimited text.
2. The table renders with:
- A visually distinct header row.
- Aligned columns based on the separator row: `|:---|` left-align, `|:---:|` center, `|---:|` right-align. `|---|` with no colons falls back to the default alignment (left for text, right for numeric-looking values).
- Visible row separators (or equivalent spacing) consistent with the active theme.
3. Inline markdown inside a cell renders inline: bold, italic, inline code, strikethrough, and links all render the same way they do in the surrounding block output. Line breaks inside a cell (`<br>` or escaped `\n`) render as in-cell line breaks.
4. Column widths are chosen to fit the table's natural content when it fits inside the block. If a single cell's content is very long, that cell wraps its text within its column rather than forcing the column to an unreasonable width.
- **Open question:** when a wrapped cell would produce an unreasonably tall row, do we clip with an "expand" affordance, or let the row grow unbounded?
5. Horizontal scrolling: when the table's total width exceeds the block width — many columns, or wide columns that can't reasonably be narrowed — the table becomes horizontally scrollable within the block. Scrolling horizontally reveals off-screen columns without clipping or truncating them. Vertical scrolling of the block continues to work independently of table scroll.
6. When the block is resized (terminal resize, pane split, sidebar open/close), the table reflows to the new width without losing row or column order.
7. Empty cells render as visibly empty (same row height as surrounding cells, no placeholder text). A row with all empty cells still renders as a row.
8. A table with only a header and separator (zero body rows) renders as a header-only table, not as raw text.
9. A single-column table renders as a single-column table (not collapsed to a bullet list or similar).
10. Malformed tables fall back gracefully:
- Missing separator row → rendered as preformatted text, not as a table.
- Ragged rows (some rows have fewer or more cells than the header) → missing cells render empty; extra cells are shown, with the header row extended visually if possible. The block should never silently drop data.
- Unclosed table (last row truncated mid-stream) → rendered as a partial table; see (11).
11. Streaming output: while a command is still producing rows, the table renders incrementally. New rows append as they arrive. The header row locks in as soon as the separator line is received; rows before the separator render as plain text until the table is recognized.
12. Selection and copy:
- Selecting across cells with the mouse or keyboard selects their visible text content.
- Copying the selection produces tab-separated plain text by default (one row per line, cells separated by tabs). An affordance (context menu, shortcut) lets the user copy the original markdown source instead.
- Copying the entire block preserves the original markdown source verbatim.
13. Search within a block (find-in-block) matches against cell text content. Matches highlight in place in the rendered cell; navigating matches scrolls the table into view, including horizontally if the match is in an off-screen column.
14. Sharing or exporting a block (Warp Drive, share link, save as file) preserves the original markdown source, not the rendered form.
15. Theming: table borders, header backgrounds, alternating row shading (if any), and link/code styles all come from the active Warp theme. No hard-coded colors.
16. Markdown tables render consistently wherever block-list markdown already renders — command output, agent responses, and any other block type that supports inline markdown. The same input produces the same table in each surface.
17. Non-table pipe content is not misrendered as a table. Text that contains `|` characters but no valid header-separator line remains plain text, even if it visually resembles a table.
````
-81
View File
@@ -1,81 +0,0 @@
---
name: write-tech-spec
description: Write a TECH.md spec for a significant Warp feature after researching the current codebase and implementation constraints. Use when the user asks for a technical spec, implementation plan, or architecture doc tied to a product spec.
---
# write-tech-spec
Write a `TECH.md` spec for a significant feature in Warp.
## Overview
The tech spec should translate product intent into an implementation plan that fits the existing codebase, documents architectural choices, and makes the work easier for agents to execute and reviewers to evaluate.
Write specs to `specs/<id>/TECH.md`, where `<id>` is one of:
- a Linear ticket number (e.g. `specs/APP-1234/TECH.md`)
- a GitHub issue id, prefixed with `gh-` (e.g. `specs/gh-4567/TECH.md`)
- a short kebab-case feature name (e.g. `specs/vertical-tabs-hover-sidecar/TECH.md`)
Match the id used by the sibling `PRODUCT.md` when one exists. `specs/` should contain only id-named directories as direct children.
Ticket / issue references are optional. If the user has a Linear ticket or GitHub issue, use its id. If they don't, ask them for a feature name to use as the directory. Only create a new Linear ticket or GitHub issue when the user explicitly asks for one; in that case use the Linear MCP tools or `gh` CLI respectively (and `ask_user_question` if team, labels, or repo are unclear).
## When to use
Use this skill when the implementation spans multiple modules, has meaningful architectural tradeoffs, or when reviewers will benefit from seeing the plan before or alongside the code. For pure UI changes or straightforward fixes, a tech spec is often unnecessary.
Prefer to have a `PRODUCT.md` first so the technical plan is anchored to agreed behavior. If the implementation is still too uncertain, build an e2e prototype first and then write the tech spec from what was learned.
## Research before writing
Before drafting, read the product spec (if any), inspect the relevant code, and identify the main files, types, data flow, and ownership boundaries. Do not guess about current architecture when the code can be inspected directly.
## Structure
Required sections:
1. **Context** — What's being built, how the current system works in the area being changed, and the most relevant files with line references. Combine the "problem," "current state," and "relevant code" into one grounded section. Example references:
- `app/src/workspace/mod.rs:42` — entry point for the user flow
- `app/src/workspace/workspace.rs (120-220)` — state and event handling that will likely change
Reference `PRODUCT.md` for user-visible behavior rather than restating it.
2. **Proposed changes** — The implementation plan: which modules change, new types/APIs/state being introduced, data flow, ownership boundaries, and how the design follows existing patterns. Call out tradeoffs when there is more than one reasonable path.
3. **Testing and validation** — How the implementation will be verified against the product behavior. Owns everything about proving the feature works: unit tests, integration tests, manual steps, screenshots, videos, and any other verification. Reference the numbered Behavior invariants from `PRODUCT.md` directly rather than restating them; each important invariant should map to a concrete test or verification step. This section is where validation lives — `PRODUCT.md` intentionally does not have a Validation section.
Optional sections — include only when they add signal. Omit the heading entirely if empty; do not write "None" as a placeholder.
- **End-to-end flow** — Include only when tracing the path through the system tells you something the Proposed changes list doesn't.
- **Diagram** — Include a Mermaid diagram only when a visual will explain the design faster than prose (data flow, state transitions, sequence across layers). Prefer one or two focused diagrams over decorative ones.
- **Risks and mitigations** — Include when there are real failure modes, regressions, migration concerns, or rollout hazards worth calling out.
- **Parallelization** — Include when work can cleanly split across multiple agents and that split is non-obvious.
- **Follow-ups** — Include when there is deferred cleanup or future work worth naming.
## Length heuristic
Right-size the spec to the feature:
- Single-file change with clear approach: skip the tech spec or keep it under ~40 lines.
- Multi-module change with some ambiguity: target ~80150 lines.
- Large cross-cutting or architecturally novel change: longer is fine when every section earns its place.
If Context and Proposed changes end up describing the same files and state from different angles, collapse them.
## Writing guidance
- Ground the plan in actual codebase structure and patterns.
- Prefer concrete implementation guidance over generic architecture language.
- Explain why the proposed design fits this repo.
- Reference `PRODUCT.md` for behavior instead of restating it.
- Each section should earn its place — if a section would repeat another or contain only boilerplate, omit it.
## Keep the spec current
Approved specs may ship in the same PR as the implementation. Update `TECH.md` in the same PR when module boundaries, implementation sequencing, risks, validation strategy, or rollout assumptions change. The checked-in spec should describe the implementation that actually ships.
For large features, the implementer may optionally keep a `DECISIONS.md` file summarizing concrete decisions. Offer it when it would help future agents; otherwise skip it.
## Related Skills
- `implement-specs`
- `write-product-spec`
- `spec-driven-implementation`
+10 -3
View File
@@ -8,11 +8,18 @@
# `script/bundle`.
MACOSX_DEPLOYMENT_TARGET = "10.14"
[build]
rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-args=-Wl,-headerpad_max_install_names"]
[net]
git-fetch-with-cli = true
# Use the v0 symbol mangling scheme on every target. It encodes generic
# instantiations (which the legacy scheme hashes away irreversibly), improving
# demangled names in backtraces and Sentry symbolication. On wasm the shipped
# bundle strips symbol names, so this only enriches the split-off debug artifact.
[target.'cfg(all())']
rustflags = ["-C", "symbol-mangling-version=v0"]
[target.'cfg(target_os = "macos")']
rustflags = ["-C", "link-args=-Wl,-headerpad_max_install_names"]
[target.'cfg(target_family = "wasm")']
rustflags = ["--cfg=web_sys_unstable_apis"]
+2 -2
View File
@@ -1,3 +1,3 @@
*.pdb filter=lfs diff=lfs merge=lfs -text
input_classifier/models/* filter=lfs diff=lfs merge=lfs -text
input_classifier/models/**/*tokenizer.json linguist-generated=true
crates/input_classifier/models/** filter=lfs diff=lfs merge=lfs -text
crates/input_classifier/models/**/*tokenizer.json -filter -diff -merge text linguist-generated=true
+1 -2
View File
@@ -1,6 +1,6 @@
name: Bug Report
description: "Found a bug? Please search through our open issues and docs, to make sure it isn't already submitted. If you have an SSH related issue, please use the SSH Template below"
labels: ["bug", "ready-to-implement"]
labels: ["bug"]
body:
- type: checkboxes
attributes:
@@ -134,4 +134,3 @@ body:
- Ignore
validations:
required: false
@@ -6,7 +6,7 @@ body:
attributes:
label: "Pre-submit Checks"
options:
- label: "I have [searched Warp feature requests](https://github.com/warpdotdev/warp/issues?q=is%3Aissue+label%3AFEATURE) and there are no duplicates"
- label: "I have [searched Warp feature requests](https://github.com/warpdotdev/warp/issues?q=is%3Aissue+label%3Aenhancement) and there are no duplicates"
required: true
- label: "I have [searched Warp docs](https://docs.warp.dev) and my feature is not there"
required: true
@@ -42,6 +42,7 @@ body:
- macOS
- Linux
- Windows
- Cross-platform
validations:
required: true
- type: dropdown
+80 -17
View File
@@ -7,7 +7,7 @@
# Source of truth: warpdotdev/feedback-triage-bot ownership-areas.md
# Default fallback: FA leads are tagged when no more specific path matches
/ @vorporeal @alokedesai @zachbai @bnavetta @szgupta @jefflloyd
/ @warpdotdev/oss-maintainers
###########################################################################
# Team: App
@@ -33,7 +33,8 @@
/crates/ai/src/index/ @kevinyang372
/crates/ai/src/project_context/ @kevinyang372
/crates/editor/ @kevinyang372 @bnavetta
/crates/lsp/ @kevinyang372
/crates/languages/ @kevinyang372 @bnavetta
/crates/lsp/ @kevinyang372 @moirahuang
/crates/repo_metadata/ @kevinyang372
# Settings and keybindings
@@ -45,7 +46,7 @@
/app/src/terminal/input/ @vkodithala
/app/src/pane_group/ @vkodithala
/app/src/tab.rs @vkodithala
/crates/warp_ripgrep/ @vkodithala @moirahuang @szgupta
/crates/warp_ripgrep/ @moirahuang @szgupta
# Command palette
/app/src/command_palette.rs @acarl005
@@ -53,7 +54,6 @@
/app/src/search/command_palette/ @acarl005
# @ context, slash commands, and global search / file tree
/app/src/context_chips/ @moirahuang
/app/src/search/ai_context_menu/ @moirahuang
/app/src/search/files/ @moirahuang
/app/src/search/search_results_menu/ @moirahuang
@@ -62,6 +62,11 @@
/app/src/code/file_tree/ @moirahuang
/app/src/workspace/view/global_search/ @moirahuang
# Tab configs and worktrees
/app/src/tab_configs/ @moirahuang
/app/resources/tab_configs/ @moirahuang
/crates/warp_util/src/worktree_names.rs @moirahuang
# Onboarding / code review and git diff
/app/src/ai/onboarding.rs @kevinchevalier
/app/src/code_review/ @kevinchevalier
@@ -70,9 +75,18 @@
/crates/onboarding/ @kevinchevalier
# MCP and skills
/app/src/ai/mcp/ @peicodes
/app/src/ai/skills/ @peicodes
/crates/mcp/ @peicodes
/app/src/ai/mcp/ @peicodes @vkodithala
/app/src/ai/skills/ @peicodes @vkodithala
/crates/mcp/ @peicodes @vkodithala
/resources/bundled/skills/feedback/ @captainsafia
# File-based MCP (overrides MCP and skills above)
/app/src/ai/mcp/file_based_manager.rs @vkodithala
/app/src/ai/mcp/file_based_manager_tests.rs @vkodithala
/app/src/ai/mcp/file_mcp_watcher.rs @vkodithala
/app/src/ai/mcp/file_mcp_watcher_tests.rs @vkodithala
/app/src/ai/mcp/dummy_file_based_manager.rs @vkodithala
/app/src/ai/mcp/dummy_file_mcp_watcher.rs @vkodithala
# Conversation management / credit usage footer / input UI / natural-language detection
/app/src/ai/active_agent_views_model.rs @harryalbert
@@ -86,18 +100,27 @@
/crates/input_classifier/ @harryalbert
/crates/natural_language_detection/ @harryalbert
# Notifications
/app/src/notification.rs @harryalbert
/app/src/ai/agent_management/notifications/ @harryalbert
/app/src/terminal/view/inline_banner/ @harryalbert
# Blocklist UX / modality and cloud mode UI / shell compatibility / completions and bootstrap / warpifying
/app/src/ai/blocklist/ @zachbai
/app/src/root_view.rs @zachbai
/app/src/ai/blocklist/ @zachbai @MaggieShan
/app/src/root_view.rs @zachbai @MaggieShan
/app/src/terminal/bootstrap.rs @zachbai
/app/src/terminal/warpify/ @zachbai
/app/src/settings_view/warpify_page.rs @zachbai
/app/src/terminal/warpify/ @zachbai @MaggieShan
/app/src/settings_view/warpify_page.rs @zachbai @MaggieShan
/app/assets/bundled/bootstrap/ @zachbai
/crates/warp_completer/ @zachbai @szgupta @alokedesai
/crates/warp_completer/ @zachbai @szgupta @alokedesai @acarl005
# Agent mode
/app/src/ai/agent/ @zachbai
# Vertical tabs
/app/src/workspace/view/vertical_tabs.rs @johnturcoo
/app/src/workspace/view/vertical_tabs/ @johnturcoo
# Image attachment, voice input, and passive suggestions
/app/src/ai/attachment_utils.rs @Advait-M
/app/src/ai/voice/ @Advait-M
@@ -106,26 +129,58 @@
/app/src/voice/ @Advait-M
/crates/voice_input/ @Advait-M
# Rich input
/app/src/terminal/view/use_agent_footer/ @Advait-M
/app/src/terminal/view/use_agent_footer/warpify_footer.rs @MaggieShan
/app/src/terminal/input/cli_agent.rs @Advait-M
# UI framework
/crates/warpui/ @vorporeal
/crates/warpui_core/ @vorporeal
/crates/warpui/ @vorporeal @alokedesai @acarl005
/crates/warpui_core/ @vorporeal @alokedesai
/crates/warpui_extras/ @vorporeal
/crates/ui_components/ @vorporeal
/crates/warpui_extras/src/user_preferences @danielpeng
/crates/ui_components/ @vorporeal @acarl005 @zachbai @bnavetta
# Conversation rewind / CLI agent UI / macOS/Linux platform issues / performance issues
/app/src/terminal/cli_agent.rs @zachbai
/app/src/terminal/cli_agent_sessions/ @zachbai
/app/src/terminal/cli_agent.rs @zachbai @harryalbert
/app/src/terminal/cli_agent_tests.rs @zachbai @harryalbert
/app/src/terminal/cli_agent_sessions/ @zachbai @harryalbert
/app/src/terminal/input/rewind/ @alokedesai
/app/src/workspace/rewind_confirmation_dialog.rs @alokedesai
/app/src/platform/mac/ @alokedesai
/resources/linux/ @acarl005
# Windows and Linux (and winit) platform
/crates/warpui/src/windowing/winit/ @acarl005 @vorporeal @alokedesai
/app/src/terminal/local_tty/windows/ @abhishekp106 @vorporeal @acarl005
/app/src/autoupdate/windows.rs @acarl005
/app/src/autoupdate/linux.rs @vorporeal
/app/src/app_services/windows/ @acarl005
/app/src/app_services/linux/ @vorporeal
/app/src/terminal/writeable_pty/bootstrap_file/windows.rs @acarl005
/app/assets/bundled/bootstrap/pwsh.ps1 @acarl005
/app/assets/bundled/bootstrap/pwsh_init_shell.ps1 @acarl005
# /pr-comments
/app/src/search/slash_command_menu/static_commands/commands.rs @lucieleblanc
# Grep tool call UI
/app/src/ai/blocklist/action_model/execute/grep.rs @vkodithala
# Long-running shell commands
/app/src/ai/blocklist/action_model/execute/shell_command.rs @vkodithala @MaggieShan
/app/src/ai/blocklist/block/cli.rs @vkodithala @MaggieShan
/app/src/ai/blocklist/block/cli_controller.rs @vkodithala @MaggieShan
# Vim
/crates/vim/ @liliwilson
/app/src/vim_registers.rs @liliwilson
/app/src/code/editor/view/vim_handler.rs @liliwilson
/app/src/code/editor/view/vim_handler_tests.rs @liliwilson
/app/src/editor/view/vim_handler_test.rs @liliwilson
/app/src/terminal/view/inline_banner/vim_mode.rs @liliwilson
/app/src/settings/vim_banner.rs @liliwilson
###########################################################################
# Team: Platform
###########################################################################
@@ -178,5 +233,13 @@
/crates/managed_secrets/ @bnavetta @ianhodge
/crates/warp_web_event_bus/ @bnavetta @ianhodge
# Command signatures v2
/command-signatures-v2/ @zachbai
# OS-level bootstrap scripts
/script/linux/bootstrap @acarl005
/script/macos/bootstrap @acarl005
/script/windows/bootstrap.ps1 @acarl005
# Shell completions in the Warp CLI
/crates/warp_cli/src/completions.rs @zachbai @szgupta @alokedesai
@@ -37,5 +37,6 @@ runs:
-e GIT_RELEASE_TAG="$GIT_RELEASE_TAG" \
-e GITHUB_ACTIONS="$GITHUB_ACTIONS" \
-e GITHUB_OUTPUT="/dev/null" \
${SETTINGS_SCHEMA_CACHE:+-e SETTINGS_SCHEMA_CACHE=/github/workspace/.settings_schema_cache.json} \
arch-bundle-builder \
${{ inputs.channel }} ${{ inputs.release-tag }} ${{ inputs.arch }} ${{ inputs.artifact }}
+22 -5
View File
@@ -53,6 +53,22 @@ runs:
echo "is-workspace-root=false" >> $GITHUB_OUTPUT
fi
# Materialize Git LFS objects
- name: Fetch Git LFS objects
shell: bash
run: |
cd "${{ steps.repo-root.outputs.path }}"
git lfs install --local
git lfs pull
# Print a per-file listing of the LFS payloads in debug mode
- name: Verify LFS payloads
if: ${{ runner.debug == '1' }}
shell: bash
run: |
cd "${{ steps.repo-root.outputs.path }}"
git lfs ls-files --size
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
if: ${{ inputs.is_self_hosted != 'true' && !startsWith(runner.name, 'nsc-runner') }}
with:
@@ -85,6 +101,11 @@ runs:
with:
ssh-private-key: ${{ inputs.ssh_key }}
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
if: ${{ inputs.target_os == 'macos' && inputs.is_self_hosted != 'true' }}
with:
xcode-version: '26'
- name: Install dependencies
shell: bash
run: |
@@ -127,7 +148,7 @@ runs:
fi
- name: Install Node
uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20.9.0
@@ -159,7 +180,3 @@ runs:
echo "::error::protoc install step failed on ${{ inputs.target_os }}"
exit 1
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
if: ${{ inputs.target_os == 'macos' && inputs.is_self_hosted != 'true' }}
with:
xcode-version: '26'
+2 -2
View File
@@ -7,7 +7,7 @@ updates:
directory: "/"
schedule:
interval: "daily"
reviewers:
assignees:
- "warpdotdev/tech-leads"
registries:
- github-private
@@ -17,7 +17,7 @@ updates:
directory: "/"
schedule:
interval: "daily"
reviewers:
assignees:
- "warpdotdev/tech-leads"
cooldown:
# Don't update to any action release that is less than two weeks old.
+24 -15
View File
@@ -1,34 +1,41 @@
## Description
<!-- Please remember to add your design buddy onto the PR for review, if it contains any UI changes! -->
## Linked Issue
<!--
Link the GitHub issue this PR addresses. Before opening this PR, please confirm:
-->
- [ ] The linked issue is labeled `ready-to-spec` or `ready-to-implement`.
- [ ] Where appropriate, screenshots or a short video of the implementation are included below (especially for user-visible or UI changes).
## Testing
<!--
How did you test this change? What automated tests did you add? If you didn't add any new tests, what's your justification for not adding any?
If you're not sure whether you should add a test, check our testing policy: https://www.notion.so/warpdev/How-We-Code-at-Warp-257fe43d556e4b3c8dfd42f70004cc72#1f97825450504baa9c5fd87a737daa09
Manual testing is required for changes that can be manually tested, and almost all changes can be manually tested. If your change can be manually tested, please include screenshots or a screen recording that show it working end to end.
You can run the app locally using `./script/run` - see AGENTS.md for more details on how to get set up.
-->
## Server API dependencies
<!-- You may remove this section if your PR does not have any server dependencies. -->
- [ ] Is this change necessary to make the client compatible with a desired [server API breaking change](https://www.notion.so/warpdev/How-to-safely-introduce-server-API-breaking-changes-0aa805ff5d5d41fd8834f3c95caba0b4?pvs=4#d55ecf8aea3449949d3c33b0e67f6800)?
- [ ] Does this change rely on a [new server API](https://www.notion.so/warpdev/How-to-add-a-new-full-stack-feature-8412cede405a4ec194b32bdd4b951035?pvs=4#04da1e6a493542d68b3e998c7d339640)?
- [ ] If so, is the use of this API restricted to client channels that rely on the staging server (e.g. WarpDev)?
- [ ] Is this change enabling the use of a server API on client channels that rely on the production server (e.g. WarpStable)?
- [ ] If so, has the new server API been stable on production for at least one server release cycle? See [here](https://www.notion.so/warpdev/How-to-add-a-new-full-stack-feature-8412cede405a4ec194b32bdd4b951035?pvs=4#73b202f939834b97ab1fbdf7fc82cd53) for more details.
- [ ] I have manually tested my changes locally with `./script/run`
### Screenshots / Videos
<!-- Attach screenshots or a short video demonstrating the change, where appropriate. Remove this section if it is not relevant to your PR. -->
## Agent Mode
- [ ] Warp Agent Mode - This PR was created via Warp's AI Agent Mode
## Changelog Entries for Stable
<!--
## Changelog Entries for Stable
The entries below will be used when constructing a soft-copy of the stable release changelog. Leave blank or remove the lines if no entry in the stable changelog is needed. Entries should be on the same line, without the `{{` `}}` brackets. You can use multiple lines, even of the same type. The valid suffixes are:
* NEW-FEATURE: for new, relatively sizable features. Features listed here will likely have docs / social media posts / marketing launches associated with them, so use sparingly.
* IMPROVEMENT: for new functionality of existing features.
* BUG-FIX: for fixes related to known bugs or regressions.
* IMAGE: the image specified by the URL (hosted on GCP) will be added to Dev & Preview releases. For Stable releases, see the pinned doc in the #release Slack channel.
* OZ: Oz-related updates. Use `CHANGELOG-OZ`. At most 4 Oz updates are shown in-app per release.
-->
- NEW-FEATURE: for new, relatively sizable features. Features listed here will likely have docs / social media posts / marketing launches associated with them, so use sparingly.
- IMPROVEMENT: for new functionality of existing features.
- BUG-FIX: for fixes related to known bugs or regressions.
- IMAGE: the image specified by the URL (hosted on GCP) will be added to Dev & Preview releases. For Stable releases, see the pinned doc in the #release Slack channel.
- OZ: Oz-related updates. Use `CHANGELOG-OZ`. At most 4 Oz updates are shown in-app per release.
- NONE: Explicitly opt out of changelog inclusion. Use `CHANGELOG-NONE` for PRs that should never appear in the changelog (e.g. refactors, internal tooling, CI changes). This prevents the changelog agent from inferring an entry.
CHANGELOG-NEW-FEATURE: {{text goes here...}}
CHANGELOG-IMPROVEMENT: {{text goes here...}}
@@ -36,3 +43,5 @@ CHANGELOG-BUG-FIX: {{text goes here...}}
CHANGELOG-BUG-FIX: {{more text goes here...}}
CHANGELOG-IMAGE: {{GCP-hosted URL goes here...}}
CHANGELOG-OZ: {{text goes here...}}
CHANGELOG-NONE
-->
@@ -0,0 +1,171 @@
// Follows up on stale external-contributor PRs with active requested-changes
// reviews: posts escalating reminders, then closes after the final warning.
// Invoked from .github/workflows/stale_requested_changes_prs.yml via
// actions/github-script; `github` and `context` are injected by that action.
module.exports = async ({ github, context }) => {
const { owner, repo } = context.repo;
// Default to "full" if unset, because scheduled (cron) runs don't provide inputs
const mode = (context.payload.inputs && context.payload.inputs.mode) || 'full';
const canRemind = mode === 'reminder-only' || mode === 'full';
const canClose = mode === 'full';
const DAY_MS = 24 * 60 * 60 * 1000;
const REMINDER_DAYS = [7, 10];
const FINAL_WARNING_DAY = 10;
const CLOSE_DAY = 14;
const EXTERNAL_LABEL = 'external-contributor';
const EXEMPT_LABEL = 'no-autoclose';
const BOT_LOGIN = 'github-actions[bot]';
const markerFor = (stage) => `<!-- stale-requested-changes:stage=${stage} -->`;
const now = Date.now();
const ts = (value) => (value ? new Date(value).getTime() : 0);
// Returns the submitted_at (ms) of the most recent active requested-changes
// review, or 0 when no reviewer currently has changes requested. A reviewer's
// latest *decisive* review (APPROVED / CHANGES_REQUESTED / DISMISSED, tracked
// separately from COMMENTED so a later comment-reply doesn't flip state)
// determines their effective state.
const latestActiveChangesRequestedAt = (reviews) => {
const decisive = new Map();
const latest = new Map();
for (const r of reviews) {
const login = r.user && r.user.login;
if (!login) continue;
const at = ts(r.submitted_at);
if (!latest.has(login) || at > latest.get(login).at) {
latest.set(login, { state: r.state, at });
}
if (['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(r.state)) {
if (!decisive.has(login) || at > decisive.get(login).at) {
decisive.set(login, { state: r.state, at });
}
}
}
let latestAt = 0;
for (const login of latest.keys()) {
const eff = decisive.get(login) || latest.get(login);
if (eff.state === 'CHANGES_REQUESTED') latestAt = Math.max(latestAt, eff.at);
}
return latestAt;
};
const openPRs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
const summary = [];
for (const pr of openPRs) {
const labels = (pr.labels || []).map((l) => l.name);
const author = pr.user && pr.user.login;
if (pr.draft) continue;
if (pr.user && pr.user.type === 'Bot') continue;
// Skip internal member PRs and PRs with the no-autoclose label
if (!labels.includes(EXTERNAL_LABEL)) continue;
if (labels.includes(EXEMPT_LABEL)) continue;
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: pr.number,
per_page: 100,
});
const changesRequestedAt = latestActiveChangesRequestedAt(reviews);
if (!changesRequestedAt) continue;
const [issueComments, reviewComments] = await Promise.all([
github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr.number, per_page: 100 }),
github.paginate(github.rest.pulls.listReviewComments, { owner, repo, pull_number: pr.number, per_page: 100 }),
]);
// Server-recorded head-branch push time. commit.pushedDate and the
// force-push event timestamp are GitHub-controlled, so a backdated or
// force-pushed commit still resets the timer (unlike commit author/committer
// dates, which the contributor controls).
const pushData = await github.graphql(
`query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
commits(last: 1) { nodes { commit { pushedDate committedDate } } }
timelineItems(last: 50, itemTypes: [HEAD_REF_FORCE_PUSHED_EVENT]) {
nodes { ... on HeadRefForcePushedEvent { createdAt } }
}
}
}
}`,
{ owner, repo, number: pr.number }
);
const prGraph = pushData.repository.pullRequest;
const headCommit = (prGraph.commits.nodes[0] || {}).commit || {};
// Staleness anchor: the most recent active requested-changes review, plus
// author-driven events that reset the timer (PR creation, head-branch push,
// and comments by the PR author). Anchoring on the latest review ensures a
// subsequent requested-changes review restarts the window instead of
// measuring from stale, pre-review author activity. Maintainer/third-party/
// bot activity is otherwise ignored.
let lastActivity = Math.max(ts(pr.created_at), changesRequestedAt);
lastActivity = Math.max(lastActivity, ts(headCommit.pushedDate || headCommit.committedDate));
for (const ev of prGraph.timelineItems.nodes) {
lastActivity = Math.max(lastActivity, ts(ev.createdAt));
}
for (const c of issueComments) {
if (c.user && c.user.login === author) lastActivity = Math.max(lastActivity, ts(c.created_at));
}
for (const c of reviewComments) {
if (c.user && c.user.login === author) lastActivity = Math.max(lastActivity, ts(c.created_at));
}
const inactiveDays = (now - lastActivity) / DAY_MS;
// A stage counts as sent only when its marker comment was posted by our own
// workflow identity in the current window (at/after the last activity).
const sentStages = new Set();
for (const c of issueComments) {
if (!c.user || c.user.login !== BOT_LOGIN) continue;
if (ts(c.created_at) < lastActivity) continue;
for (const stage of REMINDER_DAYS) {
if ((c.body || '').includes(markerFor(stage))) sentStages.add(stage);
}
}
const dueStage = [...REMINDER_DAYS].reverse().find((s) => inactiveDays >= s);
const finalWarningSent = sentStages.has(FINAL_WARNING_DAY);
const shouldClose = inactiveDays >= CLOSE_DAY && finalWarningSent;
let action = 'none';
if (shouldClose) action = 'close';
else if (dueStage !== undefined && !sentStages.has(dueStage)) action = `remind:${dueStage}`;
const days = Math.floor(inactiveDays);
console.log(`PR #${pr.number} (@${author}, inactive ${days}d): ${action}${action === 'none' ? '' : ` [mode=${mode}]`}`);
if (action === 'none') continue;
summary.push(`#${pr.number} ${action} (${days}d)`);
if (action === 'close') {
if (!canClose) continue;
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: `Closing this pull request because the requested changes have gone unaddressed for over ${CLOSE_DAY} days. If you'd like to continue, push your updates and reopen the PR (or comment to ask a maintainer to reopen) — we'd be glad to pick it back up.`,
});
await github.rest.pulls.update({ owner, repo, pull_number: pr.number, state: 'closed' });
continue;
}
if (!canRemind) continue;
const stage = Number(action.split(':')[1]);
const remaining = Math.max(1, CLOSE_DAY - days);
const body = stage === FINAL_WARNING_DAY
? `Hi @${author} — final reminder: a reviewer requested changes on this PR and it has been inactive for ${days} days. It will be **automatically closed in about ${remaining} day(s)** unless you push updates or reply. Maintainers can apply the \`${EXEMPT_LABEL}\` label to keep it open.\n\n${markerFor(stage)}`
: `Hi @${author} — a reviewer requested changes on this PR and it hasn't had activity from you in ${days} days. When you get a chance, please push updates or reply to the review so a reviewer can take another look. Without activity, this PR will be automatically closed after ${CLOSE_DAY} days of inactivity.\n\n${markerFor(stage)}`;
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body });
}
console.log(`mode=${mode}; acted on ${summary.length} PR(s): ${summary.join(', ') || 'none'}`);
};
+72
View File
@@ -0,0 +1,72 @@
name: Changelog Draft
on:
# workflow_dispatch is restricted to users with write access to the repo.
# External contributors (fork-based) cannot trigger this workflow.
workflow_dispatch:
inputs:
channel:
description: "Release channel (stable, preview, dev)"
required: true
type: choice
options:
- stable
- preview
- dev
release_tag:
description: "Release tag (e.g. v0.2026.05.06.09.12.stable_00)"
required: true
type: string
attribution:
description: "Attribution mode"
required: false
type: choice
options:
- external-only
- all
- none
default: external-only
permissions:
contents: read
pull-requests: read
jobs:
draft:
name: Generate changelog draft
runs-on: namespace-profile-ubuntu-small
steps:
- name: Check out code
uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
with:
# Check out the default branch (not the release tag) so the skill
# files and scripts are always available — older release tags may
# not contain them. The release_tag is used only as the git range
# endpoint by the skill.
fetch-depth: 0
- name: Generate changelog draft
uses: warpdotdev/oz-agent-action@ce1621abf6a8ed8afdd4e4cc994545ede8fe1c6f # main
with:
prompt: |
Generate a changelog draft for the ${{ inputs.channel }} channel, release tag ${{ inputs.release_tag }}.
Attribution mode: ${{ inputs.attribution }}
Output directory: ${{ runner.temp }}/changelog-draft
Follow the workflow in .agents/skills/changelog-draft/SKILL.md exactly.
When fetching PR data, pass the checked-out repository ("${{ github.repository }}") to fetch_prs.py and rely on the script's repo-sync normalization to resolve public warpdotdev/warp PR numbers, URLs, and authors. The script intentionally omits non-repo-sync PRs from warp-internal because they are private internal changes. Do not infer or synthesize public PR links manually.
After writing the output files, print the full contents of changelog-draft.md to stdout so it appears in the workflow log.
You are running in a GitHub Actions workflow. The repo is checked out at the default branch (HEAD). Use the release_tag input as the git range endpoint — do NOT check out the release tag. `gh` is authenticated. Do not commit, push, or create PRs.
warp_api_key: ${{ secrets.WARP_API_KEY }}
share: team
- name: Upload changelog artifacts
uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3
with:
name: changelog-draft
path: |
${{ runner.temp }}/changelog-draft/changelog-draft.md
${{ runner.temp }}/changelog-draft/changelog-draft.json
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
if: github.event.pull_request.draft == false
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
# We don't actually need the contents of the files, just their names.
+134 -22
View File
@@ -57,7 +57,7 @@ jobs:
wasm-runner: ${{ steps.wasm_runner_type.outputs.value }}
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Check changed files
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
@@ -93,11 +93,13 @@ jobs:
# Federation provider is configured to only trust the base repository.
# We skip the auth + gcloud install steps in those runs and exclude SSH
# integration tests (which require gcloud to tunnel into a GCP test VM)
# via the filter suffix below. Tests that need gcloud all have `_ssh_`
# in their name. Fork PRs lose SSH integration test coverage; those
# tests still run post-merge against `master`.
# via the filter suffix below. Tests that need gcloud either have
# `_ssh_` in their name or exercise the remote-server SSH path. Fork PRs
# lose SSH integration test coverage; those tests still run post-merge
# against `master`.
HAS_GCP_AUTH: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
EXCLUDE_SSH_TESTS_FILTER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && ' and not test(/_ssh_/)' || '' }}
EXCLUDE_SSH_TESTS_FILTER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && ' and not test(/(_ssh_|remote_server)/)' || '' }}
EXCLUDE_REMOTE_SERVER_TESTS_FILTER: " and not test(/remote_server/)"
strategy:
fail-fast: false
matrix:
@@ -131,7 +133,7 @@ jobs:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -204,7 +206,7 @@ jobs:
- name: Install cargo nextest
if: ${{ matrix.is_self_hosted == false }}
uses: taiki-e/install-action@9a29ce630c67077a359246f3e4f84941e05f28b5 # v1
uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3
with:
tool: nextest
@@ -282,7 +284,7 @@ jobs:
if: matrix.os != 'windows' && (success() || failure())
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and not test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}"
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and not test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}${{ env.EXCLUDE_REMOTE_SERVER_TESTS_FILTER }}"
env:
# We run shell-agnostic tests against zsh, as it has the shortest
# bootstrap times and tends to be the most reliable.
@@ -307,7 +309,7 @@ jobs:
if: matrix.os != 'windows' && (success() || failure())
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}"
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}${{ env.EXCLUDE_REMOTE_SERVER_TESTS_FILTER }}"
env:
WARP_SHELL_PATH: ${{ steps.echo_shells_unix.outputs.default_bash_path }}
@@ -333,7 +335,7 @@ jobs:
if: (success() || failure()) && runner.os == 'macos'
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}"
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}${{ env.EXCLUDE_REMOTE_SERVER_TESTS_FILTER }}"
env:
WARP_SHELL_PATH: ${{ steps.echo_shells_unix.outputs.latest_bash_path }}
@@ -356,7 +358,7 @@ jobs:
if: matrix.os != 'windows' && (success() || failure())
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}"
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}${{ env.EXCLUDE_REMOTE_SERVER_TESTS_FILTER }}"
env:
WARP_SHELL_PATH: ${{ steps.echo_shells_unix.outputs.fish_path }}
@@ -379,7 +381,7 @@ jobs:
if: matrix.os != 'windows' && (success() || failure())
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}"
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}${{ env.EXCLUDE_REMOTE_SERVER_TESTS_FILTER }}"
env:
WARP_SHELL_PATH: ${{ steps.echo_shells_unix.outputs.zsh_path }}
@@ -402,7 +404,7 @@ jobs:
if: matrix.os != 'windows' && (success() || failure())
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}"
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} ${{ matrix.extra_test_args }} -E "package(integration) and test(shell_integration_tests)${{ env.EXCLUDE_SSH_TESTS_FILTER }}${{ env.EXCLUDE_REMOTE_SERVER_TESTS_FILTER }}"
env:
WARP_SHELL_PATH: ${{ steps.echo_shells_unix.outputs.powershell_path }}
@@ -441,6 +443,94 @@ jobs:
# The maximum timeout for the entire job is 6 hours:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes
remote-server-tests:
name: Run Linux remote-server integration tests
# Temporarily disabled while reconsidering the approach.
if: false
timeout-minutes: 25
runs-on: ubuntu-latest-large
needs: params
concurrency:
# The dedicated VM uses a fixed binary path, so serialize remote-server
# deploy/test jobs across workflow runs to avoid cross-run clobbering.
group: remote-server-test-vm
cancel-in-progress: false
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
target_os: linux
is_self_hosted: false
install_test_deps: true
- name: Install Shells
uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30 # v1
with:
apt: zsh fish
- name: Echo default Bash
id: echo_bash
shell: bash
run: |
DEFAULT_BASH_PATH="$(command -pv bash)"
echo "default_bash_path=$DEFAULT_BASH_PATH" >> $GITHUB_OUTPUT
DEFAULT_BASH_VERSION="$($DEFAULT_BASH_PATH --version)"
echo "::notice title=Remote Server Tests - Default Bash Version::$DEFAULT_BASH_VERSION"
- name: Install cargo nextest
uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3
with:
tool: nextest
- name: Set up gcloud authentication for remote-server tests
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
workload_identity_provider: projects/63595664881/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: github-ci-workflow@warp-ssh-integration-testing.iam.gserviceaccount.com
- name: Install gcloud CLI tool
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
with:
version: '>= 397.0.0'
- name: Install remote server deploy dependencies
shell: bash
run: |
sudo apt-get install -y curl sshpass xz-utils
MUSL_CROSS_TARGET="x86_64-unknown-linux-musl"
source script/linux/configure_musl_toolchain "$MUSL_CROSS_TARGET"
MUSL_CROSS_BIN="$(dirname "$WARP_MUSL_CC")"
echo "$MUSL_CROSS_BIN" >> "$GITHUB_PATH"
rustup target add x86_64-unknown-linux-musl
- name: Deploy remote server binary to test VM
run: script/deploy_remote_server_to_test_vm
- name: Run remote-server integration tests
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1
with:
run: cargo nextest run ${{ env.WORKSPACE_TEST_ARGS }} -E "package(integration) and test(/remote_server/)"
env:
WARP_SHELL_PATH: ${{ steps.echo_bash.outputs.default_bash_path }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload results of remote-server integration tests to trunk.io
if: ${{ !cancelled() }}
continue-on-error: true
uses: trunk-io/analytics-uploader@95a0fb8b29e45b6068304261fb518644b426a803 # v2.0.8
with:
junit-paths: target/nextest/ci/junit.xml
cli-version: 0.12.5
org-slug: warp
token: ${{ secrets.TRUNK_API_TOKEN }}
tags: type=integration,category=remote-server
variant: linux
use-cache: true
database-migration:
name: Database Migration (Diesel)
timeout-minutes: 5
@@ -449,7 +539,7 @@ jobs:
if: ${{ needs.params.outputs.affects-database-schema == 'true' }}
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 # v1.18.1
@@ -503,7 +593,7 @@ jobs:
needs: params
# if: ${{ needs.params.outputs.affects-rust-sources == 'true' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -514,8 +604,9 @@ jobs:
run:
cargo metadata --locked --format-version=1 > ${{ matrix.null_device }} || (echo "::error::Cargo.lock is out-of-date with Cargo.toml. Run 'cargo check' to update." && exit 1)
- name: Run cargo fmt
run: cargo fmt --check
- name: Run ./script/format
shell: bash
run: ./script/format --check
- name: Run cargo clippy
shell: bash
@@ -554,7 +645,7 @@ jobs:
needs: params
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
@@ -589,6 +680,20 @@ jobs:
- name: Validate repo-sync markers
uses: warpdotdev/repo-sync/actions/validate-markers@main
- name: Check for incorrectly named test files
run: |
# Test files should end in _tests.rs, not _test.rs.
bad_files=$(find . -name '*_test.rs')
if [ -n "$bad_files" ]; then
while IFS= read -r f; do
echo "::error file=$f::Test file should be named ${f/_test.rs/_tests.rs} (use _tests.rs, not _test.rs)"
done <<< "$bad_files"
exit 1
fi
- name: Check for inline Rust test modules
run: ./script/check_no_inline_test_modules
wasm-lint:
name: Formatting + Clippy (wasm)
timeout-minutes: 20
@@ -603,7 +708,7 @@ jobs:
IS_SELF_HOSTED="${{ contains(fromJSON(needs.params.outputs.wasm-runner), 'self-hosted') }}"
echo "value=$IS_SELF_HOSTED" >> $GITHUB_OUTPUT
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -614,8 +719,15 @@ jobs:
run:
cargo metadata --locked --format-version=1 >/dev/null || (echo "::error::Cargo.lock is out-of-date with Cargo.toml. Run 'cargo check' to update." && exit 1)
- name: Run cargo fmt
run: cargo fmt --check
- name: Check Rust formatting
shell: bash
run: |
# TODO(vorporeal): Once people have gotten used to ./script/format, in a week or so,
# we can have CI enforce the new formatting.
#./script/format --check
# Until then, we'll keep running the traditional formatting check.
cargo fmt --check
- name: Run cargo clippy
run: |
@@ -658,7 +770,7 @@ jobs:
runs-on: ${{ matrix.runner }}
needs: params
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -1,16 +0,0 @@
name: Comment on Unready Assigned Issue (Local)
on:
issues:
types: [assigned]
concurrency:
group: comment-on-unready-assigned-issue-${{ github.event.issue.number || github.run_id }}
cancel-in-progress: false
jobs:
comment_when_unready:
if: github.event.assignee.login == 'oz-agent' && !contains(github.event.issue.labels.*.name, 'ready-to-spec') && !contains(github.event.issue.labels.*.name, 'ready-to-implement')
permissions:
issues: write
uses: warpdotdev/oz-for-oss/.github/workflows/comment-on-unready-assigned-issue.yml@main
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
@@ -1,34 +0,0 @@
name: Create Implementation from Issue (Local)
on:
issues:
types: [assigned, labeled]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue_number:
description: Issue number to create an implementation for
required: true
type: string
concurrency:
group: create-implementation-issue-${{ github.event.issue.number || inputs.issue_number || github.run_id }}
cancel-in-progress: false
jobs:
# Mention, bot, event-type, and trust gates all live in the reusable
# workflow (``create-implementation-from-issue.yml``). This adapter
# exists only to subscribe to the GitHub events that can trigger
# implementation work (``issues`` assign/label by a maintainer, or a
# trusted ``@oz-agent`` issue comment) and delegate them through
# ``workflow_call``.
create_implementation:
permissions:
contents: write
issues: write
pull-requests: write
uses: warpdotdev/oz-for-oss/.github/workflows/create-implementation-from-issue.yml@main
with:
issue_number: ${{ github.event.inputs.issue_number || '' }}
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
@@ -1,34 +0,0 @@
name: Create Spec from Issue (Local)
on:
issues:
types: [assigned, labeled]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue_number:
description: Issue number to create a spec for
required: true
type: string
concurrency:
group: create-spec-issue-${{ github.event.issue.number || inputs.issue_number || github.run_id }}
cancel-in-progress: false
jobs:
# Mention, bot, event-type, and trust gates all live in the reusable
# workflow (``create-spec-from-issue.yml``). This adapter exists only
# to subscribe to the GitHub events that can trigger spec creation
# (``issues`` assign/label by a maintainer, or a trusted
# ``@oz-agent`` issue comment) and delegate them through
# ``workflow_call``.
create_spec:
permissions:
contents: write
issues: write
pull-requests: write
uses: warpdotdev/oz-for-oss/.github/workflows/create-spec-from-issue.yml@main
with:
issue_number: ${{ github.event.inputs.issue_number || '' }}
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
+132 -68
View File
@@ -73,7 +73,7 @@ jobs:
should_publish: ${{ steps.set_publish.outputs.should_publish }}
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Get channel configuration
id: get-config
@@ -111,6 +111,9 @@ jobs:
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
# See https://github.com/orgs/community/discussions/151442 for why we need to use
# a PAT here.
GITHUB_TOKEN: ${{ secrets.CREATE_RELEASE_TAG_PUSH_PAT }}
- name: Create GitHub release
if: ${{ steps.set_publish.outputs.should_publish == 'true' }}
@@ -150,7 +153,7 @@ jobs:
- arch: x86_64
dmg_name_suffix: x86_64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -270,7 +273,7 @@ jobs:
if: ${{ inputs.build_macos != false }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -417,7 +420,7 @@ jobs:
- arch: aarch64
- arch: x86_64
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -513,14 +516,17 @@ jobs:
runs-on: namespace-profile-ubuntu-20-04
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 60
timeout-minutes: 90
env:
# Automatically extract AppImages before running them instead of mounting
# them with FUSE, which isn't available on GitHub runners (and this is
# easier and less error-prone than trying to install it).
APPIMAGE_EXTRACT_AND_RUN: "1"
# Cache the generated settings schema so prepare_bundled_resources only
# compiles and runs the generator once per job instead of per-package.
SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -588,7 +594,10 @@ jobs:
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
run: |
if [[ -d target ]]; then
find target -path '*/bundle/linux' -type d -prune -exec rm -rf {} +
fi
shell: bash
- name: Bundle app
@@ -665,12 +674,16 @@ jobs:
release_linux_cli_x86:
name: Build Release (Linux CLI x86_64)
runs-on: namespace-profile-ubuntu-20-04
runs-on: namespace-profile-ubuntu-22-04
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 60
timeout-minutes: 90
env:
# Cache the generated settings schema so prepare_bundled_resources only
# compiles and runs the generator once per job instead of per-package.
SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -735,7 +748,10 @@ jobs:
# Namespace's User Bundled Cache persists target/ between jobs on the same profile, which can
# leave stale packages from a previous channel's build in the linux bundle output dir.
- name: Clean stale bundle output
run: rm -rf target/*/bundle/linux
run: |
if [[ -d target ]]; then
find target -path '*/bundle/linux' -type d -prune -exec rm -rf {} +
fi
shell: bash
- name: Bundle CLI
@@ -839,14 +855,17 @@ jobs:
runs-on: namespace-profile-ubuntu-20-04-arm
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 60
timeout-minutes: 90
env:
# Automatically extract AppImages before running them instead of mounting
# them with FUSE, which isn't available on GitHub runners (and this is
# easier and less error-prone than trying to install it).
APPIMAGE_EXTRACT_AND_RUN: "1"
# Cache the generated settings schema so prepare_bundled_resources only
# compiles and runs the generator once per job instead of per-package.
SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -930,12 +949,12 @@ jobs:
build_linux_cli_arm_binaries:
name: Build Release (Linux CLI ARM)
runs-on: namespace-profile-ubuntu-20-04-arm
runs-on: namespace-profile-ubuntu-22-04
needs: prepare_release
if: ${{ inputs.build_linux != false }}
timeout-minutes: 60
timeout-minutes: 90
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -976,7 +995,7 @@ jobs:
id: build_cli
run: |
# Build the CLI only
script/bundle --channel $CHANNEL --artifact cli --packages none
script/bundle --channel $CHANNEL --artifact cli --packages none --arch aarch64
shell: bash
env:
CHANNEL: ${{ steps.get-config.outputs.channel }}
@@ -1011,9 +1030,13 @@ jobs:
needs: [ prepare_release, build_linux_arm_binaries, build_linux_cli_arm_binaries ]
if: ${{ inputs.build_linux != false }}
timeout-minutes: 60
env:
# Cache the generated settings schema so prepare_bundled_resources only
# compiles and runs the generator once per job instead of per-package.
SETTINGS_SCHEMA_CACHE: ${{ github.workspace }}/.settings_schema_cache.json
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.prepare_release.outputs.release_branch }}
@@ -1269,7 +1292,7 @@ jobs:
if: ${{ inputs.build_web != false }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -1394,7 +1417,7 @@ jobs:
TRUSTED_SIGNING_ACCOUNT: warpdotdev
TRUSTED_SIGNING_CERT_PROFILE: warpterminal
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: ./.github/actions/prepare_environment
with:
@@ -1615,7 +1638,7 @@ jobs:
- release_web
- release_windows
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
name: Checkout sources
with:
# Fetch history for all tags and branches so we can compare revisions
@@ -1629,75 +1652,116 @@ jobs:
config_file: ${{ env.CONFIG_FILE }}
channel: ${{ inputs.channel }}
- name: Obtain a GitHub App Installation Access Token
# Stable releases use the Oz changelog-draft agent for higher-quality,
# human-reviewable output. Non-stable channels (dev/preview/beta) use the
# legacy generate-changelog action to avoid spending Oz agent tokens on
# daily dev cuts and preview RCs.
- name: Generate changelog via Oz (stable only)
if: inputs.channel == 'stable'
uses: warpdotdev/oz-agent-action@ce1621abf6a8ed8afdd4e4cc994545ede8fe1c6f # main
with:
prompt: |
Generate a changelog draft for the ${{ inputs.channel }} channel, release tag ${{ needs.prepare_release.outputs.release_tag }}.
Output directory: ${{ runner.temp }}/changelog-draft
Follow the workflow in .agents/skills/changelog-draft/SKILL.md exactly.
Make sure to produce both output files: changelog-draft.md and changelog-draft.json.
The release workflow may run from warpdotdev/warp-internal. When fetching PR data, pass the checked-out repository ("${{ github.repository }}") to fetch_prs.py and rely on the script's repo-sync normalization to resolve public warpdotdev/warp PR numbers, URLs, and authors. The script intentionally omits non-repo-sync PRs from warp-internal because they are private internal changes. Do not infer or synthesize public PR links manually.
After writing the output files, print the full contents of changelog-draft.md to stdout so it appears in the workflow log.
You are running in a GitHub Actions workflow. The repo is checked out at the default branch (HEAD) with full history. Use the release_tag input as the git range endpoint — do NOT check out the release tag. `gh` is authenticated. Do not commit, push, or create PRs.
warp_api_key: ${{ secrets.WARP_API_KEY }}
share: team
- name: Upload raw Markdown changelog artifact (stable only)
if: inputs.channel == 'stable'
id: upload_changelog_draft_markdown
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: changelog-draft-markdown
path: ${{ runner.temp }}/changelog-draft/changelog-draft.md
if-no-files-found: error
- name: Convert draft JSON to release format (stable only)
if: inputs.channel == 'stable'
shell: bash
run: |
python3 .agents/skills/changelog-draft/scripts/convert_to_release_json.py \
--input "${{ runner.temp }}/changelog-draft/changelog-draft.json" \
--output "${{ runner.temp }}/changelog-draft/changelog-release.json"
- name: Obtain a GitHub App Installation Access Token (non-stable only)
if: inputs.channel != 'stable'
id: github_app_auth
run: |
TOKEN="$(npx obtain-github-app-installation-access-token ci ${{ secrets.GH_APP_CREDENTIALS_TOKEN }})"
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> $GITHUB_OUTPUT
- name: Generate changelog
uses: warpdotdev/generate-changelog@main
id: generate_changelog
- name: Generate changelog via legacy action (non-stable only)
if: inputs.channel != 'stable'
id: legacy_changelog
uses: warpdotdev/generate-changelog@70f534c1e030dafb45046ae57e4aa4d43a2f5c84 # main
with:
channel: ${{ inputs.channel }}
github_auth_token: ${{ steps.github_app_auth.outputs.token }}
version: ${{ needs.prepare_release.outputs.release_tag }}
- name: Load changelog into step output
id: generate_changelog
shell: bash
env:
LEGACY_CHANGELOG: ${{ steps.legacy_changelog.outputs.changelog }}
run: |
# Bridge step: picks whichever generator ran for this channel and
# re-emits the changelog as outputs.changelog so downstream Slack/GCS
# steps work unchanged.
if [[ "${{ inputs.channel }}" == "stable" ]]; then
CHANGELOG_FILE="${{ runner.temp }}/changelog-draft/changelog-release.json"
if [ ! -f "$CHANGELOG_FILE" ]; then
echo "::error::changelog-release.json not found at $CHANGELOG_FILE"
exit 1
fi
jq empty "$CHANGELOG_FILE"
{
echo "changelog<<CHANGELOG_EOF"
cat "$CHANGELOG_FILE"
echo "CHANGELOG_EOF"
} >> $GITHUB_OUTPUT
else
echo "$LEGACY_CHANGELOG" | jq empty
{
echo "changelog<<CHANGELOG_EOF"
printf '%s\n' "$LEGACY_CHANGELOG"
echo "CHANGELOG_EOF"
} >> $GITHUB_OUTPUT
fi
- name: Build Slack changelog payload
id: build_slack_payload
shell: bash
env:
CHANGELOG_MD: ${{ steps.generate_changelog.outputs.changelog }}
CHANGELOG_JSON: ${{ steps.generate_changelog.outputs.changelog }}
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
MARKDOWN_ARTIFACT_URL: ${{ steps.upload_changelog_draft_markdown.outputs.artifact-url }}
run: |
# Rename the keys in the Changelog JSON to something more human readable.
# This is the changelog that goes to slack so it doesn't need to be extremely filtered.
NEW_CHANGELOG=$(echo $CHANGELOG_MD | jq 'with_entries(if .key == "newFeatures" then .key = "New Features" else . end)' | jq 'with_entries(if .key == "improvements" then .key = "Improvements" else . end)' | jq 'with_entries(if .key == "bugFixes" then .key = "Bug Fixes" else . end)' | jq 'with_entries(if .key == "images" then .key = "Image" else . end)')
# Generate the full markdown text as individual lines.
MARKDOWN=$(echo "$NEW_CHANGELOG" | jq -r 'to_entries[] | select(.value | length > 0) | "*\(.key)*", (.value[] | " \u2022 \(.)")')
if [ -z "$MARKDOWN" ]; then
CHANGELOG_INPUT="${{ runner.temp }}/slack-changelog-input.json"
SLACK_PAYLOAD="${{ runner.temp }}/slack-changelog-payload.json"
printf '%s\n' "$CHANGELOG_JSON" > "$CHANGELOG_INPUT"
python3 .agents/skills/changelog-draft/scripts/build_slack_payload.py \
--input "$CHANGELOG_INPUT" \
--release-tag "$RELEASE_TAG" \
--markdown-artifact-url "$MARKDOWN_ARTIFACT_URL" \
--output "$SLACK_PAYLOAD"
if ! jq -e '.blocks | length > 0' "$SLACK_PAYLOAD" >/dev/null; then
echo "has_content=" >> $GITHUB_OUTPUT
exit 0
fi
echo "has_content=true" >> $GITHUB_OUTPUT
# Split markdown into chunks of <= 3000 characters at newline boundaries.
# Each chunk will become its own section block in the Slack payload.
CHUNKS='[]'
BUFFER=""
while IFS= read -r line; do
if [ -z "$BUFFER" ]; then
CANDIDATE="$line"
else
CANDIDATE="$BUFFER"
CANDIDATE+=$'\n'
CANDIDATE+="$line"
fi
if [ ${#CANDIDATE} -gt 3000 ]; then
# Flush the current buffer as a chunk (if non-empty), start a new one with this line.
if [ -n "$BUFFER" ]; then
CHUNKS=$(echo "$CHUNKS" | jq --arg chunk "$BUFFER" '. + [$chunk]')
fi
BUFFER="$line"
else
BUFFER="$CANDIDATE"
fi
done <<< "$MARKDOWN"
# Flush the remaining buffer.
if [ -n "$BUFFER" ]; then
CHUNKS=$(echo "$CHUNKS" | jq --arg chunk "$BUFFER" '. + [$chunk]')
fi
# Build the full Slack Block Kit payload: header block + one section block per chunk.
PAYLOAD=$(echo "$CHUNKS" | jq -c \
--arg version "$RELEASE_TAG" \
'{blocks: ([{type: "header", text: {type: "plain_text", text: ("Changelog for " + $version)}}] + [.[] | {type: "section", text: {type: "mrkdwn", text: .}}])}')
echo "payload<<SLACK_PAYLOAD_EOF" >> $GITHUB_OUTPUT
echo "$PAYLOAD" >> $GITHUB_OUTPUT
cat "$SLACK_PAYLOAD" >> $GITHUB_OUTPUT
echo "SLACK_PAYLOAD_EOF" >> $GITHUB_OUTPUT
- name: Post to a Slack channel
@@ -1728,7 +1792,7 @@ jobs:
NEW_FEATURES=$(echo $CHANGELOG_SECTIONS | jq 'with_entries(select(.key == "New features"))' | jq -r 'to_entries[] | "* \(.value[])"' | awk -v ORS='\n' '1')
IMPROVEMENTS=$(echo $CHANGELOG_SECTIONS | jq 'with_entries(select(.key == "Improvements"))' | jq -r 'to_entries[] | "* \(.value[])"' | awk -v ORS='\n' '1')
# Extract the image URL from the list, and use the latest URL even if there are multiple
IMAGE=$(echo $CHANGELOG | jq -r '.image | if length > 0 then .[-1] else "" end')
IMAGE=$(echo $CHANGELOG | jq -r '.images | if length > 0 then .[-1] else "" end')
# Extract Oz updates as a JSON array
OZ_UPDATES=$(echo $CHANGELOG | jq -c '.oz_updates // []')
# Tweak the structure of the JSON, add in a top-level date field, and add in the markdown_sections field
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
run: |
[ $GITHUB_REF == "refs/heads/master" ] || (echo "::error::Can only cut new releases on the master branch" && exit 1)
shell: bash
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- id: get-config
run: |
# Check to see if this was auto-run as a cron job. If so, set the
+1 -2
View File
@@ -89,10 +89,9 @@ jobs:
shell: bash
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.branch }}
fetch-depth: 0
- name: Rename branch to deleted/
env:
@@ -45,7 +45,7 @@ jobs:
&& github.actor != 'github-actions[bot]'
runs-on: namespace-profile-ubuntu-20-04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
# Check out the appropriate branch based on trigger type
# TODO: do this in sdk?
@@ -1,39 +0,0 @@
name: Enforce PR Issue State Logic
on:
workflow_call:
inputs:
pr_number:
description: Pull request number to evaluate
required: true
type: string
requester:
description: Login of the user whose action triggered enforcement, if any
required: false
default: ""
type: string
secrets:
OZ_MGMT_GHA_APP_ID:
required: true
OZ_MGMT_GHA_PRIVATE_KEY:
required: true
OSS_WARP_API_KEY:
required: true
outputs:
allow_review:
description: Whether downstream PR hooks may continue after enforcement.
value: ${{ jobs.enforce_issue_state.outputs.allow_review }}
jobs:
enforce_issue_state:
name: Enforce PR issue state
permissions:
contents: read
issues: write
pull-requests: write
uses: warpdotdev/oz-for-oss/.github/workflows/enforce-pr-issue-state.yml@main
with:
pr_number: ${{ inputs.pr_number }}
requester: ${{ inputs.requester }}
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
+10 -3
View File
@@ -63,7 +63,7 @@ jobs:
* "date_enabled" - the date on which the feature flag was enabled by default
* "enabling_commit" - the Git commit in which the feature flag was enabled by default
* You are running as part of a GitHub automation and must not commit or push any changes. Another agent will process the file you produce.
warp_api_key: ${{ secrets.WARP_API_KEY }}
warp_api_key: ${{ secrets.OSS_WARP_API_KEY }}
share: team
- name: Upload feature flag log
@@ -178,7 +178,7 @@ jobs:
You are running as part of a GitHub automation that runs with a read-only token and will package your changes into a patch for a separate job to commit. Do not create a branch, do not commit, do not push, do not create a PR, and do not call `gh`. Leave your changes in the working tree only.
share: team
warp_api_key: ${{ secrets.WARP_API_KEY }}
warp_api_key: ${{ secrets.OSS_WARP_API_KEY }}
- name: Generate cleanup patch
env:
@@ -227,11 +227,18 @@ jobs:
run: |
git apply --binary --whitespace=nowarn "$RUNNER_TEMP/cleanup.patch"
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with:
app-id: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
private-key: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
- name: Create Pull Request
id: create_pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ github.token }}
token: ${{ steps.app-token.outputs.token }}
base: ${{ github.event.repository.default_branch }}
commit-message: "Clean up ${{ needs.analyze.outputs.flag_name }} feature flag"
branch: "oz-agent/cleanup-feature-flag-${{ needs.analyze.outputs.flag_name }}"
@@ -0,0 +1,66 @@
# ======================================================================================
# Workflow: Label External Contributors
# ======================================================================================
# Usage:
# - Runs whenever a pull request is opened.
# - Adds the `external-contributor` label to the PR if the PR head repository is
# a fork (i.e. it does not belong to the same repository as the base), and the
# PR is not authored by a bot.
#
# Notes:
# - The workflow triggers on `pull_request_target` rather than `pull_request` so
# that it has the `pull-requests: write` permission needed to apply labels even
# when the PR is opened from a fork. Because we never check out the PR's code
# and only read the event payload, this trigger is safe.
# ======================================================================================
name: Label External Contributors
on:
pull_request_target:
types: [opened]
# Default to a read-only token. The job below widens permissions explicitly.
permissions:
contents: read
jobs:
label-external-contributor:
name: Label external-contributor PRs
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Determine and apply external-contributor label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const pr = context.payload.pull_request;
const author = pr.user.login;
// Ignore PRs authored by bots.
if (pr.user.type === 'Bot' || author.endsWith('[bot]')) {
console.log(`Skipping bot user: ${author}`);
return;
}
// The PR comes from a fork if its head repo differs from its base repo.
const isFork =
!pr.head.repo ||
pr.head.repo.full_name !== pr.base.repo.full_name;
console.log(
`PR #${pr.number} by ${author}: isFork=${isFork}`,
);
if (!isFork) {
return;
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['external-contributor'],
});
console.log(`Labeled PR #${pr.number} as external-contributor`);
@@ -1,20 +0,0 @@
name: Remove Stale Issue Labels on Plan Approved (Local)
on:
pull_request_target:
types: [labeled]
concurrency:
group: remove-stale-labels-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
remove_stale_labels:
if: github.event.label.name == 'plan-approved'
permissions:
contents: read
issues: write
pull-requests: read
uses: warpdotdev/oz-for-oss/.github/workflows/remove-stale-issue-labels-on-plan-approved.yml@main
with:
pr_number: ${{ github.event.pull_request.number }}
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
@@ -1,24 +0,0 @@
name: Respond to PR Comment (Local)
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
pull_request_review:
types: [submitted]
jobs:
# Mention, bot, event-type, and trust gates all live in the reusable
# workflow (``respond-to-pr-comment.yml``). This adapter exists only
# to subscribe to the three GitHub events that can carry an
# ``@oz-agent`` mention on a PR and delegate them through
# ``workflow_call``.
respond:
permissions:
contents: write
issues: write
pull-requests: write
uses: warpdotdev/oz-for-oss/.github/workflows/respond-to-pr-comment.yml@main
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
@@ -1,22 +0,0 @@
name: Respond to Triaged Issue Comment (Local)
on:
issue_comment:
types: [created]
concurrency:
group: respond-to-triaged-issue-comment-${{ github.event.comment.id || github.run_id }}
cancel-in-progress: false
jobs:
# Mention, bot, event-type, and trust gates all live in the reusable
# workflow (``respond-to-triaged-issue-comment.yml``). This adapter
# exists only to subscribe to the GitHub event that can carry an
# ``@oz-agent`` mention on a triaged issue and delegate it through
# ``workflow_call``.
respond_inline:
permissions:
contents: read
issues: write
uses: warpdotdev/oz-for-oss/.github/workflows/respond-to-triaged-issue-comment.yml@main
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
-129
View File
@@ -1,129 +0,0 @@
name: Review Pull Request
on:
workflow_call:
inputs:
pr_number:
description: Pull request number to review
required: false
default: ""
type: string
trigger_source:
description: Source that requested the review
required: false
default: ""
type: string
requester:
description: Login of the user who requested the review, if any
required: false
default: ""
type: string
comment_id:
description: Issue comment ID to react to for slash-command reviews
required: false
default: ""
type: string
secrets:
OZ_MGMT_GHA_APP_ID:
required: true
OZ_MGMT_GHA_PRIVATE_KEY:
required: true
OSS_WARP_API_KEY:
required: true
pull_request_target:
types:
- opened
- ready_for_review
- review_requested
- labeled
jobs:
resolve:
runs-on: ubuntu-slim
permissions:
contents: read
outputs:
should_run: ${{ steps.resolve.outputs.should_run }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
trigger_source: ${{ steps.resolve.outputs.trigger_source }}
requester: ${{ steps.resolve.outputs.requester }}
comment_id: ${{ steps.resolve.outputs.comment_id }}
skip_reason: ${{ steps.resolve.outputs.skip_reason }}
steps:
- name: Checkout repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Resolve review context
id: resolve
env:
INPUT_PR_NUMBER: ${{ inputs.pr_number || '' }}
INPUT_TRIGGER_SOURCE: ${{ inputs.trigger_source || '' }}
INPUT_REQUESTER: ${{ inputs.requester || '' }}
INPUT_COMMENT_ID: ${{ inputs.comment_id || '' }}
GITHUB_ACTOR_LOGIN: ${{ github.actor }}
run: |
python - <<'PY'
import json
import os
from pathlib import Path
event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text())
event_name = os.environ.get("GITHUB_EVENT_NAME", "")
input_pr_number = os.environ.get("INPUT_PR_NUMBER", "").strip()
pr = event.get("pull_request") or {}
head_ref = ((pr.get("head") or {}).get("ref") or "").strip()
action = (event.get("action") or "").strip()
requested_reviewer = ((event.get("requested_reviewer") or {}).get("login") or "").strip()
label_name = ((event.get("label") or {}).get("name") or "").strip()
has_pr_hooks = Path(".github/workflows/pr-hooks.yml").exists()
trigger_source = os.environ.get("INPUT_TRIGGER_SOURCE", "").strip() or event_name
requester = os.environ.get("INPUT_REQUESTER", "").strip() or os.environ.get("GITHUB_ACTOR_LOGIN", "")
comment_id = os.environ.get("INPUT_COMMENT_ID", "")
pr_number = input_pr_number or str(pr.get("number") or "")
matches_direct_trigger = (
(action == "opened" and not pr.get("draft", False))
or action == "ready_for_review"
or (action == "review_requested" and requested_reviewer == "oz-agent")
or (action == "labeled" and label_name == "oz-review")
)
if input_pr_number:
should_run = True
elif head_ref.startswith("cherrypick"):
should_run = False
elif has_pr_hooks and event_name == "pull_request_target":
should_run = False
else:
should_run = matches_direct_trigger and bool(pr_number)
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"should_run={'true' if should_run else 'false'}\n")
fh.write(f"pr_number={pr_number}\n")
fh.write(f"trigger_source={trigger_source}\n")
fh.write(f"requester={requester}\n")
fh.write(f"comment_id={comment_id}\n")
if has_pr_hooks and event_name == "pull_request_target" and not input_pr_number:
fh.write("skip_reason=pr-hooks-present\n")
elif head_ref.startswith("cherrypick"):
fh.write("skip_reason=cherrypick-branch\n")
elif not should_run:
fh.write("skip_reason=event-not-enabled\n")
PY
skip_direct_trigger:
needs: resolve
if: needs.resolve.outputs.should_run != 'true' && needs.resolve.outputs.skip_reason == 'pr-hooks-present'
runs-on: ubuntu-slim
steps:
- name: Explain skip
run: echo "PR review orchestration skipped because .github/workflows/pr-hooks.yml is present."
review_pr:
needs: resolve
if: needs.resolve.outputs.should_run == 'true'
permissions:
contents: read
pull-requests: write
issues: write
uses: warpdotdev/oz-for-oss/.github/workflows/review-pull-request.yml@main
with:
pr_number: ${{ needs.resolve.outputs.pr_number }}
trigger_source: ${{ needs.resolve.outputs.trigger_source }}
requester: ${{ needs.resolve.outputs.requester }}
comment_id: ${{ needs.resolve.outputs.comment_id }}
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
@@ -0,0 +1,57 @@
# ======================================================================================
# Workflow: Stale Requested-Changes PRs
# ======================================================================================
# Usage:
# - Runs daily and follows up on external-contributor PRs that have an active
# requested-changes review and have gone inactive.
# - Posts reminder comments at 7 and 10 days of author inactivity, then closes
# the PR at 14 days — but only after the day-10 final warning has been posted.
# - Inactivity is author-driven: a head-branch push (GitHub-recorded) or a comment by
# the PR author resets the timer; maintainer/third-party/bot comments do not.
# - Reminder progress is tracked via marker comments authored by github-actions[bot],
# so no datastore is needed.
#
# Modes (workflow_dispatch `mode` input; scheduled runs use `full`):
# - dry-run: log eligible PRs and intended actions, write nothing.
# - reminder-only: post reminders, never close.
# - full: post reminders and close.
# ======================================================================================
name: Stale Requested-Changes PRs
on:
schedule:
- cron: '7 12 * * *' # 12:07 UTC daily (minute 7 avoids top-of-hour scheduler congestion)
workflow_dispatch:
inputs:
mode:
description: 'Write mode'
type: choice
default: full
options:
- dry-run
- reminder-only
- full
permissions:
contents: read
pull-requests: write
issues: write
jobs:
stale-requested-changes:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
# Only the follow-up script is needed; skip blob content for speed.
sparse-checkout: .github/scripts
filter: blob:none
- name: Follow up on stale requested-changes PRs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const run = require('./.github/scripts/stale-requested-changes-prs.js')
await run({ github, context })
@@ -1,60 +0,0 @@
name: Triage New Issues (Local)
on:
issues:
types: [opened]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue_number:
description: Optional issue number to triage immediately
required: false
default: ''
type: string
lookback_minutes:
description: Minutes of issue history to scan when no issue number is provided
required: false
default: '60'
type: string
concurrency:
group: triage-new-issues-${{ github.event.issue.number || inputs.issue_number || github.run_id }}
cancel-in-progress: false
jobs:
triage_issues:
# A needs-info reply by the original reporter triggers re-triage
# even if it mentions @oz-agent, because the respond-to-triaged
# workflow handles explicit mentions on triaged issues separately.
if: |
(
github.event_name != 'issue_comment' &&
!contains(github.event.issue.labels.*.name, 'triaged') &&
!contains(github.event.issue.labels.*.name, 'ready-to-spec') &&
!contains(github.event.issue.labels.*.name, 'ready-to-implement')
) || (
github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
github.event.comment.user.type != 'Bot' &&
!endsWith(github.event.comment.user.login, '[bot]') &&
(
(
contains(github.event.comment.body, '@oz-agent') &&
!contains(github.event.issue.labels.*.name, 'triaged')
) ||
(
contains(github.event.issue.labels.*.name, 'needs-info') &&
github.event.comment.user.login == github.event.issue.user.login &&
!contains(github.event.comment.body, '@oz-agent')
)
)
)
permissions:
contents: read
issues: write
uses: warpdotdev/oz-for-oss/.github/workflows/triage-new-issues.yml@main
with:
issue_number: ${{ github.event.issue.number || github.event.inputs.issue_number || '' }}
lookback_minutes: ${{ github.event.inputs.lookback_minutes || '60' }}
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
@@ -1,22 +0,0 @@
name: Trigger Implementation on Plan Approved (Local)
on:
pull_request_target:
types: [labeled]
concurrency:
group: trigger-impl-plan-approved-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: false
jobs:
trigger_implementation:
if: >-
github.event.label.name == 'plan-approved' &&
github.event.pull_request.state == 'open'
name: Trigger implementation for approved plan
permissions:
contents: write
issues: write
pull-requests: write
uses: warpdotdev/oz-for-oss/.github/workflows/trigger-implementation-on-plan-approved.yml@main
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
@@ -1,18 +0,0 @@
name: Verify PR Comment (Local)
on:
issue_comment:
types: [created]
jobs:
# Slash-command parsing, bot gating, and trust admission live in the
# reusable workflow. This local adapter only subscribes to PR issue
# comments and delegates through ``workflow_call``.
verify:
permissions:
contents: read
issues: write
pull-requests: write
uses: warpdotdev/oz-for-oss/.github/workflows/verify-pr-comment.yml@main
secrets:
OZ_MGMT_GHA_APP_ID: ${{ secrets.OZ_MGMT_GHA_APP_ID }}
OZ_MGMT_GHA_PRIVATE_KEY: ${{ secrets.OZ_MGMT_GHA_PRIVATE_KEY }}
OSS_WARP_API_KEY: ${{ secrets.OSS_WARP_API_KEY }}
+15 -1
View File
@@ -15,8 +15,10 @@ crates/command-signatures-v2/js/node_modules
# For testing changes to the channel versions file
channel_versions_test.json
# Don't include any CPU profiling output by accident.
# Don't include any profiling output by accident.
profile.pb
*.mm_profdata
chrome_profiler.json
# Don't include any files that we write for testing purposes.
crates/warp_files/test_data/test_write
@@ -49,6 +51,15 @@ app/src/persistence/schema.rs.orig
# Don't include personal Claude Code settings
.claude/settings.local.json
# Don't include Claude Code worktrees
.claude/worktrees/
# Don't include Claude Code scheduled task lock
.claude/scheduled_tasks.lock
# Don't include ctags output
tags
# Tab drag development notes
pr_cleanup.md
desired_behavior.md
@@ -58,3 +69,6 @@ __pycache__/
# Local Warp upstream reference checkout (used by pull_warp_feature skill)
.galaxy/
# Project notes
.note/
+322 -59
View File
@@ -1,80 +1,343 @@
# Galaxy AI Agents - Ideas & Future Work
# AGENTS.md
## Build Standards
This file provides guidance when working with code in this repository.
The project must always have a **clean build with zero warnings and zero errors**. This applies to both `cargo check` and `cargo build`. Dead code warnings (`unused`, `dead_code`) should be resolved by either using the code, removing it, or adding targeted `#[allow(dead_code)]` annotations with a reason (e.g., code that's intentionally staged for upcoming work).
## Development Commands
---
### Build and Run
- `cargo run` - Build and run Warp locally
- `cargo bundle --bin warp` - Bundle the main app
## LLM-Powered Predictive Autocomplete
### Running with local warp-server
To connect Warp client to a local warp-server instance:
**Idea:** As the user types in the code editor, stream the current context (surrounding code, file structure, recent edits) to an LLM and predict what they're about to write — offering inline ghost-text completions similar to GitHub Copilot.
```bash
# Connect to server on default port 8080
cargo run --features with_local_server
**Scope options:**
- By line (predict the rest of the current line)
- By function (predict the full function body)
- By class/module (predict structural code)
# Connect to server on custom port (e.g., 8082)
SERVER_ROOT_URL=http://localhost:8082 WS_SERVER_URL=ws://localhost:8082/graphql/v2 cargo run --features with_local_server
```
**Challenges:**
- Latency: can't hit the LLM on every keystroke. Need aggressive debouncing (500ms+), speculative pre-fetching, and streaming partial results.
- Cost: high token volume. May need a small/fast model (Haiku) for inline suggestions with a larger model for multi-line predictions.
- Context window: need to efficiently pack relevant context (current file, imports, related types, recent edits) without blowing the token budget.
- Cancellation: must cancel in-flight requests when the user keeps typing past the prediction point.
- UX: ghost text rendering, Tab to accept, partial accept (word-by-word), dismiss on divergence.
Environment variables:
- `SERVER_ROOT_URL` - HTTP endpoint (default: `http://localhost:8080`)
- `WS_SERVER_URL` - WebSocket endpoint (default: `ws://localhost:8080/graphql/v2`)
**Possible approaches:**
- Debounce + streaming: wait 500ms after last keystroke, stream tokens as they arrive, render as ghost text
- Predictive pre-fetch: on function signature completion or newline, proactively request the likely next block
- Local model: run a small code model locally for instant line completions, use cloud model for multi-line
- Hybrid: use LSP completions for symbol-level, LLM for line/block-level predictions
### Testing
- `cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2` - Run tests with nextest
- `cargo nextest run -p galaxy_completer --features v2` - Run completer tests with v2 features
- `cargo test --doc` - Run doc tests
- `cargo test` - Run standard tests for individual packages
**Integration points in Galaxy:**
- `app/src/code/completion.rs` — extend the completion state machine with an LLM provider
- `crates/ai/` — existing Bedrock/LLM infrastructure can be reused
- Editor decoration system — for rendering ghost text (similar to inlay hints)
### Linting and Formatting
- `./script/presubmit` - Run all presubmit checks (fmt, clippy, tests)
- `./script/format` - Format code
- `cargo clippy --workspace --all-targets --all-features --tests -- -D warnings` - Run clippy
- `./script/run-clang-format.py -r --extensions 'c,h,cpp,m' ./crates/galaxyui/src/ ./app/src/` - Format C/C++/Obj-C code
- `find . -name "*.wgsl" -exec wgslfmt --check {} +` - Check WGSL shader formatting
---
### Bedrock Diagnostics
- Set `GALAXY_BEDROCK_DIAGNOSTICS=1` to enable Bedrock diagnostic output, including:
- `Error_<timestamp>.txt` snapshot files written to the repository root on request/stream failures (includes the serialized Bedrock context window, tool definitions, protobuf request debug payload, captured Bedrock diagnostic lines, and log tails)
- Per-event Bedrock diagnostic logs written to `bedrock-diagnostics.log` in the active Warp log directory
## Inline Token/Cache/Cost Stats on LLM Responses
### AI Provider Architecture
**Idea:** Display context window usage, cache hit percentage, and cost as a compact footer below each completed LLM response in the agent conversation view. This replaces the "context" button on the bottom-right of the input area.
Galaxy supports multiple AI backends via a **provider dispatch pattern**. Provider selection
is controlled by settings (`ai.openai.enabled` takes priority over `ai.bedrock.enabled`).
**Data to display (per response):**
- Context usage: percentage used, input tokens / context window size (e.g., "Context: 45.2% (20.6k / 200k)")
- Cache hit stats: hit percentage with breakdown (e.g., "Cache Hit: 89.3% (R: 18.4k, W: 1.2k, M: 1.0k)")
- Cost: cumulative session cost (e.g., "Cost: $0.42")
```
Provider dispatch: response_stream.rs → resolve_provider_config() → ProviderConfig enum
↓ Bedrock ↓ OpenAI
bedrock/translator.rs openai/translator.rs
```
**Data source:**
- Bedrock `InvokeModel`/`Converse` response metadata contains:
- `usage.input_tokens` — tokens sent (cache misses)
- `usage.cache_read_input_tokens` — tokens served from cache
- `usage.cache_creation_input_tokens` — tokens written to cache
- `usage.output_tokens` — tokens generated
- Cache hit % = `cache_read / (cache_read + cache_write + input_tokens) * 100`
**Shared types** in `app/src/ai/provider/`:
- `types.rs``ConversationMessage`, `MessageRole`, `MessageContent`, `ContentPart`, `ToolDefinition`
- `mod.rs``ProviderConfig` enum (Bedrock | OpenAI | None)
**Reference implementation:**
- `~/.claude/statusline-command.sh` — shell script that formats these exact metrics for Claude Code's status line. Same formula and human-readable formatting (k/M suffixes) should be used.
**Bedrock provider** in `app/src/ai/bedrock/`:
- `translator.rs` — Orchestrator: takes `api::Request` + config, returns `ResponseStream`
- `request_translator.rs` — Converts Warp proto → Bedrock SDK types (messages, system prompt, tools, sanitization)
- `response_translator.rs` — Converts Bedrock stream events → Warp proto `ResponseEvent`s
- `convert.rs` — Re-exports shared types + Bedrock SDK type builders
- `client.rs` — AWS SDK client construction and `converse_stream` call
- `models.rs` — Model registry and cross-region inference prefix logic
- `discovery.rs` — AWS profile listing and model discovery (STS identity check + ListFoundationModels)
- `diagnostic.rs` — Debug logging (enabled via `GALAXY_BEDROCK_DIAGNOSTICS=1`)
- `external_config.rs` — Fallback config from Claude Code/OpenCode settings
**UI approach:**
- Render as a single-line or two-line muted footer below each AI response block
- Use dimmed/secondary text color, monospace font, compact layout
- Remove the "context" icon button from the input area bottom-right since this replaces it
**OpenAI/LiteLLM provider** in `app/src/ai/openai/`:
- `translator.rs` — Orchestrator: same pattern as Bedrock, targets OpenAI chat completions API
- `client.rs``reqwest`-based HTTP client for `POST /v1/chat/completions` with streaming
- `convert.rs``ConversationMessage` → OpenAI JSON format (system/user/assistant/tool roles, function calling)
- `request_translator.rs` — OpenAI-specific message sanitization (lighter than Bedrock's strict alternation rules)
- `response_translator.rs` — SSE stream parser → Warp proto `ResponseEvent`s
**Integration points in Galaxy:**
- Find where Bedrock response `usage` metadata is captured after each streaming response completes
- Find the conversation block rendering (where each AI response ends) to add the footer element
- `app/src/ai/blocklist/` — likely where response blocks are rendered
- `crates/ai/` — where Bedrock API responses are parsed
**Provider settings** (in settings TOML):
- `ai.bedrock.enabled` — Use AWS Bedrock directly (default: true)
- `ai.openai.enabled` — Use OpenAI-compatible endpoint(s) (default: false, takes priority over Bedrock)
- `ai.openai.base_url` — Legacy single-provider endpoint URL (default: `http://localhost:4000/v1`)
- `ai.openai.api_key` — Legacy single-provider API key (stored in keychain)
- `ai.openai.model` — Model name override sent to the endpoint
- `ai.openai.models` — Legacy single-provider model list (`Vec<OpenAIModelConfig>`)
- `ai.providers`**Multi-provider config** (`Vec<OpenAIProviderConfig>`): each entry has `name`, `base_url`, `api_key`, `models[]`
---
**Multi-provider example** (settings.toml):
```toml
[ai.openai]
enabled = true
## LSP Rename (Phase 3 - App Wiring)
[[ai.providers]]
name = "LiteLLM"
base_url = "http://localhost:4000/v1"
api_key = "sk-..."
**Status:** LSP layer is complete (`prepare_rename` + `rename` methods exist on LspServerModel). Needs app-layer wiring.
[[ai.providers.models]]
model_id = "claude-sonnet-4-20250514[1m]"
display_name = "Claude Sonnet 4 (1M)"
context_size = 1000000
**Implementation needed:**
- F2 keybinding triggers `prepareRename` at cursor position
- If valid, show an inline text input overlay at the symbol location pre-filled with the current name
- On confirm (Enter), send `rename` request with the new name
- Apply the resulting `WorkspaceEdit` to the editor (single-file for now)
- On cancel (Escape), dismiss the input overlay
[[ai.providers]]
name = "Ollama (Local)"
base_url = "http://localhost:11434/v1"
[[ai.providers.models]]
model_id = "llama3.2"
display_name = "Llama 3.2"
context_size = 128000
```
**OpenAI/LiteLLM model discovery**:
- Models can be auto-fetched from the `/models` endpoint via the Settings > OpenAI / LiteLLM page
- For each model, the system probes `{model_id}[1m]` with a minimal chat completion request
- If the `[1m]` variant is accepted (HTTP 200 or 429), it's used with 1M context window
- Otherwise, the base model ID is used with its reported context size
- Models injected via `ai.providers[]` are routed to their specific endpoint (per-model routing map)
- Provider name shown as the description label in the model picker; icon shows OpenAI logo for all OpenAI-compatible providers
Key invariants:
- Known tools are in `KNOWN_TOOLS` constant in `response_translator.rs`
- Tool definitions are built via `tool_definition_for_name()` in `convert_request.rs`; includes `recall_tool_history` for retrieving past tool results
- Unknown/hallucinated tool calls are caught in the stream, paired with synthetic error results, and now emit a visible `AgentOutput` text message to the UI
- `recall_tool_history` is handled inline in the response translator (synthetic result from `messages_sent`)
- Tool result archive: before progressive summarization drains messages, `ConversationMessage::archive_tool_results()` extracts all tool_use/tool_result pairs into a separate `tool_result_archive` vec. `recall_tool_history` searches both live history + archived results, and supports a `tool_use_id` parameter for exact ID lookup
- Prompt caching uses three cache points: system prompt, conversation history (second-to-last message), tool config
- `ensure_tool_results_paired()` enforces Bedrock's invariant that every `tool_use` has a matching `tool_result`
- `inject_input_messages_into_task()` and `extract_user_query_text()` ensure user queries persist for session restore
- The stream emits a `UserQuery` proto message at the start of each response for conversation title
- Progressive summary (if present) is prepended to the messages array as a user/assistant pair in `translator.rs`
- Loop prevention guardrail in `controller.rs` detects repeated tool failures (3+ identical) and injects corrective instructions
### Platform Setup
- `./script/bootstrap` - Platform-specific setup plus common agent skill installation from `skills-lock.json`; prompts for project/global when an install or update is needed unless a target flag or environment override is provided.
- `./script/bootstrap --skip-common-skills` - Platform setup without installing or updating common agent skills.
- `./script/bootstrap --install-common-skills` - Explicitly install common agent skills from `skills-lock.json`; this is the default behavior.
- `./script/bootstrap --install-common-skills-in-repo` - Platform setup plus common agent skill installation in this checkout's `.agents/skills`.
- `./script/bootstrap --install-common-skills-globally` - Platform setup plus common agent skill installation in `~/.agents/skills`.
- `../common-skills/scripts/install_common_skills --repo-root "$PWD" --project --if-needed` - Install or refresh shared agent skills in this checkout's `.agents/skills`.
- `../common-skills/scripts/install_common_skills --repo-root "$PWD" --global --if-needed` - Install or refresh shared agent skills in `~/.agents/skills`.
- `../common-skills/scripts/remove_common_skills --repo-root "$PWD"` - Remove shared agent skills listed in `skills-lock.json` from this checkout's `.agents/skills`.
- `../common-skills/scripts/remove_common_skills --repo-root "$PWD" --global` - Remove shared agent skills listed in `skills-lock.json` from `~/.agents/skills`.
- `../common-skills/scripts/remove_common_skills --repo-root "$PWD" --clear-lock` - Remove shared agent skills from this checkout and delete `skills-lock.json`.
- `./script/install_cargo_build_deps` - Install Cargo build dependencies
- `./script/install_cargo_test_deps` - Install Cargo test dependencies
`skills-lock.json` is the standard project lock file managed by `npx skills`. `warpdotdev/common-skills/scripts/install_common_skills` requires an explicit install target before restoring: pass `--project`, pass `--global`, set `WARP_COMMON_SKILLS_INSTALL_TARGET`, or answer the interactive prompt from bootstrap. Non-interactive flows fail if no target is explicit. The installer creates `skills-lock.json` from `warpdotdev/common-skills` if it is missing, uses global as the recommended interactive default, errors if common skills are present in both project and global locations, prevents a global install pinned to one lock from being silently overwritten by another checkout pinned to a different lock, and verifies installed skills against the lock after successful install or skip paths. `script/run` and `script/bootstrap` execute this installer with `script/resolve_common_skills`, which uses `WARP_COMMON_SKILLS_SCRIPTS_DIR` only when explicitly set and otherwise runs the raw script from `warpdotdev/common-skills`. To test a remote common-skills branch, set `WARP_COMMON_SKILLS_REF=<branch>`. Cloud setup should use `common-skills/scripts/install_common_skills --repo-root <warp-checkout> --project --if-needed --non-interactive` or set `WARP_COMMON_SKILLS_INSTALL_TARGET=project` to avoid the prompt. To update the locked common skills, run `npx --yes skills@1.5.6 update -p -y` and commit the resulting `skills-lock.json` changes.
## Architecture Overview
This is a Rust-based terminal emulator with a custom UI framework called **GalaxyUI**.
### Key Components
**GalaxyUI Framework** (`ui/`):
- Custom UI framework with Entity-Component-Handle pattern
- Global `App` object owns all views/models (entities)
- Views hold `ViewHandle<T>` references to other views
- `AppContext` provides temporary access to handles during render/events
- Elements describe visual layout (Flutter-inspired)
- Actions system for event handling
- MouseStateHandle must be created once during construction, and then referenced/cloned anywhere we're using mouse input to track mouse changes. Inline `MouseStateHandle::default()` while rendering will cause no mouse interactions to work.
**Main App** (`app/`):
- Terminal emulation and shell management (`terminal/`)
- AI integration including Agent Mode (`ai/`)
- Cloud synchronization and Drive features (`drive/`)
- Authentication and user management (`auth/`)
- Settings and preferences (`settings/`)
- Workspace and session management (`workspace/`)
**Core Libraries**:
- `crates/galaxy_core/` - Core utilities and platform abstractions
- `crates/editor/` - Text editing functionality
- `crates/galaxyui/` and `crates/galaxyui_core/` - Custom UI framework
- `crates/ipc/` - Inter-process communication
- `crates/graphql/` - GraphQL client and schema
### Key Architectural Patterns
1. **Entity-Handle System**: Views reference other views via handles, not direct ownership
2. **Modular Structure**: Workspace contains multiple workspace configurations, each with terminals, notebooks, etc.
3. **Cross-Platform**: Native implementations for macOS, Windows, Linux, plus WASM target
4. **AI Integration**: Built-in AI assistant with context awareness and codebase indexing
5. **Cloud Sync**: Objects can be synchronized across devices via Galaxy Drive
### Development Guidelines
**Workspace Structure**:
- This is a Cargo workspace with 60+ member crates
- Main binary is in `app/`, UI framework in `crates/galaxyui/`
- Platform-specific code is conditionally compiled
- Integration tests are in `crates/integration/`
**Coding Style Preferences**:
- Avoid unnecessary type annotations, especially in closure params.
- Avoid using too many Rust path qualifiers and use imports for concision. Place import statements at the top of the file as per convention.
An exception to this is inside cfg-guarded code branches. In those cases, you can either embed the import into the relevant scope or just use an absolute path for one-offs.
- If a function takes a context parameter (`AppContext`, `ViewContext`, or `ModelContext`), it should be named `ctx` and go last. The one exception is for
functions that take a closure parameter, in which case the closure should be last.
- Always remove unused parameters completely rather than prefixing them with `_`. Update the function signature and all call sites accordingly.
- Prefer inline format arguments in macros like `println!`, `eprintln!`, and `format!` (for example, `eprintln!("{message}")` instead of `eprintln!("{}", message)`) to satisfy Clippy's `uninlined_format_args` lint.
- Do not pass `Itertools::format` results directly to logging macros (`log::*`, `safe_*`, etc.). `Itertools::format` produces a single-use formatter, while logging implementations may format a message more than once. Use a reusable `String` such as `iter.join(", ")` for logging arguments instead. Direct use in `format!` or `write!` is fine.
- Do not remove existing comments when making unrelated changes. Only remove or modify a comment if the logic it describes has changed.
- When adding a toggleable setting, also add the matching Command Palette enable/disable entry and any required context flags so the setting is discoverable outside Settings.
**Terminal Model Locking**:
- Be extremely careful when calling `model.lock()` on the terminal model (`TerminalModel`). Acquiring multiple locks on the same model from different call sites can cause a deadlock, resulting in a UI freeze (beach ball on macOS).
- Before adding a new `model.lock()` call, verify that no caller in the current call stack already holds the lock.
- Prefer passing already-locked model references down the call stack rather than acquiring new locks.
- If you must lock the model, keep the lock scope as short as possible and avoid calling other functions that might also attempt to lock.
**Testing**:
- Use `cargo nextest` for parallel test execution
- Integration tests use custom framework in `integration/`
- Tests should be run via presubmit script before submitting
- Unit tests should be placed in separate files using the naming convention `${filename}_tests.rs` or `mod_test.rs`
- Test files should be included at the end of their corresponding module with:
```rust
#[cfg(test)]
#[path = "filename_tests.rs"] // or "mod_test.rs"
mod tests;
```
**Pull Request Workflow**:
- **ALWAYS** run `./script/format` and `cargo clippy` (the versions specified in ./script/presubmit) before opening a PR or pushing updates to an existing PR branch
- Those commands must pass completely before creating or updating a pull request
- Specifically, ensure `./script/format` and `cargo clippy` checks pass
- If they fail, fix all issues before proceeding with the PR
- Do not create public pull requests or public issues that disclose a non-public security vulnerability. Refer users to `SECURITY.md` for the proper disclosure methods instead.
- This applies to:
- Opening new pull requests
- Pushing new commits to existing PR branches
- Any branch updates that will be reviewed
- When opening PRs, use the PR template at `.github/pull_request_template.md`
- Add changelog entries when appropriate using the format at the bottom of the PR template. Use the following prefixes (without the `{{}}` brackets):
- `CHANGELOG-NEW-FEATURE:` for new, relatively sizable features (use sparingly - these may get marketing/docs)
- `CHANGELOG-IMPROVEMENT:` for new functionality of existing features
- `CHANGELOG-BUG-FIX:` for fixes related to known bugs or regressions
- `CHANGELOG-IMAGE:` for GCP-hosted image URLs
- Leave changelog lines blank or remove them if no changelog entry is needed
**Database**:
- Uses Diesel ORM with SQLite
- Migrations in `migrations/` directory
- Schema defined in `app/src/persistence/schema.rs`
- Database file is `galaxy.sqlite` (renamed from Warp's `warp.sqlite`); legacy filename migration is handled in `init_db()`
**Session Restoration**:
- Controlled by `general.restore_session` setting
- App state (windows, tabs, pane tree, CWD, agent conversations) is snapshotted to SQLite on window events (close, move, resize, focus change)
- `TerminalView::active_session_path_if_local()` provides the CWD for each pane; falls back to `session_startup_path` for agent-mode or fresh tabs
- Agent conversations are persisted via `BlocklistAIHistoryEvent``ModelEvent::UpsertAIQuery` and restored via `RestoredAgentConversations` singleton
- The `active_conversation_id` field in `TerminalPaneSnapshot` controls whether agent view restores in fullscreen mode
**GraphQL**:
- Schema and client code generation from `crates/galaxy_graphql_schema/api/schema.graphql`
- TypeScript types generated for frontend integration
### Feature Flags
Warp uses compile-time feature flags with a small runtime plumbing layer.
How to add a feature flag:
- Add a new variant to `galaxy_core/src/features.rs` in the `FeatureFlag` enum
- (Optional) Enable it by default for dogfood builds by listing it in `DOGFOOD_FLAGS`
- Gate code paths with `FeatureFlag::YourFlag.is_enabled()`
- For preview or release rollout, add to `PREVIEW_FLAGS` or `RELEASE_FLAGS` respectively (as appropriate)
Best practices:
- **Prefer runtime checks over cfg directives**: Prefer `FeatureFlag::YourFlag.is_enabled()` over `#[cfg(...)]` compile-time directives so flags can be toggled without recompilation and are easier to clean up later. Use `#[cfg(...)]` only when the code cannot compile without them (for example, platform-specific code or dependencies that do not exist when the feature is disabled).
- Keep flags high-level and product-focused rather than per-call-site
- Remove the flag and dead branches after launch has stabilized
- For UI sections that expose a new feature, hide the UI behind the same flag
Example:
```rust
#[derive(Sequence)]
pub enum FeatureFlag {
YourNewFeature,
}
// Default-on for dogfood builds
pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[
FeatureFlag::YourNewFeature,
];
// Use in code
if FeatureFlag::YourNewFeature.is_enabled() {
// gated behavior
}
```
### Code Editor IntelliSense (LSP Completion)
The code editor has full LSP-powered autocompletion with documentation resolution:
**Key files:**
- `app/src/code/completion.rs` — Completion state, rendering (menu + docs panel), resolve logic
- `app/src/code/local_code_editor.rs` — Keybindings and action handling
**Behavior:**
- Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce)
- Trigger characters: `.` and `::` fire immediately
- Manual trigger: `Ctrl+Alt+Space`
- Keyboard navigation: Up/Down to select, Tab/Enter to confirm
- Mouse: hover an item to select it and show docs, click to confirm
- Documentation panel appears beside the menu when the LSP returns docs for the selected item (via `completionItem/resolve`)
**Architecture:**
- `CompletionState::Showing` holds items, filtered indices, per-item `MouseStateHandle`s, and resolved docs
- `resolve_selected_completion_docs()` sends `completionItem/resolve` to the LSP server
- The docs panel renders markdown via `FormattedTextElement` in a scrollable container beside the menu
### Exhaustive Matching
When adding/editing match statements, avoid using the wildcard _ when at all possible. Exhaustive matching is helpful for ensuring that all variants are handled, especially when adding new variants to enums in the future.
### Rules System
Global rules (behavioral instructions for the AI agent) are stored as `AIFact::Memory` cloud objects and managed via the Rules settings pane.
Key files:
- `app/src/ai/facts/mod.rs``AIFact` / `AIMemory` data model
- `app/src/ai/facts/predefined_rules.rs` — Default system-defined rules (seeded on first launch)
- `app/src/ai/facts/view/rule.rs``RuleView` UI with Global/Project tabs and "Add Predefined Rules" button
- `app/src/ai/facts/view/mod.rs``AIFactView` parent container (Rules + RuleEditor pages)
- `app/src/ai/facts/manager.rs``AIFactManager` singleton for pane tracking
- `app/src/settings/ai.rs``has_seeded_predefined_rules` setting (one-time flag)
Behavior:
- On first launch (no existing global rules and `has_seeded_predefined_rules` is false), predefined rules are automatically created
- The "Add Predefined Rules" button in the Global rules tab will add/update system-defined rules (identified by the "System Defined Rule" name prefix)
- Rules are persisted via the cloud object sync system (`UpdateManager::create_ai_fact` / `update_ai_fact`)
- The `memory_enabled` setting (`agents.knowledge.rules_enabled`) controls whether rules are sent to the AI
### Appearance Settings Notes
- Samsung-inspired built-in themes are available as `SamsungDark` and `SamsungLight`.
- UI font selection is persisted in `appearance.text.ui_font_name` and uses an empty string as the system-default sentinel.
- The one-click Samsung brand preset is implemented in `app/src/settings_view/appearance_page.rs` and applies:
- Samsung dark/light theme mapping
- terminal + AI font defaults
- a best-available Samsung-style UI font fallback
+65 -18
View File
@@ -2,12 +2,17 @@
Thanks for helping improve Warp! This guide explains how to open issues, propose changes, and get your work reviewed.
> [!TIP]
> **Chat with us in Slack.** Connect with other contributors and the Warp team in the [`#oss-contributors`](https://warpcommunity.slack.com/archives/C0B0LM8N4DB) channel — a good place for ad-hoc questions, design discussion, and pairing with maintainers as you work through an issue or PR. New here? [Join the Warp Slack community](https://go.warp.dev/join-preview) first, then hop into `#oss-contributors`.
## TL;DR
- Bug fixes are welcome for any issue. All bugs are marked as `ready-to-implement`.
- Bug fixes are welcome once the report is actionable from the provided details or maintainer triage.
- Feature requests must be marked `ready-to-spec` or `ready-to-implement` before PRs are accepted.
- Issues marked `warp:reserved-internal` are being handled by the Warp team and are not open for contributor PRs.
- Specs are the place where technical and design discussion on larger issues happen.
- Oz automatically triages incoming issues and reviews open PRs.
- Implementation PRs must include proof of manual testing.
## How Contributing to Warp Works
@@ -17,7 +22,7 @@ Warp's contribution model is shaped by [Oz](https://oz.warp.dev), an agent that
- **Feature requests differ from bug fixes:**
- Features are gated by readiness labels — `ready-to-spec`, then `ready-to-implement` once the design is settled — that signal when contributors can pick up the work. Discussion alone is not approval to begin work.
- Feature work needs a written spec first: feature requests go through a spec PR (a *product spec* + *tech spec* committed under [`specs/`](specs/)) before any code is written.
- Bug fixes skip both steps; they are implicitly `ready-to-implement` once triaged.
- Bug fixes can go straight to a code PR once the report is reproducible or otherwise actionable; they do not require spec PRs unless the scope or design is unclear.
- **Review is largely automated.** When you open a PR, Oz is auto-assigned and produces an initial review. Once Oz approves, it automatically requests a follow-up review from a Warp team subject-matter expert — you do not need to assign human reviewers yourself.
### Readiness labels
@@ -25,8 +30,9 @@ Warp's contribution model is shaped by [Oz](https://oz.warp.dev), an agent that
The Warp team applies one of the following labels when an issue is ready for contribution:
- **`ready-to-spec`** — The problem is understood but the design is open. Open a spec PR with a *product spec* (`product.md`) and a *tech spec* (`tech.md`) under [`specs/`](specs/) — see [Opening a Spec PR](#opening-a-spec-pr) for what goes in each. This label is **reserved for feature requests**.
- **`ready-to-implement`** — The design is settled. Open a code PR. **All triaged bug reports are implicitly `ready-to-implement`** once accepted — you don't need to wait for an explicit label on a confirmed bug.
- **`ready-to-implement`** — The issue is ready for a code PR. For bugs, this means the report is sufficiently reproducible or actionable and the likely fix does not need a spec, mocks, or deeper investigation.
- **`needs-mocks`** — Design mocks are required before implementation can begin. Wait for the Warp team to land them.
- **`warp:reserved-internal`** — The Warp team is reserving this work for internal implementation or alignment. Do not open a spec or code PR for issues with this label; Oz will reject contributor PRs linked to them with an explanatory comment.
Anyone can pick up a ready issue — readiness labels are not assignments, and the best implementation wins through normal review. If an issue has been sitting un-triaged or you'd like readiness re-evaluated, mention **@oss-maintainers** in a comment to flag it for the team.
@@ -41,7 +47,7 @@ flowchart TD
B -- needs-mocks --> D[Design mocks produced]
D --> E[Open code PR]
C -- specs approved --> E
B -- ready-to-implement<br/>(incl. all triaged bugs) --> E
B -- ready-to-implement<br/>(actionable bugs or settled designs) --> E
E --> F[Oz review → SME review → CI → merge]
classDef contributor fill:#fef3c7,stroke:#b45309,color:#78350f;
@@ -66,7 +72,7 @@ A good bug report includes:
- Warp version and OS (see `Settings → About`).
- Logs, screenshots, or screen recordings when relevant.
Once an issue is triaged as a bug (by Oz's triage agent or a maintainer), it is implicitly **`ready-to-implement`** you can pick it up and open a code PR without waiting for a separate label.
Once an issue is triaged as an actionable bug (by Oz's triage agent or a maintainer), it may be labeled **`ready-to-implement`** so you can pick it up and open a code PR.
### Feature requests
@@ -88,15 +94,23 @@ Issues labeled `ready-to-spec` need a spec before code can begin. A spec consist
- **`product.md`** (the *product spec*) — Defines the desired behavior from the consumer's perspective (the user, an API caller, a CLI user, etc.) and stays out of implementation detail. The core is a numbered list of **testable behavior invariants** covering the happy path, user-visible states, inputs and responses, and edge cases (empty / error / loading, cancellation, offline, permission denied, races, accessibility). Optional sections: problem statement, goals / non-goals, Figma link, open questions.
- **`tech.md`** (the *tech spec*) — The implementation plan, grounded in this codebase. Required sections: **Context** (the current system and relevant files with line references), **Proposed changes** (modules touched, new types / APIs / state, data flow, tradeoffs), and **Testing and validation** (how each invariant from the product spec will be verified). Optional: end-to-end flow, Mermaid diagrams, risks, parallelization, follow-ups.
The spec-writing skills are sourced from [`warpdotdev/common-skills`](https://github.com/warpdotdev/common-skills), not authored directly in this repository. This checkout pins the expected versions in [`skills-lock.json`](skills-lock.json), and the bootstrap scripts can restore them for you:
- `./script/bootstrap` installs or updates common skills by default and prompts for a project-local or global install target when needed.
- `./script/bootstrap --install-common-skills-in-repo` installs the pinned common skills into this checkout's `.agents/skills/`.
- `./script/bootstrap --install-common-skills-globally` installs the pinned common skills into `~/.agents/skills/`.
- `WARP_COMMON_SKILLS_INSTALL_TARGET=project ./script/bootstrap` and `WARP_COMMON_SKILLS_INSTALL_TARGET=global ./script/bootstrap` select the same targets non-interactively.
- `./script/bootstrap --skip-common-skills` leaves common skills untouched if you are managing them separately.
To open a spec PR:
1. Add `specs/GH<issue-number>/product.md` and `specs/GH<issue-number>/tech.md`. See [`specs/GH408/`](specs/GH408/), [`specs/GH1063/`](specs/GH1063/), and [`specs/GH1066/`](specs/GH1066/) for examples of well-structured specs, and browse the rest of [`specs/`](specs/) for more. The [`/write-product-spec`](.agents/skills/write-product-spec/SKILL.md) and [`/write-tech-spec`](.agents/skills/write-tech-spec/SKILL.md) skills are available to scaffold these for you.
1. Add `specs/GH<issue-number>/product.md` and `specs/GH<issue-number>/tech.md`. See [`specs/GH408/`](specs/GH408/), [`specs/GH1063/`](specs/GH1063/), and [`specs/GH1066/`](specs/GH1066/) for examples of well-structured specs, and browse the rest of [`specs/`](specs/) for more. After common skills are installed, the `/write-product-spec` and `/write-tech-spec` skills are available to scaffold these for you.
2. Use the PR as the home for product and technical discussion.
3. Once the specs are approved, implementation generally continues on the same PR. In rarer cases — for example, if a large spec is merged on its own so the implementation can be broken up — it can move to a linked follow-up PR.
## Opening a Code PR
For issues labeled `ready-to-implement` (this includes any triaged bug):
For issues labeled `ready-to-implement`:
1. Branch from `master`.
2. Implement the change and add tests (see [Testing](#testing)).
@@ -108,23 +122,50 @@ You **do not need to manually request reviewers**. Oz is auto-assigned to PRs th
After you push changes that address Oz's feedback, comment `/oz-review` on the PR to request a re-review — you can do this up to **three times** per PR. If something looks stuck or you need more reviews than that, mention **@oss-maintainers** on the PR to escalate to the team.
**You must include proof of [manual testing](#manual-testing)**. For small, isolated, and visual changes, you should include **before and after screenshots**. For larger, broad, or interactive changes, you should also include a **narrated screen recording**.
If a maintainer requests changes to your PR, you will need to request `/oz-review` again and pass it before a re-review can be requested. Oz will request the re-review for you automatically once you pass its reviews.
### PRs opened without a linked issue
We require PRs to be linked to an associated issue. This is where problems get scoped, [readiness labels](#readiness-labels) get applied, and some features go through a [spec phase](#opening-a-spec-pr) before any code is written. See the [Contribution Flow](#contribution-flow) for the full picture.
That said, if you open a PR ahead of the standard issue workflow, here's what we recommend:
First, **search for a related issue.** Due to the volume of issues we receive, there's often an existing issue for a given feature or bug fix. If you find one, link it in your PR description. Ideally, this issue will have been reviewed by a maintainer with a [readiness label](#readiness-labels) applied. If you do not find a related issue, file an issue describing what your PR resolves. Once a maintainer has reviewed the issue and associated PR, we can apply a readiness label to unblock final checks.
Then, **ensure your PR passes code review and includes relevant tests** per our [Opening a Code PR guide.](#opening-a-code-pr) If code review passes and relevant tests are present, that's high signal for us to review your work sooner.
## Using a Coding Agent
You can use **any coding agent** to implement a contribution — for example, Warp's built-in agent, Claude Code, Codex, Gemini CLI, or others — or no agent at all. This repository ships agent-readable context (skills under [`.agents/skills/`](.agents/skills/), specs under [`specs/`](specs/), and [`WARP.md`](WARP.md)) that any harness supporting these formats can pick up.
You can use **any coding agent** to implement a contribution — for example, Warp's built-in agent, Claude Code, Codex, Gemini CLI, or others — or no agent at all. This repository ships agent-readable context (skills under [`.agents/skills/`](.agents/skills/), specs under [`specs/`](specs/), and [`AGENTS.md`](AGENTS.md)) that any harness supporting these formats can pick up.
If you'd rather have an **Oz cloud agent** implement a ready issue for you, mention **@oss-maintainers** on the issue to request it. Approved requests run **for free** on complimentary Oz credits — you don't need to set up your own Oz account or pay for compute.
## Becoming a Collaborator
While you can use coding agents for implementation, we expect contributors to **collaborate with us personally**. This means that you should not be using agents like OpenClaw to engage in conversation with our team. Our maintainers will always talk to you as a human, so please talk to us as a human as well.
Contributors with several merged PRs may be invited to become collaborators. Collaborators receive expanded permissions including the ability to:
## Code Review
- Assign [Oz](https://warp.dev/oz) to work on issues by mentioning `@oz` in a comment on any issue that has a readiness label.
- Use complimentary Oz credits for contributions to this repository.
- Apply and manage issue labels.
All pull requests go through a two-stage review process:
1. **Oz review** — When you open a PR, [Oz](https://warp.dev/oz) is automatically assigned and produces the first review. Oz checks for correctness, style, test coverage, and alignment with the linked issue and any associated specs.
2. **Warp team review** — Only after Oz has **approved** the PR is it routed to a Warp team subject-matter expert for a final human review. PRs that have not yet been approved by Oz will not be assigned to a team member.
You do not need to manually request reviewers at any stage. After pushing changes that address Oz's feedback, comment `/oz-review` on the PR to request a re-review — you can do this up to **three times** per PR. If something looks stuck or you need additional reviews, mention **@oss-maintainers** on the PR to escalate to the team.
### Stale PRs with requested changes
If a review (from Oz or a maintainer) leaves your PR with **changes requested** and it then goes quiet, automation follows up and eventually closes it so the review queue stays current. This applies only to external-contributor PRs with an active requested-changes review.
- **Reminders** are posted at **7** and **10** days of inactivity, with the **day-10 reminder serving as the final warning**.
- The PR is **automatically closed at ~14 days** of inactivity — but only after that final warning, so you always get a heads-up first.
- Only **your** activity resets the timer: pushing to your branch (including a force-push) or commenting on the PR. Maintainer comments don't reset it, since the PR is waiting on you.
- To keep a PR open, just push updates or reply. A closed PR can be reopened when you're ready to continue (reopen it and push, or ask a maintainer to reopen).
- Maintainers can apply the **`no-autoclose`** label to exempt a PR that should stay open (for example, when it's blocked on us).
## Development Setup
See [README.md](README.md) and [WARP.md](WARP.md) for the full engineering guide. Quick start:
See [README.md](README.md) and [AGENTS.md](AGENTS.md) for the full engineering guide. Quick start:
```bash
./script/bootstrap # platform-specific setup
@@ -136,17 +177,23 @@ cargo run # build and run Warp
Tests are required for most code changes:
### Manual Testing
Manual testing is required for changes that can be manually tested, and almost all changes can be manually tested. For small, isolated, and visual changes, you should include **before and after screenshots**. For larger, broad, or interactive changes, you should also include a **narrated screen recording**.
You can run the app locally using `./script/run` - see [AGENTS.md](AGENTS.md) for more details on how to get set up.
### Automated Tests
- **Bug fixes** should include a regression test that would have caught the bug.
- **Algorithmic or non-trivial logic** needs unit tests.
- **User-facing flows** should have end-to-end coverage under [`crates/integration/`](crates/integration/) whenever the behavior can be exercised that way. The bar is high-quality coverage of the changes you ship — with agent-driven development the expectation is more integration tests, not just coverage of P0 paths. If a flow is worth shipping, it's usually worth an integration test.
Run unit tests with `cargo nextest run`. See [WARP.md](WARP.md) for more detail.
Run unit tests with `cargo nextest run`.
## Code Style
- `cargo fmt` and `cargo clippy --workspace --all-targets --all-features --tests -- -D warnings` must pass.
- `./script/format --check` and `cargo clippy --workspace --all-targets --all-features --tests -- -D warnings` must pass.
- Prefer imports over path qualifiers, inline format args (`println!("{x}")`), and exhaustive `match` over `_` wildcards.
- See [WARP.md](WARP.md) for the full style guide, including WarpUI patterns and terminal model locking rules.
- See [AGENTS.md](AGENTS.md) for the full style guide, including WarpUI patterns and terminal model locking rules.
## Commit and Branch Conventions
@@ -163,6 +210,6 @@ See [`SECURITY.md`](SECURITY.md) for our security disclosure policy and private
## Getting Help
- Chat with other contributors and the Warp team in [`#oss-contributors`](https://warpcommunity.slack.com/archives/C0B0LM8N4DB) on the [Warp Slack community](https://go.warp.dev/join-preview) (join the workspace first if you're new).
- Browse the [Warp docs](https://docs.warp.dev/).
- Join the [Slack Community](https://go.warp.dev/join-preview) to ask questions and connect with other contributors.
- Open a [GitHub issue](https://github.com/warpdotdev/warp/issues) for bugs or feature requests.
Generated
+3745 -2575
View File
File diff suppressed because it is too large Load Diff
+99 -36
View File
@@ -36,6 +36,10 @@ asset_macro = { path = "crates/asset_macro" }
channel_versions = { path = "crates/channel_versions", default-features = false }
command = { path = "crates/command" }
command-signatures-v2 = { path = "crates/command-signatures-v2" }
cloud_objects = { path = "crates/cloud_objects" }
cloud_object_client = { path = "crates/cloud_object_client" }
cloud_object_persistence = { path = "crates/cloud_object_persistence" }
cloud_object_models = { path = "crates/cloud_object_models" }
computer_use = { path = "crates/computer_use" }
field_mask = { path = "crates/field_mask" }
firebase = { path = "crates/firebase" }
@@ -46,16 +50,20 @@ http_server = { path = "crates/http_server" }
input_classifier = { path = "crates/input_classifier" }
integration = { path = "crates/integration" }
ipc = { path = "crates/ipc" }
ipynb_parser = { path = "crates/ipynb_parser" }
jsonrpc = { path = "crates/jsonrpc" }
languages = { path = "crates/languages" }
local_control = { path = "crates/local_control" }
local_inference = { path = "crates/local_inference" }
lsp = { path = "crates/lsp" }
markdown_parser = { path = "crates/markdown_parser" }
mcp = { path = "crates/mcp" }
natural_language_detection = { path = "crates/natural_language_detection" }
node_runtime = { path = "crates/node_runtime" }
onboarding = { path = "crates/onboarding" }
persistence = { path = "crates/persistence" }
prevent_sleep = { path = "crates/prevent_sleep" }
remote_server = { path = "crates/remote_server" }
repo_metadata = { path = "crates/repo_metadata" }
settings = { path = "crates/settings" }
settings_value = { path = "crates/settings_value", default-features = false }
@@ -69,12 +77,13 @@ vim = { path = "crates/vim" }
virtual-fs = { path = "crates/virtual_fs" }
voice_input = { path = "crates/voice_input" }
galaxy = { path = "app" }
warp_assets = { path = "crates/warp_assets" }
warp_channel_config = { path = "crates/warp_channel_config" }
galaxy_cli = { path = "crates/galaxy_cli" }
galaxy_completer = { path = "crates/galaxy_completer" }
galaxy_core = { path = "crates/galaxy_core" }
galaxy_editor = { path = "crates/editor" }
galaxy_features = { path = "crates/galaxy_features" }
remote_server = { path = "crates/remote_server" }
galaxy_files = { path = "crates/galaxy_files" }
galaxy_graphql = { path = "crates/graphql" }
galaxy_graphql_schema = { path = "crates/galaxy_graphql_schema" }
@@ -82,7 +91,10 @@ galaxy_isolation_platform = { path = "crates/isolation_platform" }
galaxy_js = { path = "crates/galaxy_js" }
galaxy_logging = { path = "crates/galaxy_logging" }
galaxy_managed_secrets = { path = "crates/managed_secrets" }
warp_multi_agent_client = { path = "crates/warp_multi_agent_client" }
galaxy_ripgrep = { path = "crates/galaxy_ripgrep" }
warp_search_core = { path = "crates/warp_search_core" }
warp_server_auth = { path = "crates/warp_server_auth" }
galaxy_server_client = { path = "crates/galaxy_server_client" }
galaxy_terminal = { path = "crates/galaxy_terminal" }
galaxy_util = { path = "crates/galaxy_util" }
@@ -93,6 +105,21 @@ galaxyui_extras = { path = "crates/galaxyui_extras", default-features = false }
watcher = { path = "crates/watcher" }
websocket = { path = "crates/websocket" }
# Backward-compat aliases: crates that haven't been fully migrated from warp→galaxy names
warp = { path = "app", package = "galaxy" }
warp_core = { path = "crates/galaxy_core", package = "galaxy_core" }
warp_features = { path = "crates/galaxy_features", package = "galaxy_features" }
warp_util = { path = "crates/galaxy_util", package = "galaxy_util" }
warp_graphql = { path = "crates/graphql", package = "galaxy_graphql" }
warp_managed_secrets = { path = "crates/managed_secrets", package = "galaxy_managed_secrets" }
warp_cli = { path = "crates/galaxy_cli", package = "galaxy_cli" }
warp_editor = { path = "crates/editor", package = "galaxy_editor" }
warp_terminal = { path = "crates/galaxy_terminal", package = "galaxy_terminal" }
warp_server_client = { path = "crates/galaxy_server_client", package = "galaxy_server_client" }
warpui = { path = "crates/galaxyui", package = "galaxyui" }
warpui_core = { path = "crates/galaxyui_core", package = "galaxyui_core" }
warpui_extras = { path = "crates/galaxyui_extras", package = "galaxyui_extras", default-features = false }
# Workspace-level dependencies used by multiple crates. Prefer adding dependencies
# here to copying-and-pasting versions.
axum = "0.8.4"
@@ -116,6 +143,7 @@ bitflags = { version = "2.4.0", features = ["serde"] }
bitflags-serde-legacy = "0.1.1"
block = "0.1.6"
blocking = "1.6.2"
bounded-vec-deque = "0.1"
bytemuck = { version = "1.13.1" }
bytes = { version = "1.11.1", features = ["serde"] }
command-corrections = { git = "https://github.com/warpdotdev/command-corrections.git", rev = "eae08c8c51d9bc9741fcb17eef2c21f696aebbeb" }
@@ -132,11 +160,12 @@ cynic = { version = "3" }
cynic-codegen = { version = "3", features = ["rkyv"] }
dashmap = "6.1.0"
derive_more = "0.99.17"
diesel = { version = "2.3.4", default-features = false }
diesel = { version = "2.3.8", default-features = false }
directories = "6.0"
dunce = "1.0.1"
enum-iterator = "1.1.3"
env_logger = "0.10.0"
event-listener = "5.4.0"
float-cmp = "0.9.0"
font-kit = { git = "https://github.com/warpdotdev/font-kit.git", rev = "a04b225ecb639bb850f21d05212cfdc7465000f3", default-features = false, features = [
"source",
@@ -166,6 +195,7 @@ image = { version = "0.25.9", default-features = false, features = [
"webp",
] }
instant = { version = "0.1.12", features = ["wasm-bindgen"] }
iso8601-duration = { version = "0.2.0", features = ["chrono", "serde"] }
itertools = "0.14.0"
jaq-json = { version = "2.0", features = ["serde"] }
# Disabling the default `formats` feature is intended to avoid pulling in
@@ -179,27 +209,38 @@ lazy_static = "1.4.0"
libc = "0.2.81"
line-ending = "1.4.0"
log = { version = "0.4", features = ["serde", "std"] }
mermaid_to_svg = { git = "https://github.com/warpdotdev/mermaid-to-svg.git", rev = "f7233f69965c59760fad98d684c786569814d821" }
mermaid_to_svg = { git = "https://github.com/warpdotdev/mermaid-to-svg.git", rev = "8d3f789c2eb49335d7bf247a06bb649f59b6d4ed" }
mime_guess = "2.0"
minimp4 = "0.1.2"
nix = { version = "0.26.4", default-features = false, features = ["signal"] }
notify-debouncer-full = { git = "https://github.com/warpdotdev/notify.git", rev = "f3afcda3058941e17e09689afea40e2a2db057e0" }
notify-debouncer-full = { git = "https://github.com/warpdotdev/notify", rev = "91b719849bc04ef251bcb2cc61076b099204e970" }
nom = "7.1.1"
num-traits = "0.2"
# We disable default features as the "rustls-tls" feature enables the reqwest
# feature of the same name, which itself enables the "rustls-tls-webpki-roots"
# feature, whereas we want to use native roots instead.
oauth2 = { version = "5.0.0", default-features = false, features = ["reqwest"] }
oauth2 = { version = "5.0.0", default-features = false }
opentelemetry = { version = "0.32.0", default-features = false, features = ["trace"] }
opentelemetry-http = "0.32.0"
opentelemetry-otlp = { version = "0.32.0", default-features = false, features = [
"gzip-http",
"http-proto",
"trace",
"zstd-http",
] }
opentelemetry_sdk = { version = "0.32.1", default-features = false, features = ["trace"] }
openh264 = "0.8"
static_assertions = "1.1.0"
url = "2.5.4"
urlocator = "0.1.4"
mockall = "0.13.1"
objc = "0.2"
objc2 = "0.6.3"
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSScreen", "objc2-core-foundation"] }
objc2-av-foundation = { version = "0.3.2", default-features = false }
objc2-core-foundation = "0.3.2"
objc2-core-graphics = "0.3.2"
objc2-foundation = { version = "0.3", default-features = false, features = ["std"] }
objc2-metal = { version = "0.3.2", default-features = false }
objc2-quartz-core = { version = "0.3.2", default-features = false }
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "block2"] }
once_cell = "1.20.2"
ordered-float = { version = "3.0.0", features = ["serde"] }
parking_lot = "0.12.1"
@@ -211,27 +252,26 @@ prost = "0.14.3"
prost-build = "0.14.3"
prost-reflect = "0.16.3"
prost-types = "0.14.3"
rand = "0.8.2"
qrcode = { version = "0.14.1", default-features = false }
rand = "0.8.6"
rangemap = "1.3.0"
reqwest = { version = "0.12.28", default-features = false, features = [
reqwest = { version = "0.13", features = [
"blocking",
"brotli",
"charset",
"gzip",
"http2",
"form",
"json",
"macos-system-configuration",
"rustls-tls-native-roots-no-provider",
"multipart",
"query",
"stream",
"system-proxy",
] }
reqwest-eventsource = "0.6.0"
reqwest-eventsource = { package = "aha-reqwest-eventsource", version = "0.1" }
resvg = "0.47.0"
rust-embed = { version = "8.7.0", features = ["include-exclude"] }
rustc-hash = "2.1.1"
rustls = "0.23.39"
schemars = { version = "1", features = ["chrono04"] }
sentry = { version = "0.41.0", default-features = false, features = [
sentry = { version = "0.47.0", default-features = false, features = [
"anyhow",
"backtrace",
"contexts",
@@ -241,7 +281,7 @@ sentry = { version = "0.41.0", default-features = false, features = [
"reqwest",
"rustls",
] }
sentry-log = "0.41.0"
sentry-log = "0.47.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_bytes = "0.11"
serde-bytes-repr = "0.3"
@@ -249,7 +289,7 @@ serde_json = { version = "1.0", features = ["raw_value"] }
serde_urlencoded = "0.7"
serde_with = "2.0.1"
serde_yaml = "0.8"
session-sharing-protocol = { git = "https://github.com/warpdotdev/session-sharing-protocol.git", rev = "3a12b871dfd1019a66057e4d9b7d5c812b73ee8c" }
session-sharing-protocol = { git = "https://github.com/warpdotdev/session-sharing-protocol.git", rev = "b30fdd06379a3d073b398eabd106abed5b443aae" }
similar = { version = "2.7", features = ["inline"] }
simplelog = "0.12.2"
smallvec = "1.6.1"
@@ -265,6 +305,12 @@ toml_edit = "0.25.5"
tower = "0.5.2"
tower-http = "0.6.6"
tracing = "0.1.40"
tracing-futures = "0.2.5"
tracing-opentelemetry = { version = "0.33.0", default-features = false }
tracing-subscriber = { version = "0.3.22", default-features = false, features = [
"registry",
"std",
] }
arborium = { version = "2", default-features = false, features = [
"lang-rust",
"lang-go",
@@ -281,8 +327,10 @@ arborium = { version = "2", default-features = false, features = [
"lang-css",
"lang-c",
"lang-json",
"lang-jq",
"lang-hcl",
"lang-lua",
"lang-nix",
"lang-ruby",
"lang-php",
"lang-toml",
@@ -298,14 +346,15 @@ arborium = { version = "2", default-features = false, features = [
"lang-vue",
"lang-dockerfile",
] }
unicode-general-category = "1.1.0"
unicode-width = "0.1.12"
uuid = { version = "1.1.2", features = ["v4", "serde", "js"] }
vec1 = { version = "1.8.0", features = ["serde"] }
version-compare = "0.1"
vte = { git = "https://github.com/warpdotdev/vte.git", rev = "4b399c87b63ba88f45709edaa6383fc519f6c900", default-features = false }
walkdir = "2"
warp-workflows = { git = "https://github.com/warpdotdev/workflows.git", rev = "793a98ddda6ef19682aed66364faebd2829f0e01" }
warp_multi_agent_api = { git = "https://github.com/warpdotdev/warp-proto-apis.git", rev = "78a78f21a75432bf0141e396fb318bf1694e47f0" }
warp-workflows = { git = "https://github.com/warpdotdev/workflows", rev = "793a98ddda6ef19682aed66364faebd2829f0e01" }
warp_multi_agent_api = { git = "https://github.com/warpdotdev/warp-proto-apis.git", rev = "45f9b1342b256b20f716aede64dc1b46a639e5e6" }
wasm-bindgen = "0.2.89"
wasm-bindgen-futures = "0.4.42"
web-sys = { version = "0.3.69", features = [
@@ -349,8 +398,8 @@ dirs = "6.0.0"
rayon = "1.10.0"
sha2 = "0.10"
shellexpand = "3.1.1"
warp-command-signatures = { git = "https://github.com/warpdotdev/command-signatures.git", rev = "00a032b8ea0ee5711e2077e39a92dc9ca2051cd3", default-features = false }
winit = { git = "https://github.com/warpdotdev/winit.git", rev = "7ef01853ae3fe952e6014080a88dc4352662dfb1" }
warp-command-signatures = { git = "https://github.com/warpdotdev/command-signatures.git", rev = "a937ae35dfe20eaed030664ad52b36657390b162", default-features = false }
winit = { git = "https://github.com/warpdotdev/winit.git", rev = "a4e0ecb5f9626ccac9445a73dc28354b52423abc" }
x11rb = "0.13.0"
mockito = "1.7.0"
sysinfo = { version = "0.37.0", default-features = false, features = [
@@ -377,14 +426,17 @@ typed-path = "0.10.0"
streaming-iterator = "0.1.0"
derivative = "2.2.0"
parquet = { version = "55.0.0", features = ["arrow"] }
rmcp = { git = "https://github.com/warpdotdev/rmcp.git", rev = "c0f65dc441af7d714b9c453ac5e7ef641451abe3" }
rmcp = { version = "1.6" }
# Comment this to disable building with debug symbols
# Building with debug symbols generates a dsym which we can use to get
# better stack traces in sentry. But there is a cost of about 20% binary size
# increase.
[profile.release]
debug = true
# Use line-tables-only (debug = 1) rather than full debuginfo (debug = 2 /
# `true`). Line tables are enough to symbolicate panics and Sentry stack
# traces with file/line info, but they omit the per-variable/type DWARF that
# dominates DWARF size. Dropping the type info significantly reduces rustc's
# peak memory during ThinLTO + codegen (which was OOM-killing release builds
# on CI), at the cost of not being able to inspect locals/types when
# attaching a debugger to a release binary.
debug = 1
# Force the rust compiler to create a dSYM. Starting in 1.53 the default on MacOS is "unpacked".
split-debuginfo = "packed"
@@ -400,6 +452,8 @@ debug = "line-tables-only"
split-debuginfo = "unpacked"
[profile.dev.package]
# Improve the performance of core terminal logic.
warp_terminal.opt-level = 3
# Minimize the runtime overhead of CPU profiling at the cost of a small
# increase in compile time for this crate.
backtrace.opt-level = 3
@@ -490,17 +544,22 @@ inherits = "dev"
opt-level = "s"
[patch.crates-io]
core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "0bcad1e103ead6bd71c4e5f85598ada9508e3a82" }
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", rev = "0bcad1e103ead6bd71c4e5f85598ada9508e3a82" }
core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "0bcad1e103ead6bd71c4e5f85598ada9508e3a82" }
core-text = { git = "https://github.com/servo/core-foundation-rs", rev = "0bcad1e103ead6bd71c4e5f85598ada9508e3a82" }
# Pinned to the merge commit of servo/core-foundation-rs#746, which fixes a
# double-retain bug in `CTFontCollection::get_descriptors` that leaks the
# CoreText font-descriptor NSArray on every call. The fix is not yet in any
# crates.io release; the latest published `core-text` (21.1.0) was cut from
# a commit that predates the merge.
core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff" }
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", rev = "6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff" }
core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff" }
core-text = { git = "https://github.com/servo/core-foundation-rs", rev = "6f844cf1a1a18e25b70fcdf1bcdc458555bd2eff" }
objc = { git = "https://github.com/warpdotdev/rust-objc.git", rev = "5b656827fa9f863ef0eb22e444a3fedac009e78a" }
pathfinder_simd = { git = "https://github.com/warpdotdev/pathfinder.git", rev = "34128a129ca6aee168fbc5060a4f21b4f7e486a9" }
yaml-rust = { git = "https://github.com/warpdotdev/yaml-rust.git", rev = "51684719d0102ca85ff4b21cec39877a0c669e19" }
tink-core = { git = "https://github.com/warpdotdev/tink-rust", branch = "warpdotdev/main" }
tink-proto = { git = "https://github.com/warpdotdev/tink-rust", branch = "warpdotdev/main" }
tink-hybrid = { git = "https://github.com/warpdotdev/tink-rust", branch = "warpdotdev/main" }
tink-core = { git = "https://github.com/warpdotdev/tink-rust", rev = "54b9ac9af93b0c08b446a7bc0582836c9403a71b" }
tink-proto = { git = "https://github.com/warpdotdev/tink-rust", rev = "54b9ac9af93b0c08b446a7bc0582836c9403a71b" }
tink-hybrid = { git = "https://github.com/warpdotdev/tink-rust", rev = "54b9ac9af93b0c08b446a7bc0582836c9403a71b" }
tikv-jemallocator = { git = "https://github.com/warpdotdev/jemallocator.git", rev = "2ee30bfdf7059223b54810e4ea6c666f0a379e0b" }
tikv-jemalloc-sys = { git = "https://github.com/warpdotdev/jemallocator.git", rev = "2ee30bfdf7059223b54810e4ea6c666f0a379e0b" }
@@ -508,3 +567,7 @@ tikv-jemalloc-sys = { git = "https://github.com/warpdotdev/jemallocator.git", re
[patch."https://github.com/warpdotdev/warp-proto-apis.git"]
# Uncomment for local development of warp-proto-apis
# warp_multi_agent_api = { path = "../warp-proto-apis/apis/multi_agent/v1/gen/rust" }
[patch."https://github.com/warpdotdev/session-sharing-protocol.git"]
# Uncomment for local development of session-sharing-protocol
# session-sharing-protocol = { path = "../session-sharing-protocol" }
+7 -7
View File
@@ -1,12 +1,12 @@
# Frequently Asked Questions
This FAQ covers the questions we hear most often about contributing to the Warp client, working with agents in this repository, and how this repo fits into Warp the product. For the full contribution flow, see [CONTRIBUTING.md](CONTRIBUTING.md). For engineering details — build setup, code style, testing — see [WARP.md](WARP.md).
This FAQ covers the questions we hear most often about contributing to the Warp client, working with agents in this repository, and how this repo fits into Warp the product. For the full contribution flow, see [CONTRIBUTING.md](CONTRIBUTING.md). For engineering details — build setup, code style, testing — see [AGENTS.md](AGENTS.md).
## Contributing
### How do I contribute?
Start with a GitHub issue. Bug reports are implicitly ready to fix once triaged; feature requests go through a short spec PR before any code is written. The full flow — readiness labels, spec PRs, code PRs, review — is documented in [CONTRIBUTING.md](CONTRIBUTING.md).
Start with a GitHub issue. Bug reports can go straight to a code PR once they are triaged as actionable; feature requests go through a short spec PR before any code is written. The full flow — readiness labels, spec PRs, code PRs, review — is documented in [CONTRIBUTING.md](CONTRIBUTING.md).
### How do I file a good bug report or feature request?
@@ -17,7 +17,7 @@ If you're already running Warp, the `/feedback` command files an issue with logs
### What do the readiness labels mean?
- **`ready-to-spec`** — the problem is understood, the design is open. Next step is a spec PR.
- **`ready-to-implement`** — the design is settled, or it's a triaged bug. Next step is a code PR.
- **`ready-to-implement`** — the issue is ready for a code PR. For bugs, this means the report is sufficiently reproducible or actionable.
- **`needs-mocks`** — design mocks are required before implementation can start.
Anyone can pick up a labeled issue. Mention **@oss-maintainers** on an issue if it needs triage or readiness re-evaluation.
@@ -34,7 +34,7 @@ cargo run # build and run Warp
./script/presubmit # fmt, clippy, and tests
```
macOS, Linux, and Windows are all supported. Platform-specific setup is handled by `./script/bootstrap`. See [WARP.md](WARP.md) for the full engineering guide.
macOS, Linux, and Windows are all supported. Platform-specific setup is handled by `./script/bootstrap`. See [AGENTS.md](AGENTS.md) for the full engineering guide.
### Will my PR be reviewed by a human or by an agent?
@@ -58,7 +58,7 @@ Contributors with several merged PRs may be invited to become collaborators. The
### Can I use my own coding agent to contribute?
Yes. Use whatever you like — Warp's built-in agent, Claude Code, Codex, Gemini CLI, Cursor, others, or no agent at all. The repo ships agent-readable context (skills under [`.agents/skills/`](.agents/skills/), specs under [`specs/`](specs/), and [`WARP.md`](WARP.md)) that any harness supporting these formats can pick up.
Yes. Use whatever you like — Warp's built-in agent, Claude Code, Codex, Gemini CLI, Cursor, others, or no agent at all. The repo ships agent-readable context (skills under [`.agents/skills/`](.agents/skills/), specs under [`specs/`](specs/), and [`AGENTS.md`](AGENTS.md)) that any harness supporting these formats can pick up.
### Can I use Codex or Claude models with my existing subscriptions in Warp, or submit a PR to add that?
@@ -80,7 +80,7 @@ No. Contributing by hand or with your own agent is free. Oz runs on Warp's credi
### Are agent-generated PRs held to the same bar as human ones?
Yes. The same Oz + SME review, the same tests, and the same `cargo fmt` / `cargo clippy` / presubmit checks apply regardless of who (or what) wrote the code. Whether a PR is hand-written or agent-written doesn't change the quality bar — it changes how quickly you can iterate to meet it.
Yes. The same Oz + SME review, the same tests, and the same `./script/format` / `cargo clippy` / presubmit checks apply regardless of who (or what) wrote the code. Whether a PR is hand-written or agent-written doesn't change the quality bar — it changes how quickly you can iterate to meet it.
### Will my issues, comments, or code be used to train models?
@@ -136,7 +136,7 @@ Yes — that's what AGPL is for. The license prevents fully-proprietary relaunch
- The [Warp docs](https://docs.warp.dev/) for using the product.
- [GitHub Issues](https://github.com/warpdotdev/warp/issues) for bug reports and feature requests.
- The [Slack community](https://go.warp.dev/join-preview) for general questions and discussion.
- The [Slack community](https://go.warp.dev/join-preview) for general questions and discussion — contributors chat with each other and the Warp team in [`#oss-contributors`](https://warpcommunity.slack.com/archives/C0B0LM8N4DB).
- Mention **@oss-maintainers** on an issue or PR to escalate to the team.
### How do I report a security vulnerability?
+34 -11
View File
@@ -1,6 +1,12 @@
<a href="https://www.warp.dev">
<img width="1024" alt="Warp Agentic Development Environment product preview" src="https://github.com/user-attachments/assets/9976b2da-2edd-4604-a36c-8fd53719c6d4" />
</a>
&nbsp;
<p align="center">
<a href="https://www.warp.dev"><img height="20" alt="Built with Warp" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/main/Github/Built-With-Warp-Export@2x.png" /></a>
&nbsp;
<a href="https://oz.warp.dev"><img height="20" alt="Powered by Oz" src="https://raw.githubusercontent.com/warpdotdev/brand-assets/main/Github/Powered-By-Oz-Export@2x.png" /></a>
</p>
<p align="center">
<a href="https://www.warp.dev">Website</a>
@@ -31,6 +37,20 @@
You can [download Warp](https://www.warp.dev/download) and [read our docs](https://docs.warp.dev/) for platform-specific instructions.
## Warp Contributions Overview Dashboard
Explore [build.warp.dev](https://build.warp.dev) to:
- Watch thousands of Oz agents triage issues, write specs, implement changes, and review PRs
- View top contributors and in-flight features
- Track your own issues with GitHub sign-in
- Click into active agent sessions in a web-compiled Warp terminal
## Oz for OSS
Maintaining a popular open-source project? [Apply for Oz credits](https://tally.so/r/LZWxqG) to explore [Oz for OSS](https://github.com/warpdotdev/oz-for-oss).
Oz for OSS is our partner program for bringing the same agentic open-source management workflows used in this repository to select partner repositories. We work directly with maintainers to implement workflows for issue triage, PR review, community management, and contributor coordination in a way that fits each project.
## Licensing
Warp's UI framework (the `warpui_core` and `warpui` crates) are licensed under the [MIT license](LICENSE-MIT).
@@ -41,6 +61,9 @@ The rest of the code in this repository is licensed under the [AGPL v3](LICENSE-
Warp's client codebase is open source and lives in this repository. We welcome community contributions and have designed a lightweight workflow to help new contributors get started. For the full contribution flow, read our [CONTRIBUTING.md](CONTRIBUTING.md) guide.
> [!TIP]
> **Chat with contributors and the Warp team** in the [`#oss-contributors`](https://warpcommunity.slack.com/archives/C0B0LM8N4DB) Slack channel — a good place for ad-hoc questions, design discussion, and pairing with maintainers. New here? [Join the Warp Slack community](https://go.warp.dev/join-preview) first, then jump into `#oss-contributors`.
### Issue to PR
Before filing, [search existing issues](https://github.com/warpdotdev/warp/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc) for your bug or feature request. If nothing exists, [file an issue](https://github.com/warpdotdev/warp/issues/new/choose) using our templates. Security vulnerabilities should be reported privately as described in [CONTRIBUTING.md](CONTRIBUTING.md#reporting-security-issues).
@@ -57,7 +80,7 @@ To build and run Warp from source:
./script/presubmit # fmt, clippy, and tests
```
See [WARP.md](WARP.md) for the full engineering guide, including coding style, testing, and platform-specific notes.
See [AGENTS.md](AGENTS.md) for the full engineering guide, including coding style, testing, and platform-specific notes.
## Joining the Team
@@ -66,7 +89,7 @@ Interested in joining the team? See our [open roles](https://www.warp.dev/career
## Support and Questions
1. See our [docs](https://docs.warp.dev/) for a comprehensive guide to Warp's features.
2. Join our [Slack Community](https://go.warp.dev/join-preview) to connect with other users and get help from the Warp team.
2. Join our [Slack Community](https://go.warp.dev/join-preview) to connect with other users and get help from the Warp team — contributors hang out in [`#oss-contributors`](https://warpcommunity.slack.com/archives/C0B0LM8N4DB).
3. Try our [Preview build](https://www.warp.dev/download-preview) to test the latest experimental features.
4. Mention **@oss-maintainers** on any issue to escalate to the team — for example, if you encounter problems with the automated agents.
@@ -78,12 +101,12 @@ We ask everyone to be respectful and empathetic. Warp follows the [Code of Condu
We'd like to call out a few of the [open source dependencies](https://docs.warp.dev/help/licenses) that have helped Warp to get off the ground:
* [Tokio](https://github.com/tokio-rs/tokio)
* [NuShell](https://github.com/nushell/nushell)
* [Fig Completion Specs](https://github.com/withfig/autocomplete)
* [Warp Server Framework](https://github.com/seanmonstar/warp)
* [Alacritty](https://github.com/alacritty/alacritty)
* [Hyper HTTP library](https://github.com/hyperium/hyper)
* [FontKit](https://github.com/servo/font-kit)
* [Core-foundation](https://github.com/servo/core-foundation-rs)
* [Smol](https://github.com/smol-rs/smol)
- [Tokio](https://github.com/tokio-rs/tokio)
- [NuShell](https://github.com/nushell/nushell)
- [Fig Completion Specs](https://github.com/withfig/autocomplete)
- [Warp Server Framework](https://github.com/seanmonstar/warp)
- [Alacritty](https://github.com/alacritty/alacritty)
- [Hyper HTTP library](https://github.com/hyperium/hyper)
- [FontKit](https://github.com/servo/font-kit)
- [Core-foundation](https://github.com/servo/core-foundation-rs)
- [Smol](https://github.com/smol-rs/smol)
+1 -1
View File
@@ -4,7 +4,7 @@ We take security seriously at Warp and appreciate the efforts of security resear
## Reporting a Vulnerability
If you believe you've found a security vulnerability, please follow responsible disclosure practices and **do not** open a public GitHub issue, as this could expose the vulnerability before a fix is available.
If you believe you've found a security vulnerability, please follow responsible disclosure practices and **do not** open a public GitHub issue or pull request, as this could expose the vulnerability before a fix is available.
Instead, please report it through one of the following channels:
-329
View File
@@ -1,329 +0,0 @@
# WARP.md
This file provides guidance when working with code in this repository.
## Development Commands
### Build and Run
- `cargo run` - Build and run Warp locally
- `cargo bundle --bin warp` - Bundle the main app
### Running with local warp-server
To connect Warp client to a local warp-server instance:
```bash
# Connect to server on default port 8080
cargo run --features with_local_server
# Connect to server on custom port (e.g., 8082)
SERVER_ROOT_URL=http://localhost:8082 WS_SERVER_URL=ws://localhost:8082/graphql/v2 cargo run --features with_local_server
```
Environment variables:
- `SERVER_ROOT_URL` - HTTP endpoint (default: `http://localhost:8080`)
- `WS_SERVER_URL` - WebSocket endpoint (default: `ws://localhost:8080/graphql/v2`)
### Testing
- `cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2` - Run tests with nextest
- `cargo nextest run -p warp_completer --features v2` - Run completer tests with v2 features
- `cargo test --doc` - Run doc tests
- `cargo test` - Run standard tests for individual packages
### Linting and Formatting
- `./script/presubmit` - Run all presubmit checks (fmt, clippy, tests)
- `cargo fmt` - Format code
- `cargo clippy --workspace --all-targets --all-features --tests -- -D warnings` - Run clippy
- `./script/run-clang-format.py -r --extensions 'c,h,cpp,m' ./crates/warpui/src/ ./app/src/` - Format C/C++/Obj-C code
- `find . -name "*.wgsl" -exec wgslfmt --check {} +` - Check WGSL shader formatting
### Bedrock Diagnostics
- Set `GALAXY_BEDROCK_DIAGNOSTICS=1` to enable Bedrock diagnostic output, including:
- `Error_<timestamp>.txt` snapshot files written to the repository root on request/stream failures (includes the serialized Bedrock context window, tool definitions, protobuf request debug payload, captured Bedrock diagnostic lines, and log tails)
- Per-event Bedrock diagnostic logs written to `bedrock-diagnostics.log` in the active Warp log directory
### AI Provider Architecture
Galaxy supports multiple AI backends via a **provider dispatch pattern**. Provider selection
is controlled by settings (`ai.openai.enabled` takes priority over `ai.bedrock.enabled`).
```
Provider dispatch: response_stream.rs → resolve_provider_config() → ProviderConfig enum
↓ Bedrock ↓ OpenAI
bedrock/translator.rs openai/translator.rs
```
**Shared types** in `app/src/ai/provider/`:
- `types.rs``ConversationMessage`, `MessageRole`, `MessageContent`, `ContentPart`, `ToolDefinition`
- `mod.rs``ProviderConfig` enum (Bedrock | OpenAI | None)
**Bedrock provider** in `app/src/ai/bedrock/`:
- `translator.rs` — Orchestrator: takes `api::Request` + config, returns `ResponseStream`
- `request_translator.rs` — Converts Warp proto → Bedrock SDK types (messages, system prompt, tools, sanitization)
- `response_translator.rs` — Converts Bedrock stream events → Warp proto `ResponseEvent`s
- `convert.rs` — Re-exports shared types + Bedrock SDK type builders
- `client.rs` — AWS SDK client construction and `converse_stream` call
- `models.rs` — Model registry and cross-region inference prefix logic
- `discovery.rs` — AWS profile listing and model discovery (STS identity check + ListFoundationModels)
- `diagnostic.rs` — Debug logging (enabled via `GALAXY_BEDROCK_DIAGNOSTICS=1`)
- `external_config.rs` — Fallback config from Claude Code/OpenCode settings
**OpenAI/LiteLLM provider** in `app/src/ai/openai/`:
- `translator.rs` — Orchestrator: same pattern as Bedrock, targets OpenAI chat completions API
- `client.rs``reqwest`-based HTTP client for `POST /v1/chat/completions` with streaming
- `convert.rs``ConversationMessage` → OpenAI JSON format (system/user/assistant/tool roles, function calling)
- `request_translator.rs` — OpenAI-specific message sanitization (lighter than Bedrock's strict alternation rules)
- `response_translator.rs` — SSE stream parser → Warp proto `ResponseEvent`s
**Provider settings** (in settings TOML):
- `ai.bedrock.enabled` — Use AWS Bedrock directly (default: true)
- `ai.openai.enabled` — Use OpenAI-compatible endpoint(s) (default: false, takes priority over Bedrock)
- `ai.openai.base_url` — Legacy single-provider endpoint URL (default: `http://localhost:4000/v1`)
- `ai.openai.api_key` — Legacy single-provider API key (stored in keychain)
- `ai.openai.model` — Model name override sent to the endpoint
- `ai.openai.models` — Legacy single-provider model list (`Vec<OpenAIModelConfig>`)
- `ai.providers`**Multi-provider config** (`Vec<OpenAIProviderConfig>`): each entry has `name`, `base_url`, `api_key`, `models[]`
**Multi-provider example** (settings.toml):
```toml
[ai.openai]
enabled = true
[[ai.providers]]
name = "LiteLLM"
base_url = "http://localhost:4000/v1"
api_key = "sk-..."
[[ai.providers.models]]
model_id = "claude-sonnet-4-20250514[1m]"
display_name = "Claude Sonnet 4 (1M)"
context_size = 1000000
[[ai.providers]]
name = "Ollama (Local)"
base_url = "http://localhost:11434/v1"
[[ai.providers.models]]
model_id = "llama3.2"
display_name = "Llama 3.2"
context_size = 128000
```
**OpenAI/LiteLLM model discovery**:
- Models can be auto-fetched from the `/models` endpoint via the Settings > OpenAI / LiteLLM page
- For each model, the system probes `{model_id}[1m]` with a minimal chat completion request
- If the `[1m]` variant is accepted (HTTP 200 or 429), it's used with 1M context window
- Otherwise, the base model ID is used with its reported context size
- Models injected via `ai.providers[]` are routed to their specific endpoint (per-model routing map)
- Provider name shown as the description label in the model picker; icon shows OpenAI logo for all OpenAI-compatible providers
Key invariants:
- Known tools are in `KNOWN_TOOLS` constant in `response_translator.rs`
- Tool definitions are built via `tool_definition_for_name()` in `convert_request.rs`; includes `recall_tool_history` for retrieving past tool results
- Unknown/hallucinated tool calls are caught in the stream, paired with synthetic error results, and now emit a visible `AgentOutput` text message to the UI
- `recall_tool_history` is handled inline in the response translator (synthetic result from `messages_sent`)
- Tool result archive: before progressive summarization drains messages, `ConversationMessage::archive_tool_results()` extracts all tool_use/tool_result pairs into a separate `tool_result_archive` vec. `recall_tool_history` searches both live history + archived results, and supports a `tool_use_id` parameter for exact ID lookup
- Prompt caching uses three cache points: system prompt, conversation history (second-to-last message), tool config
- `ensure_tool_results_paired()` enforces Bedrock's invariant that every `tool_use` has a matching `tool_result`
- `inject_input_messages_into_task()` and `extract_user_query_text()` ensure user queries persist for session restore
- The stream emits a `UserQuery` proto message at the start of each response for conversation title
- Progressive summary (if present) is prepended to the messages array as a user/assistant pair in `translator.rs`
- Loop prevention guardrail in `controller.rs` detects repeated tool failures (3+ identical) and injects corrective instructions
### Platform Setup
- `./script/bootstrap` - Platform-specific setup (calls platform-specific bootstrap scripts)
- `./script/install_cargo_build_deps` - Install Cargo build dependencies
- `./script/install_cargo_test_deps` - Install Cargo test dependencies
## Architecture Overview
This is a Rust-based terminal emulator with a custom UI framework called **WarpUI**.
### Key Components
**WarpUI Framework** (`ui/`):
- Custom UI framework with Entity-Component-Handle pattern
- Global `App` object owns all views/models (entities)
- Views hold `ViewHandle<T>` references to other views
- `AppContext` provides temporary access to handles during render/events
- Elements describe visual layout (Flutter-inspired)
- Actions system for event handling
- MouseStateHandle must be created once during construction, and then referenced/cloned anywhere we're using mouse input to track mouse changes. Inline `MouseStateHandle::default()` while rendering will cause no mouse interactions to work.
**Main App** (`app/`):
- Terminal emulation and shell management (`terminal/`)
- AI integration including Agent Mode (`ai/`)
- Cloud synchronization and Drive features (`drive/`)
- Authentication and user management (`auth/`)
- Settings and preferences (`settings/`)
- Workspace and session management (`workspace/`)
**Core Libraries**:
- `warp_core/` - Core utilities and platform abstractions
- `editor/` - Text editing functionality
- `ui/` - Custom UI framework
- `ipc/` - Inter-process communication
- `graphql/` - GraphQL client and schema
### Key Architectural Patterns
1. **Entity-Handle System**: Views reference other views via handles, not direct ownership
2. **Modular Structure**: Workspace contains multiple workspace configurations, each with terminals, notebooks, etc.
3. **Cross-Platform**: Native implementations for macOS, Windows, Linux, plus WASM target
4. **AI Integration**: Built-in AI assistant with context awareness and codebase indexing
5. **Cloud Sync**: Objects can be synchronized across devices via Warp Drive
### Development Guidelines
**Workspace Structure**:
- This is a Cargo workspace with 34+ member crates
- Main binary is in `app/`, UI framework in `ui/`
- Platform-specific code is conditionally compiled
- Integration tests are in `integration/`
**Coding Style Preferences**:
- Avoid unnecessary type annotations, especially in closure params.
- Avoid using too many Rust path qualifiers and use imports for concision. Place import statements at the top of the file as per convention.
An exception to this is inside cfg-guarded code branches. In those cases, you can either embed the import into the relevant scope or just use an absolute path for one-offs.
- If a function takes a context parameter (`AppContext`, `ViewContext`, or `ModelContext`), it should be named `ctx` and go last. The one exception is for
functions that take a closure parameter, in which case the closure should be last.
- Always remove unused parameters completely rather than prefixing them with `_`. Update the function signature and all call sites accordingly.
- Prefer inline format arguments in macros like `println!`, `eprintln!`, and `format!` (for example, `eprintln!("{message}")` instead of `eprintln!("{}", message)`) to satisfy Clippy's `uninlined_format_args` lint.
- Do not remove existing comments when making unrelated changes. Only remove or modify a comment if the logic it describes has changed.
**Terminal Model Locking**:
- Be extremely careful when calling `model.lock()` on the terminal model (`TerminalModel`). Acquiring multiple locks on the same model from different call sites can cause a deadlock, resulting in a UI freeze (beach ball on macOS).
- Before adding a new `model.lock()` call, verify that no caller in the current call stack already holds the lock.
- Prefer passing already-locked model references down the call stack rather than acquiring new locks.
- If you must lock the model, keep the lock scope as short as possible and avoid calling other functions that might also attempt to lock.
**Testing**:
- Use `cargo nextest` for parallel test execution
- Integration tests use custom framework in `integration/`
- Tests should be run via presubmit script before submitting
- Unit tests should be placed in separate files using the naming convention `${filename}_tests.rs` or `mod_test.rs`
- Test files should be included at the end of their corresponding module with:
```rust
#[cfg(test)]
#[path = "filename_tests.rs"] // or "mod_test.rs"
mod tests;
```
**Pull Request Workflow**:
- **ALWAYS** run cargo fmt and cargo clippy (the versions specified in ./script/presubmit) before opening a PR or pushing updates to an existing PR branch
- Those commands must pass completely before creating or updating a pull request
- Specifically, ensure `cargo fmt` and `cargo clippy` checks pass
- If they fail, fix all issues before proceeding with the PR
- This applies to:
- Opening new pull requests
- Pushing new commits to existing PR branches
- Any branch updates that will be reviewed
- When opening PRs, use the PR template at `.github/pull_request_template.md`
- Add changelog entries when appropriate using the format at the bottom of the PR template. Use the following prefixes (without the `{{}}` brackets):
- `CHANGELOG-NEW-FEATURE:` for new, relatively sizable features (use sparingly - these may get marketing/docs)
- `CHANGELOG-IMPROVEMENT:` for new functionality of existing features
- `CHANGELOG-BUG-FIX:` for fixes related to known bugs or regressions
- `CHANGELOG-IMAGE:` for GCP-hosted image URLs
- Leave changelog lines blank or remove them if no changelog entry is needed
**Database**:
- Uses Diesel ORM with SQLite
- Migrations in `migrations/` directory
- Schema defined in `app/src/persistence/schema.rs`
- Database file is `galaxy.sqlite` (renamed from Warp's `warp.sqlite`); legacy filename migration is handled in `init_db()`
**Session Restoration**:
- Controlled by `general.restore_session` setting
- App state (windows, tabs, pane tree, CWD, agent conversations) is snapshotted to SQLite on window events (close, move, resize, focus change)
- `TerminalView::active_session_path_if_local()` provides the CWD for each pane; falls back to `session_startup_path` for agent-mode or fresh tabs
- Agent conversations are persisted via `BlocklistAIHistoryEvent``ModelEvent::UpsertAIQuery` and restored via `RestoredAgentConversations` singleton
- The `active_conversation_id` field in `TerminalPaneSnapshot` controls whether agent view restores in fullscreen mode
**GraphQL**:
- Schema and client code generation from `graphql/api/schema.graphql`
- TypeScript types generated for frontend integration
### Feature Flags
Warp uses compile-time feature flags with a small runtime plumbing layer.
How to add a feature flag:
- Add a new variant to `warp_core/src/features.rs` in the `FeatureFlag` enum
- (Optional) Enable it by default for dogfood builds by listing it in `DOGFOOD_FLAGS`
- Gate code paths with `FeatureFlag::YourFlag.is_enabled()`
- For preview or release rollout, add to `PREVIEW_FLAGS` or `RELEASE_FLAGS` respectively (as appropriate)
Best practices:
- **Prefer runtime checks over cfg directives**: Prefer `FeatureFlag::YourFlag.is_enabled()` over `#[cfg(...)]` compile-time directives so flags can be toggled without recompilation and are easier to clean up later. Use `#[cfg(...)]` only when the code cannot compile without them (for example, platform-specific code or dependencies that do not exist when the feature is disabled).
- Keep flags high-level and product-focused rather than per-call-site
- Remove the flag and dead branches after launch has stabilized
- For UI sections that expose a new feature, hide the UI behind the same flag
Example:
```rust
#[derive(Sequence)]
pub enum FeatureFlag {
YourNewFeature,
}
// Default-on for dogfood builds
pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[
FeatureFlag::YourNewFeature,
];
// Use in code
if FeatureFlag::YourNewFeature.is_enabled() {
// gated behavior
}
```
### Code Editor IntelliSense (LSP Completion)
The code editor has full LSP-powered autocompletion with documentation resolution:
**Key files:**
- `app/src/code/completion.rs` — Completion state, rendering (menu + docs panel), resolve logic
- `app/src/code/local_code_editor.rs` — Keybindings and action handling
**Behavior:**
- Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce)
- Trigger characters: `.` and `::` fire immediately
- Manual trigger: `Ctrl+Alt+Space`
- Keyboard navigation: Up/Down to select, Tab/Enter to confirm
- Mouse: hover an item to select it and show docs, click to confirm
- Documentation panel appears beside the menu when the LSP returns docs for the selected item (via `completionItem/resolve`)
**Architecture:**
- `CompletionState::Showing` holds items, filtered indices, per-item `MouseStateHandle`s, and resolved docs
- `resolve_selected_completion_docs()` sends `completionItem/resolve` to the LSP server
- The docs panel renders markdown via `FormattedTextElement` in a scrollable container beside the menu
### Exhaustive Matching
When adding/editing match statements, avoid using the wildcard _ when at all possible. Exhaustive matching is helpful for ensuring that all variants are handled, especially when adding new variants to enums in the future.
### Rules System
Global rules (behavioral instructions for the AI agent) are stored as `AIFact::Memory` cloud objects and managed via the Rules settings pane.
Key files:
- `app/src/ai/facts/mod.rs``AIFact` / `AIMemory` data model
- `app/src/ai/facts/predefined_rules.rs` — Default system-defined rules (seeded on first launch)
- `app/src/ai/facts/view/rule.rs``RuleView` UI with Global/Project tabs and "Add Predefined Rules" button
- `app/src/ai/facts/view/mod.rs``AIFactView` parent container (Rules + RuleEditor pages)
- `app/src/ai/facts/manager.rs``AIFactManager` singleton for pane tracking
- `app/src/settings/ai.rs``has_seeded_predefined_rules` setting (one-time flag)
Behavior:
- On first launch (no existing global rules and `has_seeded_predefined_rules` is false), predefined rules are automatically created
- The "Add Predefined Rules" button in the Global rules tab will add/update system-defined rules (identified by the "System Defined Rule" name prefix)
- Rules are persisted via the cloud object sync system (`UpdateManager::create_ai_fact` / `update_ai_fact`)
- The `memory_enabled` setting (`agents.knowledge.rules_enabled`) controls whether rules are sent to the AI
### Appearance Settings Notes
- Samsung-inspired built-in themes are available as `SamsungDark` and `SamsungLight`.
- UI font selection is persisted in `appearance.text.ui_font_name` and uses an empty string as the system-default sentinel.
- The one-click Samsung brand preset is implemented in `app/src/settings_view/appearance_page.rs` and applies:
- Samsung dark/light theme mapping
- terminal + AI font defaults
- a best-available Samsung-style UI font fallback
+148 -58
View File
@@ -28,7 +28,12 @@ path = "src/bin/local.rs"
test = false
[[bin]]
name = "galaxy-stable"
name = "integration"
path = "src/bin/integration.rs"
test = false
[[bin]]
name = "stable"
path = "src/bin/stable.rs"
test = false
@@ -51,6 +56,7 @@ test = false
[dependencies]
addr = "0.15.6"
ai.workspace = true
alphanumeric-sort = "1.5.7"
anyhow.workspace = true
arrayvec.workspace = true
asset_cache.workspace = true
@@ -68,7 +74,7 @@ bincode.workspace = true
bitflags.workspace = true
bitflags-serde-legacy.workspace = true
blocking.workspace = true
bounded-vec-deque = "0.1"
bounded-vec-deque.workspace = true
bytecount = "0.6.9"
bytes.workspace = true
byte-unit = "5.1.4"
@@ -76,6 +82,9 @@ cfg-if.workspace = true
channel_versions.workspace = true
chrono.workspace = true
clap.workspace = true
cloud_object_client.workspace = true
cloud_object_models.workspace = true
cloud_objects.workspace = true
command = { workspace = true }
command-corrections.workspace = true
command-signatures-v2 = { workspace = true, optional = true }
@@ -98,9 +107,8 @@ email_address = { git = "https://github.com/warpdotdev/rust-email_address", bran
embed_plist = "1.2"
enclose = "1.1.8"
enum-iterator.workspace = true
event-listener = "5.4.0"
event-listener.workspace = true
field_mask.workspace = true
firebase.workspace = true
flate2 = "1.0.17"
float-cmp.workspace = true
futures-lite.workspace = true
@@ -116,15 +124,19 @@ image.workspace = true
infer = "0.19.0"
jaq-json.workspace = true
jaq-all.workspace = true
jemalloc_pprof = { version = "0.8.1", optional = true, features = [
"symbolize",
] }
# Built WITHOUT the `symbolize` feature: `dump_pprof()` then returns a raw
# pprof (sample addresses + mappings + GNU build-id), which is symbolized
# offline against the debug-info file uploaded to Sentry by the release
# process (matched by build-id). This keeps the shipped binary fully
# strippable.
jemalloc_pprof = { version = "0.8.1", optional = true }
lsp-types = "0.97.0"
indexmap = { version = "2.0.2", features = ["serde"] }
input_classifier.workspace = true
instant.workspace = true
local_inference = { workspace = true, optional = true }
ipc.workspace = true
iso8601-duration.workspace = true
itertools.workspace = true
kmeans_colors = { version = "0.5", default-features = false, features = [
"palette_color",
@@ -139,6 +151,7 @@ galaxy_logging.workspace = true
sentry = { workspace = true, optional = true }
lz4_flex = "0.11"
markdown_parser.workspace = true
mcp.workspace = true
memchr.workspace = true
memo-map.workspace = true
mermaid_to_svg.workspace = true
@@ -150,7 +163,6 @@ ordered-float.workspace = true
os_info = { version = "3.7.0", default-features = false }
palette = { version = "0.6.0", default-features = false, features = ["std"] }
parking_lot = { version = "0.12.1", features = ["serde"] }
paste = "1.0"
pathfinder_color = "0.5.0"
pathfinder_geometry.workspace = true
persistence.workspace = true
@@ -159,6 +171,7 @@ plist = "1"
pprof = { workspace = true, optional = true, features = ["protobuf-codec"] }
prost.workspace = true
prost-types.workspace = true
qrcode.workspace = true
rand.workspace = true
rangemap.workspace = true
rayon.workspace = true
@@ -200,9 +213,12 @@ tikv-jemallocator = { version = "0.6", optional = true, features = [
"override_allocator_on_supported_platforms",
] }
toml = "0.8.13"
toml_edit.workspace = true
tracing.workspace = true
tracing-futures = { workspace = true, features = ["futures-03"] }
ui_components.workspace = true
unicase = "2.7.0"
unicode-general-category.workspace = true
unicode-width.workspace = true
unindent = "0.1.7"
url = { workspace = true, features = ["serde"] }
@@ -216,14 +232,19 @@ version-compare.workspace = true
vte.workspace = true
walkdir.workspace = true
warp-workflows.workspace = true
warp_assets.workspace = true
warp_channel_config.workspace = true
galaxy_completer.workspace = true
galaxy_core.workspace = true
galaxy_editor.workspace = true
galaxy_graphql.workspace = true
galaxy_js = { workspace = true, optional = true }
warp_search_core.workspace = true
warp_server_auth.workspace = true
galaxy_server_client.workspace = true
galaxy_util.workspace = true
galaxyui = { workspace = true, features = ["schema_gen", "settings_value"] }
galaxyui_core.workspace = true
voice_input = { workspace = true, optional = true }
galaxyui_extras = { workspace = true, features = ["default", "user_preferences-toml"] }
syntax_tree.workspace = true
@@ -246,10 +267,12 @@ warp_multi_agent_api.workspace = true
remote_server.workspace = true
repo_metadata.workspace = true
vim.workspace = true
rmcp = { workspace = true, features = ["client"] }
rmcp.workspace = true
galaxy_isolation_platform.workspace = true
galaxy_ripgrep.workspace = true
galaxy_managed_secrets.workspace = true
warp_multi_agent_client.workspace = true
local_control.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
block.workspace = true
@@ -257,7 +280,22 @@ cocoa.workspace = true
core-foundation.workspace = true
mach2 = "0.5"
objc.workspace = true
objc2-foundation = { workspace = true, features = ["NSBundle"] }
objc2.workspace = true
objc2-app-kit = { workspace = true, features = [
"NSApplication",
"NSImage",
"NSResponder",
"NSRunningApplication",
"NSWorkspace",
] }
objc2-core-foundation.workspace = true
objc2-foundation = { workspace = true, features = [
"NSBundle",
"NSLocale",
"NSPathUtilities",
"NSString",
"NSURL",
] }
permissions = "0.4.1"
pprof = { workspace = true, optional = true, features = ["frame-pointer"] }
security-framework-sys = "2.0.0"
@@ -270,6 +308,7 @@ wgpu.workspace = true
app-installation-detection.workspace = true
async-io.workspace = true
axum.workspace = true
cloud_object_persistence.workspace = true
comfy-table = "7.1.4"
inquire = "0.9.1"
diesel = { workspace = true, features = ["sqlite", "chrono"] }
@@ -282,24 +321,21 @@ http_server.workspace = true
hyper.workspace = true
libsqlite3-sys = { version = "0.33.0", features = ["bundled"] }
mio = { version = "1.1.1", features = ["os-poll", "os-ext"] }
opentelemetry.workspace = true
opentelemetry-http.workspace = true
opentelemetry-otlp.workspace = true
opentelemetry_sdk.workspace = true
tokio.workspace = true
tokio-util.workspace = true
tracing-opentelemetry.workspace = true
tracing-subscriber.workspace = true
# AWS SDK (loading credentials for BYO LLM)
aws-config = { version = "1.8.12", features = ["credentials-login"] }
aws-config = { version = "1.8.16", features = ["credentials-login"] }
aws-credential-types = "1"
aws-sdk-bedrock = "1"
aws-sdk-bedrockruntime = "1.132"
aws-sdk-sts = "1"
aws-smithy-types = "1"
aws-sdk-sts = { version = "1", default-features = false, features = ["default-https-client", "rt-tokio"] }
aws-types = "1"
rmcp = { workspace = true, features = [
"auth",
"transport-streamable-http-client-reqwest",
"transport-sse-client-reqwest",
"transport-child-process",
] }
notify-debouncer-full.workspace = true
rquickjs = { workspace = true, optional = true }
rustls.workspace = true
@@ -310,7 +346,6 @@ is_executable = "1.0.1"
gethostname = "1.1.0"
watcher.workspace = true
galaxy_files.workspace = true
tantivy = "0.26.0"
[target.'cfg(target_family = "wasm")'.dependencies]
console_error_panic_hook = { version = "0.1.6" }
@@ -352,11 +387,11 @@ nix = { workspace = true, features = [
"mman",
] }
[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
crash-handler = { version = "0.6.3", optional = true }
minidumper = { version = "0.8.3", optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
freedesktop-desktop-entry = "0.5.0"
x11rb.workspace = true
zbus.workspace = true
@@ -387,12 +422,14 @@ winreg.workspace = true
[dev-dependencies]
ai = { workspace = true, features = ["test-util"] }
async-process.workspace = true
firebase.workspace = true
jsonschema = { workspace = true, default-features = false }
async-executor = "1.5.1"
command = { workspace = true, features = ["test-util"] }
ctor = "0.1.18"
cloud_object_client = { workspace = true, features = ["test-util"] }
http_client = { workspace = true, features = ["test-util"] }
mockall = "0.13.1"
mockall.workspace = true
mockito.workspace = true
serial_test = "0.8.0"
sum_tree = { workspace = true, features = ["test-util"] }
@@ -401,8 +438,10 @@ galaxy_completer = { workspace = true, features = ["test-util"] }
galaxy_core = { workspace = true, features = ["test-util"] }
galaxy_editor = { workspace = true, features = ["test-util"] }
galaxy_server_client = { workspace = true, features = ["test-util"] }
warp_server_auth = { workspace = true, features = ["test-util"] }
galaxy_terminal = { workspace = true, features = ["test-util"] }
galaxyui = { workspace = true, features = ["test-util"] }
galaxyui_core = { workspace = true, features = ["test-util"] }
galaxyui_extras = { workspace = true, features = ["test-util"] }
galaxy_files = { workspace = true, features = ["test-util"] }
repo_metadata = { workspace = true, features = ["test-util"] }
@@ -425,18 +464,23 @@ embed-resource = "3.0"
# Note that we support channel-specific enables for these features
[features]
tui = ["galaxyui_core/tui"]
ai_resume_button = []
autoupdate = []
figma_detection = []
bundled_skills = []
supergrok = []
gemini_enterprise = []
agent_mode = []
agent_mode_computer_use = []
background_computer_use = []
agent_mode_debug = []
agent_mode_primary_xml = []
agent_mode_pre_plan_xml = []
agent_onboarding = []
agent_shared_sessions = []
ask_user_question = []
async_find = []
changelog = []
clear_autosuggestion_on_escape = []
cloud_object_initial_load = ["enforce_revisions_to_cloud_objects"]
@@ -463,10 +507,8 @@ codebase_index_persistence = ["full_source_code_embedding"]
default = [
"agent_mode",
"render_continuous_block_selections_with_single_border",
"settings_import",
"block_toolbelt_save_as_workflow",
"remove_alt_screen_padding",
"less_horizontal_terminal_padding",
"shared_with_me",
"session_sharing_acls",
"external_agent_mode_context",
"shell_selector",
"minimalist_ui",
@@ -519,7 +561,6 @@ default = [
"allow_opening_file_links_using_editor_env",
"read_image_files",
"selection_as_context",
"changed_lines_only_apply_diff_result",
"undo_closed_panes",
"revert_diff_hunk",
"code_review_save_changes",
@@ -544,12 +585,16 @@ default = [
"web_search_ui",
"integration_command",
"artifact_command",
"conversation_api",
"cloud_environments",
"create_environment_slash_command",
"code_review_find",
"mcp_grouped_server_context",
"fork_from_command",
"context_window_usage_v2",
"v4a_file_diffs",
"team_api_keys",
"named_agents",
"agent_tips",
"pluggable_notifications",
"agent_onboarding",
@@ -558,6 +603,9 @@ default = [
"ask_user_question",
"bundled_skills",
"agent_mode_computer_use",
"oz_platform_skills",
"oz_identity_federation",
"sync_ambient_plans",
"conversation_artifacts",
"agent_view",
"agent_view_block_context",
@@ -565,6 +613,9 @@ default = [
"inline_slash_commands",
"inline_history_menu",
"inline_model_selector",
"oz_launch_modal",
"open_warp_launch_modal",
"orchestration_launch_modal",
"new_tab_styling",
"richtext_multiselect",
"inline_profile_selector",
@@ -580,6 +631,7 @@ default = [
"github_pr_prompt_chip",
"conversations_as_context",
"markdown_tables",
"blocklist_markdown_table_rendering",
"markdown_mermaid",
"blocklist_markdown_images",
"pr_comments_slash_command",
@@ -597,24 +649,39 @@ default = [
"open_code_notifications",
"cli_agent_rich_input",
"vertical_tabs",
"vertical_tabs_summary_mode",
"tab_configs",
"grouped_tabs",
"agent_harness",
"hoa_onboarding_flow",
"hoa_remote_control",
"codex_notifications",
"codex_plugin",
"trim_trailing_blank_lines",
"open_warp_new_settings_modes",
"skip_firebase_anonymous_user",
"settings_file",
"queue_slash_command",
"directory_tab_colors",
"git_credential_refresh",
"oz_handoff",
"handoff_local_cloud",
"run_agents_tool",
"orchestration_viewer_streamer",
"owner_orchestration_ancestor_streamer",
"pending_user_query_indicator",
"orchestration",
"orchestration_v2",
"lsp_as_a_tool",
"cross_repo_context",
"completions_v2",
"voice_input",
"drag_tabs_to_windows",
"plugin_host",
"file_and_diff_set_comments",
"local_ai",
"queue_slash_command",
"queued_prompts_v2",
"cloud_mode_input_v2",
"cloud_mode_setup_v2",
"handoff_cloud_cloud",
"remote_codebase_indexing",
"solo_user_byok",
"custom_inference_endpoints",
"custom_model_routers",
"supergrok",
"billing_and_usage_page_v2",
"remote_code_review",
"git_operations_in_code_review",
]
# Enable this feature to automatically perform heap profiling. NOTE: This will
# substantially slow down program execution.
@@ -630,7 +697,6 @@ interactive_conversation_management_view = []
default_waterfall_mode = []
enforce_revisions_to_cloud_objects = []
suggested_agent_mode_workflows = []
ssh_tmux_wrapper = []
ssh_enable_host_denylist_in_settings = []
extern_plist = []
fast_dev = ["skip_login"]
@@ -645,20 +711,28 @@ grep_tool = []
# For the most part, including GUI code in headless builds is fine; the main use case is
# for things that pull in external dependencies.
gui = ["voice_input"]
nld_classifier_v1 = ["input_classifier/nld_classifier_v1"]
nld_classifier_v2 = ["input_classifier/nld_classifier_v2"]
nld_classifier_v3 = ["input_classifier/nld_classifier_v3"]
nld_heuristic_v1 = ["input_classifier/nld_heuristic_v1"]
nld_heuristic_v2 = ["input_classifier/nld_heuristic_v2"]
msys2_shells = []
file_retrieval_tools = []
welcome_tab = []
get_started_tab = []
code_mode_chip = []
github_pr_prompt_chip = []
create_project_flow = []
agent_mode_evals = [
"integration_tests",
"galaxyui/defer_scene_build",
"full_source_code_embedding",
"cross_repo_context",
"cloud_object_models/agent_mode_evals",
"rust-embed/debug-embed",
"galaxyui/log_named_telemetry_events",
"galaxy_logging/agent_mode_evals",
"warp_multi_agent_client/agent_mode_evals",
"galaxy_server_client/agent_mode_evals",
]
ambient_agents_command_line = []
ambient_agents_image_upload = []
@@ -690,6 +764,7 @@ list_skills = []
# include dependencies that should only exist in such environments.
local_tty = []
local_computer_use = []
local_claude_codex_child_harnesses = []
# This feature is enabled in build.rs when compiling for platforms which
# have APIs for interacting with a local filesystem. It can be used to
# conditionally include dependencies that should only exist in such
@@ -729,6 +804,8 @@ predict_am_queries = []
quake_mode = []
record_app_active_events = []
recording_mode = []
remote_codebase_indexing = ["full_source_code_embedding"]
# This feature should only be enabled when building a release bundle with
# the bundle script, and can be used to conditionally enable functionality
# accordingly.
@@ -740,19 +817,18 @@ richtext_multiselect = []
runtime_feature_flags = []
selectable_prompt = []
settings_file = []
settings_import = []
sequential_storage = []
session_sharing = []
session_sharing_acls = []
skip_login = []
bedrock_smoke_test = ["skip_login"]
skip_login = ["galaxy_server_client/skip_login"]
# This feature should only be enabled when building a self-contained binary with the bundle script.
standalone = []
standalone = ["warp_assets/standalone"]
prompt_suggestions_via_maa = []
voice_input = ["dep:voice_input"]
system_theme = []
tab_close_button_on_left = []
team_features_override = []
test-util = ["cloud_object_client/test-util", "warp_server_auth/test-util"]
team_workflows = ["team_features_override"]
toggle_bootstrap_block = []
# Feature enabled only when app is compiled for integration tests.
@@ -761,6 +837,7 @@ integration_tests = [
"galaxyui/integration_tests",
"galaxyui/test-util",
"galaxy_core/integration_tests",
"galaxy_server_client/integration_tests",
]
traces = []
viewing_shared_sessions = []
@@ -770,14 +847,11 @@ alacritty_settings_import = []
shared_with_me = []
ai_rules = []
am_workflows = []
less_horizontal_terminal_padding = []
shell_selector = []
shared_session_long_running_commands = []
block_toolbelt_save_as_workflow = []
blocklist_markdown_table_rendering = []
blocklist_markdown_images = []
minimalist_ui = []
remove_alt_screen_padding = []
loginless_conversion = []
external_agent_mode_context = []
avatar_in_tab_bar = []
@@ -799,8 +873,6 @@ use_tantivy_search = []
agent_management_popup = []
simulate_github_unauthed = []
reload_stale_conversation_files = []
nld_fasttext_model = ["input_classifier/fasttext"]
nld_onnx_model = ["input_classifier/onnx_candle"]
shared_block_title_generation = []
retry_truncated_code_responses = []
usage_based_pricing = []
@@ -818,7 +890,6 @@ expand_edit_to_pane = []
fallback_model_load_output_messaging = []
profiles_design_revamp = []
search_codebase_ui = []
changed_lines_only_apply_diff_result = []
linked_code_blocks = []
tabbed_editor_view = []
selection_as_context = []
@@ -829,16 +900,15 @@ pr_comments_v2 = []
pr_comments_skill = []
conversation_artifacts = []
conversations_as_context = []
conversation_api = []
sync_ambient_plans = []
projects = []
vim_code_editor = []
allow_opening_file_links_using_editor_env = []
nld_improvements = ["nld_onnx_model"]
local_ai = ["dep:local_inference"]
local_ai_metal = ["local_ai", "local_inference/metal"]
undo_closed_panes = []
revert_diff_hunk = []
code_review_save_changes = []
remote_code_review = []
file_tree = []
code_launch_modal = []
api_key_authentication = []
@@ -853,6 +923,7 @@ inline_code_review = []
integration_command = []
artifact_command = []
cloud_environments = []
cloud_runners = []
create_environment_slash_command = []
summarize_conversation_command = []
mcp_grouped_server_context = []
@@ -860,6 +931,7 @@ web_search_ui = []
web_fetch_ui = []
fork_from_command = []
context_window_usage_v2 = []
context_window_usage_breakdown = []
global_search = []
file_and_diff_set_comments = []
revert_to_checkpoints = []
@@ -874,6 +946,7 @@ cloud_conversations = []
agent_view_prompt_chip = ["agent_view"]
ambient_agents_rtc = []
team_api_keys = []
named_agents = []
classic_completions = []
cloud_mode = []
cloud_mode_from_local_session = []
@@ -886,19 +959,24 @@ summarization_via_message_replacement = []
lsp_as_a_tool = []
inline_model_selector = ["agent_view"]
inline_profile_selector = ["agent_view"]
restore_prompt_on_inline_model_selector_search = []
oz_platform_skills = []
oz_identity_federation = []
oz_launch_modal = []
open_warp_launch_modal = []
orchestration_launch_modal = []
new_tab_styling = []
file_based_mcp = []
skill_arguments = []
active_conversation_requires_interaction = []
incremental_auto_reload = []
orchestration = []
orchestration_v2 = ["orchestration"]
run_agents_tool = []
orchestration_viewer_streamer = []
owner_orchestration_ancestor_streamer = []
wait_for_events_parent_registration = []
pending_user_query_indicator = []
queue_slash_command = []
queued_prompts_v2 = ["queue_slash_command"]
inline_menu_headers = []
directory_tab_colors = []
open_warp_new_settings_modes = []
@@ -906,21 +984,33 @@ hoa_code_review = []
vertical_tabs = []
vertical_tabs_summary_mode = []
tab_configs = []
grouped_tabs = []
pinned_tabs = []
warp_control_cli = []
agent_harness = []
oz_handoff = []
handoff_local_cloud = []
hoa_notifications = []
open_code_notifications = []
transfer_control_tool = []
skip_firebase_anonymous_user = []
custom_inference_endpoints = []
custom_model_routers = []
solo_user_byok = []
billing_and_usage_page_v2 = []
gpt_configurable_context_window = []
configurable_toolbar = []
warpify_footer = []
hoa_onboarding_flow = []
git_operations_in_code_review = []
hoa_remote_control = []
codex_notifications = []
codex_plugin = []
cloud_mode_setup_v2 = ["cloud_mode"]
cloud_mode_input_v2 = ["cloud_mode"]
handoff_cloud_cloud = ["cloud_mode_setup_v2"]
git_credential_refresh = []
prompt_cache_expiry_warning = []
[package.metadata.bundle.bin.galaxy-oss]
category = "public.app-category.developer-tools"
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

+105 -27
View File
@@ -12,8 +12,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# Appended to $DCS_START to signal that the following message is JSON-encoded.
DCS_JSON_MARKER="d"
# Byte used to signal the end of a DCS.
DCS_END="$(printf '\x9c')"
# Byte sequence used to signal the end of a DCS (7-bit ST: ESC \).
DCS_END="$(printf '\x1b\x5c')"
OSC_START="$(printf '\e]9278;')"
@@ -280,10 +280,11 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Preexec"
warp_send_hook_kv_pair "command" "$BASH_COMMAND"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
local truncated_command=$(warp_escape_json "$BASH_COMMAND")
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$truncated_command\"}}"
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$truncated_command\", \"session_id\": $WARP_SESSION_ID}}"
fi
warp_maybe_send_reset_grid_osc
@@ -325,7 +326,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# If the array is not empty, kill the ongoing pids.
if [[ ! -z $pids ]]; then
# Surpress stderr output; kill writes to stderr if any of the given
# Suppress stderr output; kill writes to stderr if any of the given
# PIDS are not running (which might rarely be the case due to race
# conditions in checking which PIDS to cancel and this kill command.
kill -9 $pids >/dev/null 2>/dev/null
@@ -362,7 +363,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
fi
# Note that in older versions of bash (the one builtin with MacOS) the `~` character is just that,
# howerver, when used within `""` (double quotes) on a newer (homebrew) bash version,
# however, when used within `""` (double quotes) on a newer (homebrew) bash version,
# it automatically EXPANDS to the actual value of $HOME and needs to be escaped to give a proper
# tilde character. So instead, we have it as a separate variable that uses `''` (single quote)
# to avoid expanding, and use it later within the new bash term title. This way both old and
@@ -434,13 +435,15 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# executed within this block instead of the actual last
# command that was run.
local exit_code=$?
local next_block_id="precmd-$WARP_SESSION_ID-$((block_id++))"
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "CommandFinished"
warp_send_hook_kv_pair "exit_code" "$exit_code"
warp_send_hook_kv_pair "next_block_id" "precmd-$WARP_SESSION_ID-$((block_id++))"
warp_send_hook_kv_pair "next_block_id" "$next_block_id"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$((block_id++))\"}}"
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
fi
warp_maybe_send_reset_grid_osc
@@ -463,6 +466,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
unset _WARP_GENERATOR_COMMAND
warp_send_json_message "{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"\",
\"ps1\": \"\",
\"git_head\": \"\",
@@ -574,38 +579,57 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
escaped_conda_env=$(warp_escape_json "$CONDA_DEFAULT_ENV")
fi
# Get Node.js version if node is available and we're in a Node.js project
if command -v node > /dev/null 2>&1 && [ "$WARP_IN_MSYS2" = false ]; then
# Get the Node.js version, but only when the Node.js Version chip is enabled.
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
# every prompt when the chip is not shown.
if [[ "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0" ]] && command -v node > /dev/null 2>&1 && [ "$WARP_IN_MSYS2" = false ]; then
# Check for package.json in current directory and parent directories
local current_dir="$PWD"
local found_package_json=false
local package_json_dir=""
while [[ "$current_dir" != "/" ]]; do
while [[ -n "$current_dir" ]]; do
if [[ -f "$current_dir/package.json" ]]; then
found_package_json=true
package_json_dir="$current_dir"
break
fi
current_dir=$(dirname "$current_dir")
[[ "$current_dir" == "/" ]] && break
# Strip the last path segment without spawning `dirname`.
current_dir="${current_dir%/*}"
[[ -z "$current_dir" ]] && current_dir="/"
done
# Only show node version if package.json is within a git repository
if [[ "$found_package_json" = true ]]; then
local git_dir="$package_json_dir"
local in_git_repo=false
while [[ "$git_dir" != "/" ]]; do
while [[ -n "$git_dir" ]]; do
if [[ -d "$git_dir/.git" ]]; then
in_git_repo=true
break
fi
git_dir=$(dirname "$git_dir")
[[ "$git_dir" == "/" ]] && break
git_dir="${git_dir%/*}"
[[ -z "$git_dir" ]] && git_dir="/"
done
if [[ "$in_git_repo" = true ]]; then
# Cache the resolved version keyed on PWD + PATH so we only spawn
# `node --version` when the directory or PATH changes (PATH changes
# on `nvm use`). The cache vars are global (no `local`) so they
# persist across precmd invocations.
local node_cache_key="$PWD:$PATH"
if [[ "$node_cache_key" == "$_WARP_NODE_VERSION_CACHE_KEY" ]]; then
escaped_node_version="$_WARP_NODE_VERSION_CACHE_VALUE"
else
local node_version=$(node --version 2>/dev/null)
if [[ -n "$node_version" ]]; then
escaped_node_version=$(warp_escape_json "$node_version")
fi
_WARP_NODE_VERSION_CACHE_KEY="$node_cache_key"
_WARP_NODE_VERSION_CACHE_VALUE="$escaped_node_version"
fi
fi
fi
fi
@@ -649,6 +673,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# We send the escaped PS1, if we are in active Warp prompt mode, for prompt preview rendering (note the shell's PS1 is unset in this case).
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Precmd"
warp_send_hook_kv_pair "exit_code" "$exit_code"
warp_send_hook_kv_pair "next_block_id" "$next_block_id"
warp_send_hook_kv_pair "pwd" "$PWD"
warp_send_hook_kv_pair_escaped "ps1" "$deref_ps1"
warp_send_hook_kv_pair "ps1_is_encoded" "false"
@@ -662,6 +688,8 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
warp_send_hook_via_kv_pairs_end
else
local escaped_json="{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"$escaped_pwd\",
\"ps1\": \"$escaped_ps1\",
\"honor_ps1\": $honor_ps1,
@@ -761,10 +789,11 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "InputBuffer"
warp_send_hook_kv_pair "buffer" "$READLINE_LINE"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
local escaped_input="$(warp_escape_json "$READLINE_LINE")"
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\" } }"
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
fi
# This prevents bash from re-printing typeahead after we've removed it.
READLINE_LINE=""
@@ -888,9 +917,10 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
function clear() {
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "Clear"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {}}"
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
fi
}
@@ -899,9 +929,10 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
if [ "$WARP_IN_MSYS2" = true ]; then
warp_send_hook_via_kv_pairs_start "FinishUpdate"
warp_send_hook_kv_pair "update_id" "$update_id"
warp_send_hook_kv_pair "session_id" "$WARP_SESSION_ID"
warp_send_hook_via_kv_pairs_end
else
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\"} }"
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID} }"
fi
}
@@ -972,11 +1003,51 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
function warp_ssh_helper() {
init_shell_bash=$(init_shell_hook "bash")
init_shell_zsh=$(init_shell_hook "zsh")
local remote_session_id=$(command -p od -An -N8 -tu8 /dev/urandom 2>/dev/null | command -p tr -d ' \n')
if [[ -z "$remote_session_id" || "$remote_session_id" == "0" ]]; then
# If we cannot generate a non-zero random token, run plain SSH instead.
command ssh "${@:1}"
return
fi
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
# and use printf to convert back to plaintext
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
# Optionally attach to an existing ControlMaster the user already
# runs for this destination instead of creating our own. Resolve
# the user's configured ControlPath with `ssh -G` (which expands
# tokens like %h/%p/%r/%C into a literal path), then verify the
# master is alive with `ssh -O check`. Both probes are local-only
# commands. On any failure we fall back to creating a Warp-owned
# master, preserving the existing behavior.
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
local control_master_mode="yes"
local external_control_master="false"
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
case "$user_control_path" in
"" | none)
# No ControlPath configured for this destination.
;;
*[![:alnum:]._/~@:+,-]*)
# The resolved path contains characters we cannot safely
# embed in the SSH hook JSON below (e.g. an unexpanded %
# token, quotes, or whitespace); fall back to a
# Warp-owned master.
;;
*)
if command ssh -O check -o ControlPath="$user_control_path" "${@:1}" >/dev/null 2>&1; then
# A live master exists: multiplex through it and let
# the client know Warp does not own it.
control_path="$user_control_path"
control_master_mode="no"
external_control_master="true"
fi
;;
esac
fi
# Keep remote commands up-to-date with shell.rs & bash.sh.
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
@@ -985,7 +1056,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
-t "${@:1}" \
"
export TERM_PROGRAM='WarpTerminal'
@@ -997,7 +1068,7 @@ test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSI
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
@@ -1032,7 +1103,7 @@ case "'${SHELL##*/}'" in
command -p stty raw
HISTCONTROL=ignorespace
HISTIGNORE=" *"
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID='$remote_session_id'
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
@@ -1063,7 +1134,7 @@ esac
function ssh() {
if is_interactive_ssh_session "$@"; then
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}"
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
# If the SSH wrapper is not enabled for this session, don't use it.
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
@@ -1133,17 +1204,22 @@ esac
rcfiles_end_time="$(LC_ALL="C"; echo $EPOCHREALTIME)"
fi
# Unset HISTFILESIZE if the user rcfiles didn't change it away from our
# very large sentinel value. We need to set the initial value of HISTSIZE
# to ensure that the user's history file doesn't get truncated when we spawn
# the shell, but once bootstrap has completes, we want the value to be what
# it would have been if we hadn't set an initial value.
# Unset HISTFILESIZE and HISTSIZE if the user rcfiles didn't change them
# away from our very large sentinel values. We need to set the initial
# values to ensure that the user's history file and in-memory history list
# don't get truncated when we spawn the shell, but once bootstrap has
# completed, we want the values to be what they would have been if we hadn't
# set initial values.
#
# For more context, see: https://github.com/warpdotdev/Warp/issues/1262
if [[ $HISTFILESIZE == $WARP_INITIAL_HISTFILESIZE ]]; then
unset HISTFILESIZE
fi
unset WARP_INITIAL_HISTFILESIZE
if [[ $HISTSIZE == $WARP_INITIAL_HISTSIZE ]]; then
unset HISTSIZE
fi
unset WARP_INITIAL_HISTSIZE
# Save the value of HISTCONTROL as it existed just after reading the user's
# rcfiles.
@@ -1318,6 +1394,7 @@ esac
warp_send_hook_kv_pair "user" "$_user"
warp_send_hook_kv_pair "hostname" "$_hostname"
warp_send_hook_kv_pair "path" "$PATH"
warp_send_hook_kv_pair "cdpath" "$CDPATH"
warp_send_hook_kv_pair_escaped "env_var_names" "$env_var_names"
warp_send_hook_kv_pair "abbreviations" ""
warp_send_hook_kv_pair_escaped "aliases" "$aliases"
@@ -1338,7 +1415,8 @@ esac
else
local escaped_editor="$(warp_escape_json "$EDITOR")"
local escaped_shell_path="$(warp_escape_json "$BASH")"
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
local escaped_cdpath="$(warp_escape_json "$CDPATH")"
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"home_dir\": \"$HOME\", \"user\":\"$_user\", \"host\":\"$_hostname\", \"path\": \"$escaped_path\", \"cdpath\": \"$escaped_cdpath\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$escaped_env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$escaped_function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$BASH_VERSION\", \"shell_options\": \"$escaped_shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
warp_send_json_message "$escaped_json"
fi
}
@@ -3,7 +3,7 @@
command -p stty raw
HISTCONTROL=ignorespace
HISTIGNORE=" *"
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID=@@WARP_SESSION_ID@@
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
@@ -11,5 +11,5 @@ if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false;
if [ "$WARP_IN_MSYS2" = true ]; then _msg="\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;bash\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); fi
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
# We send the InitShell hook via OSCs when on Windows and via DCSs otherwise.
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then if [ "$WARP_IN_MSYS2" = true ]; then printf "$_msg"; else printf '\e]9278;d;%s\x07' "$_msg"; fi; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then if [ "$WARP_IN_MSYS2" = true ]; then printf "$_msg"; else printf '\e]9278;d;%s\x07' "$_msg"; fi; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
unset _hostname _user _msg
@@ -5,11 +5,11 @@ unset PROMPT_COMMAND
HISTCONTROL=ignorespace
HISTIGNORE=" *"
WARP_IS_SUBSHELL=1
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID=@@WARP_SESSION_ID@@
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")
if [[ "$OS" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
unset _hostname _user _msg
@@ -1 +1 @@
echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> %
echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
+90 -22
View File
@@ -24,7 +24,7 @@ set -g DCS_START \u1b\u50\u24
# _warp_run_generator_command_internal, which instead end in 'e' (0x65).
set -g DCS_JSON_MARKER 'd'
set -g DCS_END \u9c
set -g DCS_END \x1b\x5c
set -g OSC_START (printf '\e]9278;')
@@ -159,13 +159,13 @@ end
# Run before a command is executed.
function warp_preexec --on-event fish_preexec
set -l command (warp_escape_json "$argv")
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$command\"}}"
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$command\", \"session_id\": $WARP_SESSION_ID}}"
warp_maybe_send_reset_grid_osc
# If this preexec is called for user command, kill ongoing generator command jobs.
if test (! string match -q "warp_run_generator_command*" $argv[1])
for pid in $_warp_generator_pids
# Surpress stderr output; kill writes to stderr if any of the given
# Suppress stderr output; kill writes to stderr if any of the given
# PIDS are not running (which might rarely be the case due to race
# conditions in checking which PIDS to cancel and this kill command.
kill -9 $pids >/dev/null 2>/dev/null
@@ -278,7 +278,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
set exit_code 1
end
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$block_id\"}}"
set -l next_block_id "precmd-$WARP_SESSION_ID-$block_id"
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
warp_maybe_send_reset_grid_osc
set block_id (math $block_id + 1)
@@ -286,6 +287,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
if ! test -z $_WARP_GENERATOR_COMMAND
set -e _WARP_GENERATOR_COMMAND
set -l escaped_json "{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"\",
\"ps1\": \"\",
\"git_head\": \"\",
@@ -347,38 +350,64 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
set escaped_conda_env (warp_escape_json "$CONDA_DEFAULT_ENV")
end
# Get Node.js version if node is available and we're in a Node.js project
if command -v node > /dev/null 2>&1
# Get the Node.js version, but only when the Node.js Version chip is enabled.
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
# every prompt when the chip is not shown.
if test "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0"; and command -v node > /dev/null 2>&1
# Check for package.json in current directory and parent directories
set current_dir (pwd)
set found_package_json false
set package_json_dir ""
while test "$current_dir" != "/"
while test -n "$current_dir"
if test -f "$current_dir/package.json"
set found_package_json true
set package_json_dir "$current_dir"
break
end
set current_dir (dirname "$current_dir")
if test "$current_dir" = "/"
break
end
# Strip the last path segment without spawning `dirname`.
set current_dir (string replace -r '/[^/]*$' '' -- "$current_dir")
if test -z "$current_dir"
set current_dir "/"
end
end
# Only show node version if package.json is within a git repository
if test "$found_package_json" = true
set git_dir "$package_json_dir"
set in_git_repo false
while test "$git_dir" != "/"
while test -n "$git_dir"
if test -d "$git_dir/.git"
set in_git_repo true
break
end
set git_dir (dirname "$git_dir")
if test "$git_dir" = "/"
break
end
set git_dir (string replace -r '/[^/]*$' '' -- "$git_dir")
if test -z "$git_dir"
set git_dir "/"
end
end
if test "$in_git_repo" = true
set node_version (node --version 2>/dev/null)
# Cache the resolved version keyed on PWD + PATH so we only spawn
# `node --version` when the directory or PATH changes (PATH changes
# on `nvm use`). Use global cache vars so they persist across calls.
set -l node_cache_key "$PWD:$PATH"
if test "$node_cache_key" = "$_WARP_NODE_VERSION_CACHE_KEY"
set escaped_node_version "$_WARP_NODE_VERSION_CACHE_VALUE"
else
set -l node_version (node --version 2>/dev/null)
if test -n "$node_version"
set escaped_node_version (warp_escape_json "$node_version")
end
set -g _WARP_NODE_VERSION_CACHE_KEY "$node_cache_key"
set -g _WARP_NODE_VERSION_CACHE_VALUE "$escaped_node_version"
end
end
end
end
@@ -401,7 +430,7 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
warp_update_prompt_vars
# This is used solely for prompt previews, when we're using prompt markers with combined grid.
# We need to use this since fish does not have a way to ignore printable characters for cursor
# positioning (unlike zsh/bash), so we need a separate mechansim to send the prompt to Warp
# positioning (unlike zsh/bash), so we need a separate mechanism to send the prompt to Warp
# in the case of Warp prompt (for previewing the PS1). We send an escaped version of the raw prompt
# bytes via a hex string (in a JSON payload) to Warp.
# Note that we are CALLING the `warp_original_fish_prompt` function on the next line and assigning the
@@ -414,6 +443,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
if test "$WARP_HONOR_PS1" = "1"
# Don't send lprompt or rprompt in this case - we'll use prompt markers for both directly!
set escaped_json "{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"$escaped_pwd\",
\"ps1\": \"\",
\"rprompt\": \"\",
@@ -427,6 +458,8 @@ function warp_precmd --on-event fish_prompt --on-event fish_posterror
else
# We send an lprompt to use for prompt preview purposes only (we still use prompt markers for active prompts).
set escaped_json "{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"$escaped_pwd\",
\"ps1\": \"$escaped_prompt\",
\"rprompt\": \"\",
@@ -513,7 +546,7 @@ function warp_bootstrapped
# part of its builtins (e.g. "for", "while", etc.).
set -l escaped_editor (warp_escape_json "$EDITOR")
set -l escaped_shell_path (warp_escape_json (status fish-path))
set -l escaped_json "{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"shell\": \"fish\", \"home_dir\": \"$HOME\", \"path\": \"$PATH\", \"editor\": \"$escaped_editor\", \"abbreviations\": \"$escaped_abbr\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"env_var_names\": \"$env_var_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"\", \"shell_version\": \"$FISH_VERSION\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
set -l escaped_json "{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"fish\", \"home_dir\": \"$HOME\", \"path\": \"$PATH\", \"editor\": \"$escaped_editor\", \"abbreviations\": \"$escaped_abbr\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"env_var_names\": \"$env_var_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"\", \"shell_version\": \"$FISH_VERSION\", \"vi_mode_enabled\": \"$vi_mode_enabled\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"$WSL_DISTRO_NAME\", \"shell_path\": \"$escaped_shell_path\"}}"
warp_send_json_message $escaped_json
end
@@ -529,18 +562,18 @@ end
# Binding to ESC-1 caused bootstrap failures with vi keybindings.
function warp_report_input
set -l escaped_input (warp_escape_json (commandline))
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\" } }"
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
# This prevents fish from rendering typeahead as background output once we've collected it.
commandline ''
end
function clear
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {}}"
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
end
function warp_finish_update
set -l update_id "$argv[1]"
warp_send_json_message "{\"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\"}}"
warp_send_json_message "{\"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID}}"
end
@@ -598,10 +631,45 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
function warp_ssh_helper
set -l init_shell_zsh (warp_init_shell "zsh")
set -l init_shell_bash (warp_init_shell "bash")
set -l remote_session_id (command od -An -N8 -tu8 /dev/urandom 2>/dev/null | command tr -d ' \n')
if test -z "$remote_session_id"; or test "$remote_session_id" = "0"
# If we cannot generate a non-zero random token, run plain SSH instead.
command ssh $argv
return
end
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
# and use printf to convert back to plaintext
set -l zsh_env_script (printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); printf '"'"'\x1b\x50\x24\x64%s\x9c'"'"' $_msg; unset _hostname _user _msg' | command od -An -v -tx1 | command tr -d ' \n')
set -l zsh_env_script (printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; WARP_HONOR_PS1='$WARP_HONOR_PS1'; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n"); printf '"'"'\x1b\x50\x24\x64%s\x1b\x5c'"'"' $_msg; unset _hostname _user _msg' | command od -An -v -tx1 | command tr -d ' \n')
# Optionally attach to an existing ControlMaster the user already
# runs for this destination instead of creating our own. Resolve
# the user's configured ControlPath with `ssh -G` (which expands
# tokens like %h/%p/%r/%C into a literal path), then verify the
# master is alive with `ssh -O check`. Both probes are local-only
# commands. On any failure we fall back to creating a Warp-owned
# master, preserving the existing behavior.
set -l control_path "$SSH_SOCKET_DIR/$WARP_SESSION_ID"
set -l control_master_mode "yes"
set -l external_control_master "false"
if test "$WARP_SSH_REUSE_CONTROL_MASTER" = "1"
set -l user_control_path (command ssh -G $argv 2>/dev/null | command sed -n 's/^controlpath //p')
# Skip when no ControlPath is configured, and reject resolved
# paths containing characters we cannot safely embed in the SSH
# hook JSON below (e.g. an unexpanded % token, quotes, or
# whitespace); in those cases fall back to a Warp-owned master.
if test -n "$user_control_path"
and test "$user_control_path" != "none"
and string match --quiet --regex '^[A-Za-z0-9._/~@:+,-]+$' -- "$user_control_path"
if command ssh -O check -o ControlPath="$user_control_path" $argv >/dev/null 2>&1
# A live master exists: multiplex through it and let the
# client know Warp does not own it.
set control_path "$user_control_path"
set control_master_mode "no"
set external_control_master "true"
end
end
end
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
# escaped with "''" to avoid the local shell from expanding them before they're passed to the remote shell.
@@ -609,14 +677,14 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
-t $argv \
"
export TERM_PROGRAM='WarpTerminal'
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
@@ -651,14 +719,14 @@ bash)
stty raw
HISTCONTROL=ignorespace
HISTIGNORE=" *"
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID='$remote_session_id'
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || uname -n)
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"bash\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
if [[ "'$OS'" == Windows_NT ]]; then WARP_IN_MSYS2=true; else WARP_IN_MSYS2=false; fi
printf '\''"'\eP$d%s\x9c'"'\'' \""'$_msg'"\"'
printf '\''"'\x1b\x50\x24\x64%s\x1b\x5c'"'\'' \""'$_msg'"\"'
unset _hostname _user _msg
)
;;
@@ -683,7 +751,7 @@ esac
function ssh
if is_interactive_ssh_session $argv
warp_send_json_message '{"hook": "PreInteractiveSSHSession", "value": {}}'
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]
if test $WARP_SHELL_DEBUG_MODE
@@ -1,9 +1,9 @@
set -g WARP_SESSION_ID (random)
set -g WARP_SESSION_ID @@WARP_SESSION_ID@@
set _hostname (command -v hostname >/dev/null 2>&1 && command hostname 2>/dev/null || uname -n)
set _user (command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
set -g WARP_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false)
if test "$WARP_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"fish\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end
set WARP_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@
# We send the InitShell hook via OSCs when on Windows and via DCSs otherwise.
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; end
set -e _hostname _user _msg
@@ -1,8 +1,8 @@
set -g WARP_SESSION_ID (random)
set -g WARP_SESSION_ID @@WARP_SESSION_ID@@
set _hostname (command -v hostname >/dev/null 2>&1 && command hostname 2>/dev/null || uname -n)
set _user (command -v whoami >/dev/null 2>&1 && command whoami 2>/dev/null || echo $USER)
set -g WARP_IN_MSYS2 (test "$OS" = Windows_NT; and echo true; or echo false)
if test "$WARP_IN_MSYS2" = true; set _msg "\e]9278;k;A;InitShell\a\e]9278;k;B;session_id;$WARP_SESSION_ID\a\e]9278;k;B;shell;fish\a\e]9278;k;B;user;$_user\a\e]9278;k;B;hostname;$_hostname\a\e]9278;k;B;is_subshell;true\a\e]9278;k;C\a"; else; set _msg (printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"user\": \"%s\", \"hostname\": \"%s\", \"shell\": \"fish\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command od -An -v -tx1 | command tr -d " \n"); end
set WARP_USING_WINDOWS_CON_PTY @@USING_CON_PTY_BOOLEAN@@
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\e\x50\x24\x64%s\x9c' "$_msg"; end
if test "$WARP_USING_WINDOWS_CON_PTY" = true; if test "$WARP_IN_MSYS2" = true; printf "$_msg"; else; printf '\e]9278;d;%s\x07' "$_msg"; end; else; printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; end
set -e _hostname _user _msg
@@ -1 +1 @@
echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'"% }}\x9c'\'' ' >> %
echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
+55 -10
View File
@@ -142,9 +142,27 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
(Get-Variable | Select-Object -ExpandProperty Name) -join ' '
$aliasesRaw = Get-Command -CommandType Alias | Select-Object -ExpandProperty DisplayName
$aliases = $aliasesRaw -join [Environment]::NewLine
$functionNamesRaw = Get-Command -CommandType Function | Where-Object { -not $_.Name.StartsWith('Warp') } | Select-Object -ExpandProperty Name
# Only query modules that ship with PowerShell itself. This keeps bootstrap fast by
# avoiding Windows system modules and third-party modules from the Gallery. The rest are
# loaded asynchronously later.
$corePsModules = @(
'Microsoft.PowerShell.*', # All built-in PS modules (cross-platform)
'Microsoft.WSMan.*', # WS-Management (Windows PS)
'CimCmdlets', # CIM/WMI (Windows)
'PackageManagement', # Package management
'PowerShellGet', # Package get
'PSReadLine', # Line editor bundled with PS
'ThreadJob', # Thread jobs (PS 7)
'PSDiagnostics', # PS diagnostics
'PSDesiredStateConfiguration', # DSC (Windows PS)
'PSWorkflow', # Legacy workflow (Windows PS 5)
'PSWorkflowUtility' # Legacy workflow utility (Windows PS 5)
)
$functionNamesRaw = Get-Command -CommandType Function -Module $corePsModules |
Where-Object { -not $_.Name.StartsWith('Warp') } |
Select-Object -ExpandProperty Name
$functionNames = $functionNamesRaw -join [Environment]::NewLine
$builtinsRaw = Get-Command -CommandType Cmdlet | Select-Object -ExpandProperty Name
$builtinsRaw = Get-Command -CommandType Cmdlet -Module $corePsModules | Select-Object -ExpandProperty Name
$builtins = $builtinsRaw -join [Environment]::NewLine
$shellVersion = $PSVersionTable.PSVersion.ToString()
# PowerShell wasn't cross-platform until version 6. Anything before that is definitely on Windows.
@@ -200,6 +218,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$bootstrappedMsg = @{
hook = 'Bootstrapped'
value = @{
session_id = $global:_warpSessionId
histfile = $(Get-PSReadLineOption).HistorySavePath
shell = 'pwsh'
home_dir = "$HOME"
@@ -230,6 +249,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$preexecMsg = @{
hook = 'Preexec'
value = @{
session_id = $global:_warpSessionId
command = $command
}
}
@@ -243,7 +263,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
}
# Clean up any completed warp jobs so they do not show up on the user's 'get-job'
# comands
# commands
Warp-Clean-CompletedThread
# Remove any instance of the 'Warp-Run-GeneratorCommand' call from the user's history
@@ -254,6 +274,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$updateMsg = @{
hook = 'FinishUpdate'
value = @{
session_id = $global:_warpSessionId
update_id = $updateId
}
}
@@ -288,7 +309,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
# 2. We need to make sure that we are calling the Application git, and not
# an alias or cmdlet named Git
#
# NOTE: Inlining this call in the function has a weird side effect of outputing
# NOTE: Inlining this call in the function has a weird side effect of outputting
# an escape sequence '^[i'. Since it made it more convenient to have a wrapper
# function anyway, I have not investigated this, but in case someone is working
# on this in the future, beware attempting to inline this function.
@@ -344,6 +365,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$inputBufferMsg = @{
hook = 'InputBuffer'
value = @{
session_id = $global:_warpSessionId
buffer = $inputBuffer
}
}
@@ -386,7 +408,7 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
Warp-Disable-PSPrediction
}
# Force use of the Inline PredictionViewStyle. The ListView style can occassionally cause some
# Force use of the Inline PredictionViewStyle. The ListView style can occasionally cause some
# flickering when using Warp and it doesn't matter what the value of this setting is because
# Warp has its own input editor.
function Warp-Disable-PSPrediction {
@@ -434,11 +456,13 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$HOST.UI.RawUI.WindowTitle = $newTitle
$blockId = $script:nextBlockId++
$nextBlockId = "precmd-${global:_warpSessionId}-$blockId"
$commandFinishedMsg = @{
hook = 'CommandFinished'
value = @{
session_id = $global:_warpSessionId
exit_code = $exitCode
next_block_id = "precmd-${global:_warpSessionId}-$blockId"
next_block_id = $nextBlockId
}
}
Warp-Send-JsonMessage $commandFinishedMsg
@@ -457,6 +481,8 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$precmdMsg = @{
hook = 'Precmd'
value = @{
exit_code = $exitCode
next_block_id = $nextBlockId
pwd = ''
ps1 = ''
git_head = ''
@@ -493,10 +519,19 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$kubeConfig = $env:KUBECONFIG
}
# Compute Node.js version if node is available and we're in a Node project within a Git repo.
$hasNodeCommand = Get-Command -CommandType Application node 2>$null
# Compute the Node.js version only when the Node.js Version chip is enabled
# (WARP_PROMPT_NODE_VERSION_ENABLED is '0' when the chip is not shown; default
# enabled when unset) and node is available. Cache the result keyed on the
# current location + PATH so we only spawn node when the directory or PATH
# changes (PATH changes on version-manager switches like `nvm use`).
$nodeChipEnabled = "$env:WARP_PROMPT_NODE_VERSION_ENABLED" -ne '0'
$hasNodeCommand = if ($nodeChipEnabled) { Get-Command -CommandType Application node 2>$null } else { $null }
if ($hasNodeCommand) {
try {
$nodeCacheKey = "$((Get-Location).Path)|$env:PATH"
if ($nodeCacheKey -eq $script:warpNodeVersionCacheKey) {
$nodeVersion = $script:warpNodeVersionCacheValue
} else {
# Walk up from the current directory to find a package.json
$dir = Get-Item -LiteralPath (Get-Location).Path
$foundPackageJson = $false
@@ -527,6 +562,10 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$nodeVersion = Warp-TryGet-NodeVersion
}
}
$script:warpNodeVersionCacheKey = $nodeCacheKey
$script:warpNodeVersionCacheValue = $nodeVersion
}
} catch {
# Log at verbose level so the catch block is not empty and diagnostics are available when needed.
Write-Verbose "Failed to compute Node.js context: $($_.Exception.Message)"
@@ -558,6 +597,8 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
$precmdMsg = @{
hook = 'Precmd'
value = @{
exit_code = $exitCode
next_block_id = $nextBlockId
pwd = (Get-Location).Path
# TODO(PLAT-687) - honor the PS1
ps1 = ''
@@ -896,7 +937,9 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
function Clear-Host() {
$inputBufferMsg = @{
hook = 'Clear'
value = @{}
value = @{
session_id = $global:_warpSessionId
}
}
Warp-Send-JsonMessage $inputBufferMsg
}
@@ -904,7 +947,9 @@ $null = New-Module -Name Warp-Module -ScriptBlock {
function clear() {
$inputBufferMsg = @{
hook = 'Clear'
value = @{}
value = @{
session_id = $global:_warpSessionId
}
}
Warp-Send-JsonMessage $inputBufferMsg
}
@@ -21,9 +21,7 @@ function prompt {
# Reset the prompt back to the default to avoid infinite loops if sourcing the bootstrap script has an error.
$function:global:prompt = $global:_warpOriginalPrompt
$username = [Environment]::UserName
$epoch = [int](New-TimeSpan -Start ([DateTime]::new(1970, 1, 1, 0, 0, 0, 0)) -End ([DateTime]::UtcNow)).TotalSeconds
$random = Get-Random -Maximum 32768
$global:_warpSessionId = [int64]"$epoch$random"
$global:_warpSessionId = [uint64]@@WARP_SESSION_ID@@
$msg = ConvertTo-Json -Compress -InputObject @{ hook = 'InitShell'; value = @{ session_id = $_warpSessionId; shell = 'pwsh'; user = $username; hostname = [System.Net.Dns]::GetHostName() } }
$encodedMsg = [BitConverter]::ToString([System.Text.Encoding]::UTF8.GetBytes($msg)).Replace('-', '')
$oscStart = "$([char]0x1b)]9278;"
@@ -5,4 +5,4 @@
# Thankfully, we don't need curly braces around the first expression, so we can put the fish check
# first and it early exits. This runs correctly in sh, bash, zsh, and fish.
# Replace `HOOK_NAME` with the appropriate hook name.
[ -z $WARP_BOOTSTRAPPED ] && printf "\\e]9278;f;{\"hook\": \"HOOK_NAME\", \"value\": { \"shell\": \"%s\", \"uname\": \"%s\" }}\\a" $([ $FISH_VERSION ] && echo "fish" || { echo $0 | command -p grep -q zsh && echo "zsh"; } || { echo $0 | command -p grep -q bash && echo "bash"; } || echo "unknown") $(uname)
[ -z $WARP_BOOTSTRAPPED ] && printf "\\e]9278;f;{\"hook\": \"HOOK_NAME\", \"value\": { \"shell\": \"%s\", \"uname\": \"%s\", \"session_id\": @@WARP_SESSION_ID@@ }}\\a" $([ $FISH_VERSION ] && echo "fish" || { echo $0 | command -p grep -q zsh && echo "zsh"; } || { echo $0 | command -p grep -q bash && echo "bash"; } || echo "unknown") $(uname)
+140 -26
View File
@@ -21,8 +21,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# Appended to $DCS_START to signal that the following message is JSON-encoded.
DCS_JSON_MARKER="d"
# Byte used to signal the end of a DCS.
DCS_END="$(printf '\x9c')"
# Byte sequence used to signal the end of a DCS (7-bit ST: ESC \).
DCS_END="$(printf '\x1b\x5c')"
# OSC used to mark the start of in-band command output.
#
@@ -168,14 +168,14 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
local -a command
command=("${@:2}")
# Declare raw_output prior to actually assigning it, because `local` is a command itself, which
# inteferes with capturing the exit code via $? (it overwrites $? with the 0, because the
# interferes with capturing the exit code via $? (it overwrites $? with the 0, because the
# 'local' command always succeeds).
local raw_output
# Command substitution only captures stdout, so redirect stderr to stdout.
# Note that we use `eval` here to actually execute the command, because some shell syntax
# that may be used in the command might not be valid in a command substitution (e.g. the
# '$(<command>)' syntax).
# Also note that zsh variables can contain null charcters, so this doesn't require any special
# Also note that zsh variables can contain null characters, so this doesn't require any special
# handling.
raw_output=$(eval "$command" 2>&1)
local exit_code=$?
@@ -253,7 +253,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# invoke any external commands in here.
warp_preexec () {
local warp_escaped_command="$(warp_escape_json $1)"
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$warp_escaped_command\"}}"
warp_send_json_message "{\"hook\": \"Preexec\", \"value\": {\"command\": \"$warp_escaped_command\", \"session_id\": $WARP_SESSION_ID}}"
warp_maybe_send_reset_grid_osc
# If this preexec is called for user command, kill ongoing generator command jobs and clean
@@ -277,7 +277,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# If the array is not empty, kill the ongoing pids.
if [[ ! -z $pids ]]; then
# Surpress stderr output; kill writes to stderr if any of the given
# Suppress stderr output; kill writes to stderr if any of the given
# PIDS are not running (which might rarely be the case due to race
# conditions in checking which PIDS to cancel and this kill command.
(kill -9 $pids 2>&1) >/dev/null
@@ -306,8 +306,9 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# previously run user command (as opposed to any of the commands executed
# in this function below).
local exit_code=$?
local next_block_id="precmd-$WARP_SESSION_ID-$((block_id++))"
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"precmd-$WARP_SESSION_ID-$((block_id++))\"}}"
warp_send_json_message "{\"hook\": \"CommandFinished\", \"value\": {\"exit_code\": $exit_code, \"next_block_id\": \"$next_block_id\", \"session_id\": $WARP_SESSION_ID}}"
warp_maybe_send_reset_grid_osc
# If this is being called for a generator command, short circuit and send an unpopulated
@@ -320,6 +321,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
_WARP_GENERATOR_COMMAND=""
warp_send_json_message "{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"\",
\"ps1\": \"\",
\"git_head\": \"\",
@@ -394,38 +397,57 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
escaped_conda_env=$(warp_escape_json $CONDA_DEFAULT_ENV)
fi
# Get Node.js version if node is available and we're in a Node.js project
if command -v node > /dev/null 2>&1; then
# Get the Node.js version, but only when the Node.js Version chip is enabled.
# Warp sets WARP_PROMPT_NODE_VERSION_ENABLED to "0" when the chip is not in the
# prompt (defaulting to enabled when unset), so we avoid spawning `node` on
# every prompt when the chip is not shown.
if [[ "$WARP_PROMPT_NODE_VERSION_ENABLED" != "0" ]] && command -v node > /dev/null 2>&1; then
# Check for package.json in current directory and parent directories
local current_dir="$PWD"
local found_package_json=false
local package_json_dir=""
while [[ "$current_dir" != "/" ]]; do
while [[ -n "$current_dir" ]]; do
if [[ -f "$current_dir/package.json" ]]; then
found_package_json=true
package_json_dir="$current_dir"
break
fi
current_dir=$(dirname "$current_dir")
[[ "$current_dir" == "/" ]] && break
# Strip the last path segment without spawning `dirname`.
current_dir="${current_dir%/*}"
[[ -z "$current_dir" ]] && current_dir="/"
done
# Only show node version if package.json is within a git repository
if [[ "$found_package_json" = true ]]; then
local git_dir="$package_json_dir"
local in_git_repo=false
while [[ "$git_dir" != "/" ]]; do
while [[ -n "$git_dir" ]]; do
if [[ -d "$git_dir/.git" ]]; then
in_git_repo=true
break
fi
git_dir=$(dirname "$git_dir")
[[ "$git_dir" == "/" ]] && break
git_dir="${git_dir%/*}"
[[ -z "$git_dir" ]] && git_dir="/"
done
if [[ "$in_git_repo" = true ]]; then
# Cache the resolved version keyed on PWD + PATH so we only spawn
# `node --version` when the directory or PATH changes (PATH changes
# on `nvm use`). The cache vars are global (no `local`) so they
# persist across precmd invocations.
local node_cache_key="$PWD:$PATH"
if [[ "$node_cache_key" == "$_WARP_NODE_VERSION_CACHE_KEY" ]]; then
escaped_node_version="$_WARP_NODE_VERSION_CACHE_VALUE"
else
local node_version=$(node --version 2>/dev/null)
if [[ -n "$node_version" ]]; then
escaped_node_version=$(warp_escape_json "$node_version")
fi
_WARP_NODE_VERSION_CACHE_KEY="$node_cache_key"
_WARP_NODE_VERSION_CACHE_VALUE="$escaped_node_version"
fi
fi
fi
fi
@@ -461,6 +483,8 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
fi
local escaped_json="{\"hook\": \"Precmd\", \"value\": {
\"exit_code\": $exit_code,
\"next_block_id\": \"$next_block_id\",
\"pwd\": \"$escaped_pwd\",
\"ps1\": \"\",
\"honor_ps1\": $honor_ps1,
@@ -622,19 +646,19 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
function warp_report_input {
local escaped_input="$(warp_escape_json "$BUFFER")"
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\" } }"
warp_send_json_message "{ \"hook\": \"InputBuffer\", \"value\": { \"buffer\": \"$escaped_input\", \"session_id\": $WARP_SESSION_ID } }"
# This prevents zsh from printing typeahead as background output after we've fetched it.
BUFFER=""
}
zle -N warp_report_input
function clear() {
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {}}"
warp_send_json_message "{\"hook\": \"Clear\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
}
function warp_finish_update {
local update_id="$1"
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\"} }"
warp_send_json_message "{ \"hook\": \"FinishUpdate\", \"value\": { \"update_id\": \"$update_id\", \"session_id\": $WARP_SESSION_ID} }"
}
# Check if the warp apt source file has been renamed to `warpdotdev.list.distUpgrade` due to an ubuntu version update.
@@ -661,6 +685,21 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
fi
}
# Strips prompt constructs that zsh counts as visible "glitch" columns even
# when they appear inside a %{...%} zero-width region, returning the result
# in $REPLY. The explicit-width form %n{ is rewritten to %{ (preserving the
# brace pairing), and the %G, %nG, and %-nG forms are removed entirely.
# Neither change affects the rendered prompt bytes, only zsh's internal
# width accounting. Literal %% escapes are matched first so that they cannot
# form false positives (e.g. %%1{ renders as literal text and must be left
# alone).
function warp_strip_glitch_width_constructs() {
setopt localoptions extendedglob
local match mbegin mend
REPLY=${1:-}
REPLY=${REPLY//(#b)(%%|%<->\{|%(-|)(<->|)G)/${${match[1]:#%(-|)(<->|)G}/(#s)%<->\{(#e)/%\{}}
}
# Check whether the prompt-related variables have OSC prompt marker sequences,
# and if not, wrap them with the appropriate markers so that we can direct the
# prompt bytes to the appropriate grids.
@@ -712,7 +751,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# markers. If they exist, we remove the first occurrence of the prefix
# and the last occurrence of the suffix, which should be the ones that
# Warp has added, to avoid duplicating the prefix and suffix. Shell
# parameter expansion is used to remove the first and last occurences.
# parameter expansion is used to remove the first and last occurrences.
# Specifically note that virtualenvs can add content to the prompt, so we need to
# remove the markers before re-adding them.
# https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
@@ -742,7 +781,21 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
PROMPT=$preceding_suffix$following_suffix
fi
# If the prompt we extracted is exactly the glitch-stripped value that we
# installed on a previous refresh, keep the existing ORIGINAL_PROMPT: it
# holds the pristine value, whose width annotations are still needed if
# we later switch to honoring the PS1.
if [[ "$PROMPT" != "${WARP_STRIPPED_ORIGINAL_PROMPT:-}" ]]; then
if [[ -n "${WARP_STRIPPED_ORIGINAL_PROMPT:-}" && "$PROMPT" == *"$WARP_STRIPPED_ORIGINAL_PROMPT"* ]]; then
# Another hook added content around the stripped prompt that we
# installed (e.g. a virtualenv prefix). Rehydrate the stripped
# portion back to its pristine value before saving, so that the
# width annotations survive alongside the added content.
ORIGINAL_PROMPT=${PROMPT//$WARP_STRIPPED_ORIGINAL_PROMPT/$ORIGINAL_PROMPT}
else
ORIGINAL_PROMPT=$PROMPT
fi
fi
PROMPT="$prompt_prefix$PROMPT$suffix"
fi
@@ -762,14 +815,26 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# If we are using the Warp prompt, we pass a "hidden left prompt" to the prompt
# preview grid (the hidden prompt grid) with cursor markers surrounding the entire prompt.
if [[ "$WARP_HONOR_PS1" != "1" ]]; then
if [[ "$PROMPT" != "%{$prompt_prefix$ORIGINAL_PROMPT$suffix%}" ]]; then
# Even though the entire prompt is surrounded by cursor markers below,
# zsh still counts explicit-width constructs (%n{...%} and %G) within it
# as visible "glitch" columns. Since the prompt is routed to the hidden
# prompt grid and occupies zero columns of the combined prompt/command
# grid, any nonzero counted width desyncs zle's internal cursor position
# from the physical one, which corrupts partial redraws of the command
# (e.g. when zsh-syntax-highlighting recolors individual tokens). Strip
# those constructs before wrapping; this only changes zsh's width
# accounting, never the rendered prompt bytes.
local REPLY
warp_strip_glitch_width_constructs "$ORIGINAL_PROMPT"
WARP_STRIPPED_ORIGINAL_PROMPT=$REPLY
if [[ "$PROMPT" != "%{$prompt_prefix$WARP_STRIPPED_ORIGINAL_PROMPT$suffix%}" ]]; then
# We purposefully surround this entire prompt with cursor markers to prevent
# the shell from moving its internal state of the cursor position, for purposes
# of printing the command with the Warp prompt.
# Note that the Warp prompt is always ABOVE the combined grid in finished blocks
# (same line prompt only affects the input editor with Warp prompt, not
# finished blocks).
PROMPT="%{$prompt_prefix$ORIGINAL_PROMPT$suffix%}"
PROMPT="%{$prompt_prefix$WARP_STRIPPED_ORIGINAL_PROMPT$suffix%}"
fi
# Otherwise, if we are using the PS1, we use the normal prompt markers.
else
@@ -780,7 +845,9 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
fi
fi
if [[ "${RPROMPT:-}" != "%{"*"%}" ]]; then
# Do not synthesize an empty right prompt. Even without visible content, zsh reserves
# right-prompt layout space and may corrupt wrapped command redraws in the command grid.
if [[ -n "${RPROMPT:-}" && "${RPROMPT:-}" != "%{"*"%}" ]]; then
RPROMPT="%{${RPROMPT:-}%}"
fi
@@ -864,10 +931,50 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
}
function warp_ssh_helper() {
local remote_session_id=$(command -p od -An -N8 -tu8 /dev/urandom 2>/dev/null | command -p tr -d ' \n')
if [[ -z "$remote_session_id" || "$remote_session_id" == "0" ]]; then
# If we cannot generate a non-zero random token, run plain SSH instead.
command ssh "${@:1}"
return
fi
# Hex-encode the ZSH environment script we use to bootstrap remote zsh b/c it contains control characters
# We decode on the SSH server using xxd if its available, otherwise fall back to a for-loop over each byte
# and use printf to convert back to plaintext
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID="$(command -p date +%s)$RANDOM"; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
local zsh_env_script=$(printf '%s' 'unsetopt ZLE; unset RCS; unset GLOBAL_RCS; WARP_SESSION_ID='$remote_session_id'; WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@; _hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n); _user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER); _msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d '"'"' \n'"'"'); printf '"'"'\e]9278;d;%s\x07'"'"' $_msg; unset _hostname _user _msg' | command -p od -An -v -tx1 | command -p tr -d ' \n')
# Optionally attach to an existing ControlMaster the user already
# runs for this destination instead of creating our own. Resolve
# the user's configured ControlPath with `ssh -G` (which expands
# tokens like %h/%p/%r/%C into a literal path), then verify the
# master is alive with `ssh -O check`. Both probes are local-only
# commands. On any failure we fall back to creating a Warp-owned
# master, preserving the existing behavior.
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
local control_master_mode="yes"
local external_control_master="false"
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
case "$user_control_path" in
"" | none)
# No ControlPath configured for this destination.
;;
*[![:alnum:]._/~@:+,-]*)
# The resolved path contains characters we cannot safely
# embed in the SSH hook JSON below (e.g. an unexpanded %
# token, quotes, or whitespace); fall back to a
# Warp-owned master.
;;
*)
if command ssh -O check -o ControlPath="$user_control_path" "${@:1}" >/dev/null 2>&1; then
# A live master exists: multiplex through it and let
# the client know Warp does not own it.
control_path="$user_control_path"
control_master_mode="no"
external_control_master="true"
fi
;;
esac
fi
# Keep remote commands up-to-date with shell.rs & bash.sh.
# Note that in this command, we're passing a string to the remote shell. Any variable expansions need to be
@@ -876,7 +983,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
command ssh -o ControlMaster=$control_master_mode -o ControlPath="$control_path" \
-t "${@:1}" \
"
export TERM_PROGRAM='WarpTerminal'
@@ -887,7 +994,7 @@ export WARP_IS_SSH='1'
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
@@ -922,7 +1029,7 @@ case "'${SHELL##*/}'" in
command -p stty raw
HISTCONTROL=ignorespace
HISTIGNORE=" *"
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID='$remote_session_id'
WARP_HONOR_PS1="'$WARP_HONOR_PS1'"
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
@@ -955,7 +1062,7 @@ esac
function ssh() {
if is_interactive_ssh_session "$@"; then
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {}}"
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
# If the SSH wrapper is not enabled for this session, don't use it.
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
@@ -1169,6 +1276,12 @@ esac
fi
fi
# Restore the built-in bracketed-paste widget. This works around a buggy interaction we observed
# with the bracketed-paste-magic plugin (included in oh-my-zsh by default), zsh's "allexport"
# option (set -a), and Warp's bootstrapping code.
# https://github.com/warpdotdev/warp/issues/11520
zle -A .bracketed-paste bracketed-paste
precmd_functions+=(warp_precmd warp_update_prompt_vars)
preexec_functions+=(warp_preexec)
@@ -1395,7 +1508,8 @@ esac
local escaped_editor="$(warp_escape_json "$EDITOR")"
local escaped_shell_path="$(warp_escape_json "${commands[zsh]}")"
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"shell\": \"zsh\", \"home_dir\": \"$HOME\", \"path\": \"$escaped_path\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$ZSH_VERSION\", \"shell_options\": \"$shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"shell_plugins\": \"$escaped_shell_plugins\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"${WSL_DISTRO_NAME:-}\", \"shell_path\": \"$escaped_shell_path\"}}"
local escaped_cdpath="$(warp_escape_json "$CDPATH")"
local escaped_json="{\"hook\": \"Bootstrapped\", \"value\": {\"histfile\": \"$escaped_histfile\", \"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"home_dir\": \"$HOME\", \"path\": \"$escaped_path\", \"cdpath\": \"$escaped_cdpath\", \"editor\": \"$escaped_editor\", \"env_var_names\": \"$env_var_names\", \"abbreviations\": \"$escaped_abbrs\", \"aliases\": \"$escaped_aliases\", \"function_names\": \"$function_names\", \"builtins\": \"$escaped_builtins\", \"keywords\": \"$escaped_keywords\", \"shell_version\": \"$ZSH_VERSION\", \"shell_options\": \"$shell_options\", \"rcfiles_start_time\": \"$rcfiles_start_time\", \"rcfiles_end_time\": \"$rcfiles_end_time\", \"shell_plugins\": \"$escaped_shell_plugins\", \"os_category\": \"$os_category\", \"linux_distribution\": \"$linux_distribution\", \"wsl_name\": \"${WSL_DISTRO_NAME:-}\", \"shell_path\": \"$escaped_shell_path\"}}"
warp_send_json_message "$escaped_json"
}
warp_bootstrapped
@@ -3,11 +3,11 @@
# command -p resolves the given command with the system default PATH, ensuring the shell
# can find them even if the user has a clobbered PATH value.
unsetopt ZLE
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID=@@WARP_SESSION_ID@@
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
# We send the InitShell hook via OSCs when on Windows and via DCSs otherwise.
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
unset _hostname _user _msg
@@ -7,10 +7,10 @@
setopt hist_ignore_space
unsetopt ZLE
WARP_IS_SUBSHELL=1
WARP_SESSION_ID="$(command -p date +%s)$RANDOM"
WARP_SESSION_ID=@@WARP_SESSION_ID@@
_hostname=$(command -pv hostname >/dev/null 2>&1 && command -p hostname 2>/dev/null || command -p uname -n)
_user=$(command -pv whoami >/dev/null 2>&1 && command -p whoami 2>/dev/null || echo $USER)
_msg=$(printf "{\"hook\": \"InitShell\", \"value\": {\"session_id\": $WARP_SESSION_ID, \"shell\": \"zsh\", \"user\": \"%s\", \"hostname\": \"%s\", \"is_subshell\": true, \"wsl_name\": \"$WSL_DISTRO_NAME\"}}" "$_user" "$_hostname" | command -p od -An -v -tx1 | command -p tr -d " \n")
WARP_USING_WINDOWS_CON_PTY=@@USING_CON_PTY_BOOLEAN@@
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\e\x50\x24\x64%s\x9c' "$_msg"; fi
if [ "$WARP_USING_WINDOWS_CON_PTY" = true ]; then printf '\e]9278;d;%s\x07' "$_msg"; else printf '\x1b\x50\x24\x64%s\x1b\x5c' "$_msg"; fi
unset _hostname _user _msg
@@ -1 +0,0 @@
brew install tmux && tmux -Lwarp -CC && exit
@@ -1,29 +0,0 @@
INSTALL_TMUX='set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
mkdir -p $HOME/.warp/tmux
pushd "$HOME/.warp/tmux"
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH_NAME=amd64 ;;
amd64) ARCH_NAME=amd64 ;;
aarch64) ARCH_NAME=arm64 ;;
*) echo "Unsupported architecture $ARCH"; exit 1 ;;
esac
URL="https://github.com/warpdotdev/portable-tmux/releases/download/tmux-3.5a/tmux-${ARCH_NAME}.tar.gz"
(curl -o tmux.tar.gz -L $URL || wget -O tmux.tar.gz $URL) && tar -xf tmux.tar.gz
INSTALL_PATH="$HOME/.warp/tmux/local"
echo "TERM=tmux-256color LD_LIBRARY_PATH=\"$INSTALL_PATH/lib\" TERMINFO=\"$INSTALL_PATH/share/terminfo/\" \"$INSTALL_PATH/bin/tmux\" \"\$@\";" > ~/.warp/tmux/execute_tmux.sh
chmod +x ~/.warp/tmux/execute_tmux.sh;'
bash <<< "$INSTALL_TMUX" && ~/.warp/tmux/execute_tmux.sh -Lwarp -CC && exit
@@ -1,13 +0,0 @@
INSTALL_TMUX='set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
sudo apt update -y
sudo apt install -y tmux'
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
@@ -1,13 +0,0 @@
INSTALL_TMUX='set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
sudo dnf update -y
sudo dnf install -y tmux'
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
@@ -1,13 +0,0 @@
INSTALL_TMUX='set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
sudo pacman -Syu --noconfirm
sudo pacman -S --noconfirm tmux'
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
@@ -1,13 +0,0 @@
INSTALL_TMUX='set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
sudo yum update -y
sudo yum install -y tmux'
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
@@ -1,13 +0,0 @@
INSTALL_TMUX='set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
sudo zypper refresh
sudo zypper install -y tmux'
bash <<< "$INSTALL_TMUX" && _check_tmux && command tmux -Lwarp -CC && exit
@@ -1,68 +0,0 @@
_find() {
command -v "$1" >/dev/null 2>&1
}
_log() {
_msg=$(printf "{\"hook\": \"$1\", \"value\": $2}" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\033\120\044\144%s\234' "$_msg"
}
_err() {
_log RemoteWarpificationIsUnavailable "$1"
}
_system_details() {
OS=$(uname)
if [ "$OS" = "Darwin" ]; then
if _find brew; then
PKG="homebrew"
fi
elif [ "$OS" = "Linux" ]; then
if _find pacman; then
PKG="pacman"
elif _find zypper; then
PKG="zypper"
elif _find dnf; then
PKG="dnf"
elif _find yum && _find yumdownloader; then
PKG="yum"
elif _find apt; then
PKG="apt"
fi
fi
RA="no_root_access"
if command -v sudo >/dev/null && { sudo -vn && sudo -ln; } 2>&1 | grep -E 'may run|a password' > /dev/null; then RA="can_run_sudo"
elif [ "$(id -u)" -eq 0 ] && [ "$(whoami)" = "root" ]; then RA="is_root"
fi
WH=$( [ -w ~ ] && echo true || echo false )
printf '%s' "{\"os\": \"$OS\", \"pkg\": \"$PKG\", \"shell\": \"$(basename $SHELL)\", \"root_access\": \"$RA\", \"writable_home\": $WH}"
}
# _check_tmux is used in tmux install script post install!
_check_tmux() {
if _find $HOME/.warp/tmux/execute_tmux.sh; then
_log SshTmuxInstaller "\"warp\""
TMUX="$HOME/.warp/tmux/execute_tmux.sh"
elif _find tmux; then
TMUX="tmux"
_log SshTmuxInstaller "\"user\""
fi
if [ $TMUX ]; then
VER=$(command $TMUX -V 2>/dev/null | awk '{print $2}')
if [ -z "$VER" ]; then
_err "\"TmuxFailed\""
elif [ "$(printf '%s\n' "$VER" "2.9" | sort -V | tail -n1)" = "2.9" ]; then
_err "{\"UnsupportedTmuxVersion\": $(_system_details)}"
else
return 0
fi
else
_err "{\"TmuxNotInstalled\": $(_system_details)}"
fi
return 1
}
_check_tmux && command $TMUX -Lwarp -CC && exit
@@ -1,49 +0,0 @@
_find() {
command -v "$1" >/dev/null 2>&1
}
_log() {
_msg=$(printf "{\"hook\": \"$1\", \"value\": $2}" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\033\120\044\144%s\234' "$_msg"
}
_err() {
_log RemoteWarpificationIsUnavailable "$1"
}
_sd() {
if _find brew; then
PKG="homebrew"
fi
WH=$( [ -w ~ ] && echo true || echo false )
printf '{"os": "Darwin", "pkg": "%s", "shell": "%s", "root_access": "no_root_access", "writable_home": %s}' "$PKG" "$(basename $SHELL)" $WH
}
# _check_tmux is used in tmux install script post install!
_check_tmux() {
TMUX="$HOME/.warp/tmux/execute_tmux.sh"
if _find "$TMUX"; then
_log SshTmuxInstaller "\"warp\""
elif _find tmux; then
TMUX="tmux"
_log SshTmuxInstaller "\"user\""
fi
if [ $TMUX ]; then
VER=$($TMUX -V 2>/dev/null | awk '{print $2}')
if [ -z "$VER" ]; then
_err "\"TmuxFailed\""
elif [ "$(printf '%s\n' "$VER" "2.9" | sort -V | tail -n1)" = "2.9" ]; then
_err "{\"UnsupportedTmuxVersion\": $(_sd)}"
else
return 0
fi
else
_err "{\"TmuxNotInstalled\": $(_sd)}"
fi
return 1
}
_check_tmux && $TMUX -Lwarp -CC && exit
@@ -1,6 +0,0 @@
brew install tmux
if test $status -eq 0
tmux -Lwarp -CC
exit
end
@@ -1,29 +0,0 @@
set INSTALL_TMUX 'set -e
_on_error() {
local _msg=$(printf "{\"hook\": \"TmuxInstallFailed\", \"value\": { \"line\": \"$1\", \"command\": \"$2\" } }" | command -p od -An -v -tx1 | command -p tr -d " \n")
printf '\''\033\120\044\144%s\234'\'' "$_msg"
rm -rf "$HOME/.warp/tmux"
}
trap "_on_error \"\${LINENO}\" \"\$BASH_COMMAND\"" ERR
mkdir -p $HOME/.warp/tmux
pushd "$HOME/.warp/tmux"
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH_NAME=amd64 ;;
amd64) ARCH_NAME=amd64 ;;
aarch64) ARCH_NAME=arm64 ;;
*) echo "Unsupported architecture $ARCH"; exit 1 ;;
esac
URL="https://github.com/warpdotdev/portable-tmux/releases/download/tmux-3.5a/tmux-${ARCH_NAME}.tar.gz"
(curl -o tmux.tar.gz -L $URL || wget -O tmux.tar.gz $URL) && tar -xf tmux.tar.gz
INSTALL_PATH="$HOME/.warp/tmux/local"
echo "TERM=tmux-256color LD_LIBRARY_PATH=\"$INSTALL_PATH/lib\" TERMINFO=\"$INSTALL_PATH/share/terminfo/\" \"$INSTALL_PATH/bin/tmux\" \"\$@\";" > ~/.warp/tmux/execute_tmux.sh
chmod +x ~/.warp/tmux/execute_tmux.sh;'
bash -c "$INSTALL_TMUX" && ~/.warp/tmux/execute_tmux.sh -Lwarp -CC && exit
@@ -1,80 +0,0 @@
function _is
command -v $argv[1] >/dev/null 2>&1
end
function _log
set _hook $argv[1]
set _value $argv[2]
set _m (printf "{\"hook\": \"%s\", \"value\": %s}" $_hook $_value | od -An -v -tx1 | tr -d " \n")
printf '\033\120\044\144%s\234' $_m
end
function _err
_log RemoteWarpificationIsUnavailable $argv[1]
end
function _system_details
set -l OS (uname)
set -l PK ""
if test "$OS" = "Darwin"
if _is brew
set PK "homebrew"
end
else if test "$OS" = "Linux"
if _is pacman
set PK "pacman"
else if _is zypper
set PK "zypper"
else if _is dnf
set PK "dnf"
else if _is yum; and _is yumdownloader
set PK "yum"
else if _is apt
set PK "apt"
end
end
set -l CHECK (begin; command -v sudo > /dev/null 2>&1 && sudo -vn && sudo -ln; end 2>&1)
set -l RA "no_root_access"
if string match -qr '.*(may run|a password).*' "$CHECK"
set RA "can_run_sudo"
else if test (id -u) -eq 0; and test (whoami) = "root"
set RA "is_root"
end
set -l WH $( [ -w ~ ] && echo true || echo false )
printf '%s' "{\"os\": \"$OS\", \"pkg\": \"$PK\", \"shell\": \"fish\", \"root_access\": \"$RA\", \"writable_home\": $WH}"
end
# _check_tmux is used in the install script post install!
function _check_tmux
set -g TMUX ""
if _is tmux
set TMUX "tmux"
_log SshTmuxInstaller "\"user\""
else if _is $HOME/.warp/tmux/execute_tmux.sh
set TMUX "$HOME/.warp/tmux/execute_tmux.sh"
_log SshTmuxInstaller "\"warp\""
end
if test -n "$TMUX"
command $TMUX -V | awk '{print $2}' | read VER;
if test -z "$VER"
_err "\"TmuxFailed\""
else if test (printf '%s\n' "$VER" "2.9" | sort -V | tail -n1) = "2.9"
set -l DETAILS (_system_details)
_err "{\"UnsupportedTmuxVersion\": $DETAILS}"
else;
return 0
end
else;
set -l DETAILS (_system_details)
_err "{\"TmuxNotInstalled\": $DETAILS}"
end
return 1
end
_check_tmux; and command $TMUX -Lwarp -CC; and exit
@@ -1,51 +0,0 @@
function _is
command -v $argv[1] >/dev/null 2>&1
end
function _l
set _m (printf "{\"hook\": \"%s\", \"value\": %s}" $argv[1] $argv[2] | od -An -v -tx1 | tr -d " \n")
printf '\033\120\044\144%s\234' $_m
end
function _e
_l RemoteWarpificationIsUnavailable $argv[1]
end
function _sd
set -l PK ""
if _is brew
set PK "homebrew"
end
printf '{"os": "Darwin", "pkg": "%s", "shell": "fish", "root_access": "no_root_access", "writable_home": %s}' "$PK" $( [ -w ~ ] && echo true || echo false )
end
# _check_tmux is used in tmux install script post install!
function _check_tmux
set -g TMUX "$HOME/.warp/tmux/execute_tmux.sh"
if _is "$TMUX"
_l SshTmuxInstaller "\"warp\""
else if _is tmux
set TMUX "tmux"
_l SshTmuxInstaller "\"user\""
end
if test -n "$TMUX"
$TMUX -V | awk '{print $2}' | read V;
if test -z "$V"
_e "\"TmuxFailed\""
else if test (printf '%s\n' "$V" "2.9" | sort -V | tail -n1) = "2.9"
set -l D (_sd)
_e "{\"UnsupportedTmuxVersion\": $D}"
else;
return 0
end
else;
set -l D (_sd)
_e "{\"TmuxNotInstalled\": $D}"
end
return 1
end
_check_tmux; and $TMUX -Lwarp -CC; and exit

Some files were not shown because too many files have changed in this diff Show More