mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 16:13:15 +00:00
refactor: adopt upstream domain reorg (8356eb34) — fork content, upstream layout (#204)
Some checks failed
CI / Lint (push) Has been cancelled
CI / Format check (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Unit tests (push) Has been cancelled
CI / Playwright E2E (push) Has been cancelled
CI / Docker build smoke (push) Has been cancelled
CI / Triage ledger up to date (push) Has been cancelled
CI / ui bun.lock in sync (push) Has been cancelled
Some checks failed
CI / Lint (push) Has been cancelled
CI / Format check (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Unit tests (push) Has been cancelled
CI / Playwright E2E (push) Has been cancelled
CI / Docker build smoke (push) Has been cancelled
CI / Triage ledger up to date (push) Has been cancelled
CI / ui bun.lock in sync (push) Has been cancelled
* refactor: move docs/resources/assets to domain layout
Part of the domain-reorg adoption (build plan step C1): fork content,
upstream layout. Moves INSTALLATION.md/CUSTOMIZATION.md under docs/,
static/ under assets/, and default_prompt.md under agent/resources/
(packaged via agent/resources/__init__.py), then switches prompt.py's
loader to importlib.resources with an explicit DEFAULT_PROMPT_PATH
override, matching upstream's hunk. README and CUSTOMIZATION.md links
updated for the new paths; wheel build verified to still ship
agent/resources/default_prompt.md.
* refactor: consolidate reviewer modules into agent/review/
Part of the domain-reorg adoption (build plan step C2): fork content,
upstream layout. Nine 1:1 module moves (reviewer_diff/eval_store/
findings/groups/publish/reconcile/trace_context + review_style_
collector/guidance) into agent/review/, with internal relative
imports re-wired to the new package depth. agent/review/__init__.py
mirrors upstream's thin re-export shim (one of the 21 verified "A"
structural adds).
Rewrote the 38 grep hits across importer files (agent/{analyzer,
ci_autofix,reviewer,webapp}.py, agent/dashboard/*, agent/middleware/
settle_review_check.py, agent/tools/*, agent/utils/github_feedback.py,
agent/webhooks/github.py, evals/reviewer/*, and the reviewer test
suite) to point at agent.review.*; 4 of the 38 hits were name
collisions (list_reviewer_findings, reviewer_outcomes,
_reviewer_thread_id, reviewer_thread_id — not the moved modules) and
were left untouched. tests/test_github_checks.py's module-alias
import (`from agent import reviewer_publish`) follows upstream's own
`from agent.review import publish as reviewer_publish` pattern so
downstream `reviewer_publish.*` call sites needed no changes.
agent/reviewer.py and agent/webapp.py stay in place per the hard
rule (fork content, import-only rewire) and are not part of this
package.
Gates: ruff check + ruff format --check, pytest --co -q (1637
collected), full unit suite (1637 passed), and the reviewer/findings
suite in isolation (pytest -k "review or finding", 421 passed).
* refactor: adopt graphs/runtime/providers shims + retarget langgraph.json
Part of the domain-reorg adoption (build plan step C3): fork content,
upstream layout. Adds agent/graphs/{agent,analyzer,chat,reviewer,
scheduler}.py as thin re-export shims delegating to the existing fork
graph factories (agent.server/analyzer/chat/reviewer/scheduler), plus
agent/providers/__init__.py re-exporting agent.utils.model's
make_model/provider_model_kwargs/fallback_model_id_for surface —
verbatim upstream content, verified each import resolves against fork
modules with no name changes needed.
agent/runtime/{constants,execution}.py deviate from upstream's
verbatim shim bodies: rather than duplicating DEFAULT_LLM_MODEL_ID/
DEFAULT_LLM_MAX_TOKENS/DEFAULT_RECURSION_LIMIT/MODEL_CALL_RECURSION_LIMIT
and graph_loaded_for_execution's logic (upstream's shims assume
agent/server.py already had these extracted into runtime/ modules,
which is out of this commit's scope — server.py is untouched), they
import the fork's existing agent.server attributes directly. This
keeps the values/logic single-sourced instead of forking a second
copy that could drift.
agent/runtime/sandbox.py's delegation targets also differ from
upstream: fork's sandbox lifecycle helpers are private
(_get_cached_sandbox_backend, _configure_git_identity,
_recreate_sandbox in agent/server.py) since the fork's sync
4-case `__creating__` sentinel design (AGENTS.md) never made them
public. get_cached_sandbox_backend() also drops upstream's
caller-supplied `reconnect` callback parameter — fork's
_get_cached_sandbox_backend is a plain cache lookup; reconnection is
handled internally by ensure_sandbox_for_thread/
check_or_recreate_sandbox, not via a passed-in callback. No other
signature changes.
Added fork-only agent/graphs/ci_monitor.py (delegates to
agent.ci_monitor:get_ci_monitor) for symmetry, since upstream deleted
its ci-autofix cluster and has no equivalent shim. langgraph.json's
five stock graph entrypoints plus the fork-only ci_monitor now all
point at agent.graphs.<name>; http.app stays agent.webapp:app
(unchanged, per plan).
Deliberately NOT included (owned by build plan step C4, the FastAPI
split, gated on /sh-security-review): agent/api/{__init__,app,
health}.py, agent/webhooks/common.py, and the three
agent/webhooks/{github,linear,slack}_routes.py files. Those aren't
thin structural shims like the 21-file list implies in isolation —
they carry the fork's actual webhook dispatch/verify logic split out
of the still-monolithic webapp.py, which hasn't happened yet.
Building them now against upstream's placeholder content would ship
incomplete auth surface that C4 would just discard and redo.
Pinned oven-sh/setup-bun's bun-version to 1.3.14 (the version
installed locally; ui/ has no .bun-version file or package.json
engines/packageManager field pinning one) across all three CI jobs
that install bun, removing the latest-resolution flake.
Gates: ruff check + ruff format --check (clean), pytest --co -q
(1637 collected, no import errors), a direct import smoke-test of
every new module's public symbols, and a make dev boot check —
langgraph dev registered all six graphs (agent, reviewer, analyzer,
chat, scheduler, ci_monitor) each importing from agent.graphs.*, and
loaded the custom app from agent.webapp:app, before the process was
killed. (The subsequent lifespan failure, "DEFAULT_SANDBOX_SNAPSHOT_ID
must be set when SANDBOX_TYPE=langsmith", is expected with no .env
secrets configured in this environment and unrelated to this commit.)
* refactor: split webapp.py into api/ + per-source webhook routes
Plan step C4 (docs/upstream-sync/domain-reorg/reorg-build-plan.md, approved
decisions 1-2): split the 2,590-line agent/webapp.py monolith into
agent/webhooks/common.py (shared verify/dispatch helpers), agent/api/app.py
(composition), agent/api/health.py (/health + /webhooks/run-complete), and
per-source {github,linear,slack,jira,confluence}_routes.py. Atlassian
Connect lifecycle + descriptor routes (/connect/*) fold into
confluence_routes.py; webapp.py becomes the upstream-shaped compatibility
shim (from .api.app import app). langgraph.json http.app stays
agent.webapp:app via the shim.
Fork content, upstream layout: linear/slack route files verified
content-identical to upstream 8356eb34 and taken verbatim; github_routes is
upstream + the fork's CI auto-fix trigger wiring; jira/confluence routes are
fork-only, transformed to the same common.X / service.X module-attribute
style. All signature verification (GitHub HMAC, Slack, Linear
timestamp-freshness, verify_jira_secret + opt-in HMAC/timestamp/IP
allowlist, Connect JWT/qsh), token-attribution gating, TID-COLLIDE-01 repo
binding, _is_repo_auto_review_enabled gates, and public-repo org gate move
unchanged.
Handlers rewired from webapp.X to common.X; test monkeypatch sites across
26 files + conftest.py + e2e/harness.py retargeted to
webhook_common/handler/route modules per upstream's pattern. Residual
agent.webapp importers: only the shim, langgraph.json http.app, Makefile
uvicorn target, and docs (doc-path updates land in C7).
Gates: ruff check + format, pytest --co, full unit (1637 passed), full
Playwright E2E vs real langgraph dev (9/9), residual-importer sweep.
* refactor: move tests into tests/<domain>/ layout
Applies the plan's C5 step: git mv every test per the domain-reorg
move-map (movemap-m50.txt) into tests/{agent,analyzer,auth,dashboard,
github,middleware,models,reviewer,sandbox,slack,tools,webhooks}/, plus
the 13 fork-only placements from the scoping report §2c (Atlassian
webhook tests -> tests/webhooks/, test_atlassian_connect.py and
test_auth_error_leak.py -> tests/auth/, jira/confluence util tests ->
tests/tools/, test_repo_binding_isolation.py -> tests/sandbox/,
bot-identity/autofix tests -> tests/github/).
Path-only move: the only content edits are parents[1] -> parents[2]
fixes in test_e2b_integration.py and test_daytona_integration.py,
required because their __file__-relative ROOT path gained one more
directory level in the move.
Monkeypatch retargets for these files were already completed in C4;
none remained outstanding here.
* refactor: move ui/src/{components,lib}/agents into features/ layout
Part of the domain-reorg adoption (build plan step C6): fork content,
upstream layout. git mv's all 80 pure-rename files from the move-map
(agent-scoped components/lib -> ui/src/features/{agents,automations,
reviews,settings}/...), including ported/ splitting into
features/agents/experiments/ (still-unintegrated desktop-host ports)
and features/agents/components/chat/ (CloudPromptBar, CodeBlock,
DiffView, Logo, Markdown, ReplyCard, ShellCommand, ToolExecution --
files actually wired into the live dashboard). Fork-diverged files
(PlanReview.tsx, WorkflowApprovalCard.tsx, AgentsSidebar.tsx,
SidebarFilterMenu.tsx, AgentGitPanel.tsx, AutomationEditor.tsx,
DiffView.tsx, CloudPromptBar.tsx) keep fork content -- verified via
diff that only import lines changed.
Rewrote @/components/agents and @/lib/agents imports across all 54
importer files plus the moved files' own internal imports (grep-driven,
85-entry alias map covering every old->new path pair). Two hazards
caught only by the build gate (not tsc, since both files sit under the
new experiments/** tsconfig exclude): ui/src/lib/notifications.ts had
a relative `./agents/types` import (no `@/` alias) that the grep missed;
and features/agents/experiments/index.ts's barrel re-export of the
messages module needed to switch from an `@/` alias to a relative
import (`../components/messages`) after landing inside the newly
tsconfig-excluded experiments/ directory -- vite-tsconfig-paths failed
to resolve it at build time even though tsc stayed silent.
AgentPromptBar.tsx (the 2-line CloudPromptBar re-export shim) moves to
features/agents/components/ with its export path retargeted at
CloudPromptBar's new chat/ location -- a D/A pair, not a content loss,
since the import-path edit drops it below git's rename-similarity
threshold. ported/index.ts is the same D/A story for the same reason.
Swapped the 8-file ported/ exclude lists in tsconfig.json and
eslint.config.js for the single `src/features/agents/experiments/**`
glob (upstream's simplification, no behavior change). Deleted
ui/pnpm-workspace.yaml (lockfile hygiene -- bun stays the toolchain).
Gates: bunx tsc --noEmit (clean), bun run build (clean after the two
notifications.ts / experiments-index.ts fixes above), bun run test
(33/33), and the full Playwright E2E suite against the real langgraph
dev server + built dashboard (9/9).
* docs: land reorg in ledger, fix path refs, ship plan artifacts (C7)
C7 of the domain-reorg adoption (build plan docs/upstream-sync/domain-reorg/
reorg-build-plan.md, step C7):
- CLAUDE.md / AGENTS.md: retarget architecture path references to the new
layout — graph entrypoints via agent.graphs.* shims, the agent/api/ +
agent/webhooks/*_routes.py FastAPI split (webapp.py now a shim),
agent/review/ package, tests/<domain>/ test paths, and the new-graph/
test conventions. README.md already pointed at docs/ (C1) — no change.
- triage.jsonl: flip 8356eb34 (#1726) to landed on branch
refactor/domain-reorg-adoption; add re-triage notes to the 8 unblocked
rows (#1732/#1761/#1744/#1748/#1742 clean, #1736/#1758/#1760 near-clean).
triage.md regenerated via make triage-render.
- Ship the plan, scoping report, move-map artifacts, and the
domain-reorg-adoption workflow so the exercise is reproducible.
Memory + Confluence handled out-of-band (not in this commit): project
memory updated with the new module layout; Confluence check found no IT
page documents the repo module map — no Confluence change required.
This commit is contained in:
commit
298443ac8b
325 changed files with 7738 additions and 4026 deletions
229
.claude/workflows/domain-reorg-adoption.mjs
Normal file
229
.claude/workflows/domain-reorg-adoption.mjs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
export const meta = {
|
||||
name: 'domain-reorg-adoption',
|
||||
description: 'Execute the gate-approved C1-C7 domain-reorg build plan: moves, webapp split, security review, PR — no merge',
|
||||
whenToUse: 'Run once, after Adam gives the go for the reorg build. Requires clean dev; plan + scoping artifacts live in docs/upstream-sync/domain-reorg/.',
|
||||
phases: [
|
||||
{ title: 'Preflight', detail: 'clean tree, artifacts present, branch created', model: 'haiku' },
|
||||
{ title: 'C1 docs/resources', detail: 'doc moves + prompt loader + wheel check', model: 'sonnet' },
|
||||
{ title: 'C2 review package', detail: '9 module moves + 38 importer rewrites', model: 'sonnet' },
|
||||
{ title: 'C3 shims+manifest', detail: '21 shims, ci_monitor, langgraph.json, bun pin', model: 'sonnet' },
|
||||
{ title: 'C4 webapp split', detail: 'the critical auth-surface split', model: 'fable' },
|
||||
{ title: 'C4 security review', detail: 'detector fan-out + proof-or-kill verify' },
|
||||
{ title: 'C5 test moves', detail: 'tests/<domain>/ mechanical moves', model: 'sonnet' },
|
||||
{ title: 'C6 UI moves', detail: 'ui/src/features/ moves + import rewrites', model: 'sonnet' },
|
||||
{ title: 'C7 docs/ledger/memory', detail: 'docs, triage ledger, memory, Confluence check', model: 'opus' },
|
||||
{ title: 'PR', detail: 'push branch, open PR, final report — NO merge' },
|
||||
],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared context strings
|
||||
// ---------------------------------------------------------------------------
|
||||
const REPO = '/Users/adammoussa/Documents/repositories/seahaven/open-swe'
|
||||
const SCRATCH = '/Users/adammoussa/Documents/repositories/seahaven/open-swe/docs/upstream-sync/domain-reorg'
|
||||
const BRANCH = 'refactor/domain-reorg-adoption'
|
||||
|
||||
const COMMON = `
|
||||
You are executing one commit of a gate-approved build plan in ${REPO} (branch ${BRANCH}).
|
||||
Plan: ${SCRATCH}/reorg-build-plan.md — READ IT FIRST, plus the section of ${SCRATCH}/reorg-scoping-report.md relevant to your commit.
|
||||
Move-map artifacts: ${SCRATCH}/movemap-m50.txt, ${SCRATCH}/cross.json, ${SCRATCH}/forkonly.json.
|
||||
HARD RULES (violations = abort and report, do not improvise):
|
||||
- Fork file contents, upstream layout. Upstream content ONLY for the 21 structural "A" shims listed in the scoping report.
|
||||
- NEVER adopt upstream content for agent/webhooks/{github,linear,slack}.py, tests/conftest.py, tests/e2e/harness.py.
|
||||
- Use "git mv" for moves so rename tracking survives. One commit for your cascade class only; commit message follows the repo's conventional style (see recent git log), body references the plan step.
|
||||
- No pushes from your stage. No force operations. No edits outside your commit's scope. Do not touch docs/upstream-sync/* unless your stage says so.
|
||||
- Gate ladder before committing: ruff check && ruff format --check, then uv run pytest --co -q, then the stage-specific gates. If a gate fails, fix within scope; if you cannot, ABORT: git reset --hard to the pre-stage SHA you recorded at start, and report the failure.
|
||||
Return JSON: {ok, commit_sha, gates: {name: pass|fail|skipped}, notes, aborted_reason}`
|
||||
|
||||
const RESULT_SCHEMA = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ok: { type: 'boolean' },
|
||||
commit_sha: { type: 'string' },
|
||||
gates: { type: 'object' },
|
||||
notes: { type: 'string' },
|
||||
aborted_reason: { type: 'string' },
|
||||
},
|
||||
required: ['ok', 'notes'],
|
||||
}
|
||||
|
||||
const results = { commits: [], securityReview: null, pr: null }
|
||||
|
||||
function ensure(stage, r) {
|
||||
if (!r || !r.ok) {
|
||||
throw new Error(`${stage} failed: ${r ? r.aborted_reason || r.notes : 'agent returned null'}`)
|
||||
}
|
||||
results.commits.push({ stage, sha: r.commit_sha, gates: r.gates, notes: r.notes })
|
||||
return r
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Preflight — cheap checks, haiku
|
||||
// ---------------------------------------------------------------------------
|
||||
phase('Preflight')
|
||||
const pre = await agent(
|
||||
`${COMMON}
|
||||
STAGE: Preflight (no commit).
|
||||
1. Verify ${REPO} is on dev and dev == origin/dev (fetch first is allowed here). Tree must be clean EXCEPT untracked files under docs/upstream-sync/domain-reorg/ and .claude/workflows/, which are expected (they are committed in C7).
|
||||
2. Verify the plan file, scoping report, and all three move-map artifacts exist and are non-empty.
|
||||
3. Verify branch ${BRANCH} does not already exist locally or on origin.
|
||||
4. Create ${BRANCH} off dev. Record the base SHA.
|
||||
Return JSON with ok, notes, and base_sha in notes.`,
|
||||
{ label: 'preflight', phase: 'Preflight', model: 'haiku', effort: 'low', schema: RESULT_SCHEMA },
|
||||
)
|
||||
if (!pre || !pre.ok) throw new Error(`Preflight failed: ${pre ? pre.notes : 'null'}`)
|
||||
log('Preflight OK — branch created')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C1–C3: independent-of-C4 stages, sequential commits on the shared branch
|
||||
// ---------------------------------------------------------------------------
|
||||
phase('C1 docs/resources')
|
||||
ensure('C1', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C1 — docs/resources/assets (plan section "C1").
|
||||
Moves: INSTALLATION.md and CUSTOMIZATION.md -> docs/, static/ -> assets/ (fix README refs), default_prompt.md -> agent/resources/ (+ __init__.py). Apply the importlib.resources loader hunk to the fork's prompt.py exactly as described in scoping §2b (upstream's hunk pattern, fork's file).
|
||||
Extra gate: build the wheel (uv build or python -m build) and verify agent/resources/default_prompt.md is inside it; if missing, add the explicit hatchling include and note it.`,
|
||||
{ label: 'C1', phase: 'C1 docs/resources', model: 'sonnet', schema: RESULT_SCHEMA },
|
||||
))
|
||||
|
||||
phase('C2 review package')
|
||||
ensure('C2', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C2 — agent/review/ package (plan section "C2", scoping §2a/2b reviewer rows).
|
||||
git mv the 9 reviewer/style modules per the move-map (reviewer_findings.py -> agent/review/findings.py etc.), replicate upstream's import-rewire pattern on FORK content for the R<100 ones, rewrite all 38 importer files (grep for reviewer_ and review_style_ imports to find them — do not trust the count blindly), create agent/review/__init__.py exporting the fork's existing public surface.
|
||||
Extra gate: full reviewer/findings unit suites pass (uv run pytest tests/ -k "review or finding" plus any suite the moved modules own).`,
|
||||
{ label: 'C2', phase: 'C2 review package', model: 'sonnet', schema: RESULT_SCHEMA },
|
||||
))
|
||||
|
||||
phase('C3 shims+manifest')
|
||||
ensure('C3', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C3 — graphs/runtime/providers shims + langgraph.json + CI hardening (plan section "C3").
|
||||
Adopt the 21 upstream "A" shim files verbatim-then-verify: for each, confirm every import it makes resolves against FORK modules (they delegate to agent.server etc.); fix delegation targets if fork names differ. Add fork-only agent/graphs/ci_monitor.py shim following the same pattern. Retarget langgraph.json graph entrypoints to agent.graphs.* (http.app stays agent.webapp:app). Pin bun-version in .github/workflows/ci.yml to the version in ui/ (check .bun-version / package.json engines; else current stable — note which).
|
||||
Extra gate: timeout-bounded "make dev" boot check — all six graphs (agent, reviewer, analyzer, chat, scheduler, ci_monitor) register and the FastAPI app mounts; kill it after verifying startup logs.`,
|
||||
{ label: 'C3', phase: 'C3 shims+manifest', model: 'sonnet', schema: RESULT_SCHEMA },
|
||||
))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C4 — the critical commit. Strongest model, high effort.
|
||||
// ---------------------------------------------------------------------------
|
||||
phase('C4 webapp split')
|
||||
ensure('C4', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C4 — FastAPI split (plan section "C4" + approved decisions 1-2). THE critical auth-surface commit.
|
||||
Split the fork's 2,590-line agent/webapp.py into: agent/webhooks/common.py (shared verify/dispatch helpers), agent/api/app.py (composition), agent/api/health.py (/health + /webhooks/run-complete), and per-source github_routes.py / linear_routes.py / slack_routes.py / jira_routes.py / confluence_routes.py. Atlassian Connect lifecycle + descriptor routes (/connect/*) go INTO confluence_routes.py (approved decision 2). webapp.py becomes the compatibility shim re-exporting app (mirror upstream's shim shape).
|
||||
Preserve EXACTLY: all signature-verification logic (GitHub HMAC, Slack, Linear, verify_jira_secret + optional HMAC/timestamp, Connect JWT/qsh), token-attribution gating, TID-COLLIDE-01 binding, the renamed _is_repo_auto_review_enabled gates, ci-autofix trigger wiring. Handlers keep module-attribute access style (common.X / service.X).
|
||||
Retarget the ~240 monkeypatch.setattr(webapp, ...) sites across the 11 test files + tests/conftest.py + tests/e2e/harness.py per upstream's retarget pattern (webhook_common / handler modules) — find every site by grep, not by count.
|
||||
Extra gates before committing: full unit suite; FULL E2E (Playwright + real langgraph dev server); residual-importer sweep — git grep for "agent.webapp"/"from agent import webapp" must return only the shim and intentional compat references (list what remains in notes).`,
|
||||
{ label: 'C4', phase: 'C4 webapp split', model: 'fable', effort: 'high', schema: RESULT_SCHEMA },
|
||||
))
|
||||
log('C4 committed — entering security review (hard gate before C5)')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C4 security review — detector fan-out (sonnet lenses) + proof-or-kill verifier (opus),
|
||||
// with a bounded fix loop (fable) per the approved failure path (new commits, no rewrites).
|
||||
// ---------------------------------------------------------------------------
|
||||
phase('C4 security review')
|
||||
const LENSES = [
|
||||
{ key: 'authz', focus: 'broken object-level auth, missing access checks, webhook signature verification gaps (GitHub HMAC / Slack / Linear / Jira shared-secret+HMAC / Connect JWT+qsh), token-attribution gating, TID-COLLIDE-01 thread binding, the _is_repo_auto_review_enabled gates' },
|
||||
{ key: 'injection', focus: 'untrusted webhook payload handling: parsing, header trust, SSRF-prone fetches, path handling in the moved dispatch code' },
|
||||
{ key: 'logic', focus: 'multi-step invariants broken by the split: dispatch ordering, dedup/replay protection, race conditions across the new module boundaries, dropped code paths (diff every moved function against its pre-split source)' },
|
||||
]
|
||||
const FINDINGS_SCHEMA = {
|
||||
type: 'object',
|
||||
properties: { findings: { type: 'array', items: { type: 'object' } } },
|
||||
required: ['findings'],
|
||||
}
|
||||
const VERDICT_SCHEMA = {
|
||||
type: 'object',
|
||||
properties: { confirmed: { type: 'array', items: { type: 'object' } }, unverified: { type: 'array', items: { type: 'object' } } },
|
||||
required: ['confirmed', 'unverified'],
|
||||
}
|
||||
|
||||
let reviewRound = 0
|
||||
let confirmedHighs = []
|
||||
while (true) {
|
||||
reviewRound += 1
|
||||
const c4diff = `the C4 split commit(s) on ${BRANCH} in ${REPO} (git show for each C4/C4a commit; compare moved code against pre-split agent/webapp.py at the merge base)`
|
||||
const found = (await parallel(LENSES.map(l => () =>
|
||||
agent(
|
||||
`You are a hostile ${l.key} security auditor. Audit ONLY ${l.key} issues in ${c4diff}. Focus: ${l.focus}. Read the actual files at both revisions; for each checklist area either cite the specific safe line or file a finding {id,title,claimed_severity,file,line,data_flow,proof:{input,outcome},recommendation}. Findings must be about the SPLIT (regressions vs pre-split behavior), not pre-existing issues. Return {findings:[...]}.`,
|
||||
{ label: `detect:${l.key}:r${reviewRound}`, phase: 'C4 security review', model: 'sonnet', schema: FINDINGS_SCHEMA },
|
||||
)))).filter(Boolean).flatMap(r => r.findings)
|
||||
|
||||
if (!found.length) { results.securityReview = { rounds: reviewRound, confirmed: [], outcome: 'clean' }; break }
|
||||
|
||||
const verdict = await agent(
|
||||
`You are a skeptical exploitation verifier — you did NOT find these and are rewarded for killing weak claims. For each candidate finding on ${c4diff}, demand a concrete proof-of-exploit (specific input -> specific bad outcome, consistent with the code at HEAD of ${BRANCH}). Default to unverified when uncertain. Candidates: ${JSON.stringify(found)}. Return {confirmed:[...], unverified:[...]} keeping severity only on confirmed items.`,
|
||||
{ label: `verify:r${reviewRound}`, phase: 'C4 security review', model: 'opus', effort: 'high', schema: VERDICT_SCHEMA },
|
||||
)
|
||||
confirmedHighs = (verdict?.confirmed || []).filter(f => ['critical', 'high'].includes((f.claimed_severity || f.severity || '').toLowerCase()))
|
||||
if (!confirmedHighs.length) {
|
||||
results.securityReview = { rounds: reviewRound, confirmed: verdict?.confirmed || [], unverified: verdict?.unverified || [], outcome: 'pass' }
|
||||
break
|
||||
}
|
||||
if (reviewRound >= 3) {
|
||||
results.securityReview = { rounds: reviewRound, confirmed: confirmedHighs, outcome: 'BLOCKED' }
|
||||
throw new Error(`Security review still has ${confirmedHighs.length} confirmed critical/high after ${reviewRound} rounds — plan failure path: fundamental-flaw handling, return to Adam. Branch left intact for inspection.`)
|
||||
}
|
||||
log(`Security round ${reviewRound}: ${confirmedHighs.length} confirmed critical/high — dispatching fix commit C4a`)
|
||||
ensure(`C4a-r${reviewRound}`, await agent(
|
||||
`${COMMON}
|
||||
STAGE: C4a — address confirmed security-review findings as a NEW commit on ${BRANCH} (plan failure path: no history rewrite, no force-push). Findings with proofs: ${JSON.stringify(confirmedHighs)}. Fix each within the split's scope; re-run full unit + E2E before committing.`,
|
||||
{ label: `C4a:r${reviewRound}`, phase: 'C4 security review', model: 'fable', effort: 'high', schema: RESULT_SCHEMA },
|
||||
))
|
||||
}
|
||||
log(`Security review outcome: ${results.securityReview.outcome} after ${results.securityReview.rounds} round(s)`)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HARD GATE passed — C5 and C6 (sequential commits; C6 depends only on branch order, not C5 content)
|
||||
// ---------------------------------------------------------------------------
|
||||
phase('C5 test moves')
|
||||
ensure('C5', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C5 — tests/<domain>/ moves (plan section "C5", scoping §2a + §2c placements).
|
||||
git mv every test per the move-map; apply remaining R<100 monkeypatch retargets not already done in C4; place the 13 fork-only tests per the approved table (webhooks/, auth/, tools/, sandbox/, github/). Path-only commit — zero logic edits.
|
||||
Extra gate: uv run pytest --co -q collects everything (no import errors, count matches pre-move) then full unit green.`,
|
||||
{ label: 'C5', phase: 'C5 test moves', model: 'sonnet', effort: 'low', schema: RESULT_SCHEMA },
|
||||
))
|
||||
|
||||
phase('C6 UI moves')
|
||||
ensure('C6', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C6 — ui/src/features/ moves (plan section "C6").
|
||||
git mv per the move-map including ported/ -> features/agents/experiments|chat; rewrite @/components/agents and @/lib/agents imports across the 54 importer files AND the moved files themselves (grep-driven); swap the tsconfig/eslint exclude lists for the single experiments glob; retarget the AgentPromptBar shim; delete ui/pnpm-workspace.yaml. Fork-diverged files (PlanReview.tsx, WorkflowApprovalCard.tsx, AgentsSidebar.tsx, SidebarFilterMenu.tsx, AgentGitPanel.tsx, AutomationEditor.tsx, DiffView.tsx, CloudPromptBar.tsx) keep fork content — imports only.
|
||||
Extra gate: cd ui && bun install && bunx tsc --noEmit && bun run build, then Playwright E2E.`,
|
||||
{ label: 'C6', phase: 'C6 UI moves', model: 'sonnet', schema: RESULT_SCHEMA },
|
||||
))
|
||||
|
||||
phase('C7 docs/ledger/memory')
|
||||
ensure('C7', await agent(
|
||||
`${COMMON}
|
||||
STAGE: C7 — docs + ledger + memory (plan section "C7" — read its obligations verbatim; you own them).
|
||||
1. Update fork CLAUDE.md / README.md / AGENTS.md path references (architecture sections naming agent/webapp.py, old test paths, ui paths).
|
||||
2. Ledger: flip 8356eb34 to landed in docs/upstream-sync/triage.jsonl with branch ${BRANCH}; add re-triage notes to the 8 unblocked rows per scoping §5; make triage-render; make triage-check must pass.
|
||||
3. Memory: update /Users/adammoussa/.claude/projects/-Users-adammoussa-Documents-repositories-seahaven-open-swe/memory/open-swe-upstream-triage-status.md (+ MEMORY.md hook if needed) with the new module layout summary (agent/{graphs,runtime,api,review,resources,webhooks/*_routes}, tests/<domain>/, ui/src/features/) and the reorg-landed status. Memory edits are NOT part of the git commit.
|
||||
4. Confluence check: via ToolSearch load the Atlassian MCP search tools; search the IT space for any page documenting this repo's module map. If found, update it; if none, or if Confluence is unreachable, record the exact outcome string ("updated page <id>" / "no Confluence change required" / "OUTSTANDING as of <today> — Confluence inaccessible") in your notes AND in the memory file.
|
||||
5. Stage docs/upstream-sync/domain-reorg/ and .claude/workflows/domain-reorg-adoption.mjs so the plan, scoping artifacts, and this workflow ship with the exercise.
|
||||
Commit all repo-file changes as the C7 commit; run ruff + triage-check as gates.`,
|
||||
{ label: 'C7', phase: 'C7 docs/ledger/memory', model: 'opus', schema: RESULT_SCHEMA },
|
||||
))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PR — push and open, NO merge. Merge is Adam's (after @openswe review).
|
||||
// ---------------------------------------------------------------------------
|
||||
phase('PR')
|
||||
results.pr = await agent(
|
||||
`${COMMON}
|
||||
STAGE: PR (no commit). Push ${BRANCH} to origin (plain push — the pre-push hook runs security scanners; if it blocks, report, never bypass). Open a PR against dev with gh pr create --base dev. Title: "refactor: adopt upstream domain reorg (8356eb34) — fork content, upstream layout". Body MUST include: the commit-by-commit summary from this build (paste from your reading of git log), explicit review-scope callouts for C2 (reviewer-module moves) and C4 (auth-surface split), the security-review outcome (${JSON.stringify(results.securityReview?.outcome)}), the C7 Confluence-check outcome, the post-deploy verification checklist from the plan (as unchecked boxes), and "MERGE-COMMIT ONLY — do not squash/rebase". Do NOT merge, do NOT approve, do NOT comment @openswe — Adam drives the review.
|
||||
Return JSON: {ok, notes} with the PR URL in notes.`,
|
||||
{ label: 'open-pr', phase: 'PR', model: 'sonnet', schema: RESULT_SCHEMA },
|
||||
)
|
||||
|
||||
return {
|
||||
branch: BRANCH,
|
||||
commits: results.commits,
|
||||
securityReview: results.securityReview,
|
||||
pr: results.pr?.notes || 'PR step failed — branch is pushed or local; open manually',
|
||||
next: 'Adam: review PR (C2+C4 scope), run @openswe review, merge with a MERGE COMMIT, then run the post-deploy checklist and post results to the PR.',
|
||||
}
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
|
@ -45,6 +45,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
bun-version: "1.3.14"
|
||||
- name: Install UI dependencies
|
||||
working-directory: ui
|
||||
run: bun install --frozen-lockfile
|
||||
|
|
@ -74,6 +76,8 @@ jobs:
|
|||
with:
|
||||
node-version: "24"
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
bun-version: "1.3.14"
|
||||
- name: Install Python deps (langgraph dev runtime)
|
||||
run: uv sync --locked
|
||||
- name: Install Playwright + Chromium
|
||||
|
|
@ -132,6 +136,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||
with:
|
||||
bun-version: "1.3.14"
|
||||
- name: ui/package.json and ui/bun.lock agree
|
||||
working-directory: ui
|
||||
run: bun install --frozen-lockfile
|
||||
|
|
|
|||
28
AGENTS.md
28
AGENTS.md
|
|
@ -14,25 +14,27 @@ Dependencies are managed with **uv**. Tests use pytest (`asyncio_mode = "auto"`)
|
|||
|
||||
```bash
|
||||
make install # uv pip install -e .
|
||||
make dev # uv run langgraph dev — serves all three graphs + the FastAPI app from langgraph.json
|
||||
make dev # uv run langgraph dev — serves all six graphs + the FastAPI app from langgraph.json
|
||||
make run # uvicorn agent.webapp:app --reload --port 8000 (FastAPI only, no LangGraph runtime)
|
||||
make test # uv run pytest -vvv tests/
|
||||
make test TEST_FILE=tests/test_open_pr_middleware.py # single test file
|
||||
uv run pytest -vvv tests/test_open_pr_middleware.py::test_name # single test
|
||||
make test TEST_FILE=tests/github/test_open_pull_request.py # single test file
|
||||
uv run pytest -vvv tests/github/test_open_pull_request.py::test_name # single test
|
||||
make lint # ruff check + ruff format --diff
|
||||
make format # ruff format + ruff check --fix
|
||||
```
|
||||
|
||||
`langgraph.json` declares three graph entrypoints and the FastAPI app, all served together by `langgraph dev`:
|
||||
`langgraph.json` declares six graph entrypoints and the FastAPI app, all served together by `langgraph dev`. Every graph entrypoint targets a thin `agent.graphs.*` re-export shim that delegates to the unmoved factory module (domain reorg):
|
||||
|
||||
| Graph | Entrypoint | Purpose |
|
||||
|---|---|---|
|
||||
| `agent` | `agent.server:get_agent` | Main coding agent (Slack/Linear/Jira/Confluence/GitHub-triggered). |
|
||||
| `reviewer` | `agent.reviewer:get_reviewer_agent` | Read-only PR reviewer. Findings model + `publish_review`. |
|
||||
| `analyzer` | `agent.analyzer:get_analyzer` | Learns per-repo reviewer style from historical PRs and this reviewer's own finding outcomes. |
|
||||
| `ci_monitor` | `agent.ci_monitor:get_ci_monitor` | Polling fallback for CI auto-fix: each tick sweeps open agent-authored PRs for failing checks / merge conflicts via `agent.ci_autofix.sweep_open_prs`. |
|
||||
| `agent` | `agent.graphs.agent:traced_agent` (shim → `agent.server:get_agent`) | Main coding agent (Slack/Linear/Jira/Confluence/GitHub-triggered). |
|
||||
| `reviewer` | `agent.graphs.reviewer:traced_reviewer_agent` (shim → `agent.reviewer:get_reviewer_agent`) | Read-only PR reviewer. Findings model + `publish_review`. |
|
||||
| `analyzer` | `agent.graphs.analyzer:traced_analyzer` (shim → `agent.analyzer:get_analyzer`) | Learns per-repo reviewer style from historical PRs and this reviewer's own finding outcomes. |
|
||||
| `chat` | `agent.graphs.chat:traced_chat_agent` | Dashboard Agents chat graph. |
|
||||
| `scheduler` | `agent.graphs.scheduler:get_scheduler` | Reconcile sweep for stragglers. |
|
||||
| `ci_monitor` | `agent.graphs.ci_monitor:get_ci_monitor` (shim → `agent.ci_monitor:get_ci_monitor`) | Polling fallback for CI auto-fix: each tick sweeps open agent-authored PRs for failing checks / merge conflicts via `agent.ci_autofix.sweep_open_prs`. |
|
||||
|
||||
The FastAPI app is `agent.webapp:app`.
|
||||
The FastAPI app is `agent.webapp:app` — now a compatibility shim re-exporting `agent.api.app:app`.
|
||||
|
||||
CI auto-fix ("PR babysitting") lives in `agent/ci_autofix.py`: when a CI check fails (webhook `check_run` / `check_suite` / `workflow_run` / `status`) or a reviewer leaves actionable feedback on a PR Open SWE opened, it locates the originating agent thread (by `pr_url` metadata) and dispatches a confidence-gated fix run on the `agent` graph. Gated by the per-user `auto_fix_ci` profile flag, the enabled-repos opt-in, and a per-PR `@open-swe autofix on|off` toggle (`agent/dashboard/autofix_state.py`). Skip-rules (base-branch failures, human commits, same-head dedupe, batching while runs are active, loop cap) all live in `ci_autofix.py`.
|
||||
|
||||
|
|
@ -41,9 +43,9 @@ CI auto-fix ("PR babysitting") lives in `agent/ci_autofix.py`: when a CI check f
|
|||
### Entrypoints
|
||||
|
||||
- **`agent/server.py` → `get_agent(config)`** — main graph factory. Called per-thread. Resolves the GitHub token, gets-or-creates the sandbox for the thread, resolves the team/profile/per-thread model + effort, then constructs a fresh `create_deep_agent(...)` with the curated tool list and middleware stack. The agent itself is stateless — all per-thread state lives in the sandbox + thread metadata.
|
||||
- **`agent/reviewer.py` → `get_reviewer_agent(config)`** — reviewer graph factory. Shares `ensure_sandbox_for_thread` with the main agent but wires a reviewer-only toolset (`add_finding`, `update_finding`, `list_findings`, `publish_review`, `web_search`, `fetch_url`, `http_request`) and a different system prompt that pins the single-evolving-findings model and the diff-anchored bar for filing a finding. Read-only: no commit/push/PR-opening tools.
|
||||
- **`agent/reviewer.py` → `get_reviewer_agent(config)`** — reviewer graph factory. Shares `ensure_sandbox_for_thread` with the main agent but wires a reviewer-only toolset (`add_finding`, `update_finding`, `list_findings`, `publish_review`, `web_search`, `fetch_url`, `http_request`) and a different system prompt that pins the single-evolving-findings model and the diff-anchored bar for filing a finding. Read-only: no commit/push/PR-opening tools. Its supporting modules live in the `agent/review/` package (domain reorg): `findings.py`, `publish.py`, `reconcile.py`, `trace_context.py`, `diff.py`, `groups.py`, `eval_store.py`, `style_collector.py`, `style_guidance.py`.
|
||||
- **`agent/analyzer.py` → `get_analyzer(config)`** — small graph that emits a per-repo style prompt via the `save_review_style_prompt` tool, consumed by the reviewer as a "repository-specific review style" appendix. It runs in one of two modes (`analyzer_mode` in `configurable`): **bootstrap** (cold-start: crawl historical PR reviews) and **continual** (nightly: refine using this reviewer's own finding outcomes via `read_finding_outcomes`). Each mode's procedure lives in a deepagents **skill** (`agent/skills/bootstrap-repo-analysis/`, `agent/skills/continual-learning/`) served as virtual files via a `CompositeBackend` `/skills/` route + `StateBackend` (seeded into the run's `files` channel by the launcher — never written to the sandbox). Launchers and the per-repo nightly cron live in `agent/dashboard/review_style_jobs.py` and `agent/dashboard/analyzer_cron.py`; the cron is registered when bootstrap completes.
|
||||
- **`agent/webapp.py`** — custom FastAPI routes mounted alongside the LangGraph server. Webhooks land here (GitHub, Linear, Slack, Jira, and the Confluence Atlassian Connect `/connect/*` routes). Each webhook resolves a deterministic `thread_id` (so follow-up messages route to the same agent run) and triggers/streams a run via the `langgraph_sdk` client. Also auto-reviews PRs on `opened` / `ready_for_review` events when the repo+author opt in. The Atlassian triggers (`agent/webhooks/{jira,confluence}.py`, `agent/utils/atlassian_connect.py`) verify webhook trust — a Jira Automation shared secret / optional HMAC, and a Confluence Connect HS256-JWT + `qsh` with RS256 signed-install — then re-fetch the triggering comment server-side before deriving identity.
|
||||
- **FastAPI layer (`agent/api/` + `agent/webhooks/`)** — custom FastAPI routes mounted alongside the LangGraph server. The domain reorg split the fork's former `agent/webapp.py` monolith into `agent/api/app.py` (app composition + router mounts), `agent/api/health.py` (`/health`, `/webhooks/run-complete`), shared helpers in `agent/webhooks/common.py`, and per-source route modules `agent/webhooks/{github,linear,slack,jira,confluence}_routes.py`; `agent/webapp.py` remains a compatibility shim re-exporting `app`. Webhooks land in those route modules (GitHub, Linear, Slack, Jira, and the Confluence Atlassian Connect `/connect/*` routes, which fold into `confluence_routes.py`). Each webhook resolves a deterministic `thread_id` (so follow-up messages route to the same agent run) and triggers/streams a run via the `langgraph_sdk` client. Also auto-reviews PRs on `opened` / `ready_for_review` events when the repo+author opt in. The Atlassian triggers (`agent/webhooks/{jira,confluence}.py`, `agent/utils/atlassian_connect.py`) verify webhook trust — a Jira Automation shared secret / optional HMAC, and a Confluence Connect HS256-JWT + `qsh` with RS256 signed-install — then re-fetch the triggering comment server-side before deriving identity.
|
||||
- **`agent/dashboard/`** — `router` mounted under the FastAPI app at startup (`app.include_router(dashboard_router)`). Owns GitHub OAuth, per-user profiles, admin endpoints, team defaults, enabled-repo lists, review-style management, and the Agents chat thread API used by the UI in `ui/`.
|
||||
|
||||
### Sandbox lifecycle (the tricky part)
|
||||
|
|
@ -114,10 +116,10 @@ Webhooks compute deterministic thread ids so the same Linear issue / Slack threa
|
|||
|
||||
## Conventions
|
||||
|
||||
- Tests are unit-only by default (`tests/`). Integration tests would go under `tests/integration_tests/` (currently empty — `make integration_tests` no-ops if missing).
|
||||
- Tests are unit-only by default and organized by domain under `tests/<domain>/` (`tests/agent/`, `tests/auth/`, `tests/github/`, `tests/webhooks/`, `tests/reviewer/`, `tests/sandbox/`, `tests/middleware/`, `tests/models/`, `tests/slack/`, `tests/tools/`, `tests/dashboard/`, `tests/analyzer/`); `tests/conftest.py` and the `tests/e2e/` harness stay at the top level. Integration tests would go under `tests/integration_tests/` (currently empty — `make integration_tests` no-ops if missing).
|
||||
- New sandbox providers: add a module under `agent/integrations/` and wire it into `SANDBOX_FACTORIES` in `agent/utils/sandbox.py`. See `CUSTOMIZATION.md`.
|
||||
- New tools: add to `agent/tools/`, export from `agent/tools/__init__.py`, add to the `tools=[...]` list in `server.py:get_agent` (or `reviewer.py` for reviewer-only tools).
|
||||
- New middleware: add to `agent/middleware/`, export from `agent/middleware/__init__.py`, add to the `middleware=[...]` list in `server.py:get_agent` — order is significant (see the stack above).
|
||||
- New dashboard endpoints: add to `agent/dashboard/routes.py`. The router is auto-mounted on the FastAPI app.
|
||||
- New graphs: register the entrypoint in `langgraph.json` under `graphs`.
|
||||
- New graphs: add an `agent/graphs/<name>.py` re-export shim that delegates to the factory module, then register the shim entrypoint (`agent.graphs.<name>:<symbol>`) in `langgraph.json` under `graphs`.
|
||||
- Minimal-to-no code comments — only when the *why* isn't obvious from the code.
|
||||
|
|
|
|||
33
CLAUDE.md
33
CLAUDE.md
|
|
@ -14,33 +14,40 @@ Dependencies are managed with **uv**. Tests use pytest (`asyncio_mode = "auto"`)
|
|||
|
||||
```bash
|
||||
make install # uv pip install -e .
|
||||
make dev # uv run langgraph dev — serves all three graphs + the FastAPI app from langgraph.json
|
||||
make dev # uv run langgraph dev — serves all six graphs + the FastAPI app from langgraph.json
|
||||
make run # uvicorn agent.webapp:app --reload --port 8000 (FastAPI only, no LangGraph runtime)
|
||||
make test # uv run pytest -vvv tests/
|
||||
make test TEST_FILE=tests/test_open_pr_middleware.py # single test file
|
||||
uv run pytest -vvv tests/test_open_pr_middleware.py::test_name # single test
|
||||
make test TEST_FILE=tests/github/test_open_pull_request.py # single test file
|
||||
uv run pytest -vvv tests/github/test_open_pull_request.py::test_name # single test
|
||||
make lint # ruff check + ruff format --diff
|
||||
make format # ruff format + ruff check --fix
|
||||
```
|
||||
|
||||
`langgraph.json` declares three graph entrypoints and the FastAPI app, all served together by `langgraph dev`:
|
||||
`langgraph.json` declares six graph entrypoints and the FastAPI app, all served together by `langgraph dev`. Since the domain reorg, every graph entrypoint targets a thin `agent.graphs.*` re-export shim (`agent/graphs/<name>.py`) rather than the factory module directly; the shims delegate to the unmoved factories (`agent/server.py`, `agent/reviewer.py`, `agent/analyzer.py`, `agent/chat.py`, `agent/scheduler.py`, `agent/ci_monitor.py`):
|
||||
|
||||
| Graph | Entrypoint | Purpose |
|
||||
|---|---|---|
|
||||
| `agent` | `agent.server:traced_agent` (wraps `get_agent`) | Main coding agent (Slack/Linear/Jira/Confluence/GitHub-triggered). |
|
||||
| `reviewer` | `agent.reviewer:traced_reviewer_agent` (wraps `get_reviewer_agent`) | Read-only PR reviewer. Findings model + `publish_review`. |
|
||||
| `analyzer` | `agent.analyzer:traced_analyzer` (wraps `get_analyzer`) | Learns per-repo reviewer style from historical PRs and this reviewer's own finding outcomes. |
|
||||
| `agent` | `agent.graphs.agent:traced_agent` (shim → `agent.server:get_agent`) | Main coding agent (Slack/Linear/Jira/Confluence/GitHub-triggered). |
|
||||
| `reviewer` | `agent.graphs.reviewer:traced_reviewer_agent` (shim → `agent.reviewer:get_reviewer_agent`) | Read-only PR reviewer. Findings model + `publish_review`. |
|
||||
| `analyzer` | `agent.graphs.analyzer:traced_analyzer` (shim → `agent.analyzer:get_analyzer`) | Learns per-repo reviewer style from historical PRs and this reviewer's own finding outcomes. |
|
||||
| `chat` | `agent.graphs.chat:traced_chat_agent` | Dashboard Agents chat graph. |
|
||||
| `scheduler` | `agent.graphs.scheduler:get_scheduler` | Reconcile sweep for stragglers. |
|
||||
| `ci_monitor` | `agent.graphs.ci_monitor:get_ci_monitor` | Fork-only polling fallback for the CI auto-fix flow. |
|
||||
|
||||
The FastAPI app is `agent.webapp:app`.
|
||||
The FastAPI app is `agent.webapp:app` — now a compatibility shim that re-exports `agent.api.app:app` (see the FastAPI split under "Entrypoints").
|
||||
|
||||
## Architecture
|
||||
|
||||
### Entrypoints
|
||||
|
||||
- **`agent/server.py` → `get_agent(config)`** — main graph factory. Called per-thread. Resolves the GitHub token, gets-or-creates the sandbox for the thread, resolves the team/profile/per-thread model + effort, then constructs a fresh `create_deep_agent(...)` with the curated tool list and middleware stack. The agent itself is stateless — all per-thread state lives in the sandbox + thread metadata.
|
||||
- **`agent/reviewer.py` → `get_reviewer_agent(config)`** — reviewer graph factory. Shares `ensure_sandbox_for_thread` with the main agent but wires a reviewer-only toolset (`add_finding`, `update_finding`, `list_findings`, `publish_review`, `web_search`, `fetch_url`, `http_request`) and a different system prompt that pins the single-evolving-findings model and the diff-anchored bar for filing a finding. Read-only: no commit/push/PR-opening tools.
|
||||
- **`agent/reviewer.py` → `get_reviewer_agent(config)`** — reviewer graph factory. Shares `ensure_sandbox_for_thread` with the main agent but wires a reviewer-only toolset (`add_finding`, `update_finding`, `list_findings`, `publish_review`, `web_search`, `fetch_url`, `http_request`) and a different system prompt that pins the single-evolving-findings model and the diff-anchored bar for filing a finding. Read-only: no commit/push/PR-opening tools. Its supporting modules live in the **`agent/review/`** package (domain reorg): `findings.py`, `publish.py`, `reconcile.py`, `trace_context.py`, `diff.py`, `groups.py`, `eval_store.py`, `style_collector.py`, `style_guidance.py`.
|
||||
- **`agent/analyzer.py` → `get_analyzer(config)`** — small graph that emits a per-repo style prompt via the `save_review_style_prompt` tool, consumed by the reviewer as a "repository-specific review style" appendix. It runs in one of two modes (`analyzer_mode` in `configurable`): **bootstrap** (cold-start: crawl historical PR reviews) and **continual** (nightly: refine using this reviewer's own finding outcomes via `read_finding_outcomes`). Each mode's procedure lives in a deepagents **skill** (`agent/skills/bootstrap-repo-analysis/`, `agent/skills/continual-learning/`) served as virtual files via a `CompositeBackend` `/skills/` route + `StateBackend` (seeded into the run's `files` channel by the launcher — never written to the sandbox). Launchers and the per-repo nightly cron live in `agent/dashboard/review_style_jobs.py` and `agent/dashboard/analyzer_cron.py`; the cron is registered when bootstrap completes.
|
||||
- **`agent/webapp.py`** — thin FastAPI routing layer mounted alongside the LangGraph server. Defines the webhook routes (GitHub, Linear, Slack, Jira, Confluence Connect `/connect/*`) plus `/webhooks/run-complete`, and keeps the shared helpers/constants; the per-source handlers live in **`agent/webhooks/{github,slack,linear,jira,confluence}.py`** (re-exported from `webapp` so existing call sites and tests keep working). Each webhook resolves a deterministic `thread_id` (so follow-up messages route to the same agent run) and triggers a run through the single durable dispatch contract in **`agent/dispatch.py`** (`dispatch_agent_run`: `multitask_strategy="interrupt"` + `durability="sync"` + completion webhook); `agent/completion.py` posts a failure reply if a run dies, and `agent/reconcile.py` (a `scheduler`-graph sweep) catches stragglers. The GitHub handler also auto-reviews PRs on `opened` / `ready_for_review` and drives the CI auto-fix flow (`agent/ci_autofix.py`).
|
||||
- **FastAPI layer (`agent/api/` + `agent/webhooks/`)** — the domain reorg split the fork's former 2,590-line `agent/webapp.py` monolith into a per-source layout; `agent/webapp.py` is now a 4-line compatibility shim (`from .api.app import app`). The pieces:
|
||||
- **`agent/api/app.py`** composes the FastAPI app and mounts every router; **`agent/api/health.py`** owns `/health` and `/webhooks/run-complete`.
|
||||
- **`agent/webhooks/common.py`** holds the shared verify/dispatch helpers and constants (signature verification, thread-id derivation, the dispatch entry). Route modules and handlers reach these via **module-attribute access** (`common.X`) so the ~240 `monkeypatch.setattr` test sites retarget cleanly.
|
||||
- Per-source **route modules** `agent/webhooks/{github,linear,slack,jira,confluence}_routes.py` define the HTTP routes (GitHub, Linear, Slack, Jira, and the Confluence Atlassian Connect `/connect/*` + `/webhooks/jira` routes; the fork-only `jira_routes.py`/`confluence_routes.py` mirror upstream's `github_routes.py` pattern, and the Connect lifecycle/descriptor routes fold into `confluence_routes.py`). The **handler modules** `agent/webhooks/{github,slack,linear,jira,confluence}.py` carry the per-source logic.
|
||||
- Each webhook resolves a deterministic `thread_id` (so follow-up messages route to the same agent run) and triggers a run through the single durable dispatch contract in **`agent/dispatch.py`** (`dispatch_agent_run`: `multitask_strategy="interrupt"` + `durability="sync"` + completion webhook); `agent/completion.py` posts a failure reply if a run dies, and `agent/reconcile.py` (a `scheduler`-graph sweep) catches stragglers. The GitHub handler also auto-reviews PRs on `opened` / `ready_for_review` and drives the CI auto-fix flow (`agent/ci_autofix.py`).
|
||||
- **Atlassian triggers.** The Jira trigger is a Jira **Automation** rule POSTing to `/webhooks/jira` with a shared-secret header (`verify_jira_secret`; optional HMAC-body+timestamp via `JIRA_WEBHOOK_REQUIRE_SIGNATURE`), since Jira Cloud has no native webhook signing. The Confluence trigger is a private **Atlassian Connect app** (`agent/utils/atlassian_connect.py`): the `comment_created` webhook is HS256-JWT-verified against the per-tenant stored `sharedSecret` with a hand-rolled `qsh` (query-string-hash) check; `signed-install` is on, so install/uninstall lifecycle callbacks are RS256-verified against Atlassian's published keys (no trust-on-first-use). Install secrets are stored **encrypted** in the LangGraph store, keyed by `clientKey`. Both Atlassian webhook bodies are treated as pointers only — the triggering comment's real author/text is re-fetched server-side via the Basic-auth service account before anything security-relevant is derived, and attribution is gated on an active user mapping (mirrors the GitHub-login / token-attribution flow). Descriptor served at `GET /connect/atlassian-connect.json`.
|
||||
- **`agent/dashboard/`** — `router` mounted under the FastAPI app at startup (`app.include_router(dashboard_router)`). Owns GitHub OAuth, per-user profiles, admin endpoints, team defaults, enabled-repo lists, review-style management, and the Agents chat thread API used by the UI in `ui/`.
|
||||
|
||||
|
|
@ -112,17 +119,17 @@ Webhooks compute deterministic thread ids so the same Linear issue / Slack threa
|
|||
|
||||
## Conventions
|
||||
|
||||
- Tests are unit-only by default (`tests/`). Integration tests would go under `tests/integration_tests/` (currently empty — `make integration_tests` no-ops if missing).
|
||||
- Tests are unit-only by default and organized by domain under `tests/<domain>/` (`tests/agent/`, `tests/auth/`, `tests/github/`, `tests/webhooks/`, `tests/reviewer/`, `tests/sandbox/`, `tests/middleware/`, `tests/models/`, `tests/slack/`, `tests/tools/`, `tests/dashboard/`, `tests/analyzer/`); `tests/conftest.py` and the `tests/e2e/` harness stay at the top level. Integration tests would go under `tests/integration_tests/` (currently empty — `make integration_tests` no-ops if missing).
|
||||
- New sandbox providers: add a module under `agent/integrations/` and wire it into `SANDBOX_FACTORIES` in `agent/utils/sandbox.py`. See `CUSTOMIZATION.md`.
|
||||
- New tools: add to `agent/tools/`, export from `agent/tools/__init__.py`, add to the `tools=[...]` list in `server.py:get_agent` (or `reviewer.py` for reviewer-only tools).
|
||||
- New middleware: add to `agent/middleware/`, export from `agent/middleware/__init__.py`, add to the `middleware=[...]` list in `server.py:get_agent` — order is significant (see the stack above).
|
||||
- New dashboard endpoints: add to `agent/dashboard/routes.py`. The router is auto-mounted on the FastAPI app.
|
||||
- New graphs: register the entrypoint in `langgraph.json` under `graphs`.
|
||||
- New graphs: add an `agent/graphs/<name>.py` re-export shim that delegates to the factory module, then register the shim entrypoint (`agent.graphs.<name>:<symbol>`) in `langgraph.json` under `graphs`.
|
||||
- Minimal-to-no code comments — only when the *why* isn't obvious from the code.
|
||||
|
||||
## Fork maintenance — syncing `upstream/main`
|
||||
|
||||
This is a long-lived fork of `langchain-ai/open-swe` with Sea Haven customizations woven into upstream-owned files (notably `agent/prompt.py` prompt constants, `agent/webapp.py`, and the tool/middleware wiring). Merging upstream is a triage exercise, not a fast-forward. When you want upstream's clean changes but must **defer a large structural refactor** (and its entangled features), work in this order:
|
||||
This is a long-lived fork of `langchain-ai/open-swe` with Sea Haven customizations woven into upstream-owned files (notably `agent/prompt.py` prompt constants, the `agent/api/` + `agent/webhooks/` FastAPI layer — the former `agent/webapp.py` monolith, now split and left as a shim — and the tool/middleware wiring). Merging upstream is a triage exercise, not a fast-forward. When you want upstream's clean changes but must **defer a large structural refactor** (and its entangled features), work in this order:
|
||||
|
||||
1. **Triage before resolving.** Merge-base is `git merge-base HEAD upstream/main`. The truthful conflict set is the combined merge, `git merge-tree --write-tree --name-only HEAD upstream/main` — a per-commit probe against each commit's parent *overstates* conflicts (a file a refactor merely added shows up as a phantom `modify/delete`). Decide keep-baseline vs adopt-refactor **before** resolving, and surface the choice to a human for any auth/webhook/IAM surface.
|
||||
2. **Chase the cascade, not just the textual conflicts.** The hard part is the non-conflicting files the refactor also touched. Get the refactor's file set (`git diff-tree --no-commit-id --name-status -r <refactor-sha>`) and cross-reference the files this fork modified (`git diff --name-only <fork-base> HEAD`). Files in both = hand-resolve; files only the refactor touched = mechanical.
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -1,9 +1,9 @@
|
|||
<div align="center">
|
||||
<a href="https://github.com/langchain-ai/open-swe">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="static/dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="static/light.svg">
|
||||
<img alt="Open SWE Logo" src="static/dark.svg" width="35%">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="assets/dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="assets/light.svg">
|
||||
<img alt="Open SWE Logo" src="assets/dark.svg" width="35%">
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -54,7 +54,7 @@ create_deep_agent(
|
|||
|
||||
Every task runs in its own **isolated cloud sandbox** — a remote Linux environment with full shell access. The repo is cloned in, the agent gets full permissions, and the blast radius of any mistake is fully contained. No production access, no confirmation prompts.
|
||||
|
||||
Open SWE supports multiple sandbox providers out of the box — [Modal](https://modal.com/), [Daytona](https://www.daytona.io/), [Runloop](https://www.runloop.ai/), [E2B](https://e2b.dev/), and [LangSmith](https://smith.langchain.com/) — and you can plug in your own. See the [Customization Guide](CUSTOMIZATION.md#1-sandbox) for details.
|
||||
Open SWE supports multiple sandbox providers out of the box — [Modal](https://modal.com/), [Daytona](https://www.daytona.io/), [Runloop](https://www.runloop.ai/), [E2B](https://e2b.dev/), and [LangSmith](https://smith.langchain.com/) — and you can plug in your own. See the [Customization Guide](docs/CUSTOMIZATION.md#1-sandbox) for details.
|
||||
|
||||
This follows the principle all three companies converge on: **isolate first, then give full permissions inside the boundary.**
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ All three companies in the article converge on **Slack as the primary invocation
|
|||
- **Confluence** — Comment `@openswe` on a page. A private Atlassian Connect app delivers the `comment_created` event; the agent acts and replies on the page.
|
||||
- **GitHub** — Tag `@openswe` in PR comments on agent-created PRs to have it address review feedback and push fixes to the same branch.
|
||||
|
||||
See **[INSTALLATION.md](./INSTALLATION.md) §5** for per-surface trigger setup.
|
||||
See **[INSTALLATION.md](./docs/INSTALLATION.md) §5** for per-surface trigger setup.
|
||||
|
||||
Each invocation creates a deterministic thread ID, so follow-up messages on the same issue or thread route to the same running agent.
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ Each invocation creates a deterministic thread ID, so follow-up messages on the
|
|||
### 7. Validation — Prompt-Driven
|
||||
|
||||
The agent is instructed to run linters, formatters, and tests before committing, and is responsible end-to-end for committing, pushing, opening/updating the draft PR, and replying in the source channel.
|
||||
This is an area where you can extend Open SWE for your org: add deterministic CI checks, visual verification, or review gates as additional middleware. See the [Customization Guide](CUSTOMIZATION.md#6-middleware) for how.
|
||||
This is an area where you can extend Open SWE for your org: add deterministic CI checks, visual verification, or review gates as additional middleware. See the [Customization Guide](docs/CUSTOMIZATION.md#6-middleware) for how.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -156,8 +156,8 @@ This is an area where you can extend Open SWE for your org: add deterministic CI
|
|||
|
||||
## Getting Started
|
||||
|
||||
- **[Installation Guide](INSTALLATION.md)** — local dev (backend + dashboard), GitHub App creation, LangSmith, Linear/Slack/GitHub triggers, and production deployment
|
||||
- **[Customization Guide](CUSTOMIZATION.md)** — swap the sandbox, model, tools, triggers, system prompt, and middleware for your org
|
||||
- **[Installation Guide](docs/INSTALLATION.md)** — local dev (backend + dashboard), GitHub App creation, LangSmith, Linear/Slack/GitHub triggers, and production deployment
|
||||
- **[Customization Guide](docs/CUSTOMIZATION.md)** — swap the sandbox, model, tools, triggers, system prompt, and middleware for your org
|
||||
|
||||
## Deployment (Sea Haven fork)
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ and secrets live in the LangGraph deployment config and Vercel environment
|
|||
variables. Promotion from `dev` to `prod` (`main`) is handled by
|
||||
[`.github/workflows/promote-to-main.yml`](.github/workflows/promote-to-main.yml).
|
||||
|
||||
See **[INSTALLATION.md § 10 "Production deployment"](INSTALLATION.md#10-production-deployment)**
|
||||
See **[INSTALLATION.md § 10 "Production deployment"](docs/INSTALLATION.md#10-production-deployment)**
|
||||
for the full backend + dashboard setup.
|
||||
|
||||
> The earlier self-hosted AWS stack (CDK under `infra/`, an ARM64 EC2 box + nginx
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ from .middleware import (
|
|||
TimeoutWrapupMiddleware,
|
||||
ToolErrorMiddleware,
|
||||
)
|
||||
from .review_style_guidance import REVIEWER_STYLE_THEMES
|
||||
from .review.style_guidance import REVIEWER_STYLE_THEMES
|
||||
from .server import (
|
||||
DEFAULT_LLM_MAX_TOKENS,
|
||||
DEFAULT_LLM_MODEL_ID,
|
||||
|
|
|
|||
1
agent/api/__init__.py
Normal file
1
agent/api/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""FastAPI application composition."""
|
||||
57
agent/api/app.py
Normal file
57
agent/api/app.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"""FastAPI application composition."""
|
||||
|
||||
import os
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from ..dashboard import router as dashboard_router
|
||||
from ..dashboard.plan_api import plan_router
|
||||
from ..dashboard.workflow_approval_api import workflow_approval_router
|
||||
from ..webhooks.confluence_routes import router as confluence_webhook_router
|
||||
from ..webhooks.github_routes import router as github_webhook_router
|
||||
from ..webhooks.jira_routes import router as jira_webhook_router
|
||||
from ..webhooks.linear_routes import router as linear_webhook_router
|
||||
from ..webhooks.slack_routes import router as slack_webhook_router
|
||||
from .health import router as health_router
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
||||
from ..utils.model import validate_local_dev_llm_config
|
||||
from ..utils.sandbox import validate_sandbox_startup_config
|
||||
|
||||
validate_sandbox_startup_config()
|
||||
validate_local_dev_llm_config()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
DASHBOARD_ALLOWED_ORIGINS: list[str] = [
|
||||
o.strip() for o in os.environ.get("DASHBOARD_ALLOWED_ORIGINS", "").split(",") if o.strip()
|
||||
]
|
||||
if DASHBOARD_ALLOWED_ORIGINS:
|
||||
if "*" in DASHBOARD_ALLOWED_ORIGINS:
|
||||
raise RuntimeError(
|
||||
"DASHBOARD_ALLOWED_ORIGINS must not include '*' when allow_credentials=True"
|
||||
)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=DASHBOARD_ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(dashboard_router)
|
||||
app.include_router(plan_router)
|
||||
app.include_router(workflow_approval_router)
|
||||
app.include_router(linear_webhook_router)
|
||||
app.include_router(jira_webhook_router)
|
||||
app.include_router(confluence_webhook_router)
|
||||
app.include_router(slack_webhook_router)
|
||||
app.include_router(health_router)
|
||||
app.include_router(github_webhook_router)
|
||||
27
agent/api/health.py
Normal file
27
agent/api/health.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""Health and run-completion routes."""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
|
||||
from ..completion import handle_run_completion, verify_run_complete_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check() -> dict[str, str]:
|
||||
"""Health check endpoint."""
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
@router.post("/webhooks/run-complete")
|
||||
async def run_complete_webhook(request: Request) -> dict[str, str]:
|
||||
"""Platform run-completion webhook: post a failure reply for runs that died."""
|
||||
if not verify_run_complete_token(request.query_params.get("token")):
|
||||
raise HTTPException(status_code=401, detail="Invalid run-complete token")
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception: # noqa: BLE001
|
||||
return {"status": "error", "message": "Invalid JSON"}
|
||||
if not isinstance(payload, dict):
|
||||
return {"status": "ignored", "reason": "payload not an object"}
|
||||
return await handle_run_completion(payload)
|
||||
|
|
@ -4,7 +4,7 @@ This is the shared core for "PR babysitting": when a CI check fails (or a
|
|||
reviewer leaves actionable feedback) on a PR that Open SWE opened, locate the
|
||||
originating agent thread and dispatch a confidence-gated fix run on it.
|
||||
|
||||
Both the GitHub webhook path (:mod:`agent.webapp`) and the polling fallback
|
||||
Both the GitHub webhook path (:mod:`agent.webhooks.github_routes`) and the polling fallback
|
||||
(:mod:`agent.ci_monitor`) call into here, so all the skip-rules, dedupe, and
|
||||
loop-capping live in one place. Skip-rules mirror Cursor/Claude Code:
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ from .dashboard.agent_overrides import load_profile, resolve_login_from_email_as
|
|||
from .dashboard.autofix_state import is_pr_autofix_disabled
|
||||
from .dashboard.enabled_repos import is_review_repo_enabled
|
||||
from .dispatch import dispatch_agent_run
|
||||
from .reviewer_findings import REVIEWER_THREAD_KIND
|
||||
from .review.findings import REVIEWER_THREAD_KIND
|
||||
from .utils.dashboard_links import dashboard_thread_url
|
||||
from .utils.github_app import get_github_app_installation_token
|
||||
from .utils.github_checks import post_autofix_status_check
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
``router`` is loaded lazily (PEP 562): importing any dashboard submodule
|
||||
(e.g. ``agent.dashboard.options`` from middleware) executes this __init__,
|
||||
and it must NOT drag in routes.py + FastAPI + every API/job module. Only the
|
||||
webapp, which actually mounts the router, pays that cost.
|
||||
API app (``agent.api.app``), which actually mounts the router, pays that cost.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from typing import Any, Literal
|
|||
import httpx
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
from ..reviewer_findings import REVIEWER_THREAD_KIND
|
||||
from ..review.findings import REVIEWER_THREAD_KIND
|
||||
from ..utils.github_app import get_github_app_installation_token
|
||||
|
||||
USAGE_THREAD_NAMESPACE: list[str] = ["agent_usage", "threads"]
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ from typing import Any, Literal, TypedDict
|
|||
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
from agent.reviewer_eval_store import (
|
||||
from agent.review.eval_store import (
|
||||
_HEARTBEAT_STALE_SECONDS,
|
||||
DEFAULT_EVAL_PROJECT,
|
||||
EVALS_NAMESPACE,
|
||||
REVIEWER_EVAL_KEY,
|
||||
)
|
||||
from agent.reviewer_findings import REVIEW_FINDING_CAP
|
||||
from agent.review.findings import REVIEW_FINDING_CAP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from urllib.parse import urljoin, urlparse
|
|||
import httpx
|
||||
from fastapi import HTTPException, Response
|
||||
|
||||
from ..reviewer_findings import REVIEWER_THREAD_KIND
|
||||
from ..review.findings import REVIEWER_THREAD_KIND
|
||||
from ..utils.github_app import get_github_app_installation_token
|
||||
from ..utils.github_checks import github_headers
|
||||
from ..utils.thread_ops import langgraph_client
|
||||
|
|
@ -443,7 +443,7 @@ async def create_review_comment(
|
|||
|
||||
|
||||
_HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
|
||||
# Inline comments the reviewer posts carry this hidden marker (see reviewer_publish).
|
||||
# Inline comments the reviewer posts carry this hidden marker (see review.publish).
|
||||
_OPEN_SWE_COMMENT_RE = re.compile(r"<!--\s*open-swe-review-comment\b")
|
||||
_REVIEW_COMMENTS_PER_PAGE = 100
|
||||
# Bound the fetch so a pathological PR can't trigger unbounded paging (~2000 comments).
|
||||
|
|
@ -697,7 +697,7 @@ async def proxy_pr_image(owner: str, repo: str, pr_number: int, url: str) -> Res
|
|||
|
||||
async def trigger_re_review(owner: str, repo: str, pr_number: int, login: str) -> dict[str, Any]:
|
||||
from ..utils.slack import GitHubPrRef
|
||||
from ..webapp import trigger_pr_review_from_ref
|
||||
from ..webhooks.github import trigger_pr_review_from_ref
|
||||
|
||||
pr_ref = GitHubPrRef(
|
||||
owner=owner,
|
||||
|
|
@ -715,10 +715,10 @@ async def dry_run_trace_resolution(owner: str, repo: str, pr_number: int) -> dic
|
|||
"""Resolve a PR to its author coding-agent thread without running a review."""
|
||||
from dataclasses import asdict
|
||||
|
||||
from ..reviewer_trace_context import resolve_pr_trace
|
||||
from ..review.trace_context import resolve_pr_trace
|
||||
from ..utils.github_app import get_github_app_installation_token_with_expiry
|
||||
from ..utils.slack import GitHubPrRef
|
||||
from ..webapp import fetch_github_pr_metadata
|
||||
from ..webhooks.common import fetch_github_pr_metadata
|
||||
|
||||
pr_ref = GitHubPrRef(
|
||||
owner=owner,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ from typing import Any
|
|||
import httpx
|
||||
from fastapi import HTTPException
|
||||
|
||||
from ..reviewer_diff import fetch_pr_diff
|
||||
from ..reviewer_findings import REVIEWER_THREAD_KIND
|
||||
from ..review.diff import fetch_pr_diff
|
||||
from ..review.findings import REVIEWER_THREAD_KIND
|
||||
from ..utils.github_app import get_github_app_installation_token
|
||||
from ..utils.thread_ops import langgraph_client, langgraph_url
|
||||
from .options import SUPPORTED_MODEL_IDS, model_supports_effort
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from typing import Any
|
|||
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
from ..review_style_collector import (
|
||||
from ..review.style_collector import (
|
||||
collect_review_samples,
|
||||
format_samples_for_analyzer,
|
||||
generate_review_style_thread_id,
|
||||
|
|
@ -29,7 +29,7 @@ _ASSISTANT_ID = "analyzer"
|
|||
|
||||
|
||||
def _client():
|
||||
"""LangGraph SDK client for the current deployment (same resolution as webapp)."""
|
||||
"""LangGraph SDK client for the current deployment (same resolution as webhook common)."""
|
||||
url = os.environ.get("LANGGRAPH_URL") or os.environ.get("LANGGRAPH_URL_PROD")
|
||||
if url:
|
||||
return get_client(url=url)
|
||||
|
|
|
|||
1
agent/graphs/__init__.py
Normal file
1
agent/graphs/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__all__: list[str] = []
|
||||
3
agent/graphs/agent.py
Normal file
3
agent/graphs/agent.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.server import get_agent, traced_agent
|
||||
|
||||
__all__ = ["get_agent", "traced_agent"]
|
||||
3
agent/graphs/analyzer.py
Normal file
3
agent/graphs/analyzer.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.analyzer import get_analyzer, traced_analyzer
|
||||
|
||||
__all__ = ["get_analyzer", "traced_analyzer"]
|
||||
3
agent/graphs/chat.py
Normal file
3
agent/graphs/chat.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.chat import get_chat_agent, traced_chat_agent
|
||||
|
||||
__all__ = ["get_chat_agent", "traced_chat_agent"]
|
||||
3
agent/graphs/ci_monitor.py
Normal file
3
agent/graphs/ci_monitor.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.ci_monitor import get_ci_monitor
|
||||
|
||||
__all__ = ["get_ci_monitor"]
|
||||
3
agent/graphs/reviewer.py
Normal file
3
agent/graphs/reviewer.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.reviewer import get_reviewer_agent, traced_reviewer_agent
|
||||
|
||||
__all__ = ["get_reviewer_agent", "traced_reviewer_agent"]
|
||||
3
agent/graphs/scheduler.py
Normal file
3
agent/graphs/scheduler.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.scheduler import get_scheduler
|
||||
|
||||
__all__ = ["get_scheduler"]
|
||||
|
|
@ -16,8 +16,8 @@ from langchain.agents.middleware import AgentState, after_agent
|
|||
from langgraph.config import get_config
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
from ..reviewer_findings import get_thread_metadata
|
||||
from ..reviewer_publish import settle_review_check_run
|
||||
from ..review.findings import get_thread_metadata
|
||||
from ..review.publish import settle_review_check_run
|
||||
from ..utils.github_token import get_github_token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import logging
|
||||
import os
|
||||
import shlex
|
||||
from importlib import resources
|
||||
from pathlib import Path
|
||||
|
||||
from deepagents import HarnessProfile, register_harness_profile
|
||||
|
|
@ -14,10 +15,7 @@ from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PROMPT_PATH = os.environ.get(
|
||||
"DEFAULT_PROMPT_PATH",
|
||||
str(Path(__file__).resolve().parent.parent / "default_prompt.md"),
|
||||
)
|
||||
DEFAULT_PROMPT_PATH = os.environ.get("DEFAULT_PROMPT_PATH")
|
||||
|
||||
# Tools stripped from the agent regardless of run state (none today: plan-mode
|
||||
# tool stripping is dynamic and handled by PlanModeMiddleware, not the profile).
|
||||
|
|
@ -37,19 +35,28 @@ def _load_default_prompt() -> str:
|
|||
Returns empty string if the file doesn't exist or can't be read.
|
||||
"""
|
||||
try:
|
||||
path = Path(DEFAULT_PROMPT_PATH)
|
||||
if path.is_file():
|
||||
content = path.read_text().strip()
|
||||
if content:
|
||||
# Escape curly braces so .format() doesn't choke on them
|
||||
escaped = content.replace("{", "{{").replace("}", "}}")
|
||||
return f"""---
|
||||
if DEFAULT_PROMPT_PATH:
|
||||
content = Path(DEFAULT_PROMPT_PATH).read_text().strip()
|
||||
else:
|
||||
content = (
|
||||
resources.files("agent.resources")
|
||||
.joinpath("default_prompt.md")
|
||||
.read_text(encoding="utf-8")
|
||||
.strip()
|
||||
)
|
||||
if content:
|
||||
# Escape curly braces so .format() doesn't choke on them
|
||||
escaped = content.replace("{", "{{").replace("}", "}}")
|
||||
return f"""---
|
||||
|
||||
### Custom Instructions
|
||||
|
||||
{escaped}"""
|
||||
except Exception:
|
||||
logger.warning("Failed to read default prompt file at %s", DEFAULT_PROMPT_PATH)
|
||||
logger.warning(
|
||||
"Failed to read default prompt from %s",
|
||||
DEFAULT_PROMPT_PATH or "agent.resources/default_prompt.md",
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
|
|
|
|||
15
agent/providers/__init__.py
Normal file
15
agent/providers/__init__.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from agent.utils.model import (
|
||||
DEFAULT_LLM_REASONING,
|
||||
ModelKwargs,
|
||||
fallback_model_id_for,
|
||||
make_model,
|
||||
provider_model_kwargs,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DEFAULT_LLM_REASONING",
|
||||
"ModelKwargs",
|
||||
"fallback_model_id_for",
|
||||
"make_model",
|
||||
"provider_model_kwargs",
|
||||
]
|
||||
1
agent/resources/__init__.py
Normal file
1
agent/resources/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__all__: list[str] = []
|
||||
11
agent/review/__init__.py
Normal file
11
agent/review/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from agent.review.findings import (
|
||||
REVIEW_FINDING_CAP,
|
||||
REVIEWER_THREAD_KIND,
|
||||
Finding,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Finding",
|
||||
"REVIEWER_THREAD_KIND",
|
||||
"REVIEW_FINDING_CAP",
|
||||
]
|
||||
|
|
@ -217,7 +217,7 @@ async def fetch_pr_diff(
|
|||
"""
|
||||
import httpx
|
||||
|
||||
from .utils.github_http import github_client, github_request
|
||||
from ..utils.github_http import github_client, github_request
|
||||
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
|
||||
try:
|
||||
|
|
@ -253,7 +253,7 @@ async def fetch_pr_metadata(
|
|||
"""
|
||||
import httpx
|
||||
|
||||
from .utils.github_http import github_client, github_request
|
||||
from ..utils.github_http import github_client, github_request
|
||||
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
|
||||
try:
|
||||
|
|
@ -22,8 +22,8 @@ from typing import TypedDict
|
|||
from langchain_core.language_models.chat_models import BaseChatModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .reviewer_diff import parse_unified_diff
|
||||
from .reviewer_findings import get_thread_metadata, set_reviewer_thread_metadata
|
||||
from .diff import parse_unified_diff
|
||||
from .findings import get_thread_metadata, set_reviewer_thread_metadata
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -25,22 +25,22 @@ from typing import Any, TypedDict
|
|||
|
||||
import httpx
|
||||
|
||||
from .reviewer_findings import (
|
||||
from ..utils.dashboard_links import dashboard_thread_url
|
||||
from ..utils.github_checks import CheckConclusion, complete_review_check_run
|
||||
from ..utils.github_http import (
|
||||
GITHUB_API_BASE,
|
||||
GITHUB_GRAPHQL,
|
||||
github_client,
|
||||
github_request,
|
||||
)
|
||||
from ..utils.github_token import GitHubAuthError
|
||||
from .findings import (
|
||||
DiffSide,
|
||||
Finding,
|
||||
get_thread_metadata,
|
||||
normalize_finding_title,
|
||||
set_reviewer_thread_metadata,
|
||||
)
|
||||
from .utils.dashboard_links import dashboard_thread_url
|
||||
from .utils.github_checks import CheckConclusion, complete_review_check_run
|
||||
from .utils.github_http import (
|
||||
GITHUB_API_BASE,
|
||||
GITHUB_GRAPHQL,
|
||||
github_client,
|
||||
github_request,
|
||||
)
|
||||
from .utils.github_token import GitHubAuthError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -2,14 +2,14 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from .reviewer_findings import (
|
||||
from .findings import (
|
||||
Finding,
|
||||
FindingInteraction,
|
||||
_coerce_surface,
|
||||
list_findings,
|
||||
replace_findings,
|
||||
)
|
||||
from .reviewer_publish import parse_review_comment_marker
|
||||
from .publish import parse_review_comment_marker
|
||||
|
||||
ReviewThread = dict[str, Any]
|
||||
ReviewThreadMatch = tuple[ReviewThread, int | None]
|
||||
|
|
@ -14,10 +14,10 @@ from typing import Any
|
|||
|
||||
from deepagents.backends.protocol import SandboxBackendProtocol
|
||||
|
||||
from .dashboard.team_credentials import get_langsmith_credentials
|
||||
from .dashboard.team_settings import get_team_review_tracing_project
|
||||
from .integrations.langsmith_tools import _client
|
||||
from .utils.langsmith import get_langsmith_trace_url
|
||||
from ..dashboard.team_credentials import get_langsmith_credentials
|
||||
from ..dashboard.team_settings import get_team_review_tracing_project
|
||||
from ..integrations.langsmith_tools import _client
|
||||
from ..utils.langsmith import get_langsmith_trace_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -55,17 +55,17 @@ from .middleware import (
|
|||
refresh_github_proxy_before_model,
|
||||
settle_review_check_on_exit,
|
||||
)
|
||||
from .reviewer_diff import compute_diff_line_set, fetch_pr_diff, fetch_pr_metadata
|
||||
from .reviewer_findings import (
|
||||
from .review.diff import compute_diff_line_set, fetch_pr_diff, fetch_pr_metadata
|
||||
from .review.findings import (
|
||||
REVIEW_FINDING_CAP,
|
||||
)
|
||||
from .reviewer_findings import (
|
||||
from .review.findings import (
|
||||
list_findings as list_findings_async,
|
||||
)
|
||||
from .reviewer_groups import maybe_generate_and_store_diff_groups
|
||||
from .reviewer_publish import fetch_pr_review_threads
|
||||
from .reviewer_reconcile import reconcile_findings_with_review_threads
|
||||
from .reviewer_trace_context import (
|
||||
from .review.groups import maybe_generate_and_store_diff_groups
|
||||
from .review.publish import fetch_pr_review_threads
|
||||
from .review.reconcile import reconcile_findings_with_review_threads
|
||||
from .review.trace_context import (
|
||||
PRTraceContext,
|
||||
format_pr_trace_context_prompt,
|
||||
prepare_pr_trace_context,
|
||||
|
|
|
|||
25
agent/runtime/__init__.py
Normal file
25
agent/runtime/__init__.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from .constants import (
|
||||
DEFAULT_LLM_MAX_TOKENS,
|
||||
DEFAULT_LLM_MODEL_ID,
|
||||
DEFAULT_RECURSION_LIMIT,
|
||||
MODEL_CALL_RECURSION_LIMIT,
|
||||
)
|
||||
from .execution import graph_loaded_for_execution
|
||||
from .sandbox import (
|
||||
configure_git_identity,
|
||||
ensure_sandbox_for_thread,
|
||||
get_cached_sandbox_backend,
|
||||
recreate_sandbox,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DEFAULT_LLM_MAX_TOKENS",
|
||||
"DEFAULT_LLM_MODEL_ID",
|
||||
"DEFAULT_RECURSION_LIMIT",
|
||||
"MODEL_CALL_RECURSION_LIMIT",
|
||||
"configure_git_identity",
|
||||
"ensure_sandbox_for_thread",
|
||||
"get_cached_sandbox_backend",
|
||||
"graph_loaded_for_execution",
|
||||
"recreate_sandbox",
|
||||
]
|
||||
13
agent/runtime/constants.py
Normal file
13
agent/runtime/constants.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from agent.server import (
|
||||
DEFAULT_LLM_MAX_TOKENS,
|
||||
DEFAULT_LLM_MODEL_ID,
|
||||
DEFAULT_RECURSION_LIMIT,
|
||||
MODEL_CALL_RECURSION_LIMIT,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DEFAULT_LLM_MAX_TOKENS",
|
||||
"DEFAULT_LLM_MODEL_ID",
|
||||
"DEFAULT_RECURSION_LIMIT",
|
||||
"MODEL_CALL_RECURSION_LIMIT",
|
||||
]
|
||||
3
agent/runtime/execution.py
Normal file
3
agent/runtime/execution.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from agent.server import graph_loaded_for_execution
|
||||
|
||||
__all__ = ["graph_loaded_for_execution"]
|
||||
53
agent/runtime/sandbox.py
Normal file
53
agent/runtime/sandbox.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
from collections.abc import Sequence
|
||||
|
||||
from deepagents.backends.protocol import SandboxBackendProtocol
|
||||
|
||||
|
||||
async def ensure_sandbox_for_thread(
|
||||
thread_id: str,
|
||||
*,
|
||||
github_proxy_token: str | None = None,
|
||||
github_proxy_repositories: Sequence[str] | None = None,
|
||||
repo: dict[str, str] | None = None,
|
||||
) -> SandboxBackendProtocol:
|
||||
from agent.server import ensure_sandbox_for_thread as ensure
|
||||
|
||||
return await ensure(
|
||||
thread_id,
|
||||
github_proxy_token=github_proxy_token,
|
||||
github_proxy_repositories=github_proxy_repositories,
|
||||
repo=repo,
|
||||
)
|
||||
|
||||
|
||||
def get_cached_sandbox_backend(thread_id: str) -> SandboxBackendProtocol:
|
||||
# Fork's sync 4-case `__creating__` sentinel lifecycle (see AGENTS.md) handles
|
||||
# reconnection internally in `ensure_sandbox_for_thread`/`check_or_recreate_sandbox`,
|
||||
# so unlike upstream's shim there is no caller-supplied `reconnect` callback here:
|
||||
# fork's `_get_cached_sandbox_backend` is a plain cache lookup.
|
||||
from agent.server import _get_cached_sandbox_backend as get_backend
|
||||
|
||||
return get_backend(thread_id)
|
||||
|
||||
|
||||
async def configure_git_identity(sandbox_backend: SandboxBackendProtocol) -> None:
|
||||
from agent.server import _configure_git_identity as configure
|
||||
|
||||
await configure(sandbox_backend)
|
||||
|
||||
|
||||
async def recreate_sandbox(
|
||||
thread_id: str,
|
||||
*,
|
||||
github_proxy_token: str | None = None,
|
||||
github_proxy_repositories: Sequence[str] | None = None,
|
||||
repo: dict[str, str] | None = None,
|
||||
) -> SandboxBackendProtocol:
|
||||
from agent.server import _recreate_sandbox as recreate
|
||||
|
||||
return await recreate(
|
||||
thread_id,
|
||||
github_proxy_token=github_proxy_token,
|
||||
github_proxy_repositories=github_proxy_repositories,
|
||||
repo=repo,
|
||||
)
|
||||
|
|
@ -6,8 +6,8 @@ from typing import Any
|
|||
|
||||
from langgraph.config import get_config
|
||||
|
||||
from ..reviewer_diff import is_range_in_diff
|
||||
from ..reviewer_findings import (
|
||||
from ..review.diff import is_range_in_diff
|
||||
from ..review.findings import (
|
||||
DEFAULT_FINDING_TITLE,
|
||||
MAX_SUGGESTION_LINES,
|
||||
Confidence,
|
||||
|
|
@ -133,7 +133,7 @@ async def add_finding(
|
|||
|
||||
diff_hunk: str | None = None
|
||||
if isinstance(diff_text, str) and diff_text:
|
||||
from ..reviewer_diff import extract_diff_hunk
|
||||
from ..review.diff import extract_diff_hunk
|
||||
|
||||
diff_hunk = extract_diff_hunk(diff_text, file, start_line, end_line)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from ..reviewer_findings import (
|
||||
from ..review.findings import (
|
||||
ReviewerThreadMissingError,
|
||||
get_thread_id_from_runtime,
|
||||
thread_missing_tool_result,
|
||||
)
|
||||
from ..reviewer_findings import (
|
||||
from ..review.findings import (
|
||||
list_findings as list_findings_async,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from typing import Any
|
|||
|
||||
from langgraph.config import get_config
|
||||
|
||||
from ..reviewer_findings import list_findings as list_findings_async
|
||||
from ..review.findings import list_findings as list_findings_async
|
||||
|
||||
_COMPACT_FIELDS = (
|
||||
"id",
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ from typing import Any
|
|||
from langgraph.config import get_config
|
||||
|
||||
from ..dashboard.team_settings import get_team_review_trace_links_enabled
|
||||
from ..reviewer_diff import compute_diff_line_set, fetch_pr_diff, is_range_in_diff
|
||||
from ..reviewer_findings import (
|
||||
from ..review.diff import compute_diff_line_set, fetch_pr_diff, is_range_in_diff
|
||||
from ..review.findings import (
|
||||
REVIEW_FINDING_CAP,
|
||||
REVIEWER_EVAL_PUBLICATION_KEY,
|
||||
SEVERITY_ORDER,
|
||||
|
|
@ -26,10 +26,10 @@ from ..reviewer_findings import (
|
|||
set_reviewer_thread_metadata,
|
||||
thread_missing_tool_result,
|
||||
)
|
||||
from ..reviewer_findings import (
|
||||
from ..review.findings import (
|
||||
list_findings as list_findings_async,
|
||||
)
|
||||
from ..reviewer_publish import (
|
||||
from ..review.publish import (
|
||||
clear_review_started_comment,
|
||||
fetch_pr_review_threads,
|
||||
fetch_review_comments,
|
||||
|
|
@ -44,7 +44,7 @@ from ..reviewer_publish import (
|
|||
resolve_review_thread,
|
||||
settle_review_check_run,
|
||||
)
|
||||
from ..reviewer_reconcile import reconcile_findings_with_review_threads
|
||||
from ..review.reconcile import reconcile_findings_with_review_threads
|
||||
from ..utils.dashboard_links import dashboard_review_url
|
||||
from ..utils.github_checks import review_check_conclusion
|
||||
from ..utils.github_token import (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Any
|
|||
|
||||
from langgraph.config import get_config
|
||||
|
||||
from ..reviewer_findings import (
|
||||
from ..review.findings import (
|
||||
FindingInteraction,
|
||||
ReviewerThreadMissingError,
|
||||
append_finding_interaction,
|
||||
|
|
@ -13,7 +13,7 @@ from ..reviewer_findings import (
|
|||
thread_missing_tool_result,
|
||||
update_finding_fields,
|
||||
)
|
||||
from ..reviewer_publish import reply_to_review_comment
|
||||
from ..review.publish import reply_to_review_comment
|
||||
from ..utils.github_token import get_github_token
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ async def trigger_pr_review_from_ref(
|
|||
slack_channel_id: str = "",
|
||||
slack_thread_ts: str = "",
|
||||
) -> dict[str, Any]:
|
||||
from agent.webapp import trigger_pr_review_from_ref as _trigger_pr_review_from_ref
|
||||
from agent.webhooks.github import trigger_pr_review_from_ref as _trigger_pr_review_from_ref
|
||||
|
||||
return await _trigger_pr_review_from_ref(
|
||||
pr_ref,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Any
|
|||
|
||||
from langgraph.config import get_config
|
||||
|
||||
from ..reviewer_findings import (
|
||||
from ..review.findings import (
|
||||
Finding,
|
||||
ReviewerThreadMissingError,
|
||||
get_finding,
|
||||
|
|
@ -13,14 +13,14 @@ from ..reviewer_findings import (
|
|||
update_finding_fields,
|
||||
update_finding_surface,
|
||||
)
|
||||
from ..reviewer_publish import (
|
||||
from ..review.publish import (
|
||||
fetch_pr_review_threads,
|
||||
fetch_review_thread_id_for_comment,
|
||||
render_resolution_comment,
|
||||
reply_to_review_comment,
|
||||
resolve_review_thread,
|
||||
)
|
||||
from ..reviewer_reconcile import reconcile_findings_with_review_threads
|
||||
from ..review.reconcile import reconcile_findings_with_review_threads
|
||||
from ..utils.github_token import get_github_token
|
||||
from ..utils.reviewer_outcomes import emit_finding_status_outcome
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from typing import Any
|
|||
|
||||
from langgraph.config import get_config
|
||||
|
||||
from ..reviewer_findings import (
|
||||
from ..review.findings import (
|
||||
DEFAULT_FINDING_TITLE,
|
||||
MAX_SUGGESTION_LINES,
|
||||
Finding,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from typing import Any
|
|||
from langgraph_sdk import get_client
|
||||
from langgraph_sdk.client import LangGraphClient
|
||||
|
||||
from ..reviewer_findings import list_findings
|
||||
from ..review.findings import list_findings
|
||||
from .langsmith import create_langsmith_feedback, delete_langsmith_feedback
|
||||
from .reviewer_outcomes import outcome_from_score, upsert_finding_outcome
|
||||
|
||||
|
|
|
|||
2591
agent/webapp.py
2591
agent/webapp.py
File diff suppressed because it is too large
Load diff
1732
agent/webhooks/common.py
Normal file
1732
agent/webhooks/common.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -12,9 +12,10 @@ from typing import Any
|
|||
|
||||
from langchain_core.messages.content import create_text_block
|
||||
|
||||
from agent import webapp
|
||||
from agent.utils import atlassian_connect as ac
|
||||
|
||||
from . import common
|
||||
|
||||
# The Connect app's own Confluence service-account accountId. When set, comments
|
||||
# authored by it are ignored (self-trigger loop guard, like the Linear botActor
|
||||
# / Jira comment_author_is_bot early-outs).
|
||||
|
|
@ -39,7 +40,7 @@ async def process_install(request: Any, body: dict[str, Any]) -> tuple[int, str]
|
|||
|
||||
claims = await ac.verify_asymmetric_install_jwt(request, expected_client_key=client_key)
|
||||
if claims is None:
|
||||
webapp.logger.warning("Rejecting Connect install for %s: signature unverified", client_key)
|
||||
common.logger.warning("Rejecting Connect install for %s: signature unverified", client_key)
|
||||
return 401, "Install verification failed"
|
||||
|
||||
# Mandatory tenant binding: signed-install proves the caller is *an*
|
||||
|
|
@ -47,7 +48,7 @@ async def process_install(request: Any, body: dict[str, Any]) -> tuple[int, str]
|
|||
# (signature-verified) clientKey are accepted. To bootstrap, add the
|
||||
# clientKey logged here to CONNECT_EXPECTED_CLIENT_KEYS and re-install.
|
||||
if not ac.client_key_allowed(client_key):
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Rejecting Connect install: clientKey %s not in CONNECT_EXPECTED_CLIENT_KEYS",
|
||||
client_key,
|
||||
)
|
||||
|
|
@ -56,14 +57,14 @@ async def process_install(request: Any, body: dict[str, Any]) -> tuple[int, str]
|
|||
# Defense-in-depth (only enforced when configured): the callback's baseUrl
|
||||
# host must be our Confluence site.
|
||||
if ac.CONNECT_EXPECTED_BASE_URL_HOSTS and not ac.base_url_host_allowed(base_url):
|
||||
webapp.logger.warning("Rejecting Connect install: baseUrl %s not allowed", base_url)
|
||||
common.logger.warning("Rejecting Connect install: baseUrl %s not allowed", base_url)
|
||||
return 403, "baseUrl host not allowed"
|
||||
|
||||
existing = await ac.get_installation(client_key)
|
||||
await ac.put_installation(
|
||||
client_key, shared_secret, base_url, product_type, first_install=existing is None
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Connect %s verified and stored for %s",
|
||||
"first-install" if existing is None else "re-install",
|
||||
client_key,
|
||||
|
|
@ -78,13 +79,13 @@ async def process_uninstall(request: Any, body: dict[str, Any]) -> tuple[int, st
|
|||
return 400, "Missing clientKey"
|
||||
claims = await ac.verify_asymmetric_install_jwt(request, expected_client_key=client_key)
|
||||
if claims is None:
|
||||
webapp.logger.warning("Rejecting Connect uninstall for %s: unverified", client_key)
|
||||
common.logger.warning("Rejecting Connect uninstall for %s: unverified", client_key)
|
||||
return 401, "Uninstall verification failed"
|
||||
existing = await ac.get_installation(client_key)
|
||||
if existing is None:
|
||||
return 204, "" # idempotent
|
||||
await ac.delete_installation(client_key)
|
||||
webapp.logger.info("Connect uninstall verified for %s", client_key)
|
||||
common.logger.info("Connect uninstall verified for %s", client_key)
|
||||
return 204, ""
|
||||
|
||||
|
||||
|
|
@ -104,12 +105,12 @@ async def process_confluence_comment(payload: dict[str, Any], client_key: str =
|
|||
"""Corroborate a comment_created event server-side and dispatch a run."""
|
||||
comment_id = _extract_comment_id(payload)
|
||||
if not comment_id:
|
||||
webapp.logger.debug("Ignoring Confluence webhook: no comment id in payload")
|
||||
common.logger.debug("Ignoring Confluence webhook: no comment id in payload")
|
||||
return
|
||||
|
||||
server_comment = await webapp.fetch_confluence_comment(comment_id)
|
||||
server_comment = await common.fetch_confluence_comment(comment_id)
|
||||
if not server_comment:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Rejecting Confluence webhook: comment %s could not be corroborated", comment_id
|
||||
)
|
||||
return
|
||||
|
|
@ -124,43 +125,43 @@ async def process_confluence_comment(payload: dict[str, Any], client_key: str =
|
|||
# Self-trigger loop guard: ignore the app's own comments (its confluence_comment
|
||||
# replies can echo "@openswe" and otherwise re-trigger).
|
||||
if CONFLUENCE_BOT_ACCOUNT_ID and account_id == CONFLUENCE_BOT_ACCOUNT_ID:
|
||||
webapp.logger.debug("Ignoring Confluence webhook: comment authored by the bot account")
|
||||
common.logger.debug("Ignoring Confluence webhook: comment authored by the bot account")
|
||||
return
|
||||
for prefix in webapp._GITHUB_BOT_MESSAGE_PREFIXES:
|
||||
for prefix in common._GITHUB_BOT_MESSAGE_PREFIXES:
|
||||
if body_text.startswith(prefix):
|
||||
webapp.logger.debug("Ignoring Confluence webhook: comment is our own bot message")
|
||||
common.logger.debug("Ignoring Confluence webhook: comment is our own bot message")
|
||||
return
|
||||
if "@openswe" not in body_text.lower():
|
||||
webapp.logger.debug("Ignoring Confluence webhook: comment doesn't mention @openswe")
|
||||
common.logger.debug("Ignoring Confluence webhook: comment doesn't mention @openswe")
|
||||
return
|
||||
|
||||
actor_email = await webapp.get_confluence_user_email(account_id) if account_id else None
|
||||
actor_email = await common.get_confluence_user_email(account_id) if account_id else None
|
||||
|
||||
repo_config = webapp.extract_repo_from_text(body_text, default_owner=webapp.DEFAULT_REPO_OWNER)
|
||||
repo_config = common.extract_repo_from_text(body_text, default_owner=common.DEFAULT_REPO_OWNER)
|
||||
if not repo_config:
|
||||
repo_config = webapp.get_repo_config_from_confluence_mapping(space_key)
|
||||
repo_config = common.get_repo_config_from_confluence_mapping(space_key)
|
||||
if not repo_config:
|
||||
repo_config = await webapp.get_team_default_repo()
|
||||
repo_config = await common.get_team_default_repo()
|
||||
if not repo_config:
|
||||
webapp.logger.info("Ignoring Confluence webhook: no repo resolved for space %s", space_key)
|
||||
common.logger.info("Ignoring Confluence webhook: no repo resolved for space %s", space_key)
|
||||
return
|
||||
if not webapp._is_repo_allowed(repo_config):
|
||||
webapp.logger.warning(
|
||||
if not common._is_repo_allowed(repo_config):
|
||||
common.logger.warning(
|
||||
"Rejecting Confluence webhook: repo '%s/%s' not in allowlist",
|
||||
repo_config.get("owner"),
|
||||
repo_config.get("name"),
|
||||
)
|
||||
return
|
||||
|
||||
mapped_login = await webapp.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
if mapped_login and not webapp.is_login_mapped(mapped_login):
|
||||
webapp.logger.info(
|
||||
mapped_login = await common.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
if mapped_login and not common.is_login_mapped(mapped_login):
|
||||
common.logger.info(
|
||||
"Confluence actor login %s is not an active mapping; running unattributed", mapped_login
|
||||
)
|
||||
mapped_login = None
|
||||
|
||||
thread_id = webapp.generate_thread_id_from_confluence_comment(client_key, comment_id)
|
||||
page = await webapp.fetch_confluence_page(page_id) if page_id else None
|
||||
thread_id = common.generate_thread_id_from_confluence_comment(client_key, comment_id)
|
||||
page = await common.fetch_confluence_page(page_id) if page_id else None
|
||||
page_title = (page or {}).get("title", "") or "Confluence page"
|
||||
page_url = (page or {}).get("url", "")
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ async def process_confluence_comment(payload: dict[str, Any], client_key: str =
|
|||
if mapped_login:
|
||||
configurable["github_login"] = mapped_login
|
||||
|
||||
await webapp.upsert_agent_thread_owner_metadata(
|
||||
await common.upsert_agent_thread_owner_metadata(
|
||||
thread_id,
|
||||
source="confluence",
|
||||
repo_config=repo_config,
|
||||
|
|
@ -201,14 +202,14 @@ async def process_confluence_comment(payload: dict[str, Any], client_key: str =
|
|||
source_context={"confluence": configurable["confluence"]},
|
||||
)
|
||||
|
||||
run = await webapp.dispatch_agent_run(
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
content_blocks,
|
||||
configurable,
|
||||
source="confluence",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"LangGraph run dispatched for Confluence thread %s (run=%s)",
|
||||
thread_id,
|
||||
run.get("run_id") if isinstance(run, dict) else None,
|
||||
|
|
|
|||
81
agent/webhooks/confluence_routes.py
Normal file
81
agent/webhooks/confluence_routes.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"""Confluence webhook HTTP routes (Atlassian Connect app).
|
||||
|
||||
The Confluence trigger is a private Atlassian Connect app, so the descriptor
|
||||
and install/uninstall lifecycle callbacks (``/connect/*``) live here alongside
|
||||
the JWT-verified ``comment_created`` webhook. JWT/qsh verification machinery
|
||||
stays in ``agent.utils.atlassian_connect``.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import common
|
||||
from . import confluence as service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/connect/atlassian-connect.json")
|
||||
async def connect_descriptor() -> dict[str, common.Any]:
|
||||
"""Serve the Atlassian Connect app descriptor (baseUrl from CONNECT_BASE_URL).
|
||||
|
||||
signed-install is true: Atlassian asymmetrically (RS256) signs the lifecycle
|
||||
callbacks, so install/uninstall are cryptographically authenticated against
|
||||
Atlassian's published keys (no trust-on-first-use). The comment_created
|
||||
webhook stays symmetric (HS256 against the stored per-tenant sharedSecret).
|
||||
"""
|
||||
return {
|
||||
"key": "sea-haven-open-swe-confluence",
|
||||
"name": "Open SWE",
|
||||
"description": "Triggers Open SWE runs from Confluence comments mentioning @openswe.",
|
||||
"baseUrl": common.CONNECT_BASE_URL,
|
||||
"vendor": {"name": "Sea Haven Industries", "url": "https://seahavenind.com"},
|
||||
"authentication": {"type": "jwt"},
|
||||
"apiMigrations": {"signed-install": True, "gdpr": True},
|
||||
"lifecycle": {"installed": "/connect/installed", "uninstalled": "/connect/uninstalled"},
|
||||
"scopes": ["READ"],
|
||||
"modules": {
|
||||
"webhooks": [{"event": "comment_created", "url": "/connect/webhook/comment-created"}]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.post("/connect/installed")
|
||||
async def connect_installed(request: common.Request) -> common.Response:
|
||||
"""Connect install lifecycle: trust-on-first-use (host-gated), verify re-install."""
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception: # noqa: BLE001
|
||||
raise common.HTTPException(status_code=400, detail="Invalid JSON") from None
|
||||
code, detail = await service.process_install(request, body)
|
||||
if code >= 400:
|
||||
raise common.HTTPException(status_code=code, detail=detail)
|
||||
return common.Response(status_code=code)
|
||||
|
||||
|
||||
@router.post("/connect/uninstalled")
|
||||
async def connect_uninstalled(request: common.Request) -> common.Response:
|
||||
"""Connect uninstall lifecycle: verify against the stored secret before deleting."""
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception: # noqa: BLE001
|
||||
raise common.HTTPException(status_code=400, detail="Invalid JSON") from None
|
||||
code, detail = await service.process_uninstall(request, body)
|
||||
if code >= 400:
|
||||
raise common.HTTPException(status_code=code, detail=detail)
|
||||
return common.Response(status_code=code)
|
||||
|
||||
|
||||
@router.post("/connect/webhook/comment-created")
|
||||
async def connect_comment_created(
|
||||
request: common.Request, background_tasks: common.BackgroundTasks
|
||||
) -> dict[str, str]:
|
||||
"""JWT-verified Confluence comment_created trigger."""
|
||||
claims = await common.verify_connect_webhook(request)
|
||||
if claims is None:
|
||||
raise common.HTTPException(status_code=401, detail="Invalid Connect JWT")
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception: # noqa: BLE001
|
||||
return {"status": "error", "message": "Invalid JSON"}
|
||||
background_tasks.add_task(service.process_confluence_comment, payload, claims.get("iss", ""))
|
||||
return {"status": "accepted"}
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
"""GitHub webhook handlers — moved out of webapp.py (behavior-identical).
|
||||
"""GitHub webhook handlers — moved out of common.py (behavior-identical).
|
||||
|
||||
Helpers and constants stay in webapp.py; they are accessed through the module
|
||||
object (``webapp.X``) so tests that monkeypatch them keep working.
|
||||
Helpers and constants stay in common.py; they are accessed through the module
|
||||
object (``common.X``) so tests that monkeypatch them keep working.
|
||||
"""
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from agent import webapp
|
||||
|
||||
from ..ci_autofix import handle_ci_failure, handle_review_feedback
|
||||
from ..dashboard.autofix_state import set_pr_autofix_disabled
|
||||
from ..reviewer_findings import FindingInteraction, ReviewerPRMeta, ReviewerSlackThread
|
||||
from ..review.findings import FindingInteraction, ReviewerPRMeta, ReviewerSlackThread
|
||||
from ..utils.github_ci import (
|
||||
branch_from_check_payload,
|
||||
head_sha_from_check_payload,
|
||||
|
|
@ -20,6 +18,7 @@ from ..utils.github_ci import (
|
|||
)
|
||||
from ..utils.github_comments import GitHubAuthError
|
||||
from ..utils.slack import GitHubPrRef
|
||||
from . import common
|
||||
|
||||
|
||||
def build_github_issue_prompt(
|
||||
|
|
@ -37,9 +36,9 @@ def build_github_issue_prompt(
|
|||
"""Build the user prompt for a GitHub issue-triggered run."""
|
||||
triggered_by_line = f"## Triggered by: {github_login}\n\n" if github_login else ""
|
||||
issue_url_line = f"## Issue URL: {issue_url}\n\n" if issue_url else ""
|
||||
comments_text = webapp._build_github_issue_comments_text(comments)
|
||||
sanitized_title = webapp.sanitize_github_comment_body(title)
|
||||
formatted_body = webapp.format_github_comment_body_for_prompt(
|
||||
comments_text = common._build_github_issue_comments_text(comments)
|
||||
sanitized_title = common.sanitize_github_comment_body(title)
|
||||
formatted_body = common.format_github_comment_body_for_prompt(
|
||||
issue_author or github_login, body
|
||||
)
|
||||
return (
|
||||
|
|
@ -63,13 +62,13 @@ def build_github_issue_prompt(
|
|||
|
||||
def build_github_issue_followup_prompt(github_login: str, comment_body: str) -> str:
|
||||
"""Build the prompt for a follow-up GitHub issue comment."""
|
||||
return f"**{github_login}:**\n{webapp.format_github_comment_body_for_prompt(github_login, comment_body)}"
|
||||
return f"**{github_login}:**\n{common.format_github_comment_body_for_prompt(github_login, comment_body)}"
|
||||
|
||||
|
||||
def build_github_issue_update_prompt(github_login: str, title: str, body: str) -> str:
|
||||
"""Build the prompt for a follow-up GitHub issue title/body update."""
|
||||
sanitized_title = webapp.sanitize_github_comment_body(title)
|
||||
formatted_body = webapp.format_github_comment_body_for_prompt(github_login, body)
|
||||
sanitized_title = common.sanitize_github_comment_body(title)
|
||||
formatted_body = common.format_github_comment_body_for_prompt(github_login, body)
|
||||
return (
|
||||
f"**{github_login}:** updated the GitHub issue title/body.\n\n"
|
||||
f"Title: {sanitized_title}\n\n"
|
||||
|
|
@ -110,24 +109,24 @@ async def trigger_pr_review_from_ref(
|
|||
|
||||
# Full token to read PR metadata (privacy/id aren't in the trigger ref);
|
||||
# re-scoped below once we know whether the repo is public.
|
||||
app_token, app_token_expires_at = await webapp.get_github_app_installation_token_with_expiry()
|
||||
app_token, app_token_expires_at = await common.get_github_app_installation_token_with_expiry()
|
||||
if not app_token:
|
||||
webapp.logger.warning("No GitHub App token available for PR reviewer request")
|
||||
common.logger.warning("No GitHub App token available for PR reviewer request")
|
||||
return {"success": False, "error": "No GitHub App token available"}
|
||||
|
||||
pr_metadata = await webapp.fetch_github_pr_metadata(pr_ref, token=app_token)
|
||||
pr_metadata = await common.fetch_github_pr_metadata(pr_ref, token=app_token)
|
||||
if not pr_metadata:
|
||||
return {"success": False, "error": "Could not fetch pull request metadata"}
|
||||
|
||||
repo_private = webapp._repo_private_from_pr_metadata(pr_metadata)
|
||||
repo_id = webapp._repo_id_from_pr_metadata(pr_metadata)
|
||||
app_token, app_token_expires_at = await webapp._reviewer_token_for_repo(
|
||||
repo_private = common._repo_private_from_pr_metadata(pr_metadata)
|
||||
repo_id = common._repo_id_from_pr_metadata(pr_metadata)
|
||||
app_token, app_token_expires_at = await common._reviewer_token_for_repo(
|
||||
repo_config,
|
||||
repo_private=repo_private,
|
||||
repo_id=repo_id,
|
||||
)
|
||||
if not app_token:
|
||||
webapp.logger.warning("No GitHub App token available for PR reviewer request")
|
||||
common.logger.warning("No GitHub App token available for PR reviewer request")
|
||||
return {"success": False, "error": "No GitHub App token available"}
|
||||
|
||||
base_sha = pr_metadata.get("base", {}).get("sha", "")
|
||||
|
|
@ -138,12 +137,12 @@ async def trigger_pr_review_from_ref(
|
|||
pr_title = pr_metadata.get("title", "")
|
||||
pr_url = pr_metadata.get("html_url", "") or pr_ref.url
|
||||
if not base_sha or not head_sha:
|
||||
webapp.logger.warning("Missing base/head SHA for Slack PR review request")
|
||||
common.logger.warning("Missing base/head SHA for Slack PR review request")
|
||||
return {"success": False, "error": "Pull request metadata is missing base/head SHA"}
|
||||
|
||||
thread_id = webapp.generate_reviewer_thread_id(pr_ref.owner, pr_ref.repo, pr_ref.number)
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
if not await webapp._ensure_thread_exists_for_metadata(thread_id, langgraph_client):
|
||||
thread_id = common.generate_reviewer_thread_id(pr_ref.owner, pr_ref.repo, pr_ref.number)
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
if not await common._ensure_thread_exists_for_metadata(thread_id, langgraph_client):
|
||||
return {"success": False, "error": "Could not create reviewer thread"}
|
||||
|
||||
pr_meta: ReviewerPRMeta = {
|
||||
|
|
@ -162,10 +161,10 @@ async def trigger_pr_review_from_ref(
|
|||
"channel_id": slack_channel_id,
|
||||
"thread_ts": slack_thread_ts,
|
||||
}
|
||||
await webapp.set_reviewer_thread_metadata(
|
||||
await common.set_reviewer_thread_metadata(
|
||||
thread_id, pr=pr_meta, watch=True, slack_thread=slack_thread_meta, head_sha=head_sha
|
||||
)
|
||||
await webapp.post_review_started_comment(
|
||||
await common.post_review_started_comment(
|
||||
thread_id=thread_id,
|
||||
owner=pr_ref.owner,
|
||||
repo=pr_ref.repo,
|
||||
|
|
@ -174,7 +173,7 @@ async def trigger_pr_review_from_ref(
|
|||
)
|
||||
|
||||
prompt = build_github_pr_review_prompt(repo_config, pr_ref.number, pr_url, base_sha, head_sha)
|
||||
configurable = webapp._build_reviewer_configurable(
|
||||
configurable = common._build_reviewer_configurable(
|
||||
source=source,
|
||||
github_login=github_login,
|
||||
github_user_id=github_user_id,
|
||||
|
|
@ -189,19 +188,19 @@ async def trigger_pr_review_from_ref(
|
|||
slack_thread_ts=slack_thread_ts,
|
||||
)
|
||||
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Dispatching reviewer run for thread %s from %s PR review request", thread_id, source
|
||||
)
|
||||
run = await webapp.dispatch_agent_run(
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
prompt,
|
||||
configurable,
|
||||
source=source,
|
||||
assistant_id="reviewer",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
client=langgraph_client,
|
||||
)
|
||||
await webapp._store_current_reviewer_run_id(thread_id, run)
|
||||
await common._store_current_reviewer_run_id(thread_id, run)
|
||||
return {"success": True, "queued": False, "thread_id": thread_id, "pr_url": pr_url}
|
||||
|
||||
|
||||
|
|
@ -213,8 +212,8 @@ async def _dispatch_first_review_from_pr_payload(payload: dict[str, Any], *, sou
|
|||
"owner": repo.get("owner", {}).get("login", ""),
|
||||
"name": repo.get("name", ""),
|
||||
}
|
||||
repo_private = webapp._repo_private_from_payload(payload)
|
||||
repo_id = webapp._repo_id_from_payload(payload)
|
||||
repo_private = common._repo_private_from_payload(payload)
|
||||
repo_id = common._repo_id_from_payload(payload)
|
||||
pr_number = pull_request.get("number")
|
||||
pr_url = pull_request.get("html_url", "") or pull_request.get("url", "")
|
||||
branch_name = pull_request.get("head", {}).get("ref", "")
|
||||
|
|
@ -226,10 +225,10 @@ async def _dispatch_first_review_from_pr_payload(payload: dict[str, Any], *, sou
|
|||
github_user_id = payload.get("sender", {}).get("id")
|
||||
|
||||
if not pr_number or not pr_url or not base_sha or not head_sha:
|
||||
webapp.logger.warning("Missing PR context for reviewer dispatch, skipping run")
|
||||
common.logger.warning("Missing PR context for reviewer dispatch, skipping run")
|
||||
return
|
||||
|
||||
thread_id = webapp.generate_reviewer_thread_id(
|
||||
thread_id = common.generate_reviewer_thread_id(
|
||||
repo_config.get("owner", ""), repo_config.get("name", ""), pr_number
|
||||
)
|
||||
|
||||
|
|
@ -245,13 +244,13 @@ async def _dispatch_first_review_from_pr_payload(payload: dict[str, Any], *, sou
|
|||
}
|
||||
last_reviewed_sha = ""
|
||||
if payload.get("action") == "ready_for_review":
|
||||
metadata = await webapp._get_thread_metadata_safe(thread_id)
|
||||
if metadata is not None and metadata.get("kind") == webapp.REVIEWER_THREAD_KIND:
|
||||
metadata = await common._get_thread_metadata_safe(thread_id)
|
||||
if metadata is not None and metadata.get("kind") == common.REVIEWER_THREAD_KIND:
|
||||
existing_last_reviewed_sha = metadata.get("last_reviewed_sha")
|
||||
if isinstance(existing_last_reviewed_sha, str) and existing_last_reviewed_sha:
|
||||
if existing_last_reviewed_sha == head_sha:
|
||||
await webapp.set_reviewer_thread_metadata(thread_id, pr=pr_meta, watch=True)
|
||||
webapp.logger.info(
|
||||
await common.set_reviewer_thread_metadata(thread_id, pr=pr_meta, watch=True)
|
||||
common.logger.info(
|
||||
"Skipping ready_for_review auto-review for %s/%s#%s: "
|
||||
"head_sha unchanged from last_reviewed_sha",
|
||||
repo_config.get("owner"),
|
||||
|
|
@ -261,30 +260,30 @@ async def _dispatch_first_review_from_pr_payload(payload: dict[str, Any], *, sou
|
|||
return
|
||||
last_reviewed_sha = existing_last_reviewed_sha
|
||||
|
||||
app_token, app_token_expires_at = await webapp._reviewer_token_for_repo(
|
||||
app_token, app_token_expires_at = await common._reviewer_token_for_repo(
|
||||
repo_config,
|
||||
repo_private=repo_private,
|
||||
repo_id=repo_id,
|
||||
)
|
||||
if not app_token:
|
||||
webapp.logger.warning("No GitHub App token available for reviewer dispatch")
|
||||
common.logger.warning("No GitHub App token available for reviewer dispatch")
|
||||
return
|
||||
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
if not await webapp._ensure_thread_exists_for_metadata(thread_id, langgraph_client):
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
if not await common._ensure_thread_exists_for_metadata(thread_id, langgraph_client):
|
||||
return
|
||||
|
||||
await webapp.set_reviewer_thread_metadata(thread_id, pr=pr_meta, watch=True, head_sha=head_sha)
|
||||
await common.set_reviewer_thread_metadata(thread_id, pr=pr_meta, watch=True, head_sha=head_sha)
|
||||
|
||||
check_run_id = await webapp.create_review_check_run(
|
||||
check_run_id = await common.create_review_check_run(
|
||||
owner=repo_config.get("owner", ""),
|
||||
repo=repo_config.get("name", ""),
|
||||
head_sha=head_sha,
|
||||
token=app_token,
|
||||
details_url=webapp.dashboard_thread_url(thread_id),
|
||||
details_url=common.dashboard_thread_url(thread_id),
|
||||
)
|
||||
if check_run_id is not None:
|
||||
await webapp.set_reviewer_thread_metadata(
|
||||
await common.set_reviewer_thread_metadata(
|
||||
thread_id, extra={"review_check_run_id": check_run_id}
|
||||
)
|
||||
|
||||
|
|
@ -297,7 +296,7 @@ async def _dispatch_first_review_from_pr_payload(payload: dict[str, Any], *, sou
|
|||
)
|
||||
else:
|
||||
prompt = build_github_pr_review_prompt(repo_config, pr_number, pr_url, base_sha, head_sha)
|
||||
configurable = webapp._build_reviewer_configurable(
|
||||
configurable = common._build_reviewer_configurable(
|
||||
source=source,
|
||||
github_login=github_login,
|
||||
github_user_id=github_user_id,
|
||||
|
|
@ -312,18 +311,18 @@ async def _dispatch_first_review_from_pr_payload(payload: dict[str, Any], *, sou
|
|||
last_reviewed_sha=last_reviewed_sha,
|
||||
)
|
||||
|
||||
webapp.logger.info("Dispatching reviewer run for thread %s (source=%s)", thread_id, source)
|
||||
run = await webapp.dispatch_agent_run(
|
||||
common.logger.info("Dispatching reviewer run for thread %s (source=%s)", thread_id, source)
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
prompt,
|
||||
configurable,
|
||||
source=source,
|
||||
assistant_id="reviewer",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
client=langgraph_client,
|
||||
)
|
||||
await webapp._store_current_reviewer_run_id(thread_id, run)
|
||||
webapp.logger.info("Reviewer run dispatched for thread %s (source=%s)", thread_id, source)
|
||||
await common._store_current_reviewer_run_id(thread_id, run)
|
||||
common.logger.info("Reviewer run dispatched for thread %s (source=%s)", thread_id, source)
|
||||
|
||||
|
||||
async def process_github_pr_ready(payload: dict[str, Any]) -> None:
|
||||
|
|
@ -337,8 +336,8 @@ async def process_github_pr_ready(payload: dict[str, Any]) -> None:
|
|||
if is_draft:
|
||||
author = pull_request.get("user") or {}
|
||||
author_login = author.get("login", "") if isinstance(author, dict) else ""
|
||||
if not await webapp._draft_review_enabled_for_author(author_login):
|
||||
webapp.logger.info(
|
||||
if not await common._draft_review_enabled_for_author(author_login):
|
||||
common.logger.info(
|
||||
"Skipping auto-review of draft PR by %s: review_draft_prs is disabled",
|
||||
author_login or "<unknown>",
|
||||
)
|
||||
|
|
@ -367,13 +366,13 @@ async def process_github_pr_close(payload: dict[str, Any]) -> None:
|
|||
if not pr_number or not isinstance(pr_number, int):
|
||||
return
|
||||
|
||||
thread_id = webapp.generate_reviewer_thread_id(
|
||||
thread_id = common.generate_reviewer_thread_id(
|
||||
repo_config.get("owner", ""), repo_config.get("name", ""), pr_number
|
||||
)
|
||||
metadata = await webapp._get_thread_metadata_safe(thread_id)
|
||||
if metadata is None or metadata.get("kind") != webapp.REVIEWER_THREAD_KIND:
|
||||
metadata = await common._get_thread_metadata_safe(thread_id)
|
||||
if metadata is None or metadata.get("kind") != common.REVIEWER_THREAD_KIND:
|
||||
# No reviewer thread for this PR, nothing to do.
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"PR %s/%s#%s closed/reopened: no reviewer thread, skipping watch update",
|
||||
repo_config.get("owner"),
|
||||
repo_config.get("name"),
|
||||
|
|
@ -384,8 +383,8 @@ async def process_github_pr_close(payload: dict[str, Any]) -> None:
|
|||
if action == "converted_to_draft":
|
||||
author = pull_request.get("user") or {}
|
||||
author_login = author.get("login", "") if isinstance(author, dict) else ""
|
||||
if await webapp._draft_review_enabled_for_author(author_login):
|
||||
webapp.logger.info(
|
||||
if await common._draft_review_enabled_for_author(author_login):
|
||||
common.logger.info(
|
||||
"PR %s/%s#%s converted to draft but author %s has draft reviews enabled; keeping watch",
|
||||
repo_config.get("owner"),
|
||||
repo_config.get("name"),
|
||||
|
|
@ -398,8 +397,8 @@ async def process_github_pr_close(payload: dict[str, Any]) -> None:
|
|||
desired_watch = action == "reopened"
|
||||
if metadata.get("watch") == desired_watch:
|
||||
return
|
||||
await webapp.set_reviewer_thread_metadata(thread_id, watch=desired_watch)
|
||||
webapp.logger.info(
|
||||
await common.set_reviewer_thread_metadata(thread_id, watch=desired_watch)
|
||||
common.logger.info(
|
||||
"Set watch=%s on reviewer thread %s after PR %s", desired_watch, thread_id, action
|
||||
)
|
||||
|
||||
|
|
@ -409,10 +408,10 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
ref = payload.get("ref", "")
|
||||
after_sha = payload.get("after", "")
|
||||
if not ref.startswith("refs/heads/"):
|
||||
webapp.logger.debug("Push ignored: ref %s is not a branch", ref)
|
||||
common.logger.debug("Push ignored: ref %s is not a branch", ref)
|
||||
return
|
||||
if not isinstance(after_sha, str) or not after_sha or set(after_sha) == {"0"}:
|
||||
webapp.logger.debug("Push to %s ignored: branch deletion or missing SHA", ref)
|
||||
common.logger.debug("Push to %s ignored: branch deletion or missing SHA", ref)
|
||||
return
|
||||
head_ref = ref[len("refs/heads/") :]
|
||||
|
||||
|
|
@ -421,15 +420,15 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
"owner": repo.get("owner", {}).get("login", "") or repo.get("owner", {}).get("name", ""),
|
||||
"name": repo.get("name", ""),
|
||||
}
|
||||
repo_private = webapp._repo_private_from_payload(payload)
|
||||
repo_id = webapp._repo_id_from_payload(payload)
|
||||
repo_private = common._repo_private_from_payload(payload)
|
||||
repo_id = common._repo_id_from_payload(payload)
|
||||
if not repo_config["owner"] or not repo_config["name"]:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Push to %s ignored: repository owner/name missing from payload", head_ref
|
||||
)
|
||||
return
|
||||
if not await webapp._is_repo_auto_review_enabled(repo_config):
|
||||
webapp.logger.info(
|
||||
if not await common._is_repo_auto_review_enabled(repo_config):
|
||||
common.logger.info(
|
||||
"Push to %s/%s head=%s ignored: automatic review disabled",
|
||||
repo_config["owner"],
|
||||
repo_config["name"],
|
||||
|
|
@ -437,18 +436,18 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
)
|
||||
return
|
||||
|
||||
app_token, app_token_expires_at = await webapp._reviewer_token_for_repo(
|
||||
app_token, app_token_expires_at = await common._reviewer_token_for_repo(
|
||||
repo_config,
|
||||
repo_private=repo_private,
|
||||
repo_id=repo_id,
|
||||
)
|
||||
if not app_token:
|
||||
webapp.logger.warning("No GitHub App token for push re-review on %s", head_ref)
|
||||
common.logger.warning("No GitHub App token for push re-review on %s", head_ref)
|
||||
return
|
||||
|
||||
pr = await webapp._fetch_open_pr_for_branch(repo_config, head_ref, token=app_token)
|
||||
pr = await common._fetch_open_pr_for_branch(repo_config, head_ref, token=app_token)
|
||||
if not pr:
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"No open PR found for push to %s/%s head=%s",
|
||||
repo_config["owner"],
|
||||
repo_config["name"],
|
||||
|
|
@ -460,16 +459,16 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
# If the repo turns out public, re-scope the token so reviewer.py doesn't
|
||||
# proxy a full-installation token for a public PR.
|
||||
if repo_private is None:
|
||||
repo_private = webapp._repo_private_from_pr_metadata(pr)
|
||||
repo_id = repo_id or webapp._repo_id_from_pr_metadata(pr)
|
||||
repo_private = common._repo_private_from_pr_metadata(pr)
|
||||
repo_id = repo_id or common._repo_id_from_pr_metadata(pr)
|
||||
if repo_private is False:
|
||||
app_token, app_token_expires_at = await webapp._reviewer_token_for_repo(
|
||||
app_token, app_token_expires_at = await common._reviewer_token_for_repo(
|
||||
repo_config,
|
||||
repo_private=repo_private,
|
||||
repo_id=repo_id,
|
||||
)
|
||||
if not app_token:
|
||||
webapp.logger.warning("No GitHub App token for push re-review on %s", head_ref)
|
||||
common.logger.warning("No GitHub App token for push re-review on %s", head_ref)
|
||||
return
|
||||
pr_number = pr.get("number")
|
||||
pr_url = pr.get("html_url") or pr.get("url") or ""
|
||||
|
|
@ -478,7 +477,7 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
head_sha = pr.get("head", {}).get("sha", after_sha)
|
||||
pr_title = pr.get("title", "")
|
||||
if not isinstance(pr_number, int) or not base_sha or not head_sha:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Push to %s/%s head=%s ignored: PR metadata missing number/base/head SHA",
|
||||
repo_config["owner"],
|
||||
repo_config["name"],
|
||||
|
|
@ -486,12 +485,12 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
)
|
||||
return
|
||||
|
||||
thread_id = webapp.generate_reviewer_thread_id(
|
||||
thread_id = common.generate_reviewer_thread_id(
|
||||
repo_config["owner"], repo_config["name"], pr_number
|
||||
)
|
||||
metadata = await webapp._get_thread_metadata_safe(thread_id)
|
||||
if metadata is None or metadata.get("kind") != webapp.REVIEWER_THREAD_KIND:
|
||||
webapp.logger.info(
|
||||
metadata = await common._get_thread_metadata_safe(thread_id)
|
||||
if metadata is None or metadata.get("kind") != common.REVIEWER_THREAD_KIND:
|
||||
common.logger.info(
|
||||
"Push to %s/%s#%s ignored: no reviewer thread for this PR. "
|
||||
"Trigger a first review (Slack `@open-swe review <url>` or request "
|
||||
"open-swe[bot] as a GitHub reviewer) to start watching.",
|
||||
|
|
@ -501,21 +500,21 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
)
|
||||
return
|
||||
if not metadata.get("watch"):
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Push to %s ignored: reviewer thread %s is not watching", head_ref, thread_id
|
||||
)
|
||||
return
|
||||
|
||||
last_reviewed_sha = metadata.get("last_reviewed_sha")
|
||||
if isinstance(last_reviewed_sha, str) and last_reviewed_sha == head_sha:
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Push to %s ignored: head_sha unchanged from last_reviewed_sha", head_ref
|
||||
)
|
||||
return
|
||||
if (
|
||||
isinstance(last_reviewed_sha, str)
|
||||
and last_reviewed_sha
|
||||
and await webapp._is_pr_diff_unchanged_since_last_review(
|
||||
and await common._is_pr_diff_unchanged_since_last_review(
|
||||
repo_config,
|
||||
base_ref=base_ref,
|
||||
last_reviewed_sha=last_reviewed_sha,
|
||||
|
|
@ -523,19 +522,19 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
token=app_token,
|
||||
)
|
||||
):
|
||||
await webapp.set_reviewer_thread_metadata(thread_id, last_reviewed_sha=head_sha)
|
||||
await common.set_reviewer_thread_metadata(thread_id, last_reviewed_sha=head_sha)
|
||||
# The old head's check disappears once the head moves (GitHub only
|
||||
# shows checks on the current head), so even though no re-review runs,
|
||||
# surface a settled check on the new head.
|
||||
unchanged_check_id = await webapp.create_review_check_run(
|
||||
unchanged_check_id = await common.create_review_check_run(
|
||||
owner=repo_config["owner"],
|
||||
repo=repo_config["name"],
|
||||
head_sha=head_sha,
|
||||
token=app_token,
|
||||
details_url=webapp.dashboard_thread_url(thread_id),
|
||||
details_url=common.dashboard_thread_url(thread_id),
|
||||
)
|
||||
if unchanged_check_id is not None:
|
||||
await webapp.complete_review_check_run(
|
||||
await common.complete_review_check_run(
|
||||
owner=repo_config["owner"],
|
||||
repo=repo_config["name"],
|
||||
check_run_id=unchanged_check_id,
|
||||
|
|
@ -547,26 +546,26 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
f"commit {last_reviewed_sha}."
|
||||
),
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Push to %s ignored: PR diff unchanged since last reviewed SHA %s",
|
||||
head_ref,
|
||||
last_reviewed_sha,
|
||||
)
|
||||
return
|
||||
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
if not await webapp._ensure_thread_exists_for_metadata(thread_id, langgraph_client):
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
if not await common._ensure_thread_exists_for_metadata(thread_id, langgraph_client):
|
||||
return
|
||||
try:
|
||||
threads = await webapp.fetch_pr_review_threads(
|
||||
threads = await common.fetch_pr_review_threads(
|
||||
owner=repo_config["owner"],
|
||||
repo=repo_config["name"],
|
||||
pr_number=pr_number,
|
||||
token=app_token,
|
||||
)
|
||||
await webapp.reconcile_findings_with_review_threads(thread_id, threads)
|
||||
await common.reconcile_findings_with_review_threads(thread_id, threads)
|
||||
except Exception:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Could not sync review threads before push re-review for %s", thread_id
|
||||
)
|
||||
|
||||
|
|
@ -580,21 +579,21 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
"base_ref": base_ref,
|
||||
"author": (pr.get("user") or {}).get("login", ""),
|
||||
}
|
||||
await webapp.set_reviewer_thread_metadata(thread_id, pr=pr_meta, watch=True, head_sha=head_sha)
|
||||
await common.set_reviewer_thread_metadata(thread_id, pr=pr_meta, watch=True, head_sha=head_sha)
|
||||
|
||||
# GitHub only shows check runs on a PR's current head commit, so the check
|
||||
# created on the previous head disappears after a follow-up push. Create a
|
||||
# fresh in-progress check on the new head SHA so the review stays visible;
|
||||
# publish (or the after-agent hook) settles this id.
|
||||
check_run_id = await webapp.create_review_check_run(
|
||||
check_run_id = await common.create_review_check_run(
|
||||
owner=repo_config["owner"],
|
||||
repo=repo_config["name"],
|
||||
head_sha=head_sha,
|
||||
token=app_token,
|
||||
details_url=webapp.dashboard_thread_url(thread_id),
|
||||
details_url=common.dashboard_thread_url(thread_id),
|
||||
)
|
||||
if check_run_id is not None:
|
||||
await webapp.set_reviewer_thread_metadata(
|
||||
await common.set_reviewer_thread_metadata(
|
||||
thread_id, extra={"review_check_run_id": check_run_id}
|
||||
)
|
||||
|
||||
|
|
@ -603,7 +602,7 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
f"{head_sha}. Reconcile existing findings against the new diff, add any "
|
||||
f"net-new findings, and call `publish_review` once you're done."
|
||||
)
|
||||
configurable = webapp._build_reviewer_configurable(
|
||||
configurable = common._build_reviewer_configurable(
|
||||
source="github_push",
|
||||
github_login=payload.get("sender", {}).get("login", "") or "",
|
||||
github_user_id=payload.get("sender", {}).get("id"),
|
||||
|
|
@ -618,17 +617,17 @@ async def process_github_push_event(payload: dict[str, Any]) -> None:
|
|||
last_reviewed_sha=last_reviewed_sha if isinstance(last_reviewed_sha, str) else "",
|
||||
)
|
||||
|
||||
webapp.logger.info("Dispatching push re-review run for thread %s", thread_id)
|
||||
run = await webapp.dispatch_agent_run(
|
||||
common.logger.info("Dispatching push re-review run for thread %s", thread_id)
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
re_review_prompt,
|
||||
configurable,
|
||||
source="github_push",
|
||||
assistant_id="reviewer",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
client=langgraph_client,
|
||||
)
|
||||
await webapp._store_current_reviewer_run_id(thread_id, run)
|
||||
await common._store_current_reviewer_run_id(thread_id, run)
|
||||
|
||||
|
||||
async def process_github_pr_comment(payload: dict[str, Any], event_type: str) -> None:
|
||||
|
|
@ -650,20 +649,20 @@ async def process_github_pr_comment(payload: dict[str, Any], event_type: str) ->
|
|||
pr_url,
|
||||
comment_id,
|
||||
node_id,
|
||||
) = await webapp.extract_pr_context(payload, event_type)
|
||||
) = await common.extract_pr_context(payload, event_type)
|
||||
github_user_id = payload.get("sender", {}).get("id")
|
||||
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Processing GitHub PR comment: event=%s, pr=%s, branch=%s",
|
||||
event_type,
|
||||
pr_number,
|
||||
branch_name,
|
||||
)
|
||||
|
||||
thread_id = webapp.get_thread_id_from_branch(branch_name) if branch_name else None
|
||||
thread_id = common.get_thread_id_from_branch(branch_name) if branch_name else None
|
||||
if not thread_id:
|
||||
if not pr_number:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Could not determine thread_id for branch '%s' (no pr_number), skipping",
|
||||
branch_name,
|
||||
)
|
||||
|
|
@ -672,21 +671,21 @@ async def process_github_pr_comment(payload: dict[str, Any], event_type: str) ->
|
|||
name = repo_config.get("name", "")
|
||||
stable_key = f"{owner}/{name}/pr/{pr_number}"
|
||||
thread_id = str(uuid.uuid5(uuid.NAMESPACE_URL, stable_key))
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Generated thread_id %s for non-open-swe branch '%s'", thread_id, branch_name
|
||||
)
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
try:
|
||||
await langgraph_client.threads.update(thread_id, metadata={"branch_name": branch_name})
|
||||
except Exception as exc: # noqa: BLE001
|
||||
if webapp._is_not_found_error(exc):
|
||||
if common._is_not_found_error(exc):
|
||||
await langgraph_client.threads.create(
|
||||
thread_id=thread_id,
|
||||
if_exists="do_nothing",
|
||||
metadata={"branch_name": branch_name},
|
||||
)
|
||||
else:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Failed to persist branch_name metadata for thread %s", thread_id
|
||||
)
|
||||
|
||||
|
|
@ -696,28 +695,28 @@ async def process_github_pr_comment(payload: dict[str, Any], event_type: str) ->
|
|||
# otherwise visible), which would drop a legitimately-mapped user. Mirrors
|
||||
# the Slack mention path (process_slack_mention).
|
||||
try:
|
||||
await webapp.refresh_user_mapping_cache()
|
||||
await common.refresh_user_mapping_cache()
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Could not refresh user mapping cache for GitHub PR comment", exc_info=True
|
||||
)
|
||||
|
||||
email = await webapp.email_for_login(github_login) or ""
|
||||
email = await common.email_for_login(github_login) or ""
|
||||
if email:
|
||||
github_token = await webapp._get_or_resolve_thread_github_token(
|
||||
github_token = await common._get_or_resolve_thread_github_token(
|
||||
thread_id, email, repo=repo_config
|
||||
)
|
||||
else:
|
||||
webapp.logger.warning("No email mapping for GitHub user '%s', skipping", github_login)
|
||||
common.logger.warning("No email mapping for GitHub user '%s', skipping", github_login)
|
||||
return
|
||||
|
||||
if not github_token:
|
||||
webapp.logger.warning("No GitHub token for thread %s, skipping", thread_id)
|
||||
common.logger.warning("No GitHub token for thread %s, skipping", thread_id)
|
||||
return
|
||||
|
||||
if comment_id:
|
||||
try:
|
||||
await webapp.react_to_github_comment(
|
||||
await common.react_to_github_comment(
|
||||
repo_config,
|
||||
comment_id,
|
||||
event_type=event_type,
|
||||
|
|
@ -726,13 +725,13 @@ async def process_github_pr_comment(payload: dict[str, Any], event_type: str) ->
|
|||
node_id=node_id,
|
||||
)
|
||||
except GitHubAuthError:
|
||||
github_token = await webapp._refresh_thread_github_token_after_401(
|
||||
github_token = await common._refresh_thread_github_token_after_401(
|
||||
thread_id, email, repo=repo_config
|
||||
)
|
||||
if not github_token:
|
||||
webapp.logger.warning("Re-auth failed for thread %s after 401; skipping", thread_id)
|
||||
common.logger.warning("Re-auth failed for thread %s after 401; skipping", thread_id)
|
||||
return
|
||||
await webapp.react_to_github_comment(
|
||||
await common.react_to_github_comment(
|
||||
repo_config,
|
||||
comment_id,
|
||||
event_type=event_type,
|
||||
|
|
@ -742,29 +741,29 @@ async def process_github_pr_comment(payload: dict[str, Any], event_type: str) ->
|
|||
)
|
||||
|
||||
if not pr_number:
|
||||
webapp.logger.warning("No PR number found in payload, skipping")
|
||||
common.logger.warning("No PR number found in payload, skipping")
|
||||
return
|
||||
|
||||
try:
|
||||
comments = await webapp.fetch_pr_comments_since_last_tag(
|
||||
comments = await common.fetch_pr_comments_since_last_tag(
|
||||
repo_config, pr_number, token=github_token
|
||||
)
|
||||
except GitHubAuthError:
|
||||
github_token = await webapp._refresh_thread_github_token_after_401(
|
||||
github_token = await common._refresh_thread_github_token_after_401(
|
||||
thread_id, email, repo=repo_config
|
||||
)
|
||||
if not github_token:
|
||||
webapp.logger.warning("Re-auth failed for thread %s after 401; skipping", thread_id)
|
||||
common.logger.warning("Re-auth failed for thread %s after 401; skipping", thread_id)
|
||||
return
|
||||
comments = await webapp.fetch_pr_comments_since_last_tag(
|
||||
comments = await common.fetch_pr_comments_since_last_tag(
|
||||
repo_config, pr_number, token=github_token
|
||||
)
|
||||
if not comments:
|
||||
webapp.logger.info("No comments found since last @open-swe tag for PR %s", pr_number)
|
||||
common.logger.info("No comments found since last @open-swe tag for PR %s", pr_number)
|
||||
return
|
||||
|
||||
prompt = webapp.build_pr_prompt(comments, pr_url, repo_config=repo_config)
|
||||
await webapp._trigger_or_queue_run(
|
||||
prompt = common.build_pr_prompt(comments, pr_url, repo_config=repo_config)
|
||||
await common._trigger_or_queue_run(
|
||||
thread_id,
|
||||
prompt,
|
||||
github_login=github_login,
|
||||
|
|
@ -776,13 +775,13 @@ async def process_github_pr_comment(payload: dict[str, Any], event_type: str) ->
|
|||
|
||||
async def process_github_review_finding_reply(payload: dict[str, Any]) -> None:
|
||||
"""Route replies to Open SWE review comments back to the reviewer graph."""
|
||||
parent_comment_id = webapp._review_comment_reply_parent_id(payload)
|
||||
parent_comment_id = common._review_comment_reply_parent_id(payload)
|
||||
if parent_comment_id is None:
|
||||
return
|
||||
|
||||
sender = payload.get("sender", {})
|
||||
sender_login = sender.get("login") if isinstance(sender, dict) else None
|
||||
if sender_login in webapp.INTERNAL_BOT_LOGINS:
|
||||
if sender_login in common.INTERNAL_BOT_LOGINS:
|
||||
return
|
||||
|
||||
repo = payload.get("repository", {})
|
||||
|
|
@ -791,20 +790,20 @@ async def process_github_review_finding_reply(payload: dict[str, Any]) -> None:
|
|||
"owner": repo.get("owner", {}).get("login", ""),
|
||||
"name": repo.get("name", ""),
|
||||
}
|
||||
repo_private = webapp._repo_private_from_payload(payload)
|
||||
repo_id = webapp._repo_id_from_payload(payload)
|
||||
repo_private = common._repo_private_from_payload(payload)
|
||||
repo_id = common._repo_id_from_payload(payload)
|
||||
pr_number = pull_request.get("number")
|
||||
if not isinstance(pr_number, int):
|
||||
return
|
||||
|
||||
thread_id = webapp.generate_reviewer_thread_id(
|
||||
thread_id = common.generate_reviewer_thread_id(
|
||||
repo_config.get("owner", ""), repo_config.get("name", ""), pr_number
|
||||
)
|
||||
metadata = await webapp._get_thread_metadata_safe(thread_id)
|
||||
if metadata is None or metadata.get("kind") != webapp.REVIEWER_THREAD_KIND:
|
||||
metadata = await common._get_thread_metadata_safe(thread_id)
|
||||
if metadata is None or metadata.get("kind") != common.REVIEWER_THREAD_KIND:
|
||||
return
|
||||
|
||||
app_token, app_token_expires_at = await webapp._reviewer_token_for_repo(
|
||||
app_token, app_token_expires_at = await common._reviewer_token_for_repo(
|
||||
repo_config,
|
||||
repo_private=repo_private,
|
||||
repo_id=repo_id,
|
||||
|
|
@ -812,16 +811,16 @@ async def process_github_review_finding_reply(payload: dict[str, Any]) -> None:
|
|||
if not app_token:
|
||||
return
|
||||
|
||||
threads = await webapp.fetch_pr_review_threads(
|
||||
threads = await common.fetch_pr_review_threads(
|
||||
owner=repo_config["owner"],
|
||||
repo=repo_config["name"],
|
||||
pr_number=pr_number,
|
||||
token=app_token,
|
||||
)
|
||||
await webapp.reconcile_findings_with_review_threads(thread_id, threads)
|
||||
findings = await webapp.list_reviewer_findings(thread_id)
|
||||
await common.reconcile_findings_with_review_threads(thread_id, threads)
|
||||
findings = await common.list_reviewer_findings(thread_id)
|
||||
finding = next(
|
||||
(item for item in findings if parent_comment_id in webapp._finding_comment_ids(item)), None
|
||||
(item for item in findings if parent_comment_id in common._finding_comment_ids(item)), None
|
||||
)
|
||||
if finding is None:
|
||||
return
|
||||
|
|
@ -846,13 +845,13 @@ async def process_github_review_finding_reply(payload: dict[str, Any]) -> None:
|
|||
else "",
|
||||
"needs_reassessment": True,
|
||||
}
|
||||
await webapp.append_finding_interaction(thread_id, finding_id, interaction)
|
||||
await common.append_finding_interaction(thread_id, finding_id, interaction)
|
||||
|
||||
base_sha = pull_request.get("base", {}).get("sha", "")
|
||||
head_sha = pull_request.get("head", {}).get("sha", "")
|
||||
pr_url = pull_request.get("html_url", "") or pull_request.get("url", "")
|
||||
branch_name = pull_request.get("head", {}).get("ref", "")
|
||||
configurable = webapp._build_reviewer_configurable(
|
||||
configurable = common._build_reviewer_configurable(
|
||||
source="github_review_comment",
|
||||
github_login=reply_author,
|
||||
github_user_id=sender.get("id") if isinstance(sender, dict) else None,
|
||||
|
|
@ -873,23 +872,23 @@ async def process_github_review_finding_reply(payload: dict[str, Any]) -> None:
|
|||
"finding_reply_body": reply_body,
|
||||
}
|
||||
)
|
||||
finding_reply_prompt = webapp._build_queued_finding_reply_prompt(
|
||||
finding_reply_prompt = common._build_queued_finding_reply_prompt(
|
||||
finding_id=finding_id,
|
||||
reply_author=reply_author,
|
||||
reply_body=reply_body,
|
||||
pr_number=pr_number,
|
||||
)
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
run = await webapp.dispatch_agent_run(
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
finding_reply_prompt,
|
||||
configurable,
|
||||
source="github_review_reply",
|
||||
assistant_id="reviewer",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
client=langgraph_client,
|
||||
)
|
||||
await webapp._store_current_reviewer_run_id(thread_id, run)
|
||||
await common._store_current_reviewer_run_id(thread_id, run)
|
||||
|
||||
|
||||
async def process_github_issue(payload: dict[str, Any], event_type: str) -> None:
|
||||
|
|
@ -910,7 +909,7 @@ async def process_github_issue(payload: dict[str, Any], event_type: str) -> None
|
|||
description = issue.get("body") or "No description"
|
||||
issue_author = issue.get("user", {}).get("login", "")
|
||||
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Processing GitHub issue: event=%s, issue=%s, repo=%s/%s",
|
||||
event_type,
|
||||
issue_number,
|
||||
|
|
@ -919,66 +918,66 @@ async def process_github_issue(payload: dict[str, Any], event_type: str) -> None
|
|||
)
|
||||
|
||||
if not issue_id or not issue_number:
|
||||
webapp.logger.warning("Missing GitHub issue id/number, skipping")
|
||||
common.logger.warning("Missing GitHub issue id/number, skipping")
|
||||
return
|
||||
|
||||
# Refresh the per-process user-mapping cache from the Store before
|
||||
# resolving the author's email (multi-replica staleness; mirrors the Slack
|
||||
# mention path in process_slack_mention).
|
||||
try:
|
||||
await webapp.refresh_user_mapping_cache()
|
||||
await common.refresh_user_mapping_cache()
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug("Could not refresh user mapping cache for GitHub issue", exc_info=True)
|
||||
common.logger.debug("Could not refresh user mapping cache for GitHub issue", exc_info=True)
|
||||
|
||||
email = await webapp.email_for_login(github_login) or ""
|
||||
email = await common.email_for_login(github_login) or ""
|
||||
if not email:
|
||||
webapp.logger.warning("No email mapping for GitHub user '%s', skipping", github_login)
|
||||
common.logger.warning("No email mapping for GitHub user '%s', skipping", github_login)
|
||||
return
|
||||
|
||||
thread_id = webapp.generate_thread_id_from_github_issue(issue_id)
|
||||
existing_thread = await webapp._thread_exists(thread_id)
|
||||
github_token = await webapp._get_or_resolve_thread_github_token(
|
||||
thread_id = common.generate_thread_id_from_github_issue(issue_id)
|
||||
existing_thread = await common._thread_exists(thread_id)
|
||||
github_token = await common._get_or_resolve_thread_github_token(
|
||||
thread_id, email, repo=repo_config
|
||||
)
|
||||
app_token = await webapp.get_github_app_installation_token()
|
||||
app_token = await common.get_github_app_installation_token()
|
||||
reaction_token = github_token or app_token
|
||||
comment = payload.get("comment", {})
|
||||
comment_id = comment.get("id")
|
||||
if event_type == "issue_comment" and comment_id:
|
||||
if not reaction_token:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"No GitHub token available to react to issue comment %s", comment_id
|
||||
)
|
||||
else:
|
||||
try:
|
||||
reacted = await webapp.react_to_github_comment(
|
||||
reacted = await common.react_to_github_comment(
|
||||
repo_config,
|
||||
comment_id,
|
||||
event_type="issue_comment",
|
||||
token=reaction_token,
|
||||
)
|
||||
except GitHubAuthError:
|
||||
github_token = await webapp._refresh_thread_github_token_after_401(
|
||||
github_token = await common._refresh_thread_github_token_after_401(
|
||||
thread_id, email, repo=repo_config
|
||||
)
|
||||
reaction_token = github_token or app_token
|
||||
reacted = False
|
||||
if reaction_token:
|
||||
try:
|
||||
reacted = await webapp.react_to_github_comment(
|
||||
reacted = await common.react_to_github_comment(
|
||||
repo_config,
|
||||
comment_id,
|
||||
event_type="issue_comment",
|
||||
token=reaction_token,
|
||||
)
|
||||
except GitHubAuthError:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Re-auth still produced 401 reacting to issue comment %s",
|
||||
comment_id,
|
||||
)
|
||||
reacted = False
|
||||
if not reacted:
|
||||
webapp.logger.warning("Failed to react to GitHub issue comment %s", comment_id)
|
||||
common.logger.warning("Failed to react to GitHub issue comment %s", comment_id)
|
||||
|
||||
if existing_thread:
|
||||
if event_type == "issue_comment":
|
||||
|
|
@ -990,14 +989,14 @@ async def process_github_issue(payload: dict[str, Any], event_type: str) -> None
|
|||
prompt = build_github_issue_update_prompt(github_login, title, description)
|
||||
else:
|
||||
try:
|
||||
comments = await webapp.fetch_issue_comments(
|
||||
comments = await common.fetch_issue_comments(
|
||||
repo_config, issue_number, token=github_token or app_token
|
||||
)
|
||||
except GitHubAuthError:
|
||||
github_token = await webapp._refresh_thread_github_token_after_401(
|
||||
github_token = await common._refresh_thread_github_token_after_401(
|
||||
thread_id, email, repo=repo_config
|
||||
)
|
||||
comments = await webapp.fetch_issue_comments(
|
||||
comments = await common.fetch_issue_comments(
|
||||
repo_config, issue_number, token=github_token or app_token
|
||||
)
|
||||
if comment_id and not any(item.get("comment_id") == comment_id for item in comments):
|
||||
|
|
@ -1035,7 +1034,7 @@ async def process_github_issue(payload: dict[str, Any], event_type: str) -> None
|
|||
},
|
||||
}
|
||||
|
||||
await webapp.upsert_agent_thread_owner_metadata(
|
||||
await common.upsert_agent_thread_owner_metadata(
|
||||
thread_id,
|
||||
source="github",
|
||||
repo_config=repo_config,
|
||||
|
|
@ -1044,17 +1043,17 @@ async def process_github_issue(payload: dict[str, Any], event_type: str) -> None
|
|||
source_context={"github_issue": configurable["github_issue"]},
|
||||
)
|
||||
|
||||
webapp.logger.info("Dispatching LangGraph run for thread %s from GitHub issue", thread_id)
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
await webapp.dispatch_agent_run(
|
||||
common.logger.info("Dispatching LangGraph run for thread %s from GitHub issue", thread_id)
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
prompt,
|
||||
configurable,
|
||||
source="github_issue",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
client=langgraph_client,
|
||||
)
|
||||
webapp.logger.info("LangGraph run dispatched for thread %s from GitHub issue", thread_id)
|
||||
common.logger.info("LangGraph run dispatched for thread %s from GitHub issue", thread_id)
|
||||
|
||||
|
||||
# ---- CI auto-fix handlers (re-wired onto the modular dispatch model) ----
|
||||
|
|
@ -1081,7 +1080,7 @@ async def process_github_ci_event(payload: dict[str, Any], event_type: str) -> N
|
|||
head_sha=head_sha,
|
||||
source="github_ci",
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"CI auto-fix for %s/%s@%s (%s): %s",
|
||||
repo_config["owner"],
|
||||
repo_config["name"],
|
||||
|
|
@ -1100,7 +1099,7 @@ def _parse_autofix_command(comment_body: str) -> bool | None:
|
|||
Returns ``None`` when the comment isn't an auto-fix command. Requires an
|
||||
Open SWE mention so a passing reference to "autofix off" doesn't toggle it.
|
||||
"""
|
||||
if not any(tag in comment_body.lower() for tag in webapp.OPEN_SWE_TAGS):
|
||||
if not any(tag in comment_body.lower() for tag in common.OPEN_SWE_TAGS):
|
||||
return None
|
||||
match = _AUTOFIX_COMMAND_RE.search(comment_body)
|
||||
if not match:
|
||||
|
|
@ -1135,7 +1134,7 @@ async def process_github_autofix_command(
|
|||
if ref is None:
|
||||
return
|
||||
await set_pr_autofix_disabled(ref["owner"], ref["name"], ref["number"], disabled)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Auto-fix %s for %s/%s#%s via comment",
|
||||
"disabled" if disabled else "enabled",
|
||||
ref["owner"],
|
||||
|
|
@ -1146,11 +1145,11 @@ async def process_github_autofix_command(
|
|||
comment_id = comment.get("id")
|
||||
if not isinstance(comment_id, int):
|
||||
return
|
||||
token = await webapp.get_github_app_installation_token()
|
||||
token = await common.get_github_app_installation_token()
|
||||
if not token:
|
||||
return
|
||||
try:
|
||||
await webapp.react_to_github_comment(
|
||||
await common.react_to_github_comment(
|
||||
{"owner": ref["owner"], "name": ref["name"]},
|
||||
comment_id,
|
||||
event_type=event_type,
|
||||
|
|
@ -1159,7 +1158,7 @@ async def process_github_autofix_command(
|
|||
node_id=comment.get("node_id"),
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug("Failed to react to auto-fix command comment", exc_info=True)
|
||||
common.logger.debug("Failed to react to auto-fix command comment", exc_info=True)
|
||||
|
||||
|
||||
# GitHub author_association values that imply at least repo-member trust. Used
|
||||
|
|
@ -1192,7 +1191,7 @@ def _is_actionable_review_payload(payload: dict[str, Any], event_type: str) -> b
|
|||
if not isinstance(node, dict):
|
||||
return False
|
||||
reviewer = (node.get("user") or {}).get("login", "")
|
||||
if reviewer in webapp.INTERNAL_BOT_LOGINS:
|
||||
if reviewer in common.INTERNAL_BOT_LOGINS:
|
||||
return False
|
||||
if node.get("author_association") not in _TRUSTED_REVIEW_ASSOCIATIONS:
|
||||
return False
|
||||
|
|
@ -1208,7 +1207,7 @@ async def process_github_autofix_review(payload: dict[str, Any], event_type: str
|
|||
comment = payload.get("comment") or payload.get("review", {})
|
||||
reviewer = (comment.get("user") or {}).get("login", "") if isinstance(comment, dict) else ""
|
||||
body = (comment.get("body") or "") if isinstance(comment, dict) else ""
|
||||
if not body.strip() or reviewer in webapp.INTERNAL_BOT_LOGINS:
|
||||
if not body.strip() or reviewer in common.INTERNAL_BOT_LOGINS:
|
||||
return
|
||||
result = await handle_review_feedback(
|
||||
repo_config={"owner": ref["owner"], "name": ref["name"]},
|
||||
|
|
@ -1218,7 +1217,7 @@ async def process_github_autofix_review(payload: dict[str, Any], event_type: str
|
|||
body=body,
|
||||
source="github_review",
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Auto-fix review feedback for %s/%s#%s: %s",
|
||||
ref["owner"],
|
||||
ref["name"],
|
||||
|
|
|
|||
198
agent/webhooks/github_routes.py
Normal file
198
agent/webhooks/github_routes.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
"""Github webhook HTTP routes."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import common
|
||||
from . import github as service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/webhooks/github")
|
||||
async def github_webhook(
|
||||
request: common.Request, background_tasks: common.BackgroundTasks
|
||||
) -> dict[str, str]:
|
||||
"""Handle GitHub webhooks for issue and PR events that tag @open-swe."""
|
||||
body = await request.body()
|
||||
|
||||
signature = request.headers.get("X-Hub-Signature-256", "")
|
||||
if not common.verify_github_signature(body, signature, secret=common.GITHUB_WEBHOOK_SECRET):
|
||||
common.logger.warning("Invalid GitHub webhook signature")
|
||||
raise common.HTTPException(status_code=401, detail="Invalid signature")
|
||||
|
||||
event_type = request.headers.get("X-GitHub-Event", "")
|
||||
if event_type not in common._SUPPORTED_GH_EVENTS:
|
||||
common.logger.info("Ignoring unsupported GitHub event type: %s", event_type)
|
||||
return {"status": "ignored", "reason": f"Unsupported event type: {event_type}"}
|
||||
|
||||
try:
|
||||
payload = common.json.loads(body)
|
||||
except common.json.JSONDecodeError:
|
||||
common.logger.exception("Failed to parse GitHub webhook JSON")
|
||||
return {"status": "error", "message": "Invalid JSON"}
|
||||
|
||||
webhook_repo = payload.get("repository", {})
|
||||
webhook_repo_config = {
|
||||
"owner": webhook_repo.get("owner", {}).get("login", ""),
|
||||
"name": webhook_repo.get("name", ""),
|
||||
}
|
||||
|
||||
issue = payload.get("issue", {})
|
||||
is_pull_request_comment = bool(event_type == "issue_comment" and issue.get("pull_request"))
|
||||
is_issue_comment = bool(event_type == "issue_comment" and not issue.get("pull_request"))
|
||||
is_issue_event = event_type == "issues"
|
||||
is_pull_request_event = event_type == "pull_request"
|
||||
|
||||
if is_pull_request_event:
|
||||
action = payload.get("action", "")
|
||||
if action not in common._SUPPORTED_GH_PULL_REQUEST_ACTIONS:
|
||||
common.logger.info("Ignoring unsupported GitHub pull_request action: %s", action)
|
||||
return {
|
||||
"status": "ignored",
|
||||
"reason": f"Unsupported GitHub pull_request action: {action}",
|
||||
}
|
||||
if action in common._GH_PR_AGENT_STATE_ACTIONS:
|
||||
background_tasks.add_task(common.update_agent_thread_pr_state, payload)
|
||||
if action in common._GH_PR_WATCH_TOGGLE_ACTIONS:
|
||||
common.logger.info(
|
||||
"Accepted GitHub PR %s webhook, scheduling reviewer watch update", action
|
||||
)
|
||||
background_tasks.add_task(service.process_github_pr_close, payload)
|
||||
return {"status": "accepted", "message": f"Processing PR {action} for reviewer watch"}
|
||||
if action in common._GH_PR_FIRST_REVIEW_ACTIONS:
|
||||
if not await common._is_repo_auto_review_enabled(webhook_repo_config):
|
||||
return {"status": "ignored", "reason": "Automatic review disabled for repository"}
|
||||
gate_rejection = await common._enforce_public_repo_org_gate(payload, "pull_request")
|
||||
if gate_rejection is not None:
|
||||
return gate_rejection
|
||||
common.logger.info("Accepted GitHub PR %s webhook, scheduling auto-review task", action)
|
||||
background_tasks.add_task(service.process_github_pr_ready, payload)
|
||||
return {"status": "accepted", "message": f"Processing PR {action} for auto-review"}
|
||||
common.logger.info("Ignoring unsupported GitHub pull_request action: %s", action)
|
||||
return {
|
||||
"status": "ignored",
|
||||
"reason": f"Unsupported GitHub pull_request action: {action}",
|
||||
}
|
||||
|
||||
if event_type == "push":
|
||||
if not await common._is_repo_auto_review_enabled(webhook_repo_config):
|
||||
return {"status": "ignored", "reason": "Automatic review disabled for repository"}
|
||||
common.logger.info("Accepted GitHub push webhook, scheduling reviewer watch evaluation")
|
||||
background_tasks.add_task(service.process_github_push_event, payload)
|
||||
return {"status": "accepted", "message": "Processing GitHub push for reviewer watch"}
|
||||
|
||||
if event_type in common._GH_CI_EVENTS:
|
||||
if not common.is_failing_ci_payload(payload, event_type):
|
||||
return {"status": "ignored", "reason": "CI event is not a completed failure"}
|
||||
if not await common._is_repo_auto_review_enabled(webhook_repo_config):
|
||||
return {"status": "ignored", "reason": "Automatic review disabled for repository"}
|
||||
common.logger.info(
|
||||
"Accepted GitHub %s webhook, scheduling CI auto-fix evaluation", event_type
|
||||
)
|
||||
background_tasks.add_task(service.process_github_ci_event, payload, event_type)
|
||||
return {"status": "accepted", "message": f"Processing GitHub {event_type} for auto-fix"}
|
||||
|
||||
if not common._is_repo_allowed(webhook_repo_config):
|
||||
common.logger.debug(
|
||||
"Rejecting GitHub webhook: repo '%s/%s' not in allowlist",
|
||||
webhook_repo_config.get("owner"),
|
||||
webhook_repo_config.get("name"),
|
||||
)
|
||||
return {"status": "ignored", "reason": "Repository not in allowlist"}
|
||||
|
||||
if is_issue_event:
|
||||
action = payload.get("action", "")
|
||||
if action not in common._SUPPORTED_GH_ISSUE_ACTIONS:
|
||||
common.logger.info("Ignoring unsupported GitHub issue action: %s", action)
|
||||
return {"status": "ignored", "reason": f"Unsupported GitHub issue action: {action}"}
|
||||
if action == "edited":
|
||||
changes = payload.get("changes", {})
|
||||
if not any(field in changes for field in ("body", "title")):
|
||||
common.logger.info("Ignoring GitHub issue edit without title/body changes")
|
||||
return {"status": "ignored", "reason": "Issue edit did not change title or body"}
|
||||
|
||||
issue_text = f"{issue.get('title', '')}\n\n{issue.get('body', '')}".lower()
|
||||
if not any(tag in issue_text for tag in common.OPEN_SWE_TAGS):
|
||||
common.logger.info("Ignoring issue that does not mention @openswe or @open-swe")
|
||||
return {"status": "ignored", "reason": "Issue does not mention @openswe or @open-swe"}
|
||||
|
||||
gate_rejection = await common._enforce_public_repo_org_gate(payload, event_type)
|
||||
if gate_rejection is not None:
|
||||
return gate_rejection
|
||||
|
||||
common.logger.info("Accepted GitHub issue webhook, scheduling background task")
|
||||
background_tasks.add_task(service.process_github_issue, payload, event_type)
|
||||
return {"status": "accepted", "message": "Processing GitHub issue event"}
|
||||
|
||||
action = payload.get("action", "")
|
||||
supported_comment_actions = common._SUPPORTED_GH_COMMENT_ACTIONS.get(event_type)
|
||||
if supported_comment_actions is None:
|
||||
common.logger.info("Ignoring unsupported GitHub payload shape for event=%s", event_type)
|
||||
return {"status": "ignored", "reason": f"Unsupported payload for event type: {event_type}"}
|
||||
if action and action not in supported_comment_actions:
|
||||
common.logger.debug("Ignoring unsupported GitHub %s action: %s", event_type, action)
|
||||
return {"status": "ignored", "reason": f"Unsupported GitHub {event_type} action: {action}"}
|
||||
|
||||
comment = payload.get("comment") or payload.get("review", {})
|
||||
comment_body = (comment.get("body") or "") if comment else ""
|
||||
|
||||
is_pr_related_comment = is_pull_request_comment or event_type in {
|
||||
"pull_request_review_comment",
|
||||
"pull_request_review",
|
||||
}
|
||||
autofix_command = service._parse_autofix_command(comment_body)
|
||||
if autofix_command is not None and is_pr_related_comment:
|
||||
if not await common._is_repo_auto_review_enabled(webhook_repo_config):
|
||||
return {"status": "ignored", "reason": "Automatic review disabled for repository"}
|
||||
gate_rejection = await common._enforce_public_repo_org_gate(payload, event_type)
|
||||
if gate_rejection is not None:
|
||||
return gate_rejection
|
||||
background_tasks.add_task(
|
||||
service.process_github_autofix_command, payload, event_type, disabled=autofix_command
|
||||
)
|
||||
return {"status": "accepted", "message": "Processing auto-fix toggle"}
|
||||
|
||||
if (
|
||||
event_type == "pull_request_review_comment"
|
||||
and common._review_comment_reply_parent_id(payload) is not None
|
||||
):
|
||||
gate_rejection = await common._enforce_public_repo_org_gate(payload, event_type)
|
||||
if gate_rejection is not None:
|
||||
return gate_rejection
|
||||
background_tasks.add_task(service.process_github_review_finding_reply, payload)
|
||||
return {"status": "accepted", "message": "Processing review finding reply"}
|
||||
|
||||
if not any(tag in comment_body.lower() for tag in common.OPEN_SWE_TAGS):
|
||||
if service._is_actionable_review_payload(
|
||||
payload, event_type
|
||||
) and await common._is_repo_auto_review_enabled(webhook_repo_config):
|
||||
gate_rejection = await common._enforce_public_repo_org_gate(payload, event_type)
|
||||
if gate_rejection is not None:
|
||||
return gate_rejection
|
||||
background_tasks.add_task(service.process_github_autofix_review, payload, event_type)
|
||||
return {"status": "accepted", "message": "Processing auto-fix review feedback"}
|
||||
common.logger.debug(
|
||||
"Ignoring GitHub %s%s that does not mention @openswe or @open-swe",
|
||||
event_type,
|
||||
f" action={action}" if action else "",
|
||||
)
|
||||
return {"status": "ignored", "reason": "Comment does not mention @openswe or @open-swe"}
|
||||
|
||||
gate_rejection = await common._enforce_public_repo_org_gate(payload, event_type)
|
||||
if gate_rejection is not None:
|
||||
return gate_rejection
|
||||
|
||||
common.logger.info("Accepted GitHub webhook: event=%s, scheduling background task", event_type)
|
||||
if is_pull_request_comment or event_type in {
|
||||
"pull_request_review_comment",
|
||||
"pull_request_review",
|
||||
}:
|
||||
background_tasks.add_task(service.process_github_pr_comment, payload, event_type)
|
||||
return {"status": "accepted", "message": f"Processing {event_type} event"}
|
||||
|
||||
if is_issue_comment:
|
||||
background_tasks.add_task(service.process_github_issue, payload, event_type)
|
||||
return {"status": "accepted", "message": "Processing GitHub issue comment event"}
|
||||
|
||||
common.logger.info("Ignoring unsupported GitHub payload shape for event=%s", event_type)
|
||||
return {"status": "ignored", "reason": f"Unsupported payload for event type: {event_type}"}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""Jira webhook handler — mirrors ``agent/webhooks/linear.py`` for Jira issues.
|
||||
|
||||
Helpers and constants stay in webapp.py; they are accessed through the module
|
||||
object (``webapp.X``) so tests that monkeypatch them keep working.
|
||||
Helpers and constants stay in common.py; they are accessed through the module
|
||||
object (``common.X``) so tests that monkeypatch them keep working.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
|
@ -10,7 +10,7 @@ from urllib.parse import urlparse
|
|||
import httpx
|
||||
from langchain_core.messages.content import create_text_block
|
||||
|
||||
from agent import webapp
|
||||
from . import common
|
||||
|
||||
|
||||
async def process_jira_issue( # noqa: PLR0912, PLR0915
|
||||
|
|
@ -20,20 +20,20 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
|
||||
Args:
|
||||
issue_data: The Jira issue data from the webhook (basic info + the
|
||||
triggering comment; see ``webapp.jira_webhook`` for the shape).
|
||||
triggering comment; see ``jira_routes.jira_webhook`` for the shape).
|
||||
repo_config: The repo configuration with owner and name.
|
||||
"""
|
||||
issue_key = issue_data.get("key", "")
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Processing Jira issue %s for repo %s/%s",
|
||||
issue_key,
|
||||
repo_config.get("owner"),
|
||||
repo_config.get("name"),
|
||||
)
|
||||
|
||||
thread_id = webapp.generate_thread_id_from_jira_issue(issue_key)
|
||||
thread_id = common.generate_thread_id_from_jira_issue(issue_key)
|
||||
|
||||
full_issue = await webapp.fetch_jira_issue_details(issue_key)
|
||||
full_issue = await common.fetch_jira_issue_details(issue_key)
|
||||
if not full_issue:
|
||||
full_issue = {}
|
||||
|
||||
|
|
@ -46,26 +46,26 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
user_name = comment_author.get("name") or None
|
||||
|
||||
user_email = actor_email
|
||||
webapp.logger.info("User email for issue %s: %s", issue_key, user_email)
|
||||
common.logger.info("User email for issue %s: %s", issue_key, user_email)
|
||||
|
||||
title = full_issue.get("title") or "No title"
|
||||
description = full_issue.get("description") or "No description"
|
||||
image_urls: list[str] = []
|
||||
description_image_urls = webapp.extract_image_urls(description)
|
||||
description_image_urls = common.extract_image_urls(description)
|
||||
if description_image_urls:
|
||||
image_urls.extend(description_image_urls)
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Found %d image URL(s) in issue description",
|
||||
len(description_image_urls),
|
||||
)
|
||||
|
||||
raw_comments = await webapp.fetch_jira_issue_comments(issue_key)
|
||||
raw_comments = await common.fetch_jira_issue_comments(issue_key)
|
||||
comments = [{**comment, "createdAt": comment.get("created", "")} for comment in raw_comments]
|
||||
comments_text = ""
|
||||
triggering_comment = issue_data.get("triggering_comment", "")
|
||||
triggering_comment_id = issue_data.get("triggering_comment_id", "")
|
||||
|
||||
bot_message_prefixes = webapp._GITHUB_BOT_MESSAGE_PREFIXES
|
||||
bot_message_prefixes = common._GITHUB_BOT_MESSAGE_PREFIXES
|
||||
|
||||
comment_ids: set[str] = set()
|
||||
comment_id_to_index: dict[str, int] = {}
|
||||
|
|
@ -82,22 +82,22 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
trigger_index = comment_id_to_index.get(triggering_comment_id)
|
||||
if trigger_index is not None:
|
||||
relevant_comments = comments[trigger_index:]
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Using triggering comment index %d to build relevant comments",
|
||||
trigger_index,
|
||||
)
|
||||
else:
|
||||
relevant_comments = webapp.get_recent_comments(comments, bot_message_prefixes)
|
||||
relevant_comments = common.get_recent_comments(comments, bot_message_prefixes)
|
||||
|
||||
if relevant_comments:
|
||||
comments_text = "\n\n## Comments:\n"
|
||||
for comment in relevant_comments:
|
||||
author = (comment.get("author") or {}).get("name") or "User"
|
||||
body = comment.get("body", "")
|
||||
body_image_urls = webapp.extract_image_urls(body)
|
||||
body_image_urls = common.extract_image_urls(body)
|
||||
if body_image_urls:
|
||||
image_urls.extend(body_image_urls)
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Found %d image URL(s) in comment by %s",
|
||||
len(body_image_urls),
|
||||
author,
|
||||
|
|
@ -111,16 +111,16 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
comments_text = "\n\n## Comments:\n"
|
||||
trigger_author = comment_author.get("name") or "Unknown"
|
||||
trigger_body = triggering_comment
|
||||
trigger_image_urls = webapp.extract_image_urls(trigger_body)
|
||||
trigger_image_urls = common.extract_image_urls(trigger_body)
|
||||
if trigger_image_urls:
|
||||
image_urls.extend(trigger_image_urls)
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Found %d image URL(s) in triggering comment by %s",
|
||||
len(trigger_image_urls),
|
||||
trigger_author,
|
||||
)
|
||||
comments_text += f"\n**{trigger_author}:** {trigger_body}\n"
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Appended triggering comment %s not present in issue comments list",
|
||||
triggering_comment_id or "<missing-id>",
|
||||
)
|
||||
|
|
@ -152,22 +152,22 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
# user-mapping store Slack/Linear use, so PRs open *as the triggering user*
|
||||
# and the thread is tagged for the dashboard. Restricted to the comment
|
||||
# author so token attribution never falls back to reporter/assignee.
|
||||
mapped_login = await webapp.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
mapped_login = await common.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
# Only attribute to an *active* user mapping; a pending/unconfirmed mapping
|
||||
# must never drive PR authorship or token resolution.
|
||||
if mapped_login and not webapp.is_login_mapped(mapped_login):
|
||||
webapp.logger.info(
|
||||
if mapped_login and not common.is_login_mapped(mapped_login):
|
||||
common.logger.info(
|
||||
"Jira actor login %s is not an active mapping; running unattributed", mapped_login
|
||||
)
|
||||
mapped_login = None
|
||||
|
||||
image_model_override: tuple[str, str] | None = None
|
||||
if image_urls:
|
||||
image_urls = webapp.dedupe_urls(image_urls)
|
||||
resolved_model_id = await webapp.resolve_agent_model_id(mapped_login)
|
||||
if not webapp.model_supports_images(resolved_model_id):
|
||||
fallback_model_id, fallback_effort = webapp.default_vision_model_pair()
|
||||
webapp.logger.info(
|
||||
image_urls = common.dedupe_urls(image_urls)
|
||||
resolved_model_id = await common.resolve_agent_model_id(mapped_login)
|
||||
if not common.model_supports_images(resolved_model_id):
|
||||
fallback_model_id, fallback_effort = common.default_vision_model_pair()
|
||||
common.logger.info(
|
||||
"Using vision fallback model %s for %d Jira image(s); configured model %s "
|
||||
"does not support images",
|
||||
fallback_model_id,
|
||||
|
|
@ -176,15 +176,15 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
)
|
||||
resolved_model_id = fallback_model_id
|
||||
image_model_override = (fallback_model_id, fallback_effort)
|
||||
webapp.logger.info("Preparing %d image(s) for multimodal content", len(image_urls))
|
||||
webapp.logger.debug("Image hosts: %s", [urlparse(u).hostname for u in image_urls])
|
||||
common.logger.info("Preparing %d image(s) for multimodal content", len(image_urls))
|
||||
common.logger.debug("Image hosts: %s", [urlparse(u).hostname for u in image_urls])
|
||||
|
||||
async with httpx.AsyncClient(timeout=webapp.DEFAULT_HTTP_TIMEOUT) as client:
|
||||
async with httpx.AsyncClient(timeout=common.DEFAULT_HTTP_TIMEOUT) as client:
|
||||
for image_url in image_urls:
|
||||
image_block = await webapp.fetch_image_block(image_url, client)
|
||||
image_block = await common.fetch_image_block(image_url, client)
|
||||
if image_block:
|
||||
content_blocks.append(image_block)
|
||||
webapp.logger.info("Built %d content block(s) for prompt", len(content_blocks))
|
||||
common.logger.info("Built %d content block(s) for prompt", len(content_blocks))
|
||||
|
||||
configurable: dict[str, Any] = {
|
||||
"repo": repo_config,
|
||||
|
|
@ -205,7 +205,7 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
configurable["agent_model_id"] = image_model_override[0]
|
||||
configurable["agent_effort"] = image_model_override[1]
|
||||
|
||||
await webapp.upsert_agent_thread_owner_metadata(
|
||||
await common.upsert_agent_thread_owner_metadata(
|
||||
thread_id,
|
||||
source="jira",
|
||||
repo_config=repo_config,
|
||||
|
|
@ -215,16 +215,16 @@ async def process_jira_issue( # noqa: PLR0912, PLR0915
|
|||
source_context={"jira_issue": configurable["jira_issue"]},
|
||||
)
|
||||
|
||||
run = await webapp.dispatch_agent_run(
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
content_blocks,
|
||||
configurable,
|
||||
source="jira",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"LangGraph run dispatched for thread %s (run=%s)",
|
||||
thread_id,
|
||||
run.get("run_id") if isinstance(run, dict) else None,
|
||||
)
|
||||
await webapp.post_jira_trace_comment(issue_key, thread_id)
|
||||
await common.post_jira_trace_comment(issue_key, thread_id)
|
||||
|
|
|
|||
182
agent/webhooks/jira_routes.py
Normal file
182
agent/webhooks/jira_routes.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
"""Jira webhook HTTP routes."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import common
|
||||
from . import jira as service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/webhooks/jira")
|
||||
async def jira_webhook( # noqa: PLR0911, PLR0912
|
||||
request: common.Request, background_tasks: common.BackgroundTasks
|
||||
) -> dict[str, str]:
|
||||
"""Handle Jira Automation webhooks.
|
||||
|
||||
Triggers a new LangGraph run when a comment mentioning ``@openswe`` is
|
||||
added to an issue. Unlike Linear, Jira Cloud has no native outgoing-webhook
|
||||
signing, so this is fronted by a Jira **Automation** rule (trigger:
|
||||
"Issue commented") with a "Send web request" action posting a custom JSON
|
||||
body to this route, carrying the shared-secret token in
|
||||
``X-Automation-Webhook-Token``.
|
||||
|
||||
Expected payload (the Automation rule's custom JSON body, built from smart
|
||||
values)::
|
||||
|
||||
{
|
||||
"issue_key": "PROJ-123",
|
||||
"comment_id": "10050",
|
||||
"comment_author_is_bot": false
|
||||
}
|
||||
|
||||
``issue_key`` (validated against the Jira key format) and ``comment_id`` are
|
||||
**required** — they are the only fields trusted from the unsigned body, and
|
||||
only as a pointer. The triggering comment's real author and text are then
|
||||
re-fetched from Jira server-side (``fetch_jira_comment``) and everything
|
||||
security-relevant (identity/attribution, the ``@openswe`` trigger check, the
|
||||
prompt text, repo routing) is derived from that authoritative record, never
|
||||
from payload-supplied author/body fields. ``comment_author_is_bot`` is an
|
||||
optional cheap early-out only. A comment that cannot be corroborated
|
||||
server-side is rejected.
|
||||
"""
|
||||
common.logger.info("Received Jira webhook")
|
||||
if not common.verify_jira_source_ip(request):
|
||||
raise common.HTTPException(status_code=403, detail="Source IP not allowed")
|
||||
|
||||
if not common.verify_jira_secret(request.headers):
|
||||
common.logger.warning("Invalid Jira webhook token")
|
||||
raise common.HTTPException(status_code=401, detail="Invalid token")
|
||||
|
||||
body = await request.body()
|
||||
|
||||
if not common.verify_jira_signature(body, request.headers):
|
||||
raise common.HTTPException(status_code=401, detail="Invalid signature")
|
||||
|
||||
try:
|
||||
payload = common.json.loads(body)
|
||||
except common.json.JSONDecodeError:
|
||||
common.logger.exception("Failed to parse Jira webhook JSON")
|
||||
return {"status": "error", "message": "Invalid JSON"}
|
||||
|
||||
# Cheap early-out on the (untrusted) payload before any Jira API call.
|
||||
if payload.get("comment_author_is_bot"):
|
||||
common.logger.debug("Ignoring webhook: comment is from a bot")
|
||||
return {"status": "ignored", "reason": "Comment is from a bot"}
|
||||
|
||||
issue_key = payload.get("issue_key", "") or ""
|
||||
if not common.is_valid_jira_issue_key(issue_key):
|
||||
common.logger.debug("Ignoring webhook: missing or malformed issue key")
|
||||
return {"status": "ignored", "reason": "Missing or malformed issue key"}
|
||||
|
||||
comment_id = payload.get("comment_id", "") or ""
|
||||
if not comment_id:
|
||||
common.logger.debug("Ignoring webhook: no comment id to corroborate")
|
||||
return {"status": "ignored", "reason": "No comment id in payload"}
|
||||
|
||||
# Corroborate against the real Jira record. The webhook body is unsigned, so
|
||||
# the triggering comment's author and text are read server-side (matched by
|
||||
# comment_id) rather than trusted from the payload — this is what prevents a
|
||||
# secret-holder from spoofing the author (to hijack another user's token) or
|
||||
# injecting arbitrary agent instructions. A comment that can't be fetched
|
||||
# (nonexistent issue/comment or a forged event) is rejected.
|
||||
server_comment = await common.fetch_jira_comment(issue_key, comment_id)
|
||||
if not server_comment:
|
||||
common.logger.warning(
|
||||
"Rejecting Jira webhook: comment %s on %s could not be corroborated",
|
||||
comment_id,
|
||||
issue_key,
|
||||
)
|
||||
return {"status": "ignored", "reason": "Triggering comment not found"}
|
||||
|
||||
author = server_comment.get("author") or {}
|
||||
account_id = author.get("account_id") or ""
|
||||
display_name = author.get("name") or ""
|
||||
comment_body = server_comment.get("body") or ""
|
||||
|
||||
for prefix in common._GITHUB_BOT_MESSAGE_PREFIXES:
|
||||
if comment_body.startswith(prefix):
|
||||
common.logger.debug("Ignoring webhook: comment is our own bot message")
|
||||
return {"status": "ignored", "reason": "Comment is our own bot message"}
|
||||
if "@openswe" not in comment_body.lower():
|
||||
common.logger.debug("Ignoring webhook: comment doesn't mention @openswe")
|
||||
return {"status": "ignored", "reason": "Comment doesn't mention @openswe"}
|
||||
|
||||
# Derive the project key from the (validated, corroborated) issue key rather
|
||||
# than trusting the payload's project_key for repo routing.
|
||||
project_key = issue_key.split("-", 1)[0]
|
||||
actor_email = await common.get_jira_user_email(account_id) if account_id else None
|
||||
|
||||
repo_config = common.extract_repo_from_text(
|
||||
comment_body, default_owner=common.DEFAULT_REPO_OWNER
|
||||
)
|
||||
|
||||
if repo_config:
|
||||
common.logger.debug(
|
||||
"Using repo from comment body: %s/%s",
|
||||
repo_config["owner"],
|
||||
repo_config["name"],
|
||||
)
|
||||
else:
|
||||
try:
|
||||
profile_repo = await common.get_profile_default_repo(
|
||||
await common.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
common.logger.exception("Failed to apply dashboard default_repo for Jira user")
|
||||
profile_repo = None
|
||||
if profile_repo:
|
||||
common.logger.info(
|
||||
"Applying dashboard default_repo for Jira user %s: %s/%s",
|
||||
account_id,
|
||||
profile_repo["owner"],
|
||||
profile_repo["name"],
|
||||
)
|
||||
repo_config = profile_repo
|
||||
|
||||
if not repo_config:
|
||||
repo_config = common.get_repo_config_from_jira_mapping(project_key)
|
||||
|
||||
if not repo_config:
|
||||
repo_config = await common.get_team_default_repo()
|
||||
|
||||
if not repo_config:
|
||||
return {"status": "ignored", "reason": "No default repository configured"}
|
||||
|
||||
if not common._is_repo_allowed(repo_config):
|
||||
common.logger.warning(
|
||||
"Rejecting Jira webhook: repo '%s/%s' not in allowlist",
|
||||
repo_config.get("owner"),
|
||||
repo_config.get("name"),
|
||||
)
|
||||
return {"status": "ignored", "reason": "Repository not in allowlist"}
|
||||
|
||||
issue_data = {
|
||||
"key": issue_key,
|
||||
"project_key": project_key,
|
||||
"triggering_comment": comment_body,
|
||||
"triggering_comment_id": comment_id,
|
||||
"comment_author": {
|
||||
"account_id": account_id,
|
||||
"email": actor_email,
|
||||
"name": display_name,
|
||||
},
|
||||
}
|
||||
|
||||
common.logger.info(
|
||||
"Accepted webhook for issue '%s', scheduling background task",
|
||||
issue_key,
|
||||
)
|
||||
background_tasks.add_task(service.process_jira_issue, issue_data, repo_config)
|
||||
|
||||
return {
|
||||
"status": "accepted",
|
||||
"message": f"Processing issue '{issue_key}' for repo "
|
||||
f"{repo_config['owner']}/{repo_config['name']}",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/webhooks/jira")
|
||||
async def jira_webhook_verify() -> dict[str, str]:
|
||||
"""Verify endpoint for Jira webhook setup."""
|
||||
return {"status": "ok", "message": "Jira webhook endpoint is active"}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""Linear webhook handler — moved out of webapp.py (behavior-identical).
|
||||
"""Linear webhook handler — moved out of common.py (behavior-identical).
|
||||
|
||||
Helpers and constants stay in webapp.py; they are accessed through the module
|
||||
object (``webapp.X``) so tests that monkeypatch them keep working.
|
||||
Helpers and constants stay in common.py; they are accessed through the module
|
||||
object (``common.X``) so tests that monkeypatch them keep working.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
|
@ -10,7 +10,7 @@ from urllib.parse import urlparse
|
|||
import httpx
|
||||
from langchain_core.messages.content import create_text_block
|
||||
|
||||
from agent import webapp
|
||||
from . import common
|
||||
|
||||
|
||||
async def process_linear_issue( # noqa: PLR0912, PLR0915
|
||||
|
|
@ -23,7 +23,7 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
repo_config: The repo configuration with owner and name.
|
||||
"""
|
||||
issue_id = issue_data.get("id", "")
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Processing Linear issue %s for repo %s/%s",
|
||||
issue_id,
|
||||
repo_config.get("owner"),
|
||||
|
|
@ -32,11 +32,11 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
|
||||
triggering_comment_id = issue_data.get("triggering_comment_id", "")
|
||||
if triggering_comment_id:
|
||||
await webapp.react_to_linear_comment(triggering_comment_id, "👀")
|
||||
await common.react_to_linear_comment(triggering_comment_id, "👀")
|
||||
|
||||
thread_id = webapp.generate_thread_id_from_issue(issue_id)
|
||||
thread_id = common.generate_thread_id_from_issue(issue_id)
|
||||
|
||||
full_issue = await webapp.fetch_linear_issue_details(issue_id)
|
||||
full_issue = await common.fetch_linear_issue_details(issue_id)
|
||||
if not full_issue:
|
||||
full_issue = issue_data
|
||||
|
||||
|
|
@ -64,15 +64,15 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
user_email = assignee.get("email")
|
||||
user_name = user_name or assignee.get("name")
|
||||
|
||||
webapp.logger.info("User email for issue %s: %s", issue_id, user_email)
|
||||
common.logger.info("User email for issue %s: %s", issue_id, user_email)
|
||||
|
||||
title = full_issue.get("title", "No title")
|
||||
description = full_issue.get("description") or "No description"
|
||||
image_urls: list[str] = []
|
||||
description_image_urls = webapp.extract_image_urls(description)
|
||||
description_image_urls = common.extract_image_urls(description)
|
||||
if description_image_urls:
|
||||
image_urls.extend(description_image_urls)
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Found %d image URL(s) in issue description",
|
||||
len(description_image_urls),
|
||||
)
|
||||
|
|
@ -107,12 +107,12 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
trigger_index = comment_id_to_index.get(triggering_comment_id)
|
||||
if trigger_index is not None:
|
||||
relevant_comments = comments[trigger_index:]
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Using triggering comment index %d to build relevant comments",
|
||||
trigger_index,
|
||||
)
|
||||
else:
|
||||
relevant_comments = webapp.get_recent_comments(comments, bot_message_prefixes)
|
||||
relevant_comments = common.get_recent_comments(comments, bot_message_prefixes)
|
||||
|
||||
if relevant_comments:
|
||||
comments_text = "\n\n## Comments:\n"
|
||||
|
|
@ -120,10 +120,10 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
user = comment.get("user") or {}
|
||||
author = user.get("name", "User")
|
||||
body = comment.get("body", "")
|
||||
body_image_urls = webapp.extract_image_urls(body)
|
||||
body_image_urls = common.extract_image_urls(body)
|
||||
if body_image_urls:
|
||||
image_urls.extend(body_image_urls)
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Found %d image URL(s) in comment by %s",
|
||||
len(body_image_urls),
|
||||
author,
|
||||
|
|
@ -137,16 +137,16 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
comments_text = "\n\n## Comments:\n"
|
||||
trigger_author = comment_author.get("name", "Unknown")
|
||||
trigger_body = triggering_comment
|
||||
trigger_image_urls = webapp.extract_image_urls(trigger_body)
|
||||
trigger_image_urls = common.extract_image_urls(trigger_body)
|
||||
if trigger_image_urls:
|
||||
image_urls.extend(trigger_image_urls)
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Found %d image URL(s) in triggering comment by %s",
|
||||
len(trigger_image_urls),
|
||||
trigger_author,
|
||||
)
|
||||
comments_text += f"\n**{trigger_author}:** {trigger_body}\n"
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Appended triggering comment %s not present in issue comments list",
|
||||
triggering_comment_id or "<missing-id>",
|
||||
)
|
||||
|
|
@ -183,15 +183,15 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
# user-mapping store Slack uses, so PRs open *as the triggering user* and the
|
||||
# thread is tagged for the dashboard. Restricted to the comment author so
|
||||
# token attribution never falls back to creator/assignee.
|
||||
mapped_login = await webapp.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
mapped_login = await common.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
|
||||
image_model_override: tuple[str, str] | None = None
|
||||
if image_urls:
|
||||
image_urls = webapp.dedupe_urls(image_urls)
|
||||
resolved_model_id = await webapp.resolve_agent_model_id(mapped_login)
|
||||
if not webapp.model_supports_images(resolved_model_id):
|
||||
fallback_model_id, fallback_effort = webapp.default_vision_model_pair()
|
||||
webapp.logger.info(
|
||||
image_urls = common.dedupe_urls(image_urls)
|
||||
resolved_model_id = await common.resolve_agent_model_id(mapped_login)
|
||||
if not common.model_supports_images(resolved_model_id):
|
||||
fallback_model_id, fallback_effort = common.default_vision_model_pair()
|
||||
common.logger.info(
|
||||
"Using vision fallback model %s for %d Linear image(s); configured model %s "
|
||||
"does not support images",
|
||||
fallback_model_id,
|
||||
|
|
@ -200,15 +200,15 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
)
|
||||
resolved_model_id = fallback_model_id
|
||||
image_model_override = (fallback_model_id, fallback_effort)
|
||||
webapp.logger.info("Preparing %d image(s) for multimodal content", len(image_urls))
|
||||
webapp.logger.debug("Image hosts: %s", [urlparse(u).hostname for u in image_urls])
|
||||
common.logger.info("Preparing %d image(s) for multimodal content", len(image_urls))
|
||||
common.logger.debug("Image hosts: %s", [urlparse(u).hostname for u in image_urls])
|
||||
|
||||
async with httpx.AsyncClient(timeout=webapp.DEFAULT_HTTP_TIMEOUT) as client:
|
||||
async with httpx.AsyncClient(timeout=common.DEFAULT_HTTP_TIMEOUT) as client:
|
||||
for image_url in image_urls:
|
||||
image_block = await webapp.fetch_image_block(image_url, client)
|
||||
image_block = await common.fetch_image_block(image_url, client)
|
||||
if image_block:
|
||||
content_blocks.append(image_block)
|
||||
webapp.logger.info("Built %d content block(s) for prompt", len(content_blocks))
|
||||
common.logger.info("Built %d content block(s) for prompt", len(content_blocks))
|
||||
|
||||
linear_project_id = ""
|
||||
linear_issue_number = ""
|
||||
|
|
@ -237,7 +237,7 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
configurable["agent_model_id"] = image_model_override[0]
|
||||
configurable["agent_effort"] = image_model_override[1]
|
||||
|
||||
await webapp.upsert_agent_thread_owner_metadata(
|
||||
await common.upsert_agent_thread_owner_metadata(
|
||||
thread_id,
|
||||
source="linear",
|
||||
repo_config=repo_config,
|
||||
|
|
@ -247,16 +247,16 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
source_context={"linear_issue": configurable["linear_issue"]},
|
||||
)
|
||||
|
||||
run = await webapp.dispatch_agent_run(
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
content_blocks,
|
||||
configurable,
|
||||
source="linear",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"LangGraph run dispatched for thread %s (run=%s)",
|
||||
thread_id,
|
||||
run.get("run_id") if isinstance(run, dict) else None,
|
||||
)
|
||||
await webapp.post_linear_trace_comment(issue_id, thread_id, triggering_comment_id)
|
||||
await common.post_linear_trace_comment(issue_id, thread_id, triggering_comment_id)
|
||||
|
|
|
|||
168
agent/webhooks/linear_routes.py
Normal file
168
agent/webhooks/linear_routes.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
"""Linear webhook HTTP routes."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import common
|
||||
from . import linear as service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/webhooks/linear")
|
||||
async def linear_webhook( # noqa: PLR0911, PLR0912, PLR0915
|
||||
request: common.Request, background_tasks: common.BackgroundTasks
|
||||
) -> dict[str, str]:
|
||||
"""Handle Linear webhooks.
|
||||
|
||||
Triggers a new LangGraph run when an issue gets the 'open-swe' label added.
|
||||
"""
|
||||
common.logger.info("Received Linear webhook")
|
||||
body = await request.body()
|
||||
|
||||
signature = request.headers.get("Linear-Signature", "")
|
||||
if not common.verify_linear_signature(body, signature, common.LINEAR_WEBHOOK_SECRET):
|
||||
common.logger.warning("Invalid webhook signature")
|
||||
raise common.HTTPException(status_code=401, detail="Invalid signature")
|
||||
|
||||
try:
|
||||
payload = common.json.loads(body)
|
||||
except common.json.JSONDecodeError:
|
||||
common.logger.exception("Failed to parse webhook JSON")
|
||||
return {"status": "error", "message": "Invalid JSON"}
|
||||
|
||||
if payload.get("type") != "Comment":
|
||||
common.logger.debug("Ignoring webhook: not a Comment event")
|
||||
return {"status": "ignored", "reason": "Not a Comment event"}
|
||||
|
||||
action = payload.get("action")
|
||||
if action != "create":
|
||||
common.logger.debug("Ignoring webhook: action is %s, not create", action)
|
||||
return {
|
||||
"status": "ignored",
|
||||
"reason": f"Comment action is '{action}', only processing 'create'",
|
||||
}
|
||||
|
||||
data = payload.get("data", {})
|
||||
|
||||
if data.get("botActor"):
|
||||
common.logger.debug("Ignoring webhook: comment is from a bot")
|
||||
return {"status": "ignored", "reason": "Comment is from a bot"}
|
||||
|
||||
comment_body = data.get("body", "")
|
||||
bot_message_prefixes = [
|
||||
"🔐 **GitHub Authentication Required**",
|
||||
"✅ **Pull Request Created**",
|
||||
"✅ **Pull Request Updated**",
|
||||
"**Pull Request Created**",
|
||||
"**Pull Request Updated**",
|
||||
"🤖 **Agent Response**",
|
||||
"❌ **Agent Error**",
|
||||
]
|
||||
for prefix in bot_message_prefixes:
|
||||
if comment_body.startswith(prefix):
|
||||
common.logger.debug("Ignoring webhook: comment is our own bot message")
|
||||
return {"status": "ignored", "reason": "Comment is our own bot message"}
|
||||
if "@openswe" not in comment_body.lower():
|
||||
common.logger.debug("Ignoring webhook: comment doesn't mention @openswe")
|
||||
return {"status": "ignored", "reason": "Comment doesn't mention @openswe"}
|
||||
|
||||
issue = data.get("issue", {})
|
||||
if not issue:
|
||||
common.logger.debug("Ignoring webhook: no issue data in comment")
|
||||
return {"status": "ignored", "reason": "No issue data in comment"}
|
||||
|
||||
# Fetch full issue details to get project info (webhook doesn't include it)
|
||||
issue_id = issue.get("id", "")
|
||||
full_issue = await common.fetch_linear_issue_details(issue_id)
|
||||
if not full_issue:
|
||||
common.logger.warning("Failed to fetch full issue details, using webhook data")
|
||||
full_issue = issue
|
||||
|
||||
repo_config = common.extract_repo_from_text(
|
||||
comment_body, default_owner=common.DEFAULT_REPO_OWNER
|
||||
)
|
||||
|
||||
if repo_config:
|
||||
common.logger.debug(
|
||||
"Using repo from comment body: %s/%s",
|
||||
repo_config["owner"],
|
||||
repo_config["name"],
|
||||
)
|
||||
else:
|
||||
comment_user_email = (data.get("user") or {}).get("email")
|
||||
try:
|
||||
profile_repo = await common.get_profile_default_repo(
|
||||
await common.resolve_login_from_email_async(comment_user_email)
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
common.logger.exception("Failed to apply dashboard default_repo for Linear user")
|
||||
profile_repo = None
|
||||
if profile_repo:
|
||||
common.logger.info(
|
||||
"Applying dashboard default_repo for Linear user %s: %s/%s",
|
||||
comment_user_email,
|
||||
profile_repo["owner"],
|
||||
profile_repo["name"],
|
||||
)
|
||||
repo_config = profile_repo
|
||||
|
||||
if not repo_config:
|
||||
team = full_issue.get("team", {})
|
||||
team_name = team.get("name", "") if team else ""
|
||||
project = full_issue.get("project")
|
||||
project_name = project.get("name", "") if project else ""
|
||||
|
||||
team_identifier = team_name.strip() if team_name else ""
|
||||
project_key = project_name.strip() if project_name else ""
|
||||
|
||||
repo_config = common.get_repo_config_from_team_mapping(team_identifier, project_key)
|
||||
|
||||
common.logger.debug(
|
||||
"Team/project lookup result",
|
||||
extra={
|
||||
"team_name": team_identifier,
|
||||
"project_name": project_key,
|
||||
"repo_config": repo_config,
|
||||
},
|
||||
)
|
||||
|
||||
if not repo_config:
|
||||
repo_config = await common.get_team_default_repo()
|
||||
|
||||
if not repo_config:
|
||||
return {"status": "ignored", "reason": "No default repository configured"}
|
||||
|
||||
if not common._is_repo_allowed(repo_config):
|
||||
common.logger.warning(
|
||||
"Rejecting Linear webhook: repo '%s/%s' not in allowlist",
|
||||
repo_config.get("owner"),
|
||||
repo_config.get("name"),
|
||||
)
|
||||
return {"status": "ignored", "reason": "Repository not in allowlist"}
|
||||
|
||||
repo_owner = repo_config["owner"]
|
||||
repo_name = repo_config["name"]
|
||||
|
||||
issue["triggering_comment"] = comment_body
|
||||
issue["triggering_comment_id"] = data.get("id", "")
|
||||
comment_user = data.get("user", {})
|
||||
if comment_user:
|
||||
issue["comment_author"] = comment_user
|
||||
|
||||
common.logger.info(
|
||||
"Accepted webhook for issue '%s' (%s), scheduling background task",
|
||||
issue.get("title"),
|
||||
issue.get("id"),
|
||||
)
|
||||
background_tasks.add_task(service.process_linear_issue, issue, repo_config)
|
||||
|
||||
return {
|
||||
"status": "accepted",
|
||||
"message": f"Processing issue '{issue.get('title')}' for repo {repo_owner}/{repo_name}",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/webhooks/linear")
|
||||
async def linear_webhook_verify() -> dict[str, str]:
|
||||
"""Verify endpoint for Linear webhook setup."""
|
||||
return {"status": "ok", "message": "Linear webhook endpoint is active"}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""Slack webhook handler — moved out of webapp.py (behavior-identical).
|
||||
"""Slack webhook handler — moved out of common.py (behavior-identical).
|
||||
|
||||
Helpers and constants stay in webapp.py; they are accessed through the module
|
||||
object (``webapp.X``) so tests that monkeypatch them keep working.
|
||||
Helpers and constants stay in common.py; they are accessed through the module
|
||||
object (``common.X``) so tests that monkeypatch them keep working.
|
||||
"""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
|
@ -10,7 +10,7 @@ from typing import Any
|
|||
import httpx
|
||||
from langchain_core.messages.content import create_text_block
|
||||
|
||||
from agent import webapp
|
||||
from . import common
|
||||
|
||||
|
||||
def _format_slack_thread_section(
|
||||
|
|
@ -31,9 +31,9 @@ def _format_slack_thread_section(
|
|||
lines.append(f"- Channel name: #{channel_name}")
|
||||
lines.append(f"- Thread TS: {thread_ts}")
|
||||
lines.append(f"- Context starts at: {context_source}")
|
||||
channel_description = webapp.get_slack_channel_context_description(channel_context)
|
||||
channel_description = common.get_slack_channel_context_description(channel_context)
|
||||
if channel_description:
|
||||
lines.extend(webapp.format_untrusted_channel_description(channel_description))
|
||||
lines.extend(common.format_untrusted_channel_description(channel_description))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ async def process_slack_mention(event_data: dict[str, Any], repo_config: dict[st
|
|||
try:
|
||||
await _process_slack_mention_impl(event_data, repo_config)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.exception("Unexpected error while processing Slack mention")
|
||||
common.logger.exception("Unexpected error while processing Slack mention")
|
||||
await _notify_slack_processing_error(event_data, repo_config)
|
||||
|
||||
|
||||
|
|
@ -58,13 +58,13 @@ async def _notify_slack_processing_error(
|
|||
if not channel_id or not thread_ts:
|
||||
return
|
||||
|
||||
thread_id = webapp.generate_thread_id_from_slack_thread(channel_id, thread_ts)
|
||||
thread_id = common.generate_thread_id_from_slack_thread(channel_id, thread_ts)
|
||||
try:
|
||||
clean_text = (
|
||||
webapp.strip_bot_mention(text, bot_user_id, bot_username=webapp.SLACK_BOT_USERNAME)
|
||||
common.strip_bot_mention(text, bot_user_id, bot_username=common.SLACK_BOT_USERNAME)
|
||||
or "Slack request"
|
||||
)
|
||||
await webapp.upsert_agent_thread_owner_metadata(
|
||||
await common.upsert_agent_thread_owner_metadata(
|
||||
thread_id,
|
||||
source="slack",
|
||||
repo_config=repo_config,
|
||||
|
|
@ -79,12 +79,12 @@ async def _notify_slack_processing_error(
|
|||
},
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Could not persist Slack error metadata for thread %s", thread_id, exc_info=True
|
||||
)
|
||||
|
||||
try:
|
||||
await webapp.get_client(url=webapp.LANGGRAPH_URL).threads.update(
|
||||
await common.get_client(url=common.LANGGRAPH_URL).threads.update(
|
||||
thread_id=thread_id,
|
||||
metadata={
|
||||
"latest_run_status": "error",
|
||||
|
|
@ -92,14 +92,14 @@ async def _notify_slack_processing_error(
|
|||
},
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.warning("Could not mark Slack thread %s as errored", thread_id, exc_info=True)
|
||||
common.logger.warning("Could not mark Slack thread %s as errored", thread_id, exc_info=True)
|
||||
|
||||
try:
|
||||
await webapp.set_slack_assistant_status(channel_id, thread_ts, status="")
|
||||
await common.set_slack_assistant_status(channel_id, thread_ts, status="")
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug("Could not clear Slack assistant status", exc_info=True)
|
||||
common.logger.debug("Could not clear Slack assistant status", exc_info=True)
|
||||
|
||||
dashboard_url = webapp.dashboard_thread_url(thread_id)
|
||||
dashboard_url = common.dashboard_thread_url(thread_id)
|
||||
message = (
|
||||
"⚠️ I hit an unexpected error while handling this Slack thread. "
|
||||
"Send another message and I'll try again."
|
||||
|
|
@ -107,9 +107,9 @@ async def _notify_slack_processing_error(
|
|||
if dashboard_url:
|
||||
message += f" You can view the error in <{dashboard_url}|Open SWE Web>."
|
||||
try:
|
||||
await webapp.post_slack_thread_reply(channel_id, thread_ts, message)
|
||||
await common.post_slack_thread_reply(channel_id, thread_ts, message)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Could not post Slack error notification for thread %s", thread_id, exc_info=True
|
||||
)
|
||||
|
||||
|
|
@ -127,11 +127,11 @@ async def _process_slack_mention_impl(
|
|||
channel_context = (
|
||||
channel_context_raw
|
||||
if isinstance(channel_context_raw, dict)
|
||||
else webapp.normalize_slack_channel_context(channel_id, None)
|
||||
else common.normalize_slack_channel_context(channel_id, None)
|
||||
)
|
||||
|
||||
if not channel_id or not thread_ts or not event_ts:
|
||||
webapp.logger.warning(
|
||||
common.logger.warning(
|
||||
"Missing Slack event fields (channel_id=%s, thread_ts=%s, event_ts=%s)",
|
||||
channel_id,
|
||||
thread_ts,
|
||||
|
|
@ -139,20 +139,20 @@ async def _process_slack_mention_impl(
|
|||
)
|
||||
return
|
||||
|
||||
await webapp.set_slack_assistant_status(channel_id, thread_ts)
|
||||
await common.set_slack_assistant_status(channel_id, thread_ts)
|
||||
|
||||
thread_id = webapp.generate_thread_id_from_slack_thread(channel_id, thread_ts)
|
||||
thread_id = common.generate_thread_id_from_slack_thread(channel_id, thread_ts)
|
||||
|
||||
# Prime the user-mapping cache so login/email/slack-id lookups below are warm.
|
||||
try:
|
||||
await webapp.refresh_user_mapping_cache()
|
||||
await common.refresh_user_mapping_cache()
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug("Could not refresh user mapping cache for Slack mention", exc_info=True)
|
||||
common.logger.debug("Could not refresh user mapping cache for Slack mention", exc_info=True)
|
||||
|
||||
user_email = None
|
||||
user_name = ""
|
||||
if user_id:
|
||||
slack_user = await webapp.get_slack_user_info(user_id)
|
||||
slack_user = await common.get_slack_user_info(user_id)
|
||||
if slack_user:
|
||||
profile = slack_user.get("profile", {})
|
||||
if isinstance(profile, dict):
|
||||
|
|
@ -165,26 +165,26 @@ async def _process_slack_mention_impl(
|
|||
or ""
|
||||
)
|
||||
|
||||
thread_messages = await webapp.fetch_slack_thread_messages(channel_id, thread_ts)
|
||||
thread_messages = await common.fetch_slack_thread_messages(channel_id, thread_ts)
|
||||
if not any(str(message.get("ts")) == str(event_ts) for message in thread_messages):
|
||||
thread_messages.append({"ts": event_ts, "text": text, "user": user_id})
|
||||
|
||||
context_messages, context_mode = webapp.select_slack_context_messages(
|
||||
thread_messages, event_ts, bot_user_id, webapp.SLACK_BOT_USERNAME
|
||||
context_messages, context_mode = common.select_slack_context_messages(
|
||||
thread_messages, event_ts, bot_user_id, common.SLACK_BOT_USERNAME
|
||||
)
|
||||
context_user_ids = [
|
||||
value
|
||||
for value in (message.get("user") for message in context_messages)
|
||||
if isinstance(value, str) and value
|
||||
]
|
||||
user_names_by_id = await webapp.get_slack_user_names(context_user_ids)
|
||||
user_names_by_id = await common.get_slack_user_names(context_user_ids)
|
||||
if user_id and user_name and user_id not in user_names_by_id:
|
||||
user_names_by_id[user_id] = user_name
|
||||
context_text = webapp.format_slack_messages_for_prompt(
|
||||
context_text = common.format_slack_messages_for_prompt(
|
||||
context_messages,
|
||||
user_names_by_id,
|
||||
bot_user_id=bot_user_id,
|
||||
bot_username=webapp.SLACK_BOT_USERNAME,
|
||||
bot_username=common.SLACK_BOT_USERNAME,
|
||||
)
|
||||
context_source = (
|
||||
"the previous message where I was tagged"
|
||||
|
|
@ -192,13 +192,13 @@ async def _process_slack_mention_impl(
|
|||
else "the beginning of the thread"
|
||||
)
|
||||
clean_text = (
|
||||
webapp.strip_bot_mention(text, bot_user_id, bot_username=webapp.SLACK_BOT_USERNAME)
|
||||
common.strip_bot_mention(text, bot_user_id, bot_username=common.SLACK_BOT_USERNAME)
|
||||
or "(no text in mention)"
|
||||
)
|
||||
trigger_user = user_name or (f"<@{user_id}>" if user_id else "Unknown user")
|
||||
|
||||
# Auto-resolve cross-posted Slack message links in context
|
||||
resolved_links_section, image_urls_from_links = await webapp.resolve_slack_links_in_context(
|
||||
resolved_links_section, image_urls_from_links = await common.resolve_slack_links_in_context(
|
||||
context_messages, user_names_by_id
|
||||
)
|
||||
|
||||
|
|
@ -223,8 +223,8 @@ async def _process_slack_mention_impl(
|
|||
)
|
||||
content_blocks: list[dict[str, Any]] = [create_text_block(prompt)]
|
||||
|
||||
image_urls = webapp.dedupe_urls(
|
||||
[url for msg in context_messages for url in webapp.extract_image_urls(msg.get("text", ""))]
|
||||
image_urls = common.dedupe_urls(
|
||||
[url for msg in context_messages for url in common.extract_image_urls(msg.get("text", ""))]
|
||||
+ [
|
||||
f["url_private"]
|
||||
for msg in context_messages
|
||||
|
|
@ -236,16 +236,16 @@ async def _process_slack_mention_impl(
|
|||
+ image_urls_from_links
|
||||
)
|
||||
|
||||
mapped_login = await webapp.login_for_slack_id(user_id)
|
||||
mapped_login = await common.login_for_slack_id(user_id)
|
||||
if not mapped_login and user_email:
|
||||
mapped_login = await webapp.login_for_email(user_email)
|
||||
mapped_login = await common.login_for_email(user_email)
|
||||
|
||||
image_model_override: tuple[str, str] | None = None
|
||||
if image_urls:
|
||||
resolved_model_id = await webapp.resolve_agent_model_id(mapped_login)
|
||||
if not webapp.model_supports_images(resolved_model_id):
|
||||
fallback_model_id, fallback_effort = webapp.default_vision_model_pair()
|
||||
webapp.logger.info(
|
||||
resolved_model_id = await common.resolve_agent_model_id(mapped_login)
|
||||
if not common.model_supports_images(resolved_model_id):
|
||||
fallback_model_id, fallback_effort = common.default_vision_model_pair()
|
||||
common.logger.info(
|
||||
"Using vision fallback model %s for %d Slack image(s); configured model %s "
|
||||
"does not support images",
|
||||
fallback_model_id,
|
||||
|
|
@ -254,10 +254,10 @@ async def _process_slack_mention_impl(
|
|||
)
|
||||
resolved_model_id = fallback_model_id
|
||||
image_model_override = (fallback_model_id, fallback_effort)
|
||||
webapp.logger.info("Preparing %d image(s) for Slack mention", len(image_urls))
|
||||
async with httpx.AsyncClient(timeout=webapp.DEFAULT_HTTP_TIMEOUT) as http_client:
|
||||
common.logger.info("Preparing %d image(s) for Slack mention", len(image_urls))
|
||||
async with httpx.AsyncClient(timeout=common.DEFAULT_HTTP_TIMEOUT) as http_client:
|
||||
for image_url in image_urls:
|
||||
image_block = await webapp.fetch_image_block(image_url, http_client)
|
||||
image_block = await common.fetch_image_block(image_url, http_client)
|
||||
if image_block:
|
||||
content_blocks.append(image_block)
|
||||
|
||||
|
|
@ -269,9 +269,9 @@ async def _process_slack_mention_impl(
|
|||
user_token: str | None = None
|
||||
if mapped_login:
|
||||
try:
|
||||
user_token = await webapp.get_valid_access_token(mapped_login)
|
||||
user_token = await common.get_valid_access_token(mapped_login)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Failed to resolve GitHub token for %s; treating as unauthenticated",
|
||||
mapped_login,
|
||||
exc_info=True,
|
||||
|
|
@ -279,7 +279,7 @@ async def _process_slack_mention_impl(
|
|||
user_token = None
|
||||
has_valid_user_token = bool(user_token)
|
||||
|
||||
if not has_valid_user_token and not webapp.is_bot_token_only_mode():
|
||||
if not has_valid_user_token and not common.is_bot_token_only_mode():
|
||||
# A stored-but-unusable token means "sign in again"; no record at all
|
||||
# means the user has never connected GitHub + Slack via the dashboard.
|
||||
# Guard the store read like token resolution above so a transient
|
||||
|
|
@ -287,24 +287,24 @@ async def _process_slack_mention_impl(
|
|||
has_token_record = False
|
||||
if mapped_login:
|
||||
try:
|
||||
has_token_record = await webapp.has_access_token_record(mapped_login)
|
||||
has_token_record = await common.has_access_token_record(mapped_login)
|
||||
except Exception: # noqa: BLE001
|
||||
webapp.logger.debug(
|
||||
common.logger.debug(
|
||||
"Failed to check GitHub token record for %s; prompting sign-in",
|
||||
mapped_login,
|
||||
exc_info=True,
|
||||
)
|
||||
reason = "revoked" if has_token_record else "unlinked"
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Blocking Slack run for thread %s: no valid user GitHub token (%s)",
|
||||
thread_id,
|
||||
reason,
|
||||
)
|
||||
if user_id:
|
||||
await webapp._post_account_link_prompt(
|
||||
await common._post_account_link_prompt(
|
||||
channel_id, thread_ts, user_id, user_email, reason=reason
|
||||
)
|
||||
await webapp.set_slack_assistant_status(channel_id, thread_ts, status="")
|
||||
await common.set_slack_assistant_status(channel_id, thread_ts, status="")
|
||||
return
|
||||
|
||||
configurable: dict[str, Any] = {
|
||||
|
|
@ -327,17 +327,17 @@ async def _process_slack_mention_impl(
|
|||
configurable["agent_model_id"] = image_model_override[0]
|
||||
configurable["agent_effort"] = image_model_override[1]
|
||||
|
||||
thread_plan_mode = await webapp._get_thread_plan_mode(thread_id)
|
||||
thread_plan_mode = await common._get_thread_plan_mode(thread_id)
|
||||
if thread_plan_mode is not None:
|
||||
configurable["plan_mode"] = thread_plan_mode
|
||||
|
||||
langgraph_client = webapp.get_client(url=webapp.LANGGRAPH_URL)
|
||||
is_first_mention = not await webapp._thread_exists(thread_id)
|
||||
await webapp._upsert_slack_thread_repo_metadata(thread_id, repo_config, langgraph_client)
|
||||
langgraph_client = common.get_client(url=common.LANGGRAPH_URL)
|
||||
is_first_mention = not await common._thread_exists(thread_id)
|
||||
await common._upsert_slack_thread_repo_metadata(thread_id, repo_config, langgraph_client)
|
||||
# Pass the login resolved above (from the stable Slack user id) so the thread is
|
||||
# always tagged with github_login — the key the dashboard searches by. Without
|
||||
# it, upsert re-resolves from the Slack profile email, which can miss.
|
||||
await webapp.upsert_agent_thread_owner_metadata(
|
||||
await common.upsert_agent_thread_owner_metadata(
|
||||
thread_id,
|
||||
source="slack",
|
||||
repo_config=repo_config,
|
||||
|
|
@ -347,25 +347,25 @@ async def _process_slack_mention_impl(
|
|||
source_context={"slack_thread": configurable["slack_thread"]},
|
||||
)
|
||||
|
||||
run = await webapp.dispatch_agent_run(
|
||||
run = await common.dispatch_agent_run(
|
||||
thread_id,
|
||||
content_blocks,
|
||||
configurable,
|
||||
source="slack",
|
||||
metadata=webapp._AGENT_VERSION_METADATA,
|
||||
metadata=common._AGENT_VERSION_METADATA,
|
||||
client=langgraph_client,
|
||||
)
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Slack LangGraph run %s dispatched for thread %s",
|
||||
webapp._run_id_for_logging(run),
|
||||
common._run_id_for_logging(run),
|
||||
thread_id,
|
||||
)
|
||||
run_id = run.get("run_id")
|
||||
if is_first_mention:
|
||||
trace_message_ts = await webapp.post_slack_trace_reply(channel_id, thread_ts, thread_id)
|
||||
await webapp.set_slack_assistant_status(channel_id, thread_ts)
|
||||
trace_message_ts = await common.post_slack_trace_reply(channel_id, thread_ts, thread_id)
|
||||
await common.set_slack_assistant_status(channel_id, thread_ts)
|
||||
if isinstance(run_id, str) and run_id:
|
||||
await webapp.store_slack_run_mapping(
|
||||
await common.store_slack_run_mapping(
|
||||
langgraph_client,
|
||||
channel_id,
|
||||
thread_ts,
|
||||
|
|
@ -375,12 +375,12 @@ async def _process_slack_mention_impl(
|
|||
triggering_user_id=user_id,
|
||||
)
|
||||
else:
|
||||
webapp.logger.info(
|
||||
common.logger.info(
|
||||
"Skipping Slack trace reply for thread %s — agent will reply when run completes",
|
||||
thread_id,
|
||||
)
|
||||
if isinstance(run_id, str) and run_id:
|
||||
await webapp.store_slack_run_mapping(
|
||||
await common.store_slack_run_mapping(
|
||||
langgraph_client,
|
||||
channel_id,
|
||||
thread_ts,
|
||||
|
|
|
|||
347
agent/webhooks/slack_routes.py
Normal file
347
agent/webhooks/slack_routes.py
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
"""Slack webhook HTTP routes."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import common
|
||||
from . import slack as service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/webhooks/slack")
|
||||
async def slack_webhook(
|
||||
request: common.Request, background_tasks: common.BackgroundTasks
|
||||
) -> dict[str, str]:
|
||||
"""Handle Slack Event API webhooks for app mentions."""
|
||||
body = await request.body()
|
||||
|
||||
signature = request.headers.get("X-Slack-Signature", "")
|
||||
timestamp = request.headers.get("X-Slack-Request-Timestamp", "")
|
||||
if not common.verify_slack_signature(
|
||||
body=body,
|
||||
timestamp=timestamp,
|
||||
signature=signature,
|
||||
secret=common.SLACK_SIGNING_SECRET,
|
||||
):
|
||||
common.logger.warning("Invalid Slack signature")
|
||||
raise common.HTTPException(status_code=401, detail="Invalid signature")
|
||||
|
||||
try:
|
||||
payload = common.json.loads(body)
|
||||
except common.json.JSONDecodeError:
|
||||
common.logger.exception("Failed to parse Slack webhook JSON")
|
||||
return {"status": "error", "message": "Invalid JSON"}
|
||||
|
||||
if payload.get("type") == "url_verification":
|
||||
challenge = payload.get("challenge", "")
|
||||
return {"challenge": challenge}
|
||||
|
||||
if payload.get("type") != "event_callback":
|
||||
return {"status": "ignored", "reason": "Not an event callback"}
|
||||
|
||||
event = payload.get("event", {})
|
||||
|
||||
if event.get("type") == "reaction_added":
|
||||
reaction = event.get("reaction")
|
||||
if reaction in common.FEEDBACK_REACTIONS:
|
||||
background_tasks.add_task(
|
||||
common.process_slack_reaction_added, event, payload.get("event_id", "")
|
||||
)
|
||||
return {"status": "accepted", "message": "Reaction feedback queued"}
|
||||
return {"status": "ignored", "reason": "Reaction not tracked for feedback"}
|
||||
|
||||
if event.get("type") == "reaction_removed":
|
||||
reaction = event.get("reaction")
|
||||
if reaction in common.FEEDBACK_REACTIONS:
|
||||
background_tasks.add_task(
|
||||
common.process_slack_reaction_removed, event, payload.get("event_id", "")
|
||||
)
|
||||
return {"status": "accepted", "message": "Reaction removal queued"}
|
||||
return {"status": "ignored", "reason": "Reaction not tracked for feedback"}
|
||||
|
||||
if event.get("type") != "app_mention":
|
||||
message_text = event.get("text", "")
|
||||
has_username_mention = bool(
|
||||
event.get("type") == "message"
|
||||
and common.SLACK_BOT_USERNAME
|
||||
and f"@{common.SLACK_BOT_USERNAME}" in message_text
|
||||
)
|
||||
has_id_mention = bool(
|
||||
event.get("type") == "message"
|
||||
and common.SLACK_BOT_USER_ID
|
||||
and f"<@{common.SLACK_BOT_USER_ID}>" in message_text
|
||||
)
|
||||
if not (has_username_mention or has_id_mention):
|
||||
return {"status": "ignored", "reason": "Not an app_mention event"}
|
||||
|
||||
if event.get("subtype") == "bot_message" or event.get("bot_id"):
|
||||
return {"status": "ignored", "reason": "Event from a bot"}
|
||||
|
||||
channel_id = event.get("channel", "")
|
||||
event_ts = event.get("ts", "")
|
||||
thread_ts = event.get("thread_ts") or event_ts
|
||||
user_id = event.get("user", "")
|
||||
text = event.get("text", "")
|
||||
if not channel_id or not event_ts or not thread_ts:
|
||||
return {"status": "ignored", "reason": "Missing channel/thread timestamp"}
|
||||
|
||||
bot_user_id = common.SLACK_BOT_USER_ID
|
||||
if not bot_user_id:
|
||||
authorizations = payload.get("authorizations", [])
|
||||
if isinstance(authorizations, list) and authorizations:
|
||||
auth_user_id = authorizations[0].get("user_id")
|
||||
if isinstance(auth_user_id, str):
|
||||
bot_user_id = auth_user_id
|
||||
if not bot_user_id:
|
||||
authed_users = payload.get("authed_users", [])
|
||||
if isinstance(authed_users, list) and authed_users:
|
||||
first_user = authed_users[0]
|
||||
if isinstance(first_user, str):
|
||||
bot_user_id = first_user
|
||||
|
||||
if bot_user_id and user_id == bot_user_id:
|
||||
return {"status": "ignored", "reason": "Event from this bot user"}
|
||||
|
||||
channel_context = await common._get_slack_channel_context(channel_id)
|
||||
|
||||
if await common._is_docs_plz_slack_channel(channel_id, channel_context):
|
||||
background_tasks.add_task(
|
||||
common.post_slack_thread_reply,
|
||||
channel_id,
|
||||
thread_ts,
|
||||
common.DOCS_PLZ_SLACK_GATE_REPLY,
|
||||
)
|
||||
return {"status": "accepted", "message": "Slack mention gated for docs-plz"}
|
||||
|
||||
event_data = {
|
||||
"channel_id": channel_id,
|
||||
"channel_context": channel_context,
|
||||
"thread_ts": thread_ts,
|
||||
"event_ts": event_ts,
|
||||
"user_id": user_id,
|
||||
"text": text,
|
||||
"bot_user_id": bot_user_id,
|
||||
}
|
||||
repo_config = await common.get_slack_repo_config(
|
||||
channel_id, thread_ts, slack_user_id=user_id, channel_context=channel_context
|
||||
)
|
||||
|
||||
background_tasks.add_task(service.process_slack_mention, event_data, repo_config)
|
||||
|
||||
return {"status": "accepted", "message": "Slack mention queued"}
|
||||
|
||||
|
||||
@router.post("/webhooks/slack/interactivity")
|
||||
async def slack_interactivity(
|
||||
request: common.Request, background_tasks: common.BackgroundTasks
|
||||
) -> dict[str, str]:
|
||||
"""Handle Slack Block Kit interactions."""
|
||||
body = await request.body()
|
||||
signature = request.headers.get("X-Slack-Signature", "")
|
||||
timestamp = request.headers.get("X-Slack-Request-Timestamp", "")
|
||||
if not common.verify_slack_signature(
|
||||
body=body,
|
||||
timestamp=timestamp,
|
||||
signature=signature,
|
||||
secret=common.SLACK_SIGNING_SECRET,
|
||||
):
|
||||
common.logger.warning("Invalid Slack interactivity signature")
|
||||
raise common.HTTPException(status_code=401, detail="Invalid signature")
|
||||
|
||||
form = common.parse_qs(body.decode("utf-8"))
|
||||
payload_raw = (form.get("payload") or [""])[0]
|
||||
try:
|
||||
payload = common.json.loads(payload_raw)
|
||||
except common.json.JSONDecodeError:
|
||||
common.logger.exception("Failed to parse Slack interactivity payload")
|
||||
return {"status": "error", "message": "Invalid payload"}
|
||||
|
||||
action = _first_open_swe_option_action(payload.get("actions"))
|
||||
if action is None:
|
||||
return {"status": "ignored", "reason": "No Open SWE action"}
|
||||
|
||||
try:
|
||||
action_value = common.json.loads(str(action.get("value") or "{}"))
|
||||
except common.json.JSONDecodeError:
|
||||
return {"status": "ignored", "reason": "Invalid action value"}
|
||||
if action_value.get("type") == "workflow_push_approval":
|
||||
workflow_action = str(action_value.get("action") or "").strip()
|
||||
fingerprint = str(action_value.get("fingerprint") or "").strip()
|
||||
channel = payload.get("channel") if isinstance(payload.get("channel"), dict) else {}
|
||||
message = payload.get("message") if isinstance(payload.get("message"), dict) else {}
|
||||
container = payload.get("container") if isinstance(payload.get("container"), dict) else {}
|
||||
user = payload.get("user") if isinstance(payload.get("user"), dict) else {}
|
||||
channel_id = str(channel.get("id") or container.get("channel_id") or "")
|
||||
thread_ts = str(
|
||||
message.get("thread_ts") or message.get("ts") or container.get("thread_ts") or ""
|
||||
)
|
||||
user_id = str(user.get("id") or "")
|
||||
if not channel_id or not thread_ts or not fingerprint:
|
||||
return {"status": "ignored", "reason": "Missing workflow approval context"}
|
||||
|
||||
thread_id = common.generate_thread_id_from_slack_thread(channel_id, thread_ts)
|
||||
if not await common._slack_user_is_thread_owner(thread_id, user_id):
|
||||
await common.post_slack_thread_reply(
|
||||
channel_id=channel_id,
|
||||
thread_ts=thread_ts,
|
||||
text="Only the person who requested this run can approve workflow file pushes.",
|
||||
)
|
||||
return {"status": "ignored", "reason": "approver is not the thread owner"}
|
||||
|
||||
if workflow_action not in {"approve", "reject"}:
|
||||
return {"status": "ignored", "reason": "Unknown workflow approval action"}
|
||||
approved = workflow_action == "approve"
|
||||
record = await common.decide_workflow_push_approval(
|
||||
thread_id, fingerprint, approved=approved, actor=user_id
|
||||
)
|
||||
if record is None:
|
||||
await common.post_slack_thread_reply(
|
||||
channel_id=channel_id,
|
||||
thread_ts=thread_ts,
|
||||
text="I couldn't find that workflow approval request. Trigger the push again to create a fresh approval.",
|
||||
)
|
||||
return {"status": "ignored", "reason": "workflow approval not found"}
|
||||
if not approved:
|
||||
await common.post_slack_thread_reply(
|
||||
channel_id=channel_id,
|
||||
thread_ts=thread_ts,
|
||||
text=f"Workflow push rejected for fingerprint `{fingerprint}`. No workflow files will be pushed.",
|
||||
)
|
||||
return {"status": "accepted", "message": "Workflow push rejected"}
|
||||
|
||||
await common.post_slack_thread_reply(
|
||||
channel_id=channel_id,
|
||||
thread_ts=thread_ts,
|
||||
text=f"Workflow push approved for fingerprint `{fingerprint}`. Open SWE will retry the blocked push.",
|
||||
)
|
||||
channel_context = await common._get_slack_channel_context(channel_id)
|
||||
repo_config = await common.get_slack_repo_config(
|
||||
channel_id, thread_ts, slack_user_id=user_id, channel_context=channel_context
|
||||
)
|
||||
background_tasks.add_task(
|
||||
service.process_slack_mention,
|
||||
{
|
||||
"channel_id": channel_id,
|
||||
"channel_context": channel_context,
|
||||
"thread_ts": thread_ts,
|
||||
"event_ts": str(message.get("ts") or ""),
|
||||
"user_id": user_id,
|
||||
"text": (
|
||||
"The workflow-file push approval was approved. Retry the blocked "
|
||||
"git push now; do not alter workflow files before pushing."
|
||||
),
|
||||
"bot_user_id": common.SLACK_BOT_USER_ID,
|
||||
},
|
||||
repo_config,
|
||||
)
|
||||
return {"status": "accepted", "message": "Workflow push approved, retry queued"}
|
||||
|
||||
if action_value.get("type") == "plan_approval":
|
||||
plan_action = str(action_value.get("action") or "").strip()
|
||||
channel = payload.get("channel") if isinstance(payload.get("channel"), dict) else {}
|
||||
message = payload.get("message") if isinstance(payload.get("message"), dict) else {}
|
||||
container = payload.get("container") if isinstance(payload.get("container"), dict) else {}
|
||||
user = payload.get("user") if isinstance(payload.get("user"), dict) else {}
|
||||
channel_id = str(channel.get("id") or container.get("channel_id") or "")
|
||||
thread_ts = str(
|
||||
message.get("thread_ts") or message.get("ts") or container.get("thread_ts") or ""
|
||||
)
|
||||
user_id = str(user.get("id") or "")
|
||||
if not channel_id or not thread_ts:
|
||||
return {"status": "ignored", "reason": "Missing Slack action context"}
|
||||
|
||||
thread_id = common.generate_thread_id_from_slack_thread(channel_id, thread_ts)
|
||||
|
||||
if plan_action == "cancel":
|
||||
await common.post_slack_thread_reply(
|
||||
channel_id=channel_id,
|
||||
thread_ts=thread_ts,
|
||||
text="Plan cancelled. No changes will be made.",
|
||||
)
|
||||
return {"status": "accepted", "message": "Plan cancelled"}
|
||||
|
||||
if plan_action == "approve":
|
||||
if not await common._slack_user_is_thread_owner(thread_id, user_id):
|
||||
await common.post_slack_thread_reply(
|
||||
channel_id=channel_id,
|
||||
thread_ts=thread_ts,
|
||||
text="Only the person who requested this plan can approve it. Anyone can reply with feedback or use *Revise Plan*.",
|
||||
)
|
||||
return {"status": "ignored", "reason": "approver is not the thread owner"}
|
||||
await common._set_thread_plan_mode(thread_id, False)
|
||||
channel_context = await common._get_slack_channel_context(channel_id)
|
||||
repo_config = await common.get_slack_repo_config(
|
||||
channel_id, thread_ts, slack_user_id=user_id, channel_context=channel_context
|
||||
)
|
||||
background_tasks.add_task(
|
||||
service.process_slack_mention,
|
||||
{
|
||||
"channel_id": channel_id,
|
||||
"channel_context": channel_context,
|
||||
"thread_ts": thread_ts,
|
||||
"event_ts": str(message.get("ts") or ""),
|
||||
"user_id": user_id,
|
||||
"text": "Proceed with the approved plan. Implement the changes as described in the plan.",
|
||||
"bot_user_id": common.SLACK_BOT_USER_ID,
|
||||
},
|
||||
repo_config,
|
||||
)
|
||||
return {"status": "accepted", "message": "Plan approved, starting implementation"}
|
||||
|
||||
return {"status": "accepted", "message": "Reply to revise the plan"}
|
||||
|
||||
if action_value.get("type") != "open_swe_option":
|
||||
return {"status": "ignored", "reason": "Unknown action type"}
|
||||
|
||||
response = str(action_value.get("response") or "").strip()
|
||||
if not response:
|
||||
return {"status": "ignored", "reason": "Empty response"}
|
||||
|
||||
channel = payload.get("channel") if isinstance(payload.get("channel"), dict) else {}
|
||||
message = payload.get("message") if isinstance(payload.get("message"), dict) else {}
|
||||
container = payload.get("container") if isinstance(payload.get("container"), dict) else {}
|
||||
user = payload.get("user") if isinstance(payload.get("user"), dict) else {}
|
||||
channel_id = str(channel.get("id") or container.get("channel_id") or "")
|
||||
event_ts = str(
|
||||
action.get("action_ts") or message.get("ts") or container.get("message_ts") or ""
|
||||
)
|
||||
thread_ts = str(
|
||||
message.get("thread_ts") or message.get("ts") or container.get("thread_ts") or event_ts
|
||||
)
|
||||
user_id = str(user.get("id") or "")
|
||||
if not channel_id or not thread_ts or not event_ts or not user_id:
|
||||
return {"status": "ignored", "reason": "Missing Slack action context"}
|
||||
|
||||
channel_context = await common._get_slack_channel_context(channel_id)
|
||||
repo_config = await common.get_slack_repo_config(
|
||||
channel_id, thread_ts, slack_user_id=user_id, channel_context=channel_context
|
||||
)
|
||||
background_tasks.add_task(
|
||||
service.process_slack_mention,
|
||||
{
|
||||
"channel_id": channel_id,
|
||||
"channel_context": channel_context,
|
||||
"thread_ts": thread_ts,
|
||||
"event_ts": event_ts,
|
||||
"user_id": user_id,
|
||||
"text": response,
|
||||
"bot_user_id": common.SLACK_BOT_USER_ID,
|
||||
},
|
||||
repo_config,
|
||||
)
|
||||
return {"status": "accepted", "message": "Slack option queued"}
|
||||
|
||||
|
||||
def _first_open_swe_option_action(actions: common.Any) -> dict[str, common.Any] | None:
|
||||
if not isinstance(actions, list):
|
||||
return None
|
||||
for action in actions:
|
||||
if isinstance(action, dict) and action.get("action_id") == "open_swe_option_select":
|
||||
return action
|
||||
return None
|
||||
|
||||
|
||||
@router.get("/webhooks/slack")
|
||||
async def slack_webhook_verify() -> dict[str, str]:
|
||||
"""Verify endpoint for Slack webhook setup."""
|
||||
return {"status": "ok", "message": "Slack webhook endpoint is active"}
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
|
@ -439,7 +439,7 @@ Open SWE supports a `default_prompt.md` file for org-level instructions that app
|
|||
|
||||
The file is loaded at agent startup and injected into the system prompt between the task overview and repository setup sections.
|
||||
|
||||
**Location:** [`default_prompt.md`](./default_prompt.md) in the project root.
|
||||
**Location:** [`agent/resources/default_prompt.md`](../agent/resources/default_prompt.md) for the bundled default.
|
||||
|
||||
**Override:** Set the `DEFAULT_PROMPT_PATH` environment variable to use a different file:
|
||||
|
||||
1
docs/upstream-sync/domain-reorg/cross.json
Normal file
1
docs/upstream-sync/domain-reorg/cross.json
Normal file
File diff suppressed because one or more lines are too long
322
docs/upstream-sync/domain-reorg/fork-divergence-status.txt
Normal file
322
docs/upstream-sync/domain-reorg/fork-divergence-status.txt
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
A .env.example
|
||||
A .githooks/_reject_guard.sh
|
||||
A .githooks/commit-msg
|
||||
A .githooks/post-commit
|
||||
A .githooks/pre-push
|
||||
A .githooks/prepare-commit-msg
|
||||
A .github/CODEOWNERS
|
||||
A .github/ISSUE_TEMPLATE/bug.yml
|
||||
A .github/ISSUE_TEMPLATE/config.yml
|
||||
A .github/ISSUE_TEMPLATE/feature.yml
|
||||
A .github/ISSUE_TEMPLATE/task.yml
|
||||
A .github/PULL_REQUEST_TEMPLATE.md
|
||||
M .github/dependabot.yml
|
||||
A .github/scripts/check-dev-green.sh
|
||||
M .github/workflows/ci.yml
|
||||
A .github/workflows/dependency-review.yml
|
||||
A .github/workflows/labeler.yml
|
||||
M .github/workflows/pr_lint.yml
|
||||
A .github/workflows/promote-to-main.yml
|
||||
D .github/workflows/promote_main_to_prod.yml
|
||||
R096 .github/workflows/reviewer_eval.yml .github/workflows/reviewer-eval.yml
|
||||
A .github/workflows/upstream-ledger-sync.yml
|
||||
M .gitignore
|
||||
A .nvmrc
|
||||
A .security-review/suppressions.json
|
||||
M AGENTS.md
|
||||
M CLAUDE.md
|
||||
M CUSTOMIZATION.md
|
||||
M Dockerfile
|
||||
M INSTALLATION.md
|
||||
M Makefile
|
||||
M README.md
|
||||
M SECURITY.md
|
||||
M agent/analyzer.py
|
||||
M agent/chat.py
|
||||
M agent/ci_autofix.py
|
||||
A agent/completion.py
|
||||
M agent/dashboard/__init__.py
|
||||
M agent/dashboard/agent_overrides.py
|
||||
M agent/dashboard/eval_jobs.py
|
||||
M agent/dashboard/oauth.py
|
||||
M agent/dashboard/options.py
|
||||
M agent/dashboard/plan_api.py
|
||||
M agent/dashboard/plan_store.py
|
||||
M agent/dashboard/repo_access.py
|
||||
M agent/dashboard/review_api.py
|
||||
M agent/dashboard/review_chat_api.py
|
||||
M agent/dashboard/routes.py
|
||||
M agent/dashboard/schedules.py
|
||||
M agent/dashboard/slack_oauth.py
|
||||
M agent/dashboard/team_settings.py
|
||||
M agent/dashboard/thread_api.py
|
||||
M agent/dashboard/user_mappings.py
|
||||
A agent/dashboard/workflow_approval.py
|
||||
A agent/dashboard/workflow_approval_api.py
|
||||
A agent/dispatch.py
|
||||
A agent/integrations/e2b.py
|
||||
M agent/integrations/local.py
|
||||
M agent/middleware/__init__.py
|
||||
M agent/middleware/check_message_queue.py
|
||||
A agent/middleware/ensure_no_empty_msg.py
|
||||
M agent/middleware/model_fallback.py
|
||||
M agent/middleware/plan_mode.py
|
||||
A agent/middleware/pr_creation_guard.py
|
||||
M agent/middleware/refresh_slack_status.py
|
||||
A agent/middleware/sanitize_fireworks_messages.py
|
||||
A agent/middleware/sanitize_openai_responses.py
|
||||
M agent/middleware/sanitize_thinking_blocks.py
|
||||
A agent/middleware/subdir_agents.py
|
||||
A agent/middleware/task_retry.py
|
||||
A agent/middleware/timeout_wrapup.py
|
||||
A agent/middleware/workflow_push_guard.py
|
||||
M agent/prompt.py
|
||||
A agent/reconcile.py
|
||||
M agent/reviewer.py
|
||||
M agent/reviewer_findings.py
|
||||
M agent/reviewer_publish.py
|
||||
M agent/reviewer_reconcile.py
|
||||
A agent/reviewer_trace_context.py
|
||||
M agent/scheduler.py
|
||||
M agent/server.py
|
||||
M agent/tools/__init__.py
|
||||
M agent/tools/add_finding.py
|
||||
A agent/tools/confluence_comment.py
|
||||
A agent/tools/confluence_create_page.py
|
||||
A agent/tools/confluence_get_page.py
|
||||
A agent/tools/confluence_search.py
|
||||
A agent/tools/confluence_update_page.py
|
||||
M agent/tools/enter_plan_mode.py
|
||||
M agent/tools/fetch_url.py
|
||||
M agent/tools/http_request.py
|
||||
A agent/tools/jira_comment.py
|
||||
A agent/tools/jira_create_issue.py
|
||||
A agent/tools/jira_get_issue.py
|
||||
A agent/tools/jira_get_issue_comments.py
|
||||
A agent/tools/jira_list_projects.py
|
||||
A agent/tools/jira_update_issue.py
|
||||
M agent/tools/linear_comment.py
|
||||
M agent/tools/linear_create_issue.py
|
||||
M agent/tools/linear_delete_issue.py
|
||||
M agent/tools/linear_get_issue.py
|
||||
M agent/tools/linear_get_issue_comments.py
|
||||
M agent/tools/linear_list_teams.py
|
||||
M agent/tools/linear_update_issue.py
|
||||
M agent/tools/list_findings.py
|
||||
M agent/tools/list_review_findings.py
|
||||
M agent/tools/open_pull_request.py
|
||||
M agent/tools/publish_review.py
|
||||
M agent/tools/read_repo_file.py
|
||||
M agent/tools/reply_to_finding_thread.py
|
||||
A agent/tools/report_platform_issue.py
|
||||
M agent/tools/request_pr_review.py
|
||||
M agent/tools/resolve_finding_thread.py
|
||||
M agent/tools/save_plan.py
|
||||
M agent/tools/save_review_style.py
|
||||
M agent/tools/schedule_thread_wakeup.py
|
||||
M agent/tools/search_repo_code.py
|
||||
A agent/tools/slack_add_reaction.py
|
||||
M agent/tools/slack_read_thread_messages.py
|
||||
A agent/tools/slack_start_new_thread.py
|
||||
M agent/tools/slack_thread_reply.py
|
||||
M agent/tools/update_finding.py
|
||||
M agent/tools/web_search.py
|
||||
A agent/utils/adf.py
|
||||
M agent/utils/analyzer_skills.py
|
||||
A agent/utils/atlassian_connect.py
|
||||
M agent/utils/auth.py
|
||||
M agent/utils/authorship.py
|
||||
A agent/utils/confluence.py
|
||||
A agent/utils/confluence_space_repo_map.py
|
||||
A agent/utils/dashboard_handoff.py
|
||||
M agent/utils/dashboard_links.py
|
||||
A agent/utils/deferred_model.py
|
||||
A agent/utils/gateway.py
|
||||
M agent/utils/github_app.py
|
||||
M agent/utils/github_comments.py
|
||||
M agent/utils/github_org_membership.py
|
||||
M agent/utils/github_proxy.py
|
||||
M agent/utils/github_token.py
|
||||
A agent/utils/http.py
|
||||
A agent/utils/jira.py
|
||||
A agent/utils/jira_project_repo_map.py
|
||||
M agent/utils/linear.py
|
||||
M agent/utils/model.py
|
||||
M agent/utils/multimodal.py
|
||||
M agent/utils/sandbox.py
|
||||
M agent/utils/sandbox_state.py
|
||||
M agent/utils/slack.py
|
||||
A agent/utils/thread_ids.py
|
||||
M agent/utils/thread_ops.py
|
||||
A agent/utils/url_safety.py
|
||||
M agent/webapp.py
|
||||
A agent/webhooks/__init__.py
|
||||
A agent/webhooks/confluence.py
|
||||
A agent/webhooks/github.py
|
||||
A agent/webhooks/jira.py
|
||||
A agent/webhooks/linear.py
|
||||
A agent/webhooks/slack.py
|
||||
M default_prompt.md
|
||||
A deploy/MIGRATION.md
|
||||
A docs/repo-conventions/AGENTS.md.aws
|
||||
A docs/repo-conventions/AGENTS.md.cdk
|
||||
A docs/repo-conventions/AGENTS.md.ec2
|
||||
A docs/repo-conventions/AGENTS.md.sam
|
||||
A docs/repo-conventions/README.md
|
||||
A docs/upstream-sync/cherry-pick-hook-plan.md
|
||||
A docs/upstream-sync/cherry-pick-runbook.md
|
||||
A docs/upstream-sync/triage.jsonl
|
||||
A docs/upstream-sync/triage.md
|
||||
M evals/reviewer/README.md
|
||||
M evals/reviewer/config.toml
|
||||
M evals/reviewer/judge.py
|
||||
M evals/reviewer/run_eval.py
|
||||
M evals/reviewer/target.py
|
||||
M pyproject.toml
|
||||
A scripts/git-cp
|
||||
A scripts/install-hooks.sh
|
||||
A scripts/purge_wakeup_crons.py
|
||||
A scripts/triage.py
|
||||
M tests/conftest.py
|
||||
M tests/e2e/README.md
|
||||
M tests/e2e/fake_llm.py
|
||||
M tests/e2e/fakes.py
|
||||
M tests/e2e/harness.py
|
||||
M tests/e2e/package-lock.json
|
||||
M tests/e2e/package.json
|
||||
M tests/e2e/patches.py
|
||||
A tests/e2e/screenshots/queued-messages-dashboard.png
|
||||
M tests/e2e/static/slack.html
|
||||
M tests/e2e/tests/dashboard.spec.ts
|
||||
M tests/e2e/tests/full_flow.spec.ts
|
||||
M tests/e2e/tests/plan_review.spec.ts
|
||||
A tests/e2e/tests/sandbox_id.spec.ts
|
||||
A tests/test_agent_assembly_context.py
|
||||
M tests/test_agent_schedules.py
|
||||
M tests/test_agent_subagent_models.py
|
||||
A tests/test_anthropic_model.py
|
||||
A tests/test_app_bot_identity.py
|
||||
A tests/test_atlassian_connect.py
|
||||
A tests/test_auth_error_leak.py
|
||||
M tests/test_auth_sources.py
|
||||
M tests/test_authorship.py
|
||||
M tests/test_autofix_webhook.py
|
||||
M tests/test_ci_autofix.py
|
||||
A tests/test_completion_webhook.py
|
||||
A tests/test_confluence_utils.py
|
||||
A tests/test_confluence_webhook.py
|
||||
M tests/test_corridor_mcp.py
|
||||
A tests/test_dashboard_oauth_redirect.py
|
||||
M tests/test_dashboard_thread_api.py
|
||||
M tests/test_dashboard_web_handoff.py
|
||||
A tests/test_dispatch.py
|
||||
A tests/test_e2b_integration.py
|
||||
A tests/test_ensure_no_empty_msg.py
|
||||
M tests/test_fireworks_model.py
|
||||
A tests/test_gateway.py
|
||||
M tests/test_github_app.py
|
||||
M tests/test_github_comment_prompts.py
|
||||
M tests/test_github_issue_webhook.py
|
||||
M tests/test_github_proxy_refresh.py
|
||||
M tests/test_github_token_ttl.py
|
||||
D tests/test_google_model.py
|
||||
M tests/test_http_security.py
|
||||
A tests/test_jira_utils.py
|
||||
A tests/test_jira_webhook_author.py
|
||||
A tests/test_jira_webhook_corroboration.py
|
||||
A tests/test_jira_webhook_replay.py
|
||||
A tests/test_linear_webhook_author.py
|
||||
A tests/test_linear_webhook_replay.py
|
||||
M tests/test_local_integration.py
|
||||
M tests/test_model_fallback_middleware.py
|
||||
M tests/test_model_fallback_resolution.py
|
||||
M tests/test_open_pull_request.py
|
||||
M tests/test_plan_mode.py
|
||||
M tests/test_plan_review.py
|
||||
A tests/test_pr_creation_guard.py
|
||||
M tests/test_pr_ready_auto_review.py
|
||||
M tests/test_proxy_auth.py
|
||||
A tests/test_reconcile_sweep.py
|
||||
A tests/test_repo_binding_isolation.py
|
||||
A tests/test_report_platform_issue_tool.py
|
||||
M tests/test_review_chat.py
|
||||
M tests/test_reviewer.py
|
||||
A tests/test_reviewer_eval_judge.py
|
||||
M tests/test_reviewer_eval_target.py
|
||||
M tests/test_reviewer_findings.py
|
||||
M tests/test_reviewer_publish.py
|
||||
M tests/test_reviewer_reconcile.py
|
||||
M tests/test_reviewer_tools.py
|
||||
A tests/test_reviewer_trace_context.py
|
||||
M tests/test_reviewer_watch.py
|
||||
A tests/test_sanitize_fireworks_messages.py
|
||||
A tests/test_sanitize_openai_responses.py
|
||||
M tests/test_sanitize_thinking_blocks.py
|
||||
M tests/test_schedule_thread_wakeup.py
|
||||
A tests/test_slack_add_reaction_tool.py
|
||||
M tests/test_slack_assistants_status.py
|
||||
M tests/test_slack_context.py
|
||||
A tests/test_slack_start_new_thread_tool.py
|
||||
M tests/test_slack_thread_reply_tool.py
|
||||
A tests/test_slack_webhook_errors.py
|
||||
A tests/test_subdir_agents_middleware.py
|
||||
A tests/test_team_settings_fable.py
|
||||
M tests/test_team_settings_grouping.py
|
||||
M tests/test_team_settings_org_guidelines.py
|
||||
A tests/test_workflow_push_guard.py
|
||||
A ui/AGENTS.md
|
||||
M ui/bun.lock
|
||||
M ui/package.json
|
||||
A ui/pnpm-workspace.yaml
|
||||
M ui/src/client.tsx
|
||||
A ui/src/components/PwaUpdateToast.tsx
|
||||
M ui/src/components/agents/AgentGitPanel.tsx
|
||||
M ui/src/components/agents/AgentThreadView.tsx
|
||||
M ui/src/components/agents/AgentsSidebar.tsx
|
||||
M ui/src/components/agents/AutomationEditor.tsx
|
||||
M ui/src/components/agents/PlanReview.tsx
|
||||
A ui/src/components/agents/PrHeader.tsx
|
||||
M ui/src/components/agents/ReviewMainBody.tsx
|
||||
M ui/src/components/agents/ReviewSidebar.tsx
|
||||
A ui/src/components/agents/SidebarFilterMenu.tsx
|
||||
A ui/src/components/agents/WorkflowApprovalCard.tsx
|
||||
M ui/src/components/agents/messages/AgentMessage.tsx
|
||||
A ui/src/components/agents/messages/MessageTimestamp.tsx
|
||||
M ui/src/components/agents/messages/Messages.tsx
|
||||
M ui/src/components/agents/messages/UserMessage.tsx
|
||||
M ui/src/components/agents/messages/types.ts
|
||||
M ui/src/components/agents/utils/diffUtils.ts
|
||||
M ui/src/lib/agents/api.ts
|
||||
A ui/src/lib/agents/messageTimestamps.ts
|
||||
M ui/src/lib/agents/provider/useSubmitAgentMessage.ts
|
||||
M ui/src/lib/agents/queries.ts
|
||||
A ui/src/lib/agents/sidebarFilter.test.ts
|
||||
A ui/src/lib/agents/sidebarFilter.ts
|
||||
A ui/src/lib/agents/sidebarPrefs.ts
|
||||
M ui/src/lib/agents/streamMessagesToUi.ts
|
||||
M ui/src/lib/agents/types.ts
|
||||
M ui/src/lib/api.ts
|
||||
A ui/src/lib/auth-redirect-core.ts
|
||||
A ui/src/lib/auth-redirect.test.ts
|
||||
A ui/src/lib/auth-redirect.tsx
|
||||
M ui/src/lib/plan.ts
|
||||
M ui/src/routeTree.gen.ts
|
||||
A ui/src/routes/$owner.$repo.pull.$number.tsx
|
||||
M ui/src/routes/admin.tsx
|
||||
M ui/src/routes/admin_.evals.tsx
|
||||
M ui/src/routes/agents.tsx
|
||||
M ui/src/routes/agents/$threadId_.plan.tsx
|
||||
M ui/src/routes/agents/reviews/$owner.$repo.$number.tsx
|
||||
M ui/src/routes/agents_.instructions.tsx
|
||||
M ui/src/routes/agents_.snapshots.tsx
|
||||
M ui/src/routes/cloud-agents.tsx
|
||||
M ui/src/routes/login.tsx
|
||||
M ui/src/routes/my-settings.tsx
|
||||
M ui/src/routes/review.tsx
|
||||
M ui/src/routes/review_.repositories.$owner.tsx
|
||||
M ui/src/routes/review_.styles.tsx
|
||||
M ui/src/routes/usage.tsx
|
||||
M ui/vercel.json
|
||||
M ui/vite.config.ts
|
||||
D ui/yarn.lock
|
||||
M uv.lock
|
||||
1
docs/upstream-sync/domain-reorg/forkonly.json
Normal file
1
docs/upstream-sync/domain-reorg/forkonly.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"true_fork_only": [".env.example", ".githooks/_reject_guard.sh", ".githooks/commit-msg", ".githooks/post-commit", ".githooks/pre-push", ".githooks/prepare-commit-msg", ".github/CODEOWNERS", ".github/ISSUE_TEMPLATE/bug.yml", ".github/ISSUE_TEMPLATE/config.yml", ".github/ISSUE_TEMPLATE/feature.yml", ".github/ISSUE_TEMPLATE/task.yml", ".github/PULL_REQUEST_TEMPLATE.md", ".github/scripts/check-dev-green.sh", ".github/workflows/dependency-review.yml", ".github/workflows/labeler.yml", ".github/workflows/promote-to-main.yml", ".github/workflows/reviewer-eval.yml", ".github/workflows/upstream-ledger-sync.yml", ".nvmrc", ".security-review/suppressions.json", "agent/tools/confluence_comment.py", "agent/tools/confluence_create_page.py", "agent/tools/confluence_get_page.py", "agent/tools/confluence_search.py", "agent/tools/confluence_update_page.py", "agent/tools/jira_comment.py", "agent/tools/jira_create_issue.py", "agent/tools/jira_get_issue.py", "agent/tools/jira_get_issue_comments.py", "agent/tools/jira_list_projects.py", "agent/tools/jira_update_issue.py", "agent/utils/adf.py", "agent/utils/atlassian_connect.py", "agent/utils/confluence.py", "agent/utils/confluence_space_repo_map.py", "agent/utils/jira.py", "agent/utils/jira_project_repo_map.py", "agent/webhooks/confluence.py", "agent/webhooks/jira.py", "deploy/MIGRATION.md", "docs/repo-conventions/AGENTS.md.aws", "docs/repo-conventions/AGENTS.md.cdk", "docs/repo-conventions/AGENTS.md.ec2", "docs/repo-conventions/AGENTS.md.sam", "docs/repo-conventions/README.md", "docs/upstream-sync/cherry-pick-hook-plan.md", "docs/upstream-sync/cherry-pick-runbook.md", "docs/upstream-sync/triage.jsonl", "docs/upstream-sync/triage.md", "scripts/git-cp", "scripts/install-hooks.sh", "scripts/triage.py", "tests/test_app_bot_identity.py", "tests/test_atlassian_connect.py", "tests/test_auth_error_leak.py", "tests/test_confluence_utils.py", "tests/test_confluence_webhook.py", "tests/test_jira_utils.py", "tests/test_jira_webhook_author.py", "tests/test_jira_webhook_corroboration.py", "tests/test_jira_webhook_replay.py", "tests/test_linear_webhook_replay.py", "tests/test_repo_binding_isolation.py", "ui/src/components/PwaUpdateToast.tsx"], "shared_pre": ["agent/completion.py", "agent/dashboard/workflow_approval.py", "agent/dashboard/workflow_approval_api.py", "agent/dispatch.py", "agent/integrations/e2b.py", "agent/middleware/ensure_no_empty_msg.py", "agent/middleware/pr_creation_guard.py", "agent/middleware/sanitize_fireworks_messages.py", "agent/middleware/sanitize_openai_responses.py", "agent/middleware/subdir_agents.py", "agent/middleware/task_retry.py", "agent/middleware/timeout_wrapup.py", "agent/middleware/workflow_push_guard.py", "agent/reconcile.py", "agent/reviewer_trace_context.py", "agent/tools/report_platform_issue.py", "agent/tools/slack_add_reaction.py", "agent/tools/slack_start_new_thread.py", "agent/utils/dashboard_handoff.py", "agent/utils/deferred_model.py", "agent/utils/gateway.py", "agent/utils/http.py", "agent/utils/thread_ids.py", "agent/utils/url_safety.py", "agent/webhooks/__init__.py", "agent/webhooks/github.py", "agent/webhooks/linear.py", "agent/webhooks/slack.py", "scripts/purge_wakeup_crons.py", "tests/e2e/screenshots/queued-messages-dashboard.png", "tests/e2e/tests/sandbox_id.spec.ts", "tests/test_agent_assembly_context.py", "tests/test_anthropic_model.py", "tests/test_completion_webhook.py", "tests/test_dashboard_oauth_redirect.py", "tests/test_dispatch.py", "tests/test_e2b_integration.py", "tests/test_ensure_no_empty_msg.py", "tests/test_gateway.py", "tests/test_linear_webhook_author.py", "tests/test_pr_creation_guard.py", "tests/test_reconcile_sweep.py", "tests/test_report_platform_issue_tool.py", "tests/test_reviewer_eval_judge.py", "tests/test_reviewer_trace_context.py", "tests/test_sanitize_fireworks_messages.py", "tests/test_sanitize_openai_responses.py", "tests/test_slack_add_reaction_tool.py", "tests/test_slack_start_new_thread_tool.py", "tests/test_slack_webhook_errors.py", "tests/test_subdir_agents_middleware.py", "tests/test_team_settings_fable.py", "tests/test_workflow_push_guard.py", "ui/AGENTS.md", "ui/pnpm-workspace.yaml", "ui/src/components/agents/PrHeader.tsx", "ui/src/components/agents/SidebarFilterMenu.tsx", "ui/src/components/agents/WorkflowApprovalCard.tsx", "ui/src/components/agents/messages/MessageTimestamp.tsx", "ui/src/lib/agents/messageTimestamps.ts", "ui/src/lib/agents/sidebarFilter.test.ts", "ui/src/lib/agents/sidebarFilter.ts", "ui/src/lib/agents/sidebarPrefs.ts", "ui/src/lib/auth-redirect-core.ts", "ui/src/lib/auth-redirect.test.ts", "ui/src/lib/auth-redirect.tsx", "ui/src/routes/$owner.$repo.pull.$number.tsx"], "shared_post": []}
|
||||
589
docs/upstream-sync/domain-reorg/head-tree.txt
Normal file
589
docs/upstream-sync/domain-reorg/head-tree.txt
Normal file
|
|
@ -0,0 +1,589 @@
|
|||
.codespellignore
|
||||
.dockerignore
|
||||
.env.example
|
||||
.githooks/_reject_guard.sh
|
||||
.githooks/commit-msg
|
||||
.githooks/post-commit
|
||||
.githooks/pre-push
|
||||
.githooks/prepare-commit-msg
|
||||
.github/CODEOWNERS
|
||||
.github/ISSUE_TEMPLATE/bug.yml
|
||||
.github/ISSUE_TEMPLATE/config.yml
|
||||
.github/ISSUE_TEMPLATE/feature.yml
|
||||
.github/ISSUE_TEMPLATE/task.yml
|
||||
.github/PULL_REQUEST_TEMPLATE.md
|
||||
.github/dependabot.yml
|
||||
.github/scripts/check-dev-green.sh
|
||||
.github/workflows/ci.yml
|
||||
.github/workflows/dependency-review.yml
|
||||
.github/workflows/labeler.yml
|
||||
.github/workflows/pr_lint.yml
|
||||
.github/workflows/promote-to-main.yml
|
||||
.github/workflows/reviewer-eval.yml
|
||||
.github/workflows/upstream-ledger-sync.yml
|
||||
.gitignore
|
||||
.nvmrc
|
||||
.security-review/suppressions.json
|
||||
.vscode/settings.json
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
CUSTOMIZATION.md
|
||||
Dockerfile
|
||||
INSTALLATION.md
|
||||
LICENSE
|
||||
Makefile
|
||||
README.md
|
||||
SECURITY.md
|
||||
agent/analyzer.py
|
||||
agent/chat.py
|
||||
agent/ci_autofix.py
|
||||
agent/ci_monitor.py
|
||||
agent/completion.py
|
||||
agent/dashboard/__init__.py
|
||||
agent/dashboard/admin.py
|
||||
agent/dashboard/agent_instructions.py
|
||||
agent/dashboard/agent_overrides.py
|
||||
agent/dashboard/agent_usage.py
|
||||
agent/dashboard/analyzer_cron.py
|
||||
agent/dashboard/autofix_state.py
|
||||
agent/dashboard/enabled_repos.py
|
||||
agent/dashboard/eval_jobs.py
|
||||
agent/dashboard/notion_oauth.py
|
||||
agent/dashboard/oauth.py
|
||||
agent/dashboard/options.py
|
||||
agent/dashboard/plan_api.py
|
||||
agent/dashboard/plan_store.py
|
||||
agent/dashboard/pr_diff.py
|
||||
agent/dashboard/profiles.py
|
||||
agent/dashboard/repo_access.py
|
||||
agent/dashboard/repo_snapshots.py
|
||||
agent/dashboard/review_api.py
|
||||
agent/dashboard/review_chat_api.py
|
||||
agent/dashboard/review_style_jobs.py
|
||||
agent/dashboard/review_styles.py
|
||||
agent/dashboard/routes.py
|
||||
agent/dashboard/schedules.py
|
||||
agent/dashboard/slack_oauth.py
|
||||
agent/dashboard/team_credentials.py
|
||||
agent/dashboard/team_settings.py
|
||||
agent/dashboard/thread_api.py
|
||||
agent/dashboard/user_credentials.py
|
||||
agent/dashboard/user_mappings.py
|
||||
agent/dashboard/workflow_approval.py
|
||||
agent/dashboard/workflow_approval_api.py
|
||||
agent/dispatch.py
|
||||
agent/encryption.py
|
||||
agent/integrations/__init__.py
|
||||
agent/integrations/corridor_mcp.py
|
||||
agent/integrations/currents_tools.py
|
||||
agent/integrations/datadog_mcp.py
|
||||
agent/integrations/daytona.py
|
||||
agent/integrations/e2b.py
|
||||
agent/integrations/langsmith.py
|
||||
agent/integrations/langsmith_tools.py
|
||||
agent/integrations/local.py
|
||||
agent/integrations/modal.py
|
||||
agent/integrations/notion_mcp.py
|
||||
agent/integrations/runloop.py
|
||||
agent/middleware/__init__.py
|
||||
agent/middleware/check_message_queue.py
|
||||
agent/middleware/ensure_no_empty_msg.py
|
||||
agent/middleware/exclude_tools.py
|
||||
agent/middleware/model_fallback.py
|
||||
agent/middleware/notify_step_limit.py
|
||||
agent/middleware/plan_mode.py
|
||||
agent/middleware/pr_creation_guard.py
|
||||
agent/middleware/refresh_github_proxy.py
|
||||
agent/middleware/refresh_slack_status.py
|
||||
agent/middleware/repair_orphaned_tool_calls.py
|
||||
agent/middleware/sandbox_circuit_breaker.py
|
||||
agent/middleware/sanitize_fireworks_messages.py
|
||||
agent/middleware/sanitize_openai_responses.py
|
||||
agent/middleware/sanitize_thinking_blocks.py
|
||||
agent/middleware/sanitize_tool_inputs.py
|
||||
agent/middleware/settle_review_check.py
|
||||
agent/middleware/subdir_agents.py
|
||||
agent/middleware/task_retry.py
|
||||
agent/middleware/timeout_wrapup.py
|
||||
agent/middleware/tool_artifact.py
|
||||
agent/middleware/tool_error_handler.py
|
||||
agent/middleware/workflow_push_guard.py
|
||||
agent/prompt.py
|
||||
agent/reconcile.py
|
||||
agent/review_style_collector.py
|
||||
agent/review_style_guidance.py
|
||||
agent/reviewer.py
|
||||
agent/reviewer_diff.py
|
||||
agent/reviewer_eval_store.py
|
||||
agent/reviewer_findings.py
|
||||
agent/reviewer_groups.py
|
||||
agent/reviewer_publish.py
|
||||
agent/reviewer_reconcile.py
|
||||
agent/reviewer_trace_context.py
|
||||
agent/scheduler.py
|
||||
agent/server.py
|
||||
agent/skills/bootstrap-repo-analysis/SKILL.md
|
||||
agent/skills/continual-learning/SKILL.md
|
||||
agent/tools/__init__.py
|
||||
agent/tools/add_finding.py
|
||||
agent/tools/confluence_comment.py
|
||||
agent/tools/confluence_create_page.py
|
||||
agent/tools/confluence_get_page.py
|
||||
agent/tools/confluence_search.py
|
||||
agent/tools/confluence_update_page.py
|
||||
agent/tools/enter_plan_mode.py
|
||||
agent/tools/fetch_url.py
|
||||
agent/tools/http_request.py
|
||||
agent/tools/jira_comment.py
|
||||
agent/tools/jira_create_issue.py
|
||||
agent/tools/jira_get_issue.py
|
||||
agent/tools/jira_get_issue_comments.py
|
||||
agent/tools/jira_list_projects.py
|
||||
agent/tools/jira_update_issue.py
|
||||
agent/tools/linear_comment.py
|
||||
agent/tools/linear_create_issue.py
|
||||
agent/tools/linear_delete_issue.py
|
||||
agent/tools/linear_get_issue.py
|
||||
agent/tools/linear_get_issue_comments.py
|
||||
agent/tools/linear_list_teams.py
|
||||
agent/tools/linear_update_issue.py
|
||||
agent/tools/list_findings.py
|
||||
agent/tools/list_review_findings.py
|
||||
agent/tools/open_pull_request.py
|
||||
agent/tools/publish_review.py
|
||||
agent/tools/read_finding_outcomes.py
|
||||
agent/tools/read_repo_file.py
|
||||
agent/tools/reply_to_finding_thread.py
|
||||
agent/tools/report_platform_issue.py
|
||||
agent/tools/request_pr_review.py
|
||||
agent/tools/resolve_finding_thread.py
|
||||
agent/tools/save_plan.py
|
||||
agent/tools/save_review_style.py
|
||||
agent/tools/schedule_thread_wakeup.py
|
||||
agent/tools/search_repo_code.py
|
||||
agent/tools/slack_add_reaction.py
|
||||
agent/tools/slack_read_thread_messages.py
|
||||
agent/tools/slack_start_new_thread.py
|
||||
agent/tools/slack_thread_reply.py
|
||||
agent/tools/update_finding.py
|
||||
agent/tools/web_search.py
|
||||
agent/utils/adf.py
|
||||
agent/utils/agents_md.py
|
||||
agent/utils/analyzer_skills.py
|
||||
agent/utils/api_standards_skill.py
|
||||
agent/utils/atlassian_connect.py
|
||||
agent/utils/auth.py
|
||||
agent/utils/authorship.py
|
||||
agent/utils/comments.py
|
||||
agent/utils/confluence.py
|
||||
agent/utils/confluence_space_repo_map.py
|
||||
agent/utils/dashboard_handoff.py
|
||||
agent/utils/dashboard_links.py
|
||||
agent/utils/deferred_model.py
|
||||
agent/utils/gateway.py
|
||||
agent/utils/github_app.py
|
||||
agent/utils/github_checks.py
|
||||
agent/utils/github_ci.py
|
||||
agent/utils/github_comments.py
|
||||
agent/utils/github_feedback.py
|
||||
agent/utils/github_http.py
|
||||
agent/utils/github_org_membership.py
|
||||
agent/utils/github_proxy.py
|
||||
agent/utils/github_token.py
|
||||
agent/utils/http.py
|
||||
agent/utils/jira.py
|
||||
agent/utils/jira_project_repo_map.py
|
||||
agent/utils/langsmith.py
|
||||
agent/utils/linear.py
|
||||
agent/utils/linear_team_repo_map.py
|
||||
agent/utils/model.py
|
||||
agent/utils/multimodal.py
|
||||
agent/utils/repo.py
|
||||
agent/utils/repo_prep.py
|
||||
agent/utils/reviewer_outcomes.py
|
||||
agent/utils/sandbox.py
|
||||
agent/utils/sandbox_paths.py
|
||||
agent/utils/sandbox_state.py
|
||||
agent/utils/slack.py
|
||||
agent/utils/slack_feedback.py
|
||||
agent/utils/thread_ids.py
|
||||
agent/utils/thread_ops.py
|
||||
agent/utils/tracing.py
|
||||
agent/utils/url_safety.py
|
||||
agent/webapp.py
|
||||
agent/webhooks/__init__.py
|
||||
agent/webhooks/confluence.py
|
||||
agent/webhooks/github.py
|
||||
agent/webhooks/jira.py
|
||||
agent/webhooks/linear.py
|
||||
agent/webhooks/slack.py
|
||||
default_prompt.md
|
||||
deploy/MIGRATION.md
|
||||
docs/repo-conventions/AGENTS.md.aws
|
||||
docs/repo-conventions/AGENTS.md.cdk
|
||||
docs/repo-conventions/AGENTS.md.ec2
|
||||
docs/repo-conventions/AGENTS.md.sam
|
||||
docs/repo-conventions/README.md
|
||||
docs/upstream-sync/cherry-pick-hook-plan.md
|
||||
docs/upstream-sync/cherry-pick-runbook.md
|
||||
docs/upstream-sync/triage.jsonl
|
||||
docs/upstream-sync/triage.md
|
||||
evals/reviewer/README.md
|
||||
evals/reviewer/build_dataset.py
|
||||
evals/reviewer/config.toml
|
||||
evals/reviewer/golden_comments/cal_dot_com.json
|
||||
evals/reviewer/golden_comments/discourse.json
|
||||
evals/reviewer/golden_comments/grafana.json
|
||||
evals/reviewer/golden_comments/keycloak.json
|
||||
evals/reviewer/golden_comments/sentry.json
|
||||
evals/reviewer/judge.py
|
||||
evals/reviewer/run_eval.py
|
||||
evals/reviewer/store_reporter.py
|
||||
evals/reviewer/target.py
|
||||
langgraph.json
|
||||
package.json
|
||||
pyproject.toml
|
||||
scripts/__init__.py
|
||||
scripts/check_pr_merge_status.py
|
||||
scripts/create_sandbox_snapshot.py
|
||||
scripts/git-cp
|
||||
scripts/install-hooks.sh
|
||||
scripts/list_snapshots.py
|
||||
scripts/purge_wakeup_crons.py
|
||||
scripts/triage.py
|
||||
static/dark.svg
|
||||
static/light.svg
|
||||
tests/conftest.py
|
||||
tests/e2e/.gitignore
|
||||
tests/e2e/README.md
|
||||
tests/e2e/agent_entrypoint.py
|
||||
tests/e2e/dev-mock.sh
|
||||
tests/e2e/e2e_env.py
|
||||
tests/e2e/fake_llm.py
|
||||
tests/e2e/fakes.py
|
||||
tests/e2e/global-setup.ts
|
||||
tests/e2e/harness.py
|
||||
tests/e2e/langgraph.e2e.json
|
||||
tests/e2e/package-lock.json
|
||||
tests/e2e/package.json
|
||||
tests/e2e/patches.py
|
||||
tests/e2e/playwright.config.ts
|
||||
tests/e2e/screenshots/queued-messages-dashboard.png
|
||||
tests/e2e/static/github.html
|
||||
tests/e2e/static/slack.html
|
||||
tests/e2e/tests/dashboard.spec.ts
|
||||
tests/e2e/tests/full_flow.spec.ts
|
||||
tests/e2e/tests/plan_review.spec.ts
|
||||
tests/e2e/tests/sandbox_id.spec.ts
|
||||
tests/middleware/test_sandbox_recovery.py
|
||||
tests/test_account_link.py
|
||||
tests/test_agent_assembly_context.py
|
||||
tests/test_agent_instructions.py
|
||||
tests/test_agent_schedules.py
|
||||
tests/test_agent_subagent_models.py
|
||||
tests/test_agent_thread_pr_state.py
|
||||
tests/test_agent_usage.py
|
||||
tests/test_agents_md.py
|
||||
tests/test_analyzer_cron.py
|
||||
tests/test_analyzer_skills.py
|
||||
tests/test_anthropic_effort.py
|
||||
tests/test_anthropic_model.py
|
||||
tests/test_api_standards_skill.py
|
||||
tests/test_app_bot_identity.py
|
||||
tests/test_atlassian_connect.py
|
||||
tests/test_auth_error_leak.py
|
||||
tests/test_auth_sources.py
|
||||
tests/test_authorship.py
|
||||
tests/test_autofix_state.py
|
||||
tests/test_autofix_webhook.py
|
||||
tests/test_check_message_queue.py
|
||||
tests/test_ci_autofix.py
|
||||
tests/test_completion_webhook.py
|
||||
tests/test_confluence_utils.py
|
||||
tests/test_confluence_webhook.py
|
||||
tests/test_corridor_mcp.py
|
||||
tests/test_currents_tools.py
|
||||
tests/test_dashboard_admin.py
|
||||
tests/test_dashboard_csrf.py
|
||||
tests/test_dashboard_links.py
|
||||
tests/test_dashboard_oauth_redirect.py
|
||||
tests/test_dashboard_org_login_gate.py
|
||||
tests/test_dashboard_repo_optional.py
|
||||
tests/test_dashboard_repos.py
|
||||
tests/test_dashboard_reviews.py
|
||||
tests/test_dashboard_run_email.py
|
||||
tests/test_dashboard_thread_api.py
|
||||
tests/test_dashboard_thread_api_activity.py
|
||||
tests/test_dashboard_web_handoff.py
|
||||
tests/test_daytona_integration.py
|
||||
tests/test_dispatch.py
|
||||
tests/test_e2b_integration.py
|
||||
tests/test_encryption.py
|
||||
tests/test_ensure_no_empty_msg.py
|
||||
tests/test_eval_jobs.py
|
||||
tests/test_eval_store_reporter.py
|
||||
tests/test_fireworks_model.py
|
||||
tests/test_gateway.py
|
||||
tests/test_github_app.py
|
||||
tests/test_github_checks.py
|
||||
tests/test_github_ci.py
|
||||
tests/test_github_comment_prompts.py
|
||||
tests/test_github_feedback.py
|
||||
tests/test_github_http.py
|
||||
tests/test_github_issue_webhook.py
|
||||
tests/test_github_oauth_refresh.py
|
||||
tests/test_github_proxy_refresh.py
|
||||
tests/test_github_token_ttl.py
|
||||
tests/test_http_security.py
|
||||
tests/test_jira_utils.py
|
||||
tests/test_jira_webhook_author.py
|
||||
tests/test_jira_webhook_corroboration.py
|
||||
tests/test_jira_webhook_replay.py
|
||||
tests/test_langsmith_sandbox_config.py
|
||||
tests/test_langsmith_sandbox_timeout.py
|
||||
tests/test_langsmith_trace_url.py
|
||||
tests/test_linear_webhook_author.py
|
||||
tests/test_linear_webhook_replay.py
|
||||
tests/test_local_integration.py
|
||||
tests/test_model_fallback_middleware.py
|
||||
tests/test_model_fallback_resolution.py
|
||||
tests/test_multimodal.py
|
||||
tests/test_normalize_repo.py
|
||||
tests/test_notify_step_limit_middleware.py
|
||||
tests/test_notion_oauth.py
|
||||
tests/test_observability_tools.py
|
||||
tests/test_open_pull_request.py
|
||||
tests/test_plan_mode.py
|
||||
tests/test_plan_review.py
|
||||
tests/test_pr_creation_guard.py
|
||||
tests/test_pr_ready_auto_review.py
|
||||
tests/test_prompt_default_repo.py
|
||||
tests/test_proxy_auth.py
|
||||
tests/test_public_repo_org_gate.py
|
||||
tests/test_recent_comments.py
|
||||
tests/test_reconcile_sweep.py
|
||||
tests/test_refresh_slack_status_middleware.py
|
||||
tests/test_repair_orphaned_tool_calls.py
|
||||
tests/test_repo_binding_isolation.py
|
||||
tests/test_repo_extraction.py
|
||||
tests/test_repo_prep.py
|
||||
tests/test_repo_snapshots.py
|
||||
tests/test_report_platform_issue_tool.py
|
||||
tests/test_review_api.py
|
||||
tests/test_review_chat.py
|
||||
tests/test_review_style_collector.py
|
||||
tests/test_review_style_sync.py
|
||||
tests/test_review_styles_store.py
|
||||
tests/test_reviewer.py
|
||||
tests/test_reviewer_diff.py
|
||||
tests/test_reviewer_eval_judge.py
|
||||
tests/test_reviewer_eval_run.py
|
||||
tests/test_reviewer_eval_target.py
|
||||
tests/test_reviewer_findings.py
|
||||
tests/test_reviewer_groups.py
|
||||
tests/test_reviewer_outcomes.py
|
||||
tests/test_reviewer_publish.py
|
||||
tests/test_reviewer_reconcile.py
|
||||
tests/test_reviewer_tools.py
|
||||
tests/test_reviewer_trace_context.py
|
||||
tests/test_reviewer_watch.py
|
||||
tests/test_sandbox_paths.py
|
||||
tests/test_sanitize_fireworks_messages.py
|
||||
tests/test_sanitize_openai_responses.py
|
||||
tests/test_sanitize_thinking_blocks.py
|
||||
tests/test_sanitize_tool_inputs.py
|
||||
tests/test_schedule_thread_wakeup.py
|
||||
tests/test_slack_add_reaction_tool.py
|
||||
tests/test_slack_assistants_status.py
|
||||
tests/test_slack_context.py
|
||||
tests/test_slack_feedback.py
|
||||
tests/test_slack_oauth.py
|
||||
tests/test_slack_start_new_thread_tool.py
|
||||
tests/test_slack_thread_reply_tool.py
|
||||
tests/test_slack_webhook_errors.py
|
||||
tests/test_stale_sandbox_creating.py
|
||||
tests/test_subdir_agents_middleware.py
|
||||
tests/test_team_credentials.py
|
||||
tests/test_team_settings_fable.py
|
||||
tests/test_team_settings_grouping.py
|
||||
tests/test_team_settings_org_guidelines.py
|
||||
tests/test_tool_artifact_middleware.py
|
||||
tests/test_user_credentials.py
|
||||
tests/test_user_mappings.py
|
||||
tests/test_workflow_push_guard.py
|
||||
ui/.cta.json
|
||||
ui/.gitignore
|
||||
ui/.prettierignore
|
||||
ui/.prettierrc
|
||||
ui/AGENTS.md
|
||||
ui/README.md
|
||||
ui/assets/LangChain_Symbol_LightBlue.png
|
||||
ui/assets/ic_launcher_round.png
|
||||
ui/assets/playstore-icon.png
|
||||
ui/bun.lock
|
||||
ui/components.json
|
||||
ui/eslint.config.js
|
||||
ui/package.json
|
||||
ui/pnpm-workspace.yaml
|
||||
ui/public/apple-touch-icon.png
|
||||
ui/public/favicon.png
|
||||
ui/public/gh-open-in-open-swe-dark.svg
|
||||
ui/public/gh-open-in-open-swe-light.svg
|
||||
ui/public/logo-mark.png
|
||||
ui/public/logo512.png
|
||||
ui/public/robots.txt
|
||||
ui/src/client.tsx
|
||||
ui/src/components/AgentInstructionsPanel.tsx
|
||||
ui/src/components/AppShell.tsx
|
||||
ui/src/components/AppSidebar.tsx
|
||||
ui/src/components/InstructionsEditor.tsx
|
||||
ui/src/components/PwaUpdateToast.tsx
|
||||
ui/src/components/RepoSnapshotsPanel.tsx
|
||||
ui/src/components/ReviewStylesPanel.tsx
|
||||
ui/src/components/SidebarUserMenu.tsx
|
||||
ui/src/components/agents/AgentGitPanel.tsx
|
||||
ui/src/components/agents/AgentPromptBar.tsx
|
||||
ui/src/components/agents/AgentRunCard.tsx
|
||||
ui/src/components/agents/AgentThreadView.tsx
|
||||
ui/src/components/agents/AgentsHome.tsx
|
||||
ui/src/components/agents/AgentsSidebar.tsx
|
||||
ui/src/components/agents/AgentsThreadsPage.tsx
|
||||
ui/src/components/agents/AutomationEditor.tsx
|
||||
ui/src/components/agents/AutomationTemplates.tsx
|
||||
ui/src/components/agents/AutomationsList.tsx
|
||||
ui/src/components/agents/OnboardingDialog.tsx
|
||||
ui/src/components/agents/PlanReview.tsx
|
||||
ui/src/components/agents/PrHeader.tsx
|
||||
ui/src/components/agents/RepoSelector.tsx
|
||||
ui/src/components/agents/ReviewChat.tsx
|
||||
ui/src/components/agents/ReviewCommentsMenu.tsx
|
||||
ui/src/components/agents/ReviewMainBody.tsx
|
||||
ui/src/components/agents/ReviewSidebar.tsx
|
||||
ui/src/components/agents/ReviewTab.tsx
|
||||
ui/src/components/agents/ScheduleTriggerPicker.tsx
|
||||
ui/src/components/agents/SidebarFilterMenu.tsx
|
||||
ui/src/components/agents/WorkflowApprovalCard.tsx
|
||||
ui/src/components/agents/messages/AgentMessage.tsx
|
||||
ui/src/components/agents/messages/ChunkRenderer.tsx
|
||||
ui/src/components/agents/messages/MessageTimestamp.tsx
|
||||
ui/src/components/agents/messages/Messages.tsx
|
||||
ui/src/components/agents/messages/ReasoningBlock.tsx
|
||||
ui/src/components/agents/messages/ThinkingSpinner.tsx
|
||||
ui/src/components/agents/messages/TurnChangedFilesCard.tsx
|
||||
ui/src/components/agents/messages/UserMessage.tsx
|
||||
ui/src/components/agents/messages/WorkSummary.tsx
|
||||
ui/src/components/agents/messages/index.ts
|
||||
ui/src/components/agents/messages/renderItems.ts
|
||||
ui/src/components/agents/messages/summarizeChangedFiles.ts
|
||||
ui/src/components/agents/messages/types.ts
|
||||
ui/src/components/agents/ported/BranchSelector.tsx
|
||||
ui/src/components/agents/ported/BubblePrefix.tsx
|
||||
ui/src/components/agents/ported/ChatView.tsx
|
||||
ui/src/components/agents/ported/CloudPromptBar.tsx
|
||||
ui/src/components/agents/ported/CodeBlock.tsx
|
||||
ui/src/components/agents/ported/CompactingIndicator.tsx
|
||||
ui/src/components/agents/ported/ContextIndicator.tsx
|
||||
ui/src/components/agents/ported/DiffView.tsx
|
||||
ui/src/components/agents/ported/Footer.tsx
|
||||
ui/src/components/agents/ported/HeaderBar.tsx
|
||||
ui/src/components/agents/ported/Logo.tsx
|
||||
ui/src/components/agents/ported/Markdown.tsx
|
||||
ui/src/components/agents/ported/MarkdownTable.tsx
|
||||
ui/src/components/agents/ported/PanelResizeHandle.tsx
|
||||
ui/src/components/agents/ported/PromptBar.tsx
|
||||
ui/src/components/agents/ported/ReplyCard.tsx
|
||||
ui/src/components/agents/ported/ShellCommand.tsx
|
||||
ui/src/components/agents/ported/SourceControlPanel.tsx
|
||||
ui/src/components/agents/ported/SourceControlTile.tsx
|
||||
ui/src/components/agents/ported/ThreadPicker.tsx
|
||||
ui/src/components/agents/ported/TodoList.tsx
|
||||
ui/src/components/agents/ported/ToolExecution.tsx
|
||||
ui/src/components/agents/ported/index.ts
|
||||
ui/src/components/agents/subagents/SubagentActivity.tsx
|
||||
ui/src/components/agents/subagents/SubagentCard.tsx
|
||||
ui/src/components/agents/subagents/SubagentGroup.tsx
|
||||
ui/src/components/agents/subagents/index.ts
|
||||
ui/src/components/agents/utils/diffStats.ts
|
||||
ui/src/components/agents/utils/diffUtils.test.ts
|
||||
ui/src/components/agents/utils/diffUtils.ts
|
||||
ui/src/components/agents/z-index.ts
|
||||
ui/src/components/sidebar-layout.tsx
|
||||
ui/src/components/ui/avatar.tsx
|
||||
ui/src/components/ui/badge.tsx
|
||||
ui/src/components/ui/button.tsx
|
||||
ui/src/components/ui/card.tsx
|
||||
ui/src/components/ui/combobox.tsx
|
||||
ui/src/components/ui/input-group.tsx
|
||||
ui/src/components/ui/input.tsx
|
||||
ui/src/components/ui/label.tsx
|
||||
ui/src/components/ui/select.tsx
|
||||
ui/src/components/ui/separator.tsx
|
||||
ui/src/components/ui/skeleton.tsx
|
||||
ui/src/components/ui/switch.tsx
|
||||
ui/src/components/ui/textarea.tsx
|
||||
ui/src/lib/agents/AgentThreadStreamProvider.tsx
|
||||
ui/src/lib/agents/api.ts
|
||||
ui/src/lib/agents/automation-templates.ts
|
||||
ui/src/lib/agents/cron.ts
|
||||
ui/src/lib/agents/messageTimestamps.ts
|
||||
ui/src/lib/agents/provider/useIsInAgentThreadStream.tsx
|
||||
ui/src/lib/agents/provider/useLiveMarkdownMessageId.ts
|
||||
ui/src/lib/agents/provider/useModelOptions.ts
|
||||
ui/src/lib/agents/provider/useSubmitAgentMessage.ts
|
||||
ui/src/lib/agents/queries.ts
|
||||
ui/src/lib/agents/sidebarFilter.test.ts
|
||||
ui/src/lib/agents/sidebarFilter.ts
|
||||
ui/src/lib/agents/sidebarPrefs.ts
|
||||
ui/src/lib/agents/streamMessagesToUi.ts
|
||||
ui/src/lib/agents/types.ts
|
||||
ui/src/lib/agents/useRunCompletionNotifier.ts
|
||||
ui/src/lib/api.ts
|
||||
ui/src/lib/auth-redirect-core.ts
|
||||
ui/src/lib/auth-redirect.test.ts
|
||||
ui/src/lib/auth-redirect.tsx
|
||||
ui/src/lib/hotkeys.ts
|
||||
ui/src/lib/notifications.ts
|
||||
ui/src/lib/plan.ts
|
||||
ui/src/lib/profile.ts
|
||||
ui/src/lib/query.ts
|
||||
ui/src/lib/repo.test.ts
|
||||
ui/src/lib/repo.ts
|
||||
ui/src/lib/reviewImage.test.ts
|
||||
ui/src/lib/session.ts
|
||||
ui/src/lib/theme.ts
|
||||
ui/src/lib/useIsMobile.ts
|
||||
ui/src/lib/utils.ts
|
||||
ui/src/logo.svg
|
||||
ui/src/routeTree.gen.ts
|
||||
ui/src/router.tsx
|
||||
ui/src/routes/$owner.$repo.pull.$number.tsx
|
||||
ui/src/routes/__root.tsx
|
||||
ui/src/routes/admin.tsx
|
||||
ui/src/routes/admin_.evals.tsx
|
||||
ui/src/routes/agents.tsx
|
||||
ui/src/routes/agents/$threadId.tsx
|
||||
ui/src/routes/agents/$threadId_.plan.tsx
|
||||
ui/src/routes/agents/automations/$scheduleId.tsx
|
||||
ui/src/routes/agents/automations/index.tsx
|
||||
ui/src/routes/agents/automations/new.tsx
|
||||
ui/src/routes/agents/index.tsx
|
||||
ui/src/routes/agents/reviews/$owner.$repo.$number.tsx
|
||||
ui/src/routes/agents/reviews/index.tsx
|
||||
ui/src/routes/agents/threads.tsx
|
||||
ui/src/routes/agents_.instructions.tsx
|
||||
ui/src/routes/agents_.snapshots.tsx
|
||||
ui/src/routes/cloud-agents.tsx
|
||||
ui/src/routes/index.tsx
|
||||
ui/src/routes/integrations.tsx
|
||||
ui/src/routes/login.tsx
|
||||
ui/src/routes/my-settings.tsx
|
||||
ui/src/routes/review.tsx
|
||||
ui/src/routes/review_.repositories.$owner.tsx
|
||||
ui/src/routes/review_.styles.tsx
|
||||
ui/src/routes/usage.tsx
|
||||
ui/src/styles.css
|
||||
ui/src/styles/agents.css
|
||||
ui/tsconfig.json
|
||||
ui/vercel.json
|
||||
ui/vite.config.ts
|
||||
uv.lock
|
||||
435
docs/upstream-sync/domain-reorg/merge-tree-now.txt
Normal file
435
docs/upstream-sync/domain-reorg/merge-tree-now.txt
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
2e93de8920fab3ba052901838106fa250708e1f0
|
||||
.github/workflows/ci.yml
|
||||
.github/workflows/promote_main_to_prod.yml
|
||||
.github/workflows/reviewer-eval.yml
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
Dockerfile
|
||||
Makefile
|
||||
README.md
|
||||
agent/analyzer.py
|
||||
agent/chat.py
|
||||
agent/ci_autofix.py
|
||||
agent/completion.py
|
||||
agent/dashboard/__init__.py
|
||||
agent/dashboard/eval_jobs.py
|
||||
agent/dashboard/options.py
|
||||
agent/dashboard/plan_api.py
|
||||
agent/dashboard/review_api.py
|
||||
agent/dashboard/review_chat_api.py
|
||||
agent/dashboard/schedules.py
|
||||
agent/dashboard/team_settings.py
|
||||
agent/dashboard/thread_api.py
|
||||
agent/dashboard/workflow_approval.py
|
||||
agent/dashboard/workflow_approval_api.py
|
||||
agent/dispatch.py
|
||||
agent/middleware/__init__.py
|
||||
agent/middleware/ensure_no_empty_msg.py
|
||||
agent/middleware/model_fallback.py
|
||||
agent/middleware/pr_creation_guard.py
|
||||
agent/middleware/sanitize_fireworks_messages.py
|
||||
agent/middleware/subdir_agents.py
|
||||
agent/middleware/timeout_wrapup.py
|
||||
agent/middleware/workflow_push_guard.py
|
||||
agent/prompt.py
|
||||
agent/review/findings.py
|
||||
agent/reviewer.py
|
||||
agent/server.py
|
||||
agent/tools/__init__.py
|
||||
agent/tools/add_finding.py
|
||||
agent/tools/fetch_url.py
|
||||
agent/tools/http_request.py
|
||||
agent/tools/open_pull_request.py
|
||||
agent/tools/publish_review.py
|
||||
agent/tools/request_pr_review.py
|
||||
agent/tools/slack_start_new_thread.py
|
||||
agent/tools/slack_thread_reply.py
|
||||
agent/tools/update_finding.py
|
||||
agent/tools/web_search.py
|
||||
agent/utils/analyzer_skills.py
|
||||
agent/utils/auth.py
|
||||
agent/utils/deferred_model.py
|
||||
agent/utils/gateway.py
|
||||
agent/utils/github_app.py
|
||||
agent/utils/github_proxy.py
|
||||
agent/utils/github_token.py
|
||||
agent/utils/model.py
|
||||
agent/utils/multimodal.py
|
||||
agent/utils/sandbox.py
|
||||
agent/utils/sandbox_state.py
|
||||
agent/utils/slack.py
|
||||
agent/utils/url_safety.py
|
||||
agent/webapp.py
|
||||
agent/webhooks/github.py
|
||||
agent/webhooks/linear.py
|
||||
agent/webhooks/slack.py
|
||||
docs/INSTALLATION.md
|
||||
evals/reviewer/config.toml
|
||||
evals/reviewer/judge.py
|
||||
evals/reviewer/run_eval.py
|
||||
evals/reviewer/target.py
|
||||
pyproject.toml
|
||||
tests/auth/test_auth_sources.py
|
||||
tests/auth/test_github_token_ttl.py
|
||||
tests/conftest.py
|
||||
tests/dashboard/test_dashboard_thread_api.py
|
||||
tests/dashboard/test_dashboard_web_handoff.py
|
||||
tests/dashboard/test_team_settings_grouping.py
|
||||
tests/dashboard/test_team_settings_org_guidelines.py
|
||||
tests/e2e/fake_llm.py
|
||||
tests/e2e/harness.py
|
||||
tests/e2e/tests/dashboard.spec.ts
|
||||
tests/e2e/tests/full_flow.spec.ts
|
||||
tests/github/test_github_comment_prompts.py
|
||||
tests/github/test_github_issue_webhook.py
|
||||
tests/github/test_open_pull_request.py
|
||||
tests/middleware/test_model_fallback_middleware.py
|
||||
tests/middleware/test_sanitize_thinking_blocks.py
|
||||
tests/models/test_agent_subagent_models.py
|
||||
tests/models/test_google_model.py
|
||||
tests/reviewer/test_pr_ready_auto_review.py
|
||||
tests/reviewer/test_reviewer.py
|
||||
tests/reviewer/test_reviewer_findings.py
|
||||
tests/reviewer/test_reviewer_publish.py
|
||||
tests/reviewer/test_reviewer_tools.py
|
||||
tests/reviewer/test_reviewer_watch.py
|
||||
tests/sandbox/test_local_integration.py
|
||||
tests/sandbox/test_proxy_auth.py
|
||||
tests/slack/test_slack_context.py
|
||||
tests/slack/test_slack_thread_reply_tool.py
|
||||
tests/test_autofix_webhook.py
|
||||
tests/test_ci_autofix.py
|
||||
tests/test_http_security.py
|
||||
tests/test_model_fallback_resolution.py
|
||||
tests/test_plan_review.py
|
||||
ui/AGENTS.md
|
||||
ui/bun.lock
|
||||
ui/package.json
|
||||
ui/src/client.tsx
|
||||
ui/src/components/agents/PlanReview.tsx
|
||||
ui/src/features/agents/components/AgentGitPanel.tsx
|
||||
ui/src/features/agents/components/AgentThreadView.tsx
|
||||
ui/src/features/agents/components/AgentsSidebar.tsx
|
||||
ui/src/features/agents/components/PrHeader.tsx
|
||||
ui/src/features/agents/components/SidebarFilterMenu.tsx
|
||||
ui/src/features/agents/components/WorkflowApprovalCard.tsx
|
||||
ui/src/features/agents/components/messages/AgentMessage.tsx
|
||||
ui/src/features/agents/components/messages/MessageTimestamp.tsx
|
||||
ui/src/features/agents/components/messages/UserMessage.tsx
|
||||
ui/src/features/agents/components/messages/types.ts
|
||||
ui/src/features/agents/lib/api.ts
|
||||
ui/src/features/agents/lib/messageTimestamps.ts
|
||||
ui/src/features/agents/lib/queries.ts
|
||||
ui/src/features/agents/lib/sidebarFilter.test.ts
|
||||
ui/src/features/agents/lib/sidebarFilter.ts
|
||||
ui/src/features/agents/lib/sidebarPrefs.ts
|
||||
ui/src/features/agents/lib/streamMessagesToUi.ts
|
||||
ui/src/features/agents/lib/types.ts
|
||||
ui/src/features/reviews/components/ReviewMainBody.tsx
|
||||
ui/src/lib/agents/provider/useSubmitAgentMessage.ts
|
||||
ui/src/lib/auth-redirect-core.ts
|
||||
ui/src/lib/auth-redirect.test.ts
|
||||
ui/src/routes/admin.tsx
|
||||
ui/src/routes/agents.tsx
|
||||
ui/src/routes/agents/$threadId_.plan.tsx
|
||||
ui/vercel.json
|
||||
ui/vite.config.ts
|
||||
uv.lock
|
||||
|
||||
Auto-merging .github/workflows/ci.yml
|
||||
CONFLICT (content): Merge conflict in .github/workflows/ci.yml
|
||||
CONFLICT (modify/delete): .github/workflows/promote_main_to_prod.yml deleted in HEAD and modified in upstream/main. Version upstream/main of .github/workflows/promote_main_to_prod.yml left in tree.
|
||||
Auto-merging .github/workflows/reviewer-eval.yml
|
||||
CONFLICT (content): Merge conflict in .github/workflows/reviewer-eval.yml
|
||||
Auto-merging .gitignore
|
||||
Auto-merging AGENTS.md
|
||||
CONFLICT (content): Merge conflict in AGENTS.md
|
||||
Auto-merging CLAUDE.md
|
||||
CONFLICT (content): Merge conflict in CLAUDE.md
|
||||
Auto-merging Dockerfile
|
||||
CONFLICT (content): Merge conflict in Dockerfile
|
||||
Auto-merging Makefile
|
||||
CONFLICT (content): Merge conflict in Makefile
|
||||
Auto-merging README.md
|
||||
CONFLICT (content): Merge conflict in README.md
|
||||
Auto-merging agent/analyzer.py
|
||||
CONFLICT (content): Merge conflict in agent/analyzer.py
|
||||
Auto-merging agent/chat.py
|
||||
CONFLICT (content): Merge conflict in agent/chat.py
|
||||
CONFLICT (modify/delete): agent/ci_autofix.py deleted in upstream/main and modified in HEAD. Version HEAD of agent/ci_autofix.py left in tree.
|
||||
Auto-merging agent/completion.py
|
||||
CONFLICT (add/add): Merge conflict in agent/completion.py
|
||||
Auto-merging agent/dashboard/__init__.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/__init__.py
|
||||
Auto-merging agent/dashboard/eval_jobs.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/eval_jobs.py
|
||||
Auto-merging agent/dashboard/options.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/options.py
|
||||
Auto-merging agent/dashboard/plan_api.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/plan_api.py
|
||||
Auto-merging agent/dashboard/review_api.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/review_api.py
|
||||
Auto-merging agent/dashboard/review_chat_api.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/review_chat_api.py
|
||||
Auto-merging agent/dashboard/routes.py
|
||||
Auto-merging agent/dashboard/schedules.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/schedules.py
|
||||
Auto-merging agent/dashboard/team_settings.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/team_settings.py
|
||||
Auto-merging agent/dashboard/thread_api.py
|
||||
CONFLICT (content): Merge conflict in agent/dashboard/thread_api.py
|
||||
Auto-merging agent/dashboard/workflow_approval.py
|
||||
CONFLICT (add/add): Merge conflict in agent/dashboard/workflow_approval.py
|
||||
Auto-merging agent/dashboard/workflow_approval_api.py
|
||||
CONFLICT (add/add): Merge conflict in agent/dashboard/workflow_approval_api.py
|
||||
Auto-merging agent/dispatch.py
|
||||
CONFLICT (add/add): Merge conflict in agent/dispatch.py
|
||||
Auto-merging agent/middleware/__init__.py
|
||||
CONFLICT (content): Merge conflict in agent/middleware/__init__.py
|
||||
Auto-merging agent/middleware/check_message_queue.py
|
||||
Auto-merging agent/middleware/ensure_no_empty_msg.py
|
||||
CONFLICT (add/add): Merge conflict in agent/middleware/ensure_no_empty_msg.py
|
||||
Auto-merging agent/middleware/model_fallback.py
|
||||
CONFLICT (content): Merge conflict in agent/middleware/model_fallback.py
|
||||
Auto-merging agent/middleware/plan_mode.py
|
||||
Auto-merging agent/middleware/pr_creation_guard.py
|
||||
CONFLICT (add/add): Merge conflict in agent/middleware/pr_creation_guard.py
|
||||
Auto-merging agent/middleware/refresh_slack_status.py
|
||||
Auto-merging agent/middleware/sanitize_fireworks_messages.py
|
||||
CONFLICT (add/add): Merge conflict in agent/middleware/sanitize_fireworks_messages.py
|
||||
Auto-merging agent/middleware/sanitize_thinking_blocks.py
|
||||
Auto-merging agent/middleware/subdir_agents.py
|
||||
CONFLICT (add/add): Merge conflict in agent/middleware/subdir_agents.py
|
||||
Auto-merging agent/middleware/timeout_wrapup.py
|
||||
CONFLICT (add/add): Merge conflict in agent/middleware/timeout_wrapup.py
|
||||
Auto-merging agent/middleware/workflow_push_guard.py
|
||||
CONFLICT (add/add): Merge conflict in agent/middleware/workflow_push_guard.py
|
||||
Auto-merging agent/prompt.py
|
||||
CONFLICT (content): Merge conflict in agent/prompt.py
|
||||
Auto-merging agent/review/findings.py
|
||||
CONFLICT (content): Merge conflict in agent/review/findings.py
|
||||
Auto-merging agent/review/publish.py
|
||||
Auto-merging agent/review/reconcile.py
|
||||
Auto-merging agent/reviewer.py
|
||||
CONFLICT (content): Merge conflict in agent/reviewer.py
|
||||
Auto-merging agent/server.py
|
||||
CONFLICT (content): Merge conflict in agent/server.py
|
||||
Auto-merging agent/tools/__init__.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/__init__.py
|
||||
Auto-merging agent/tools/add_finding.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/add_finding.py
|
||||
Auto-merging agent/tools/fetch_url.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/fetch_url.py
|
||||
Auto-merging agent/tools/http_request.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/http_request.py
|
||||
Auto-merging agent/tools/list_findings.py
|
||||
Auto-merging agent/tools/list_review_findings.py
|
||||
Auto-merging agent/tools/open_pull_request.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/open_pull_request.py
|
||||
Auto-merging agent/tools/publish_review.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/publish_review.py
|
||||
Auto-merging agent/tools/reply_to_finding_thread.py
|
||||
Auto-merging agent/tools/request_pr_review.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/request_pr_review.py
|
||||
Auto-merging agent/tools/resolve_finding_thread.py
|
||||
Auto-merging agent/tools/schedule_thread_wakeup.py
|
||||
Auto-merging agent/tools/slack_read_thread_messages.py
|
||||
Auto-merging agent/tools/slack_start_new_thread.py
|
||||
CONFLICT (add/add): Merge conflict in agent/tools/slack_start_new_thread.py
|
||||
Auto-merging agent/tools/slack_thread_reply.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/slack_thread_reply.py
|
||||
Auto-merging agent/tools/update_finding.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/update_finding.py
|
||||
Auto-merging agent/tools/web_search.py
|
||||
CONFLICT (content): Merge conflict in agent/tools/web_search.py
|
||||
Auto-merging agent/utils/analyzer_skills.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/analyzer_skills.py
|
||||
Auto-merging agent/utils/auth.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/auth.py
|
||||
Auto-merging agent/utils/deferred_model.py
|
||||
CONFLICT (add/add): Merge conflict in agent/utils/deferred_model.py
|
||||
Auto-merging agent/utils/gateway.py
|
||||
CONFLICT (add/add): Merge conflict in agent/utils/gateway.py
|
||||
Auto-merging agent/utils/github_app.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/github_app.py
|
||||
Auto-merging agent/utils/github_comments.py
|
||||
Auto-merging agent/utils/github_org_membership.py
|
||||
Auto-merging agent/utils/github_proxy.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/github_proxy.py
|
||||
Auto-merging agent/utils/github_token.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/github_token.py
|
||||
Auto-merging agent/utils/linear.py
|
||||
Auto-merging agent/utils/model.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/model.py
|
||||
Auto-merging agent/utils/multimodal.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/multimodal.py
|
||||
Auto-merging agent/utils/sandbox.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/sandbox.py
|
||||
Auto-merging agent/utils/sandbox_state.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/sandbox_state.py
|
||||
Auto-merging agent/utils/slack.py
|
||||
CONFLICT (content): Merge conflict in agent/utils/slack.py
|
||||
Auto-merging agent/utils/url_safety.py
|
||||
CONFLICT (add/add): Merge conflict in agent/utils/url_safety.py
|
||||
Auto-merging agent/webapp.py
|
||||
CONFLICT (content): Merge conflict in agent/webapp.py
|
||||
Auto-merging agent/webhooks/github.py
|
||||
CONFLICT (add/add): Merge conflict in agent/webhooks/github.py
|
||||
Auto-merging agent/webhooks/linear.py
|
||||
CONFLICT (add/add): Merge conflict in agent/webhooks/linear.py
|
||||
Auto-merging agent/webhooks/slack.py
|
||||
CONFLICT (add/add): Merge conflict in agent/webhooks/slack.py
|
||||
Auto-merging docs/CUSTOMIZATION.md
|
||||
Auto-merging docs/INSTALLATION.md
|
||||
CONFLICT (content): Merge conflict in docs/INSTALLATION.md
|
||||
Auto-merging evals/reviewer/README.md
|
||||
Auto-merging evals/reviewer/config.toml
|
||||
CONFLICT (content): Merge conflict in evals/reviewer/config.toml
|
||||
Auto-merging evals/reviewer/judge.py
|
||||
CONFLICT (content): Merge conflict in evals/reviewer/judge.py
|
||||
Auto-merging evals/reviewer/run_eval.py
|
||||
CONFLICT (content): Merge conflict in evals/reviewer/run_eval.py
|
||||
Auto-merging evals/reviewer/target.py
|
||||
CONFLICT (content): Merge conflict in evals/reviewer/target.py
|
||||
Auto-merging pyproject.toml
|
||||
CONFLICT (content): Merge conflict in pyproject.toml
|
||||
Auto-merging tests/agent/test_agent_schedules.py
|
||||
Auto-merging tests/agent/test_plan_mode.py
|
||||
Auto-merging tests/auth/test_auth_sources.py
|
||||
CONFLICT (content): Merge conflict in tests/auth/test_auth_sources.py
|
||||
Auto-merging tests/auth/test_github_token_ttl.py
|
||||
CONFLICT (content): Merge conflict in tests/auth/test_github_token_ttl.py
|
||||
Auto-merging tests/conftest.py
|
||||
CONFLICT (content): Merge conflict in tests/conftest.py
|
||||
Auto-merging tests/dashboard/test_dashboard_thread_api.py
|
||||
CONFLICT (content): Merge conflict in tests/dashboard/test_dashboard_thread_api.py
|
||||
Auto-merging tests/dashboard/test_dashboard_web_handoff.py
|
||||
CONFLICT (content): Merge conflict in tests/dashboard/test_dashboard_web_handoff.py
|
||||
Auto-merging tests/dashboard/test_team_settings_grouping.py
|
||||
CONFLICT (content): Merge conflict in tests/dashboard/test_team_settings_grouping.py
|
||||
Auto-merging tests/dashboard/test_team_settings_org_guidelines.py
|
||||
CONFLICT (content): Merge conflict in tests/dashboard/test_team_settings_org_guidelines.py
|
||||
Auto-merging tests/e2e/fake_llm.py
|
||||
CONFLICT (content): Merge conflict in tests/e2e/fake_llm.py
|
||||
Auto-merging tests/e2e/harness.py
|
||||
CONFLICT (content): Merge conflict in tests/e2e/harness.py
|
||||
Auto-merging tests/e2e/patches.py
|
||||
Auto-merging tests/e2e/tests/dashboard.spec.ts
|
||||
CONFLICT (content): Merge conflict in tests/e2e/tests/dashboard.spec.ts
|
||||
Auto-merging tests/e2e/tests/full_flow.spec.ts
|
||||
CONFLICT (content): Merge conflict in tests/e2e/tests/full_flow.spec.ts
|
||||
Auto-merging tests/e2e/tests/plan_review.spec.ts
|
||||
Auto-merging tests/github/test_github_app.py
|
||||
Auto-merging tests/github/test_github_comment_prompts.py
|
||||
CONFLICT (content): Merge conflict in tests/github/test_github_comment_prompts.py
|
||||
Auto-merging tests/github/test_github_issue_webhook.py
|
||||
CONFLICT (content): Merge conflict in tests/github/test_github_issue_webhook.py
|
||||
Auto-merging tests/github/test_github_proxy_refresh.py
|
||||
Auto-merging tests/github/test_open_pull_request.py
|
||||
CONFLICT (content): Merge conflict in tests/github/test_open_pull_request.py
|
||||
Auto-merging tests/middleware/test_model_fallback_middleware.py
|
||||
CONFLICT (content): Merge conflict in tests/middleware/test_model_fallback_middleware.py
|
||||
Auto-merging tests/middleware/test_sanitize_thinking_blocks.py
|
||||
CONFLICT (content): Merge conflict in tests/middleware/test_sanitize_thinking_blocks.py
|
||||
Auto-merging tests/models/test_agent_subagent_models.py
|
||||
CONFLICT (content): Merge conflict in tests/models/test_agent_subagent_models.py
|
||||
Auto-merging tests/models/test_fireworks_model.py
|
||||
CONFLICT (rename/delete): tests/test_google_model.py renamed to tests/models/test_google_model.py in upstream/main, but deleted in HEAD.
|
||||
CONFLICT (modify/delete): tests/models/test_google_model.py deleted in HEAD and modified in upstream/main. Version upstream/main of tests/models/test_google_model.py left in tree.
|
||||
Auto-merging tests/reviewer/test_pr_ready_auto_review.py
|
||||
CONFLICT (content): Merge conflict in tests/reviewer/test_pr_ready_auto_review.py
|
||||
Auto-merging tests/reviewer/test_review_chat.py
|
||||
Auto-merging tests/reviewer/test_reviewer.py
|
||||
CONFLICT (content): Merge conflict in tests/reviewer/test_reviewer.py
|
||||
Auto-merging tests/reviewer/test_reviewer_eval_target.py
|
||||
Auto-merging tests/reviewer/test_reviewer_findings.py
|
||||
CONFLICT (content): Merge conflict in tests/reviewer/test_reviewer_findings.py
|
||||
Auto-merging tests/reviewer/test_reviewer_publish.py
|
||||
CONFLICT (content): Merge conflict in tests/reviewer/test_reviewer_publish.py
|
||||
Auto-merging tests/reviewer/test_reviewer_reconcile.py
|
||||
Auto-merging tests/reviewer/test_reviewer_tools.py
|
||||
CONFLICT (content): Merge conflict in tests/reviewer/test_reviewer_tools.py
|
||||
Auto-merging tests/reviewer/test_reviewer_watch.py
|
||||
CONFLICT (content): Merge conflict in tests/reviewer/test_reviewer_watch.py
|
||||
Auto-merging tests/sandbox/test_local_integration.py
|
||||
CONFLICT (content): Merge conflict in tests/sandbox/test_local_integration.py
|
||||
Auto-merging tests/sandbox/test_proxy_auth.py
|
||||
CONFLICT (content): Merge conflict in tests/sandbox/test_proxy_auth.py
|
||||
Auto-merging tests/slack/test_slack_assistants_status.py
|
||||
Auto-merging tests/slack/test_slack_context.py
|
||||
CONFLICT (content): Merge conflict in tests/slack/test_slack_context.py
|
||||
Auto-merging tests/slack/test_slack_thread_reply_tool.py
|
||||
CONFLICT (content): Merge conflict in tests/slack/test_slack_thread_reply_tool.py
|
||||
CONFLICT (modify/delete): tests/test_autofix_webhook.py deleted in upstream/main and modified in HEAD. Version HEAD of tests/test_autofix_webhook.py left in tree.
|
||||
CONFLICT (modify/delete): tests/test_ci_autofix.py deleted in upstream/main and modified in HEAD. Version HEAD of tests/test_ci_autofix.py left in tree.
|
||||
CONFLICT (modify/delete): tests/test_http_security.py deleted in upstream/main and modified in HEAD. Version HEAD of tests/test_http_security.py left in tree.
|
||||
CONFLICT (modify/delete): tests/test_model_fallback_resolution.py deleted in upstream/main and modified in HEAD. Version HEAD of tests/test_model_fallback_resolution.py left in tree.
|
||||
CONFLICT (modify/delete): tests/test_plan_review.py deleted in upstream/main and modified in HEAD. Version HEAD of tests/test_plan_review.py left in tree.
|
||||
Auto-merging tests/tools/test_corridor_mcp.py
|
||||
Auto-merging ui/AGENTS.md
|
||||
CONFLICT (add/add): Merge conflict in ui/AGENTS.md
|
||||
CONFLICT (modify/delete): ui/bun.lock deleted in upstream/main and modified in HEAD. Version HEAD of ui/bun.lock left in tree.
|
||||
Auto-merging ui/package.json
|
||||
CONFLICT (content): Merge conflict in ui/package.json
|
||||
Auto-merging ui/src/client.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/client.tsx
|
||||
CONFLICT (modify/delete): ui/src/components/agents/PlanReview.tsx deleted in upstream/main and modified in HEAD. Version HEAD of ui/src/components/agents/PlanReview.tsx left in tree.
|
||||
Auto-merging ui/src/features/agents/components/AgentGitPanel.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/components/AgentGitPanel.tsx
|
||||
Auto-merging ui/src/features/agents/components/AgentThreadView.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/components/AgentThreadView.tsx
|
||||
Auto-merging ui/src/features/agents/components/AgentsSidebar.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/components/AgentsSidebar.tsx
|
||||
CONFLICT (file location): ui/src/components/agents/PrHeader.tsx added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/components/PrHeader.tsx.
|
||||
CONFLICT (file location): ui/src/components/agents/SidebarFilterMenu.tsx added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/components/SidebarFilterMenu.tsx.
|
||||
Auto-merging ui/src/features/agents/components/SidebarFilterMenu.tsx
|
||||
CONFLICT (add/add): Merge conflict in ui/src/features/agents/components/SidebarFilterMenu.tsx
|
||||
CONFLICT (file location): ui/src/components/agents/WorkflowApprovalCard.tsx added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/components/WorkflowApprovalCard.tsx.
|
||||
Auto-merging ui/src/features/agents/components/WorkflowApprovalCard.tsx
|
||||
CONFLICT (add/add): Merge conflict in ui/src/features/agents/components/WorkflowApprovalCard.tsx
|
||||
Auto-merging ui/src/features/agents/components/messages/AgentMessage.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/components/messages/AgentMessage.tsx
|
||||
CONFLICT (file location): ui/src/components/agents/messages/MessageTimestamp.tsx added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/components/messages/MessageTimestamp.tsx.
|
||||
Auto-merging ui/src/features/agents/components/messages/Messages.tsx
|
||||
Auto-merging ui/src/features/agents/components/messages/UserMessage.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/components/messages/UserMessage.tsx
|
||||
Auto-merging ui/src/features/agents/components/messages/types.ts
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/components/messages/types.ts
|
||||
Auto-merging ui/src/features/agents/lib/api.ts
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/lib/api.ts
|
||||
CONFLICT (file location): ui/src/lib/agents/messageTimestamps.ts added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/lib/messageTimestamps.ts.
|
||||
Auto-merging ui/src/features/agents/lib/queries.ts
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/lib/queries.ts
|
||||
CONFLICT (file location): ui/src/lib/agents/sidebarFilter.test.ts added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/lib/sidebarFilter.test.ts.
|
||||
CONFLICT (file location): ui/src/lib/agents/sidebarFilter.ts added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/lib/sidebarFilter.ts.
|
||||
CONFLICT (file location): ui/src/lib/agents/sidebarPrefs.ts added in HEAD inside a directory that was renamed in upstream/main, suggesting it should perhaps be moved to ui/src/features/agents/lib/sidebarPrefs.ts.
|
||||
Auto-merging ui/src/features/agents/lib/sidebarPrefs.ts
|
||||
CONFLICT (add/add): Merge conflict in ui/src/features/agents/lib/sidebarPrefs.ts
|
||||
Auto-merging ui/src/features/agents/lib/streamMessagesToUi.ts
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/lib/streamMessagesToUi.ts
|
||||
Auto-merging ui/src/features/agents/lib/types.ts
|
||||
CONFLICT (content): Merge conflict in ui/src/features/agents/lib/types.ts
|
||||
Auto-merging ui/src/features/automations/components/AutomationEditor.tsx
|
||||
Auto-merging ui/src/features/reviews/components/ReviewMainBody.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/features/reviews/components/ReviewMainBody.tsx
|
||||
Auto-merging ui/src/features/reviews/components/ReviewSidebar.tsx
|
||||
CONFLICT (modify/delete): ui/src/lib/agents/provider/useSubmitAgentMessage.ts deleted in upstream/main and modified in HEAD. Version HEAD of ui/src/lib/agents/provider/useSubmitAgentMessage.ts left in tree.
|
||||
Auto-merging ui/src/lib/api.ts
|
||||
Auto-merging ui/src/lib/auth-redirect-core.ts
|
||||
CONFLICT (add/add): Merge conflict in ui/src/lib/auth-redirect-core.ts
|
||||
Auto-merging ui/src/lib/auth-redirect.test.ts
|
||||
CONFLICT (add/add): Merge conflict in ui/src/lib/auth-redirect.test.ts
|
||||
Auto-merging ui/src/routes/admin.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/routes/admin.tsx
|
||||
Auto-merging ui/src/routes/agents.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/routes/agents.tsx
|
||||
Auto-merging ui/src/routes/agents/$threadId_.plan.tsx
|
||||
CONFLICT (content): Merge conflict in ui/src/routes/agents/$threadId_.plan.tsx
|
||||
Auto-merging ui/src/routes/agents/reviews/$owner.$repo.$number.tsx
|
||||
Auto-merging ui/src/routes/cloud-agents.tsx
|
||||
Auto-merging ui/vercel.json
|
||||
CONFLICT (content): Merge conflict in ui/vercel.json
|
||||
Auto-merging ui/vite.config.ts
|
||||
CONFLICT (content): Merge conflict in ui/vite.config.ts
|
||||
Auto-merging uv.lock
|
||||
CONFLICT (content): Merge conflict in uv.lock
|
||||
298
docs/upstream-sync/domain-reorg/movemap-m50.txt
Normal file
298
docs/upstream-sync/domain-reorg/movemap-m50.txt
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
M AGENTS.md
|
||||
M CLAUDE.md
|
||||
M README.md
|
||||
M agent/analyzer.py
|
||||
A agent/api/__init__.py
|
||||
A agent/api/app.py
|
||||
A agent/api/health.py
|
||||
M agent/chat.py
|
||||
M agent/dashboard/agent_usage.py
|
||||
M agent/dashboard/eval_jobs.py
|
||||
M agent/dashboard/review_api.py
|
||||
M agent/dashboard/review_chat_api.py
|
||||
M agent/dashboard/review_style_jobs.py
|
||||
A agent/graphs/__init__.py
|
||||
A agent/graphs/agent.py
|
||||
A agent/graphs/analyzer.py
|
||||
A agent/graphs/chat.py
|
||||
A agent/graphs/reviewer.py
|
||||
A agent/graphs/scheduler.py
|
||||
M agent/middleware/settle_review_check.py
|
||||
M agent/prompt.py
|
||||
A agent/providers/__init__.py
|
||||
A agent/resources/__init__.py
|
||||
R100 default_prompt.md agent/resources/default_prompt.md
|
||||
A agent/review/__init__.py
|
||||
R098 agent/reviewer_diff.py agent/review/diff.py
|
||||
R100 agent/reviewer_eval_store.py agent/review/eval_store.py
|
||||
R100 agent/reviewer_findings.py agent/review/findings.py
|
||||
R098 agent/reviewer_groups.py agent/review/groups.py
|
||||
R099 agent/reviewer_publish.py agent/review/publish.py
|
||||
R099 agent/reviewer_reconcile.py agent/review/reconcile.py
|
||||
R100 agent/review_style_collector.py agent/review/style_collector.py
|
||||
R100 agent/review_style_guidance.py agent/review/style_guidance.py
|
||||
R098 agent/reviewer_trace_context.py agent/review/trace_context.py
|
||||
M agent/reviewer.py
|
||||
A agent/runtime/__init__.py
|
||||
A agent/runtime/constants.py
|
||||
A agent/runtime/execution.py
|
||||
A agent/runtime/sandbox.py
|
||||
M agent/server.py
|
||||
M agent/tools/add_finding.py
|
||||
M agent/tools/list_findings.py
|
||||
M agent/tools/list_review_findings.py
|
||||
M agent/tools/publish_review.py
|
||||
M agent/tools/reply_to_finding_thread.py
|
||||
M agent/tools/request_pr_review.py
|
||||
M agent/tools/resolve_finding_thread.py
|
||||
M agent/tools/update_finding.py
|
||||
M agent/utils/github_feedback.py
|
||||
M agent/utils/github_org_membership.py
|
||||
M agent/webapp.py
|
||||
A agent/webhooks/common.py
|
||||
M agent/webhooks/github.py
|
||||
A agent/webhooks/github_routes.py
|
||||
M agent/webhooks/linear.py
|
||||
A agent/webhooks/linear_routes.py
|
||||
M agent/webhooks/slack.py
|
||||
A agent/webhooks/slack_routes.py
|
||||
R100 static/dark.svg assets/dark.svg
|
||||
R100 static/light.svg assets/light.svg
|
||||
R099 CUSTOMIZATION.md docs/CUSTOMIZATION.md
|
||||
R100 INSTALLATION.md docs/INSTALLATION.md
|
||||
M evals/reviewer/judge.py
|
||||
M evals/reviewer/run_eval.py
|
||||
M evals/reviewer/store_reporter.py
|
||||
M evals/reviewer/target.py
|
||||
M langgraph.json
|
||||
R100 tests/test_agent_assembly_context.py tests/agent/test_agent_assembly_context.py
|
||||
R100 tests/test_agent_instructions.py tests/agent/test_agent_instructions.py
|
||||
R100 tests/test_agent_schedules.py tests/agent/test_agent_schedules.py
|
||||
R068 tests/test_agent_thread_pr_state.py tests/agent/test_agent_thread_pr_state.py
|
||||
R100 tests/test_agent_usage.py tests/agent/test_agent_usage.py
|
||||
R100 tests/test_agents_md.py tests/agent/test_agents_md.py
|
||||
R100 tests/test_api_standards_skill.py tests/agent/test_api_standards_skill.py
|
||||
R100 tests/test_dispatch.py tests/agent/test_dispatch.py
|
||||
R100 tests/test_import_hygiene.py tests/agent/test_import_hygiene.py
|
||||
R100 tests/test_langsmith_trace_url.py tests/agent/test_langsmith_trace_url.py
|
||||
R100 tests/test_multimodal.py tests/agent/test_multimodal.py
|
||||
R100 tests/test_plan_mode.py tests/agent/test_plan_mode.py
|
||||
R100 tests/test_plan_review.py tests/agent/test_plan_review.py
|
||||
R100 tests/test_prompt_default_repo.py tests/agent/test_prompt_default_repo.py
|
||||
R100 tests/test_repo_prep.py tests/agent/test_repo_prep.py
|
||||
R100 tests/test_task_retry.py tests/agent/test_task_retry.py
|
||||
R100 tests/test_timeout_wrapup.py tests/agent/test_timeout_wrapup.py
|
||||
R100 tests/test_workflow_push_guard.py tests/agent/test_workflow_push_guard.py
|
||||
R100 tests/test_analyzer_cron.py tests/analyzer/test_analyzer_cron.py
|
||||
R100 tests/test_analyzer_skills.py tests/analyzer/test_analyzer_skills.py
|
||||
R100 tests/test_eval_jobs.py tests/analyzer/test_eval_jobs.py
|
||||
R100 tests/test_eval_store_reporter.py tests/analyzer/test_eval_store_reporter.py
|
||||
R100 tests/test_auth_sources.py tests/auth/test_auth_sources.py
|
||||
R100 tests/test_authorship.py tests/auth/test_authorship.py
|
||||
R100 tests/test_encryption.py tests/auth/test_encryption.py
|
||||
R100 tests/test_github_oauth_refresh.py tests/auth/test_github_oauth_refresh.py
|
||||
R093 tests/test_github_token_ttl.py tests/auth/test_github_token_ttl.py
|
||||
R100 tests/test_notion_oauth.py tests/auth/test_notion_oauth.py
|
||||
R100 tests/test_slack_oauth.py tests/auth/test_slack_oauth.py
|
||||
R100 tests/test_user_credentials.py tests/auth/test_user_credentials.py
|
||||
M tests/conftest.py
|
||||
R073 tests/test_account_link.py tests/dashboard/test_account_link.py
|
||||
R100 tests/test_autofix_state.py tests/dashboard/test_autofix_state.py
|
||||
R100 tests/test_dashboard_admin.py tests/dashboard/test_dashboard_admin.py
|
||||
R100 tests/test_dashboard_csrf.py tests/dashboard/test_dashboard_csrf.py
|
||||
R100 tests/test_dashboard_links.py tests/dashboard/test_dashboard_links.py
|
||||
R100 tests/test_dashboard_oauth_redirect.py tests/dashboard/test_dashboard_oauth_redirect.py
|
||||
R100 tests/test_dashboard_org_login_gate.py tests/dashboard/test_dashboard_org_login_gate.py
|
||||
R100 tests/test_dashboard_repo_optional.py tests/dashboard/test_dashboard_repo_optional.py
|
||||
R100 tests/test_dashboard_repos.py tests/dashboard/test_dashboard_repos.py
|
||||
R098 tests/test_dashboard_reviews.py tests/dashboard/test_dashboard_reviews.py
|
||||
R100 tests/test_dashboard_run_email.py tests/dashboard/test_dashboard_run_email.py
|
||||
R100 tests/test_dashboard_thread_api.py tests/dashboard/test_dashboard_thread_api.py
|
||||
R100 tests/test_dashboard_thread_api_activity.py tests/dashboard/test_dashboard_thread_api_activity.py
|
||||
R100 tests/test_dashboard_web_handoff.py tests/dashboard/test_dashboard_web_handoff.py
|
||||
R089 tests/test_public_repo_org_gate.py tests/dashboard/test_public_repo_org_gate.py
|
||||
R100 tests/test_team_credentials.py tests/dashboard/test_team_credentials.py
|
||||
R100 tests/test_team_settings_fable.py tests/dashboard/test_team_settings_fable.py
|
||||
R100 tests/test_team_settings_grouping.py tests/dashboard/test_team_settings_grouping.py
|
||||
R100 tests/test_team_settings_org_guidelines.py tests/dashboard/test_team_settings_org_guidelines.py
|
||||
R100 tests/test_user_mappings.py tests/dashboard/test_user_mappings.py
|
||||
R100 tests/test_workflow_approval_api.py tests/dashboard/test_workflow_approval_api.py
|
||||
M tests/e2e/harness.py
|
||||
R100 tests/test_github_app.py tests/github/test_github_app.py
|
||||
R099 tests/test_github_checks.py tests/github/test_github_checks.py
|
||||
R100 tests/test_github_ci.py tests/github/test_github_ci.py
|
||||
R099 tests/test_github_comment_prompts.py tests/github/test_github_comment_prompts.py
|
||||
R095 tests/test_github_feedback.py tests/github/test_github_feedback.py
|
||||
R100 tests/test_github_http.py tests/github/test_github_http.py
|
||||
R078 tests/test_github_issue_webhook.py tests/github/test_github_issue_webhook.py
|
||||
R100 tests/test_github_proxy_refresh.py tests/github/test_github_proxy_refresh.py
|
||||
R100 tests/test_normalize_repo.py tests/github/test_normalize_repo.py
|
||||
R100 tests/test_open_pull_request.py tests/github/test_open_pull_request.py
|
||||
R100 tests/test_pr_creation_guard.py tests/github/test_pr_creation_guard.py
|
||||
R091 tests/test_repo_extraction.py tests/github/test_repo_extraction.py
|
||||
R100 tests/test_check_message_queue.py tests/middleware/test_check_message_queue.py
|
||||
R100 tests/test_ensure_no_empty_msg.py tests/middleware/test_ensure_no_empty_msg.py
|
||||
R100 tests/test_model_fallback_middleware.py tests/middleware/test_model_fallback_middleware.py
|
||||
R100 tests/test_notify_step_limit_middleware.py tests/middleware/test_notify_step_limit_middleware.py
|
||||
R100 tests/test_prepare_run_middleware.py tests/middleware/test_prepare_run_middleware.py
|
||||
R100 tests/test_repair_orphaned_tool_calls.py tests/middleware/test_repair_orphaned_tool_calls.py
|
||||
R100 tests/test_sanitize_fireworks_messages.py tests/middleware/test_sanitize_fireworks_messages.py
|
||||
R100 tests/test_sanitize_openai_responses.py tests/middleware/test_sanitize_openai_responses.py
|
||||
R100 tests/test_sanitize_thinking_blocks.py tests/middleware/test_sanitize_thinking_blocks.py
|
||||
R100 tests/test_sanitize_tool_inputs.py tests/middleware/test_sanitize_tool_inputs.py
|
||||
R100 tests/test_subdir_agents_middleware.py tests/middleware/test_subdir_agents_middleware.py
|
||||
R100 tests/test_tool_artifact_middleware.py tests/middleware/test_tool_artifact_middleware.py
|
||||
R100 tests/test_agent_subagent_models.py tests/models/test_agent_subagent_models.py
|
||||
R100 tests/test_anthropic_effort.py tests/models/test_anthropic_effort.py
|
||||
R100 tests/test_anthropic_model.py tests/models/test_anthropic_model.py
|
||||
R100 tests/test_deferred_model.py tests/models/test_deferred_model.py
|
||||
R100 tests/test_fireworks_model.py tests/models/test_fireworks_model.py
|
||||
R100 tests/test_google_model.py tests/models/test_google_model.py
|
||||
R100 tests/test_model_fallback_resolution.py tests/models/test_model_fallback_resolution.py
|
||||
R062 tests/test_pr_ready_auto_review.py tests/reviewer/test_pr_ready_auto_review.py
|
||||
R100 tests/test_recent_comments.py tests/reviewer/test_recent_comments.py
|
||||
R100 tests/test_reconcile_sweep.py tests/reviewer/test_reconcile_sweep.py
|
||||
R099 tests/test_review_api.py tests/reviewer/test_review_api.py
|
||||
R100 tests/test_review_chat.py tests/reviewer/test_review_chat.py
|
||||
R094 tests/test_review_style_collector.py tests/reviewer/test_review_style_collector.py
|
||||
R100 tests/test_review_style_sync.py tests/reviewer/test_review_style_sync.py
|
||||
R100 tests/test_review_styles_store.py tests/reviewer/test_review_styles_store.py
|
||||
R100 tests/test_reviewer.py tests/reviewer/test_reviewer.py
|
||||
R096 tests/test_reviewer_diff.py tests/reviewer/test_reviewer_diff.py
|
||||
R100 tests/test_reviewer_eval_judge.py tests/reviewer/test_reviewer_eval_judge.py
|
||||
R100 tests/test_reviewer_eval_run.py tests/reviewer/test_reviewer_eval_run.py
|
||||
R099 tests/test_reviewer_eval_target.py tests/reviewer/test_reviewer_eval_target.py
|
||||
R088 tests/test_reviewer_findings.py tests/reviewer/test_reviewer_findings.py
|
||||
R092 tests/test_reviewer_groups.py tests/reviewer/test_reviewer_groups.py
|
||||
R100 tests/test_reviewer_outcomes.py tests/reviewer/test_reviewer_outcomes.py
|
||||
R098 tests/test_reviewer_publish.py tests/reviewer/test_reviewer_publish.py
|
||||
R087 tests/test_reviewer_reconcile.py tests/reviewer/test_reviewer_reconcile.py
|
||||
R099 tests/test_reviewer_tools.py tests/reviewer/test_reviewer_tools.py
|
||||
R096 tests/test_reviewer_trace_context.py tests/reviewer/test_reviewer_trace_context.py
|
||||
R064 tests/test_reviewer_watch.py tests/reviewer/test_reviewer_watch.py
|
||||
R098 tests/test_daytona_integration.py tests/sandbox/test_daytona_integration.py
|
||||
R098 tests/test_e2b_integration.py tests/sandbox/test_e2b_integration.py
|
||||
R100 tests/test_gateway.py tests/sandbox/test_gateway.py
|
||||
R100 tests/test_langsmith_sandbox_config.py tests/sandbox/test_langsmith_sandbox_config.py
|
||||
R100 tests/test_langsmith_sandbox_timeout.py tests/sandbox/test_langsmith_sandbox_timeout.py
|
||||
R100 tests/test_local_integration.py tests/sandbox/test_local_integration.py
|
||||
R100 tests/test_proxy_auth.py tests/sandbox/test_proxy_auth.py
|
||||
R100 tests/test_repo_snapshots.py tests/sandbox/test_repo_snapshots.py
|
||||
R100 tests/test_sandbox_paths.py tests/sandbox/test_sandbox_paths.py
|
||||
R100 tests/middleware/test_sandbox_recovery.py tests/sandbox/test_sandbox_recovery.py
|
||||
R100 tests/test_sandbox_state.py tests/sandbox/test_sandbox_state.py
|
||||
R100 tests/test_stale_sandbox_creating.py tests/sandbox/test_stale_sandbox_creating.py
|
||||
R100 tests/test_refresh_slack_status_middleware.py tests/slack/test_refresh_slack_status_middleware.py
|
||||
R100 tests/test_slack_add_reaction_tool.py tests/slack/test_slack_add_reaction_tool.py
|
||||
R100 tests/test_slack_assistants_status.py tests/slack/test_slack_assistants_status.py
|
||||
R082 tests/test_slack_context.py tests/slack/test_slack_context.py
|
||||
R090 tests/test_slack_feedback.py tests/slack/test_slack_feedback.py
|
||||
R100 tests/test_slack_start_new_thread_tool.py tests/slack/test_slack_start_new_thread_tool.py
|
||||
R100 tests/test_slack_thread_reply_tool.py tests/slack/test_slack_thread_reply_tool.py
|
||||
R082 tests/test_slack_webhook_errors.py tests/slack/test_slack_webhook_errors.py
|
||||
R100 tests/test_corridor_mcp.py tests/tools/test_corridor_mcp.py
|
||||
R100 tests/test_currents_tools.py tests/tools/test_currents_tools.py
|
||||
R100 tests/test_http_security.py tests/tools/test_http_security.py
|
||||
R100 tests/test_observability_tools.py tests/tools/test_observability_tools.py
|
||||
R100 tests/test_report_platform_issue_tool.py tests/tools/test_report_platform_issue_tool.py
|
||||
R100 tests/test_schedule_thread_wakeup.py tests/tools/test_schedule_thread_wakeup.py
|
||||
R100 tests/test_stagehand_browser.py tests/tools/test_stagehand_browser.py
|
||||
R100 tests/test_completion_webhook.py tests/webhooks/test_completion_webhook.py
|
||||
R091 tests/test_linear_webhook_author.py tests/webhooks/test_linear_webhook_author.py
|
||||
M ui/eslint.config.js
|
||||
D ui/src/components/agents/AgentPromptBar.tsx
|
||||
R097 ui/src/components/agents/AgentGitPanel.tsx ui/src/features/agents/components/AgentGitPanel.tsx
|
||||
A ui/src/features/agents/components/AgentPromptBar.tsx
|
||||
R098 ui/src/components/agents/AgentRunCard.tsx ui/src/features/agents/components/AgentRunCard.tsx
|
||||
R092 ui/src/components/agents/AgentThreadView.tsx ui/src/features/agents/components/AgentThreadView.tsx
|
||||
R089 ui/src/components/agents/AgentsHome.tsx ui/src/features/agents/components/AgentsHome.tsx
|
||||
R098 ui/src/components/agents/AgentsSidebar.tsx ui/src/features/agents/components/AgentsSidebar.tsx
|
||||
R098 ui/src/components/agents/AgentsThreadsPage.tsx ui/src/features/agents/components/AgentsThreadsPage.tsx
|
||||
R100 ui/src/components/agents/OnboardingDialog.tsx ui/src/features/agents/components/OnboardingDialog.tsx
|
||||
R099 ui/src/components/agents/PlanReview.tsx ui/src/features/agents/components/PlanReview.tsx
|
||||
R097 ui/src/components/agents/SidebarFilterMenu.tsx ui/src/features/agents/components/SidebarFilterMenu.tsx
|
||||
R098 ui/src/components/agents/WorkflowApprovalCard.tsx ui/src/features/agents/components/WorkflowApprovalCard.tsx
|
||||
R097 ui/src/components/agents/ported/CloudPromptBar.tsx ui/src/features/agents/components/chat/CloudPromptBar.tsx
|
||||
R100 ui/src/components/agents/ported/CodeBlock.tsx ui/src/features/agents/components/chat/CodeBlock.tsx
|
||||
R088 ui/src/components/agents/ported/DiffView.tsx ui/src/features/agents/components/chat/DiffView.tsx
|
||||
R100 ui/src/components/agents/ported/Logo.tsx ui/src/features/agents/components/chat/Logo.tsx
|
||||
R100 ui/src/components/agents/ported/Markdown.tsx ui/src/features/agents/components/chat/Markdown.tsx
|
||||
R098 ui/src/components/agents/ported/ReplyCard.tsx ui/src/features/agents/components/chat/ReplyCard.tsx
|
||||
R098 ui/src/components/agents/ported/ShellCommand.tsx ui/src/features/agents/components/chat/ShellCommand.tsx
|
||||
R097 ui/src/components/agents/ported/ToolExecution.tsx ui/src/features/agents/components/chat/ToolExecution.tsx
|
||||
R096 ui/src/components/agents/messages/AgentMessage.tsx ui/src/features/agents/components/messages/AgentMessage.tsx
|
||||
R083 ui/src/components/agents/messages/ChunkRenderer.tsx ui/src/features/agents/components/messages/ChunkRenderer.tsx
|
||||
R100 ui/src/components/agents/messages/MessageTimestamp.tsx ui/src/features/agents/components/messages/MessageTimestamp.tsx
|
||||
R098 ui/src/components/agents/messages/Messages.tsx ui/src/features/agents/components/messages/Messages.tsx
|
||||
R100 ui/src/components/agents/messages/ReasoningBlock.tsx ui/src/features/agents/components/messages/ReasoningBlock.tsx
|
||||
R100 ui/src/components/agents/messages/ThinkingSpinner.tsx ui/src/features/agents/components/messages/ThinkingSpinner.tsx
|
||||
R097 ui/src/components/agents/messages/TurnChangedFilesCard.tsx ui/src/features/agents/components/messages/TurnChangedFilesCard.tsx
|
||||
R097 ui/src/components/agents/messages/UserMessage.tsx ui/src/features/agents/components/messages/UserMessage.tsx
|
||||
R100 ui/src/components/agents/messages/WorkSummary.tsx ui/src/features/agents/components/messages/WorkSummary.tsx
|
||||
R100 ui/src/components/agents/messages/index.ts ui/src/features/agents/components/messages/index.ts
|
||||
R098 ui/src/components/agents/messages/renderItems.ts ui/src/features/agents/components/messages/renderItems.ts
|
||||
R092 ui/src/components/agents/messages/summarizeChangedFiles.ts ui/src/features/agents/components/messages/summarizeChangedFiles.ts
|
||||
R094 ui/src/components/agents/messages/types.ts ui/src/features/agents/components/messages/types.ts
|
||||
R100 ui/src/components/agents/subagents/SubagentActivity.tsx ui/src/features/agents/components/subagents/SubagentActivity.tsx
|
||||
R092 ui/src/components/agents/subagents/SubagentCard.tsx ui/src/features/agents/components/subagents/SubagentCard.tsx
|
||||
R091 ui/src/components/agents/subagents/SubagentGroup.tsx ui/src/features/agents/components/subagents/SubagentGroup.tsx
|
||||
R100 ui/src/components/agents/subagents/index.ts ui/src/features/agents/components/subagents/index.ts
|
||||
R100 ui/src/components/agents/z-index.ts ui/src/features/agents/components/z-index.ts
|
||||
R100 ui/src/components/agents/ported/BranchSelector.tsx ui/src/features/agents/experiments/BranchSelector.tsx
|
||||
R089 ui/src/components/agents/ported/BubblePrefix.tsx ui/src/features/agents/experiments/BubblePrefix.tsx
|
||||
R099 ui/src/components/agents/ported/ChatView.tsx ui/src/features/agents/experiments/ChatView.tsx
|
||||
R100 ui/src/components/agents/ported/CompactingIndicator.tsx ui/src/features/agents/experiments/CompactingIndicator.tsx
|
||||
R100 ui/src/components/agents/ported/ContextIndicator.tsx ui/src/features/agents/experiments/ContextIndicator.tsx
|
||||
R100 ui/src/components/agents/ported/Footer.tsx ui/src/features/agents/experiments/Footer.tsx
|
||||
R100 ui/src/components/agents/ported/HeaderBar.tsx ui/src/features/agents/experiments/HeaderBar.tsx
|
||||
R100 ui/src/components/agents/ported/MarkdownTable.tsx ui/src/features/agents/experiments/MarkdownTable.tsx
|
||||
R100 ui/src/components/agents/ported/PanelResizeHandle.tsx ui/src/features/agents/experiments/PanelResizeHandle.tsx
|
||||
R099 ui/src/components/agents/ported/PromptBar.tsx ui/src/features/agents/experiments/PromptBar.tsx
|
||||
R100 ui/src/components/agents/ported/SourceControlPanel.tsx ui/src/features/agents/experiments/SourceControlPanel.tsx
|
||||
R099 ui/src/components/agents/ported/SourceControlTile.tsx ui/src/features/agents/experiments/SourceControlTile.tsx
|
||||
R099 ui/src/components/agents/ported/ThreadPicker.tsx ui/src/features/agents/experiments/ThreadPicker.tsx
|
||||
R098 ui/src/components/agents/ported/TodoList.tsx ui/src/features/agents/experiments/TodoList.tsx
|
||||
R093 ui/src/components/agents/ported/index.ts ui/src/features/agents/experiments/index.ts
|
||||
R100 ui/src/lib/agents/AgentThreadStreamProvider.tsx ui/src/features/agents/lib/AgentThreadStreamProvider.tsx
|
||||
R100 ui/src/lib/agents/api.ts ui/src/features/agents/lib/api.ts
|
||||
R100 ui/src/lib/agents/messageTimestamps.ts ui/src/features/agents/lib/messageTimestamps.ts
|
||||
R100 ui/src/lib/agents/provider/useIsInAgentThreadStream.tsx ui/src/features/agents/lib/provider/useIsInAgentThreadStream.tsx
|
||||
R096 ui/src/lib/agents/provider/useLiveMarkdownMessageId.ts ui/src/features/agents/lib/provider/useLiveMarkdownMessageId.ts
|
||||
R100 ui/src/lib/agents/provider/useModelOptions.ts ui/src/features/agents/lib/provider/useModelOptions.ts
|
||||
R095 ui/src/lib/agents/provider/useSubmitAgentMessage.ts ui/src/features/agents/lib/provider/useSubmitAgentMessage.ts
|
||||
R100 ui/src/lib/agents/queries.ts ui/src/features/agents/lib/queries.ts
|
||||
R100 ui/src/lib/agents/sidebarFilter.test.ts ui/src/features/agents/lib/sidebarFilter.test.ts
|
||||
R100 ui/src/lib/agents/sidebarFilter.ts ui/src/features/agents/lib/sidebarFilter.ts
|
||||
R096 ui/src/lib/agents/sidebarPrefs.ts ui/src/features/agents/lib/sidebarPrefs.ts
|
||||
R100 ui/src/lib/agents/streamMessagesToUi.ts ui/src/features/agents/lib/streamMessagesToUi.ts
|
||||
R100 ui/src/lib/agents/types.ts ui/src/features/agents/lib/types.ts
|
||||
R095 ui/src/lib/agents/useRunCompletionNotifier.ts ui/src/features/agents/lib/useRunCompletionNotifier.ts
|
||||
R100 ui/src/components/agents/utils/diffStats.ts ui/src/features/agents/utils/diffStats.ts
|
||||
R100 ui/src/components/agents/utils/diffUtils.test.ts ui/src/features/agents/utils/diffUtils.test.ts
|
||||
R100 ui/src/components/agents/utils/diffUtils.ts ui/src/features/agents/utils/diffUtils.ts
|
||||
R095 ui/src/components/agents/AutomationEditor.tsx ui/src/features/automations/components/AutomationEditor.tsx
|
||||
R091 ui/src/components/agents/AutomationTemplates.tsx ui/src/features/automations/components/AutomationTemplates.tsx
|
||||
R096 ui/src/components/agents/AutomationsList.tsx ui/src/features/automations/components/AutomationsList.tsx
|
||||
R097 ui/src/components/agents/ScheduleTriggerPicker.tsx ui/src/features/automations/components/ScheduleTriggerPicker.tsx
|
||||
R100 ui/src/lib/agents/automation-templates.ts ui/src/features/automations/lib/automation-templates.ts
|
||||
R100 ui/src/lib/agents/cron.ts ui/src/features/automations/lib/cron.ts
|
||||
R100 ui/src/components/agents/PrHeader.tsx ui/src/features/reviews/components/PrHeader.tsx
|
||||
R099 ui/src/components/agents/ReviewChat.tsx ui/src/features/reviews/components/ReviewChat.tsx
|
||||
R100 ui/src/components/agents/ReviewCommentsMenu.tsx ui/src/features/reviews/components/ReviewCommentsMenu.tsx
|
||||
R099 ui/src/components/agents/ReviewMainBody.tsx ui/src/features/reviews/components/ReviewMainBody.tsx
|
||||
R099 ui/src/components/agents/ReviewSidebar.tsx ui/src/features/reviews/components/ReviewSidebar.tsx
|
||||
R093 ui/src/components/agents/ReviewTab.tsx ui/src/features/reviews/components/ReviewTab.tsx
|
||||
R100 ui/src/components/agents/RepoSelector.tsx ui/src/features/settings/components/RepoSelector.tsx
|
||||
M ui/src/lib/notifications.ts
|
||||
M ui/src/routes/agents.tsx
|
||||
M ui/src/routes/agents/$threadId.tsx
|
||||
M ui/src/routes/agents/$threadId_.plan.tsx
|
||||
M ui/src/routes/agents/automations/$scheduleId.tsx
|
||||
M ui/src/routes/agents/automations/index.tsx
|
||||
M ui/src/routes/agents/automations/new.tsx
|
||||
M ui/src/routes/agents/index.tsx
|
||||
M ui/src/routes/agents/reviews/$owner.$repo.$number.tsx
|
||||
M ui/src/routes/agents/threads.tsx
|
||||
M ui/src/routes/cloud-agents.tsx
|
||||
M ui/tsconfig.json
|
||||
M ui/vite.config.ts
|
||||
66
docs/upstream-sync/domain-reorg/reorg-build-plan.md
Normal file
66
docs/upstream-sync/domain-reorg/reorg-build-plan.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Build plan — adopt upstream domain reorg `8356eb34` (#1726) into the Sea Haven open-swe fork
|
||||
|
||||
Date: 2026-07-17. Base: `dev` (clean, `a518a129`). Scoping report: `reorg-scoping-report.md` (same directory: `docs/upstream-sync/domain-reorg/`).
|
||||
|
||||
## Goal & strategy
|
||||
|
||||
Adopt upstream's 298-file domain reorg as a **file-move exercise applied to the fork's tree**: fork file contents, upstream layout. Never take upstream file contents except the 21 verified thin structural "A" shims (`agent/graphs/*`, `agent/runtime/*`, `agent/api/*`, `agent/webhooks/{common,*_routes}.py` skeletons, package `__init__.py`s). Rationale: the fork has settled permanent divergences (sync sandbox lifecycle + 4-case `__creating__` sentinel, workflow-token security model, bun toolchain, Atlassian integration, plan-mode #130, TID-COLLIDE-01 guard) — adoption is layout convergence, not content convergence.
|
||||
|
||||
Payoff: kills the per-pick path-remap tax on all future upstream cherry-picks; 8 of 19 deferred ledger rows become clean/near-clean, including priority security pick #1736 (written against `agent/webhooks/common.py`, which only exists after this exercise).
|
||||
|
||||
## Approved decisions (Adam, 2026-07-17)
|
||||
|
||||
1. **FastAPI/webapp split: APPROVED.** Fork's 2,590-line `agent/webapp.py` splits into `agent/webhooks/common.py` (shared verify/dispatch helpers), `agent/api/{app,health}.py`, and per-source `{github,linear,slack,jira,confluence}_routes.py`; `webapp.py` remains a compatibility shim. `/sh-security-review` required on that commit before merge.
|
||||
2. **Atlassian placement (re-confirmed by Adam 2026-07-17):** fork-only `jira_routes.py` + `confluence_routes.py` mirroring upstream's per-source pattern; **Atlassian Connect lifecycle/descriptor routes (`/connect/*`) fold into `confluence_routes.py`** (Connect is the Confluence trigger; Jira uses Automation webhooks). JWT/qsh machinery stays in `agent/utils/atlassian_connect.py` (unmoved). Split out a `connect_routes.py` later only if Jira migrates to Connect.
|
||||
3. `langgraph.json` graph entrypoints retarget to `agent.graphs.*` shims; add fork-only `agent/graphs/ci_monitor.py` shim for symmetry. `http.app` stays `agent.webapp:app`.
|
||||
4. Reviewer modules → `agent/review/` package (9 pure moves; fork reviewer content rides along).
|
||||
5. CI-autofix cluster (upstream-deleted): keep-baseline; wire its webhook triggers through the split `github.py`/`common.py`; tests → `tests/github/`.
|
||||
6. UI `ported/` → `features/agents/experiments/` + single tsconfig/eslint exclude glob; bun stays; delete stray `ui/pnpm-workspace.yaml`.
|
||||
7. Sequencing: reorg lands **before** #1736.
|
||||
|
||||
## Hard rules (from fork-maintenance playbook + scoping hazards)
|
||||
|
||||
- **Content-source discipline:** upstream content only for the 21 "A" shims. `agent/webhooks/{github,linear,slack}.py`, `tests/conftest.py`, `tests/e2e/harness.py` exist on both sides with different content — always fork content + replicated import rewires; never `git checkout upstream` on them.
|
||||
- Handlers keep **module-attribute access style** (`common.X`, `service.X`) exactly as upstream did, so the 240 `monkeypatch.setattr(webapp, ...)` test sites retarget cleanly and no re-import rebind hazard is introduced.
|
||||
- Every moved test keeps its impl-side pairing (impls mostly don't move; 13 fork-only tests get the placements from scoping §2c: Atlassian webhook tests → `tests/webhooks/`, `test_atlassian_connect.py` → `tests/auth/`, client-util tests → `tests/tools/`, `test_repo_binding_isolation.py` → `tests/sandbox/`, `test_auth_error_leak.py` → `tests/auth/`, `test_app_bot_identity.py`/autofix tests → `tests/github/`).
|
||||
- Verify wheel packaging ships `agent/resources/default_prompt.md` (hatchling `packages=["agent"]` should carry it — build and inspect the wheel in C1; add explicit include only if missing).
|
||||
- No secrets/config placement changes anywhere in this exercise (no new env vars, no Secrets Manager/SSM changes).
|
||||
- CI ladder per commit: `ruff check` + `ruff format --check` → `pytest --co -q` → full unit → E2E (Playwright + real LangGraph dev server) where the commit touches wiring; `bunx tsc --noEmit` + bun build for UI commits. `make dev` boot-check for C3 (all graphs + FastAPI app load).
|
||||
|
||||
## Jira linkage
|
||||
|
||||
No Jira issue tracks this work; per handbook `git-workflow.md`, the branch omits the issue key and uses a plain description. (If Adam cuts a ticket, rename branch to `refactor/<KEY>-domain-reorg-adoption` before the PR opens.)
|
||||
|
||||
## Commit sequence (branch `refactor/domain-reorg-adoption` off dev; one PR; **merge-commit merge ONLY** — squash and rebase are explicitly forbidden for this PR to preserve per-cascade bisectability; dev ruleset allows merge commits)
|
||||
|
||||
- **C1 — docs/resources/assets** (~1h): `git mv` `INSTALLATION.md`/`CUSTOMIZATION.md` → `docs/`, `static/` → `assets/` (fix README refs), `default_prompt.md` → `agent/resources/` + apply the `importlib.resources` loader hunk to fork's `prompt.py` + `__init__.py`. Gate: ruff + unit + **wheel build inspection**.
|
||||
- **C2 — `agent/review/` package** (~half day): mv 9 reviewer/style modules per map, rewrite 38 importer files, `agent/review/__init__.py` with fork's export surface. Gate: ruff + `pytest --co` + reviewer unit suite.
|
||||
- **C3 — graphs/runtime/providers shims + `langgraph.json` + CI hardening** (~2h): adopt the 21 upstream A-shims (verify each delegates to fork modules), add `agent/graphs/ci_monitor.py`, retarget `langgraph.json`; **pin `bun-version` in `.github/workflows/ci.yml`** (removes the setup-bun latest-resolution flake documented in project memory). Gate: `pytest --co` + `make dev` boots all graphs (incl. `ci_monitor`) + FastAPI app.
|
||||
- **C4 — FastAPI split** (~1–1.5 days, critical): split fork `webapp.py` per approved decisions 1–2; sed handler modules `webapp.` → `common.`; retarget the 240 monkeypatch sites, `tests/conftest.py`, `tests/e2e/harness.py`; keep `webapp.py` shim. Gate: full unit + **full E2E** (the only layer catching wiring drift) + **residual-importer sweep**: `git grep -l 'agent\.webapp\|from agent import webapp'` across the whole tree must return only the shim and intentional compat references, so the shim cannot silently mask missed rewires. **`/sh-security-review` on this commit's diff. Failure path: findings are addressed as NEW commits on top of the branch (e.g. "C4a: address security-review findings") — no history rewrite or force-push once any review (security or PR) has started, preserving the review/CI trail; the PR must not merge until the review passes — no exceptions.**
|
||||
- **HARD GATE:** C5 must not begin until C4 has full unit + E2E green **and** `/sh-security-review` passed. (C1–C3 may proceed independently before C4.)
|
||||
- **C5 — `tests/<domain>/` moves** (~half day): mechanical map moves + remaining R<100 monkeypatch retargets + the 13 fork-only placements. Gate: `pytest --co` + full unit.
|
||||
- **C6 — UI `features/` moves** (~half day–1 day): `git mv` per map incl. `ported/` → `experiments|chat`, rewrite `@/` imports (54 importers + moved files), exclude-glob swap, AgentPromptBar shim retarget, delete `ui/pnpm-workspace.yaml`. Gate: `bunx tsc --noEmit` + bun build + Playwright E2E.
|
||||
- **C7 — docs + ledger + memory** (~2h): update fork `CLAUDE.md`/`README.md`/`AGENTS.md` path references (CLAUDE.md architecture sections name `agent/webapp.py` and old test paths); flip `8356eb34` → Landed in `triage.jsonl` + re-triage notes on the 8 unblocked rows + `make triage-render`. **Documentation/memory obligations (owned by the C7 executor, i.e. Claude in this conversation):** (a) update the `open-swe-upstream-triage-status` and project memory entries **including a summary of the new module layout** (`agent/{graphs,runtime,api,review,resources,webhooks/*_routes}`, `tests/<domain>/`, `ui/src/features/`); (b) perform the Confluence check — search IT-space pages for any repo module-map documentation; update it if found, and **record the outcome either way ("updated page <id>" or "no Confluence change required — no IT page documents the module map") in the PR description and in project memory**. The AWS Architecture Map is unaffected (no AWS resource changes). **Fallback per standing policy: if Confluence is inaccessible, state so explicitly in the PR description and project memory — the documentation update is then outstanding, not silently skipped.** C7's doc/ledger changes ship inside the PR, so they are covered by the PR review; memory-file updates are summarized in the PR description for the same visibility.
|
||||
|
||||
## Merge & deployment
|
||||
|
||||
- PR from `refactor/domain-reorg-adoption` → `dev`; CI green (all layers) → `@openswe` review (or Adam-delegated review) → **merge commit only** (no squash/rebase). PR review scope must explicitly name the C2 reviewer-module moves (review-automation surface) and the C4 auth-surface split so the reviewer examines both.
|
||||
- `/sh-security-review` must have run on C4 with confirmed critical/highs resolved (or machine-suppressed with justification) before merge — see C4 failure path. GPT-4.1 cross-family review: not strictly triggered (no IAM/policy or Lambda-signature change — `publish_review`-style tool contracts untouched; `langgraph.json` strings are deployment manifest, not IAM), but run as **opt-in on the C4 diff** given webhook-verification code movement.
|
||||
- **Post-deploy verification checklist (explicit, before declaring done):** LangGraph deployment redeploys with the retargeted `langgraph.json`; all graphs (agent, reviewer, analyzer, chat, scheduler, ci_monitor) registered; `/health` OK; `GET /connect/atlassian-connect.json` serves the descriptor; one webhook round-trip per source (GitHub PR comment, Slack mention, Linear, Jira Automation test-fire, Confluence comment) where feasible. Note: external registrations (GitHub App webhook URL, Slack event URL, Jira Automation rule, Connect descriptor URL) are verified UNCHANGED by this exercise — the HTTP surface and all routes keep their paths — so no external system updates are required; the checklist confirms this empirically rather than assuming it.
|
||||
- **Rollback:** revert the merge commit restores the pre-reorg layout wholesale; because the HTTP surface and external registrations are unchanged, a rollback requires NO external-system reverts — re-verify with the same checklist after any rollback. C1–C3 independently revertible pre-merge; C5/C6 path-only.
|
||||
- **Post-merge cleanup (git-workflow convention):** delete `refactor/domain-reorg-adoption` on origin after merge; delete local copies and prune worktrees.
|
||||
- **Verification records:** post-deploy checklist results are recorded in the PR description (or a comment) as the traceability artifact — **responsibility: the C7 executor (Claude in this conversation)**. After any rollback, normal operation does not resume until the same checklist fully passes.
|
||||
- **Confluence-outstanding follow-up:** if Confluence is inaccessible at merge time, the outstanding update is recorded in project memory with an explicit "outstanding as of <date>" marker so it surfaces at the next session on this project — it does not defer indefinitely.
|
||||
- **Severity clarification for the C4 failure path:** fixes are stacked as new commits regardless of severity, full stop. If the security review reveals a *fundamental design flaw* in the split (not a fixable finding), the response is not a history rewrite — the PR is closed, the plan returns to the drafting stage, and a revised plan goes back through `/sh-plan-review` on a fresh branch.
|
||||
- **Shim lifetime:** `webapp.py` (and the `agent/graphs/*` shims) mirror upstream's own permanent compatibility shims — they are kept for as long as upstream keeps theirs, since layout parity is the goal; revisit only if upstream removes them.
|
||||
- **bun-version pin:** permanent policy (version bumped deliberately like any dependency), not a temporary workaround — resolves the setup-bun latest-resolution flake class permanently.
|
||||
|
||||
## Post-merge follow-ups (separate work, not this PR)
|
||||
|
||||
1. Pick #1736 (GitHub token binding) — near-clean post-split; still gated by its ledger row: GPT-4.1 cross-family review + `/sh-security-review`.
|
||||
2. Re-triage the 5 now-clean picks (#1732, #1761, #1744, #1748, #1742) — cheap batch.
|
||||
3. Update `sh-openswe` deployment notes/memory if the redeploy surfaces anything.
|
||||
|
||||
## Rollback story
|
||||
|
||||
Single revert of the merge commit restores pre-reorg layout wholesale. Before merge: each cascade commit is independently droppable except C4→C5 ordering (C5 assumes post-split monkeypatch targets). Working artifacts for the build agents: `docs/upstream-sync/domain-reorg/{movemap-m50.txt, cross.json, forkonly.json}`.
|
||||
163
docs/upstream-sync/domain-reorg/reorg-scoping-report.md
Normal file
163
docs/upstream-sync/domain-reorg/reorg-scoping-report.md
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# Scoping report — adopting upstream domain reorg `8356eb34` (#1726) as a file-move exercise
|
||||
|
||||
Date: 2026-07-17. Repo: `/Users/adammoussa/Documents/repositories/seahaven/open-swe` (branch `dev`, clean).
|
||||
Strategy scoped: **fork file contents, upstream layout** — replay the reorg's *moves* onto the fork's own tree; never take upstream file contents.
|
||||
Merge-base with `upstream/main`: `f6c215ff`. Reorg commit sits 91 upstream commits past the merge-base.
|
||||
Current truthful conflict surface (`git merge-tree --write-tree --name-only HEAD upstream/main`): **140 conflicted paths** (`docs/upstream-sync/domain-reorg/merge-tree-now.txt`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Move-map (`git diff-tree -r -M50 --name-status 8356eb34^ 8356eb34`)
|
||||
|
||||
298 entries total (raw file: `docs/upstream-sync/domain-reorg/movemap-m50.txt`):
|
||||
|
||||
| class | count | meaning |
|
||||
|---|---|---|
|
||||
| **R100** pure renames | **150** | byte-identical moves (mostly `tests/*` → `tests/<domain>/`, `ui/src/{components,lib}/agents/*` → `ui/src/features/*`, `agent/reviewer_*`/`review_style_*` → `agent/review/`) |
|
||||
| **R<100** rename+edit | **77** | scores R062–R099; **every sampled edit is an import-path / monkeypatch-target rewrite only** (see §Spot-checks) |
|
||||
| **M** in-place edits | **49** | shim-ification (`webapp.py` 2007-line reduction, `server.py` constant extraction), import rewires in `agent/tools/*`, `agent/webhooks/*`, `evals/`, `ui/src/routes/*`, config files |
|
||||
| **A** true adds | **21** | all thin structure: `agent/graphs/{agent,analyzer,chat,reviewer,scheduler}.py` (re-export shims), `agent/runtime/{constants,execution,sandbox}.py` (constants + delegating wrappers around `agent.server`), `agent/api/{app,health}.py`, `agent/webhooks/{common,github_routes,linear_routes,slack_routes}.py`, `agent/{providers,resources,review,runtime,graphs,api}/__init__.py` |
|
||||
| **D** true deletes | **1** | `ui/src/components/agents/AgentPromptBar.tsx` — a 2-line re-export shim, re-added at `ui/src/features/agents/components/AgentPromptBar.tsx` with the path retargeted (A/D pair is cosmetic, not a content loss) |
|
||||
|
||||
Content-consolidations (real redistribution rather than move): effectively **one** — the FastAPI layer.
|
||||
`agent/webapp.py` (upstream pre-reorg ~2k lines) is split into `agent/webhooks/common.py` (shared dispatch/verify helpers), `agent/api/app.py` (composition), `agent/api/health.py` (`/health` + `/webhooks/run-complete`), and per-source `*_routes.py`; `webapp.py` becomes a 4-line compatibility shim (`from .api.app import app`). Secondary micro-extractions: `server.py` → `runtime/constants.py` (`DEFAULT_LLM_MODEL_ID`, `DEFAULT_LLM_MAX_TOKENS`, `DEFAULT_RECURSION_LIMIT`, `MODEL_CALL_RECURSION_LIMIT`) and `runtime/execution.py` (`graph_loaded_for_execution`); `prompt.py` → `default_prompt.md` loaded via `importlib.resources` from `agent/resources/`.
|
||||
The "consolidate reviewer modules" sub-commit is **not** a content merge — it is nine 1:1 module moves into `agent/review/` (R098–R100) plus import rewires.
|
||||
|
||||
`langgraph.json` **does change**: graph entrypoints retarget from `agent.server:traced_agent` / `agent.reviewer:...` / `agent.analyzer:...` / `agent.chat:...` / `agent.scheduler:...` to `agent.graphs.<name>:<same symbol>`. The targets are pure re-export shims (e.g. `agent/graphs/agent.py` = `from agent.server import get_agent, traced_agent`), so *behavior* is unchanged, but the deployment manifest strings are not. `http.app` stays `agent.webapp:app` (via the compat shim). See §Hazards (d).
|
||||
|
||||
---
|
||||
|
||||
## 2. Collision set (move-map × fork divergence)
|
||||
|
||||
Fork divergence since merge-base: 322 paths (130 A / 188 M / 3 D / 1 R; raw: `docs/upstream-sync/domain-reorg/fork-divergence-status.txt`). Fork-added files split against upstream trees: **67 are shared** (cherry-picked, exist upstream pre-reorg → move-map applies) and **64 are TRUE fork-only** (exist nowhere upstream).
|
||||
|
||||
Headline classification:
|
||||
|
||||
| bucket | count |
|
||||
|---|---|
|
||||
| MECHANICAL — fork-touched, R100 pure rename → `git mv` fork's version | **57** |
|
||||
| MECHANICAL-trivial — moved by reorg, fork never touched → `git mv` (identical either way) | **127** |
|
||||
| HAND-CARRY (light) — fork-touched, R<100 → `git mv` + replicate upstream's import-rewrite pattern on fork content | **34** |
|
||||
| HAND-CARRY (real) — fork-modified AND reorg-modified-in-place (M×M) | **34** (≈10 substantive, rest are 1–3-line import seds) |
|
||||
| FORK-ONLY placement decisions | **64** files, of which **13 test files** need explicit new homes; nearly all fork-only *source* stays put (reorg does not move `agent/tools/`, `agent/utils/`, `agent/middleware/`, `agent/integrations/`, `agent/dashboard/`, `agent/webhooks/` handler modules) |
|
||||
| moved upstream but absent from fork (added in the 91 interim commits — no-op for us) | 9 |
|
||||
|
||||
### 2a. MECHANICAL (fork-touched, pure rename — the bulk)
|
||||
`git mv` fork's file to the new path; no content change. 57 files:
|
||||
|
||||
- Root/docs/resources: `INSTALLATION.md → docs/INSTALLATION.md`, `default_prompt.md → agent/resources/default_prompt.md` (pairs with the `prompt.py` loader hunk — see HAND-CARRY), `agent/reviewer_findings.py → agent/review/findings.py`.
|
||||
- Tests (46): the full `tests/test_*.py → tests/<domain>/test_*.py` set, e.g. `tests/test_dispatch.py → tests/agent/`, `tests/test_workflow_push_guard.py → tests/agent/`, `tests/test_plan_mode.py`, `tests/test_plan_review.py → tests/agent/`, `tests/test_auth_sources.py`, `tests/test_authorship.py`, `tests/test_encryption*→ tests/auth/`, `tests/test_github_app.py`, `tests/test_open_pull_request.py`, `tests/test_pr_creation_guard.py → tests/github/`, `tests/test_gateway.py`, `tests/test_proxy_auth.py`, `tests/test_local_integration.py → tests/sandbox/`, sanitize/middleware tests → `tests/middleware/`, model tests → `tests/models/`, Slack tool tests → `tests/slack/`, dashboard/team-settings tests → `tests/dashboard/`, `tests/test_completion_webhook.py → tests/webhooks/`, etc. (full list in `docs/upstream-sync/domain-reorg/cross.json`, key `mech`).
|
||||
- UI (10): `ui/src/lib/agents/{api,queries,types,streamMessagesToUi,sidebarFilter,messageTimestamps}.ts → ui/src/features/agents/lib/`, `ui/src/components/agents/PrHeader.tsx → ui/src/features/reviews/components/`, `MessageTimestamp.tsx`, `diffUtils.ts`.
|
||||
|
||||
Plus the 127 fork-untouched moved files (same treatment, zero divergence risk).
|
||||
|
||||
### 2b. HAND-CARRY
|
||||
|
||||
**(i) R<100 moved files fork also touched — 34 files.** Upstream's edits are exclusively path rewires; replicate the same rewires on the fork's content after `git mv`:
|
||||
- Python: `agent/reviewer_publish.py → agent/review/publish.py` (R099: `from .reviewer_findings import` → `from .findings import`, `from .utils.X` → `from ..utils.X`), same for `reviewer_reconcile.py`, `reviewer_trace_context.py`; reviewer test files R062–R099 retarget `webapp` → `webhook_common` / `github_webhooks` monkeypatch modules; `tests/test_e2b_integration.py → tests/sandbox/` (fork re-implemented E2B sync — keep fork content, fix imports only).
|
||||
- UI: 20 `.tsx/.ts` files where the only edits are `@/components/agents/...` → `@/features/agents/components/...` and `@/lib/agents/...` → `@/features/agents/lib/...` (verified zero non-import edits on the worst-looking case, `AgentThreadView.tsx` R092). Fork-diverged files in this set that must keep fork content: `PlanReview.tsx` (plan-mode #130), `WorkflowApprovalCard.tsx`, `AgentsSidebar.tsx`, `SidebarFilterMenu.tsx`, `AgentGitPanel.tsx`, `AutomationEditor.tsx`.
|
||||
|
||||
**(ii) M×M in-place overlap — 34 files, of which the substantive ten:**
|
||||
|
||||
| file | upstream's reorg edit | fork action |
|
||||
|---|---|---|
|
||||
| `agent/webapp.py` (fork: **2,590 lines**) | gutted to 4-line shim; content → `webhooks/common.py` + `api/app.py` + `api/health.py` + `*_routes.py` | **the big one** — split the fork's monolith the same way, carrying fork-only Jira/Confluence/Connect routes (see §3) |
|
||||
| `agent/server.py` | extract 4 constants → `runtime/constants.py`, `graph_loaded_for_execution` → `runtime/execution.py`; adds public aliases `get_cached_sandbox_backend` etc. | replicate extraction on fork content; fork's sync sandbox lifecycle stays in `server.py`/`utils/sandbox_state.py` untouched — `runtime/sandbox.py` shim delegates to `agent.server` (upstream's own shim does exactly this, so it wraps fork code cleanly) |
|
||||
| `agent/prompt.py` | `DEFAULT_PROMPT_PATH` env-only + `importlib.resources` load of `agent/resources/default_prompt.md` | apply this one hunk by hand to fork's heavily customized `prompt.py`; **verify the wheel packages the .md** (upstream did *not* touch `pyproject.toml`; `packages=["agent"]` under hatchling should carry it — build and check) |
|
||||
| `agent/reviewer.py` / `agent/analyzer.py` / `agent/chat.py` | import rewires to `agent.review.*` / small | sed-level on fork content |
|
||||
| `agent/webhooks/{github,linear,slack}.py` | `from agent import webapp` → `from . import common`; `webapp.X` → `common.X` (module-attribute style preserved so monkeypatching keeps working) | **caution:** these exist on *both* sides with different content (fork built its own split earlier). No `git mv` applies — sed the fork's files; never adopt upstream's |
|
||||
| `tests/conftest.py` | `from agent import webapp` → `from agent.webhooks import common as webhook_common` (auto-review fixture) | same sed on fork's conftest |
|
||||
| `tests/e2e/harness.py` | `from agent.webapp import app, generate_thread_id_from_slack_thread` → `from agent.api.app import app` + `from agent.utils.thread_ids import ...` | fork already has `agent/utils/thread_ids.py` — same retarget |
|
||||
| `ui/tsconfig.json` / `ui/eslint.config.js` | replace the 8-file `ported/` exclude lists with `src/features/agents/experiments/**` | adopt the glob (simplification, no behavior change); `ui/vite.config.ts` upstream edit is cosmetic churn on the fork-only mock-harness plugin — skip |
|
||||
| `langgraph.json` | entrypoints → `agent.graphs.*` | retarget fork's file **and add `ci_monitor": "agent.graphs.ci_monitor:get_ci_monitor"` shim** for the fork-only graph (upstream deleted its ci-autofix cluster; fork keeps it) |
|
||||
| `AGENTS.md`/`CLAUDE.md`/`README.md` | doc-path updates | fork-rewritten docs — update path references manually (CLAUDE.md architecture sections name `agent/webapp.py`, tool/test paths) |
|
||||
|
||||
The remaining ~24 M×M files (`agent/tools/add_finding.py` and the other reviewer tools, `agent/dashboard/{eval_jobs,review_api,review_chat_api}.py`, `evals/reviewer/*`, `ui/src/routes/*.tsx`, `agent/utils/github_org_membership.py`, `agent/middleware/settle_review_check.py`) take 1–3-line import seds (`..reviewer_findings` → `..review.findings`, `@/components/agents` → `@/features/...`).
|
||||
|
||||
**Blast-radius counts (fork tree):** 38 Python files import `reviewer_*`/`review_style_*` modules; 12 Python files import `agent.webapp`; **240 `monkeypatch.setattr(webapp, ...)` sites across 11 test files** must retarget to `webhook_common`/handler modules; 54 UI files import `@/components/agents` or `@/lib/agents`.
|
||||
|
||||
### 2c. FORK-ONLY placement proposals (64 true fork-only files)
|
||||
|
||||
Upstream's domain logic: tools stay flat in `agent/tools/`; util clients in `agent/utils/`; webhook *handlers* in `agent/webhooks/<source>.py` with HTTP routes in `agent/webhooks/<source>_routes.py`; tests in `tests/<domain>/`.
|
||||
|
||||
**Source — stays put (no move needed):**
|
||||
- Atlassian tools `agent/tools/{jira_*,confluence_*}.py` (11 files) → stay (`agent/tools/` unmoved by reorg).
|
||||
- `agent/utils/{jira,confluence,adf,atlassian_connect,jira_project_repo_map,confluence_space_repo_map}.py` → stay.
|
||||
- Fork middleware, `agent/integrations/e2b.py`, dashboard additions, `.githooks/`, `.github/`, `scripts/`, `docs/upstream-sync/`, `docs/repo-conventions/`, `deploy/` → stay.
|
||||
|
||||
**Source — new homes created by the split (part of the webapp-split commit):**
|
||||
- `agent/webhooks/jira.py`, `agent/webhooks/confluence.py` → stay as handler modules; **add fork-only `agent/webhooks/jira_routes.py` and `agent/webhooks/confluence_routes.py`** mirroring upstream's `github_routes.py` pattern (routes access helpers via `common.X`).
|
||||
- Atlassian Connect lifecycle/descriptor routes (`/connect/*`, `GET /connect/atlassian-connect.json`) → propose `agent/webhooks/confluence_routes.py` (they are Confluence-trigger plumbing) or a dedicated `agent/webhooks/connect_routes.py` if cleaner; register in fork's `agent/api/app.py`.
|
||||
|
||||
**Tests — 13 fork-only/uncovered files:**
|
||||
|
||||
| file | proposed home |
|
||||
|---|---|
|
||||
| `tests/test_jira_webhook_{author,corroboration,replay}.py`, `tests/test_confluence_webhook.py`, `tests/test_linear_webhook_replay.py` | `tests/webhooks/` |
|
||||
| `tests/test_atlassian_connect.py` | `tests/auth/` (JWT/qsh verification surface; alternative `tests/webhooks/`) |
|
||||
| `tests/test_jira_utils.py`, `tests/test_confluence_utils.py` | `tests/tools/` (client layer backing the Jira/Confluence tools) |
|
||||
| `tests/test_repo_binding_isolation.py` (TID-COLLIDE-01 guard) | `tests/sandbox/` (it exercises the sandbox/GitHub-token per-thread caches; alternative `tests/webhooks/`) |
|
||||
| `tests/test_auth_error_leak.py` | `tests/auth/` |
|
||||
| `tests/test_app_bot_identity.py` | `tests/github/` (bot authorship/PR attribution) |
|
||||
| `tests/test_ci_autofix.py`, `tests/test_autofix_webhook.py` | `tests/github/` (upstream deleted the impl cluster; fork keeps `agent/ci_autofix.py`, `agent/ci_monitor.py` in place) |
|
||||
|
||||
UI fork-only: `ui/src/components/PwaUpdateToast.tsx` → `ui/src/components/` stays (not an agents-feature file); `ui/src/lib/auth-redirect*.{ts,tsx}` → stay in `ui/src/lib/` (upstream kept non-agents lib files there); `ui/src/routes/$owner.$repo.pull.$number.tsx` → stays (`routes/` not moved). `tests/e2e/**` (fork harness + specs) → stays; upstream kept `tests/e2e/` in place.
|
||||
|
||||
---
|
||||
|
||||
## 3. Consolidations / decision list for Adam
|
||||
|
||||
1. **FastAPI layer split (the only true consolidation) — FLAG-HUMAN, auth surface.** Fork's 2,590-line `agent/webapp.py` carries fork-only webhook auth: `verify_jira_secret` (+ optional HMAC/timestamp), Atlassian Connect JWT/qsh handling, the GitHub/Slack/Linear signature paths, token-attribution gating, TID-COLLIDE-01 thread-binding. Splitting it into `webhooks/common.py` + `api/` + `*_routes.py` moves signature-verification and dispatch code across files. Recommendation: **adopt the split** (it is behavior-preserving by construction — upstream kept module-attribute access precisely so monkeypatched tests stay valid, and the fork's webhook tests are the safety net), but treat the split commit as a sensitive change: **/sh-security-review required** (auth/webhook surface), and per the fork playbook surface the resolution choice to you before merge. No GPT-4.1 cross-family review strictly triggered (no IAM/Lambda-signature change), but the split of webhook verification code is a reasonable opt-in candidate.
|
||||
2. **`langgraph.json` entrypoint retarget** — deployment-manifest change (graphs → `agent.graphs.*` shims; `http.app` unchanged). Verify the LangGraph deployment redeploys cleanly; decide whether fork-only `ci_monitor` gets a `agent/graphs/ci_monitor.py` shim (recommended, for symmetry) or keeps its old path (inconsistent).
|
||||
3. **Reviewer consolidation** → `agent/review/` package: adopt. It is 9 pure moves + import rewires; fork's reviewer customizations (#1713 reconciliation, findings model, publish flow) ride along untouched. No content decision needed.
|
||||
4. **CI-autofix cluster** (upstream deleted `agent/ci_autofix.py`; fork keeps it): confirm keep-baseline, and approve its post-split wiring point (its webhook triggers live in the split `github.py`/`common.py`) + test placement (`tests/github/`).
|
||||
5. **UI `experiments/` adoption**: upstream renames `ported/` → `features/agents/experiments/` + `chat/` and swaps the 8-file tsconfig/eslint exclude lists for one glob. Adopt (pure simplification). Fork-diverged `DiffView.tsx`, `CloudPromptBar.tsx`, etc. keep fork content.
|
||||
6. **Atlassian placements** (§2c): approve `jira_routes.py`/`confluence_routes.py` naming and where Connect lifecycle routes land — this is fork-only auth surface; your call on `connect_routes.py` vs folding into `confluence_routes.py`.
|
||||
7. **Sequencing vs #1736 (bind cached GitHub tokens to users — priority security pick)**: recommend adopting the reorg **first**, because #1736 is written against `agent/webhooks/common.py`, which only exists after the split; it then lands near-clean (still gated by cross-review + /sh-security-review per its ledger row).
|
||||
|
||||
## 4. Hazards check (CLAUDE.md playbook)
|
||||
|
||||
- **(a) Thin-router re-import rebinds:** post-reorg upstream is *clean* — `git grep 'from \.webhooks\.' 8356eb34 -- agent/webapp.py agent/api agent/webhooks/common.py` returns nothing; routes access handlers via `from . import github as service` + `service.X`/`common.X` module-attribute style, so the rebind hazard the playbook documents is designed out. Residual risk only if a hand-carry file drifts toward upstream *content*: `agent/webhooks/{github,linear,slack}.py` exist on both sides with different content (fork versions carry the fixes) — taking upstream's file here silently drops fork behavior. Same for `tests/conftest.py` and `tests/e2e/harness.py`. Rule for the exercise: **upstream content may only be taken for the 21 "A" structural shims**, everything else moves fork content.
|
||||
- **(b) Paired tests move with impls:** the map's test moves are 1:1 with unmoved impls (impl modules stay importable at old or shimmed paths), so no vertical splits arise; the 13 uncovered fork tests get the placements in §2c. Keep `tests/test_e2b_integration.py` (fork's sync E2B) as fork content when moving to `tests/sandbox/`. `tests/middleware/test_sandbox_recovery.py` → `tests/sandbox/` (fork has `tests/middleware/`? — it exists only as the upstream pre-path; fork's copy sits wherever it was cherry-picked; include in the mechanical sweep).
|
||||
- **(c) Tooling path references:** verified —
|
||||
- `Makefile`: only generic `tests/` (`TEST_FILE ?= tests/`) — unaffected; `make triage-render/-check` scripts don't reference moved paths.
|
||||
- `pyproject.toml`: `packages = ["agent"]`, `testpaths = ["tests"]` — both recursive, unaffected; **verify wheel includes `agent/resources/default_prompt.md`** (only real packaging check).
|
||||
- `.github/workflows/ci.yml`: references `tests/e2e/**` (unmoved) and `ui/` bun checks — unaffected; `reviewer-eval.yml` → `evals/` (unmoved).
|
||||
- `langgraph.json` — changes (see decision 2); fork-only `tests/e2e/langgraph.e2e.json` uses its own `agent_entrypoint.py` (imports `agent.server:traced_agent`, which remains valid) — no change needed.
|
||||
- UI: `@` alias root (`src/`) unchanged; `vite.config.ts`/`tsconfig.json`/`eslint.config.js` need only the exclude-glob swap; **bun stays** — do not import upstream's pnpm bits (fork oddly carries a `ui/pnpm-workspace.yaml`; consider deleting it in this exercise as lockfile hygiene, per playbook rule 7).
|
||||
- `scripts/`, `.githooks/`: no moved-path references found.
|
||||
- **(d) Public entrypoints:** upstream's claim "without changing its public entrypoints" is true **behaviorally, false textually** — `langgraph.json` graph strings all change to `agent.graphs.*` (verified by diff). HTTP surface (`agent.webapp:app`) and all webhook URLs unchanged. Fork must mirror the graph-string change plus its `ci_monitor` extra.
|
||||
|
||||
## 5. Payoff — the 20 deferred ledger rows (docs/upstream-sync/triage.md)
|
||||
|
||||
Of the 19 deferred rows besides `8356eb34` itself:
|
||||
|
||||
- **Clean picks after adoption (path remap was the blocker): 5** — #1732 dashboard label rendering, #1761 capitalize labels, #1744 collapse git panel, #1748 Linear issue search (test-path remap), #1742 defensive copy.
|
||||
- **Near-clean after adoption (small content deltas or review gates remain): 3** — **#1736 GitHub token binding** (biggest beneficiary: written against `webhooks/common.py`; auth gates still apply), #1758 sandbox-create JSON env, #1760 separate LangSmith key/endpoint (both additive to diverged `integrations/langsmith.py` — small reconciles).
|
||||
- **Still content reconciliation for fork-divergence reasons: 9** — #1724 (Slack NL plan approvals), #1731 (OpenAI Responses history vs #155 middleware), #1734 (Slack DMs), #1737 (reviewer diff bounds), #1741 (SSRF image fetches), #1733 (todos default-off vs fork prompt), #1735 (untagged two-party Slack), #1747 (Slack run links), #1719 (web-tool offload vs sync sandbox lifecycle). Adoption still removes their test/UI path-remap tax.
|
||||
- **Not path-related (product/deps decisions): 2** — #1727 (GPT-5.5 default; rides the no-OpenAI-models decision), #1745 (deepagents 0.7.0a7 alpha validation).
|
||||
|
||||
Net: **8 of 19 rows become clean/near-clean**, and *every future upstream commit* stops needing path remaps — the standing tax on the cherry-pick pipeline disappears (recent won't-merge rows #1759/#1769 cited the layout mismatch explicitly).
|
||||
|
||||
## 6. Execution plan sketch (one commit per cascade class; validate ruff → `pytest --co` → unit → e2e per layer)
|
||||
|
||||
Work on a throwaway branch off `dev`; promote as one PR with granular commits.
|
||||
|
||||
1. **C1 — docs/resources/assets** (~1h): `git mv` `INSTALLATION.md`/`CUSTOMIZATION.md` → `docs/`, `static/` → `assets/` (check README badge/image refs), `default_prompt.md` → `agent/resources/` + apply the `prompt.py` importlib.resources hunk + `agent/resources/__init__.py`; build wheel, confirm the .md ships. Gate: ruff + unit.
|
||||
2. **C2 — `agent/review/` package** (~half day): mv 9 reviewer/style modules, rewrite the 38 importer files, add `agent/review/__init__.py` (fork's export surface). Gate: ruff + `pytest --co` + reviewer unit tests.
|
||||
3. **C3 — graphs/runtime/providers shims + `langgraph.json`** (~2h): take upstream's 21 structural A-files nearly verbatim (they delegate to `agent.server` etc., which is fork code), add fork-only `agent/graphs/ci_monitor.py`, retarget `langgraph.json`. Gate: `pytest --co` + `make dev` boots all graphs. **Adam sign-off: decision 2.**
|
||||
4. **C4 — FastAPI split** (~1–1.5 days, the critical commit): split fork's `webapp.py` → `webhooks/common.py` + `api/{app,health}.py` + `{github,linear,slack,jira,confluence}_routes.py` (+ Connect routes per decision 6); sed handlers `webapp.` → `common.`; retarget 240 test monkeypatch sites + `tests/conftest.py` + `tests/e2e/harness.py`; keep `webapp.py` shim. Gate: full unit + **E2E (Playwright + real LangGraph dev server)** — the only layer that catches wiring drift. **Adam sign-offs: decisions 1, 4, 6; /sh-security-review on this commit.**
|
||||
5. **C5 — tests/<domain>/ moves** (~half day): mechanical map moves (§2a) + R<100 monkeypatch retargets not already done in C4 + the 13 fork-only placements (§2c). Gate: `pytest --co` + full unit (paths only, no logic).
|
||||
6. **C6 — UI features/ moves** (~half day–1 day): `git mv` per map incl. `ported/` → `experiments|chat`, rewrite `@/` imports in the 54 importer files + moved files, tsconfig/eslint exclude glob, AgentPromptBar shim retarget, delete `ui/pnpm-workspace.yaml` (lockfile hygiene). Gate: `bunx tsc --noEmit` + bun build + Playwright e2e. **Adam sign-off: decision 5.**
|
||||
7. **C7 — docs + ledger + memory** (~2h): update fork `CLAUDE.md`/`README`/`AGENTS.md` path references, flip `8356eb34` to Landed in `triage.jsonl` (+ re-triage notes on the 8 unblocked rows), update the open-swe project memory, Confluence architecture page if module map is documented there.
|
||||
|
||||
**Effort estimate: 3–5 focused days** end-to-end, dominated by C4. Rollback story is clean: each commit is independently revertable until C4; after C4, C5/C6 are path-only.
|
||||
|
||||
## Spot-checks validating the classification method
|
||||
|
||||
1. **Mechanical** — `tests/test_dispatch.py → tests/agent/test_dispatch.py` listed `R100` in the diff-tree output (byte-identical move); fork's diverged copy moves via `git mv` untouched.
|
||||
2. **Hand-carry (R<100)** — `agent/reviewer_publish.py → agent/review/publish.py` (R099): upstream's entire edit is the import block (`from .reviewer_findings import` → `from .findings import`, `from .utils.github_http import` → `from ..utils.github_http import`); no logic lines. Worst-scoring test `tests/test_pr_ready_auto_review.py` (R062): every hunk is `monkeypatch.setattr(webapp, ...)` → `monkeypatch.setattr(webhook_common, ...)` / `webapp.process_github_pr_ready` → `github_webhooks.process_github_pr_ready`. Worst-scoring fork-diverged UI file `AgentThreadView.tsx` (R092): grep of the rename diff shows 0 non-import changed lines.
|
||||
3. **Consolidation** — post-reorg `agent/webapp.py` is exactly `from .api.app import app` (4 lines, verified via `git show 8356eb34:agent/webapp.py`); `agent/webhooks/github.py`'s reorg diff swaps `from agent import webapp` for `from . import common` and rewrites `webapp.X` → `common.X` with the docstring explicitly noting module-attribute access is preserved for monkeypatching.
|
||||
4. **Test pairing** — `tests/conftest.py` reorg diff retargets the autouse auto-review fixture from `agent.webapp` to `agent.webhooks.common`, confirming test fixtures follow the split, not the moves.
|
||||
|
||||
Working artifacts: `docs/upstream-sync/domain-reorg/{movemap-m50.txt, fork-divergence-status.txt, cross.json, forkonly.json, merge-tree-now.txt, head-tree.txt, upstream-pre-tree.txt, upstream-main-tree.txt}`.
|
||||
568
docs/upstream-sync/domain-reorg/upstream-main-tree.txt
Normal file
568
docs/upstream-sync/domain-reorg/upstream-main-tree.txt
Normal file
|
|
@ -0,0 +1,568 @@
|
|||
.codespellignore
|
||||
.dockerignore
|
||||
.github/dependabot.yml
|
||||
.github/workflows/ci.yml
|
||||
.github/workflows/pr_lint.yml
|
||||
.github/workflows/promote_main_to_prod.yml
|
||||
.github/workflows/reviewer_eval.yml
|
||||
.gitignore
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
Dockerfile
|
||||
LICENSE
|
||||
Makefile
|
||||
README.md
|
||||
SECURITY.md
|
||||
agent/analyzer.py
|
||||
agent/api/__init__.py
|
||||
agent/api/app.py
|
||||
agent/api/health.py
|
||||
agent/chat.py
|
||||
agent/completion.py
|
||||
agent/dashboard/__init__.py
|
||||
agent/dashboard/admin.py
|
||||
agent/dashboard/agent_instructions.py
|
||||
agent/dashboard/agent_overrides.py
|
||||
agent/dashboard/agent_usage.py
|
||||
agent/dashboard/analyzer_cron.py
|
||||
agent/dashboard/autofix_state.py
|
||||
agent/dashboard/enabled_repos.py
|
||||
agent/dashboard/eval_jobs.py
|
||||
agent/dashboard/notion_oauth.py
|
||||
agent/dashboard/oauth.py
|
||||
agent/dashboard/options.py
|
||||
agent/dashboard/plan_api.py
|
||||
agent/dashboard/plan_store.py
|
||||
agent/dashboard/pr_diff.py
|
||||
agent/dashboard/profiles.py
|
||||
agent/dashboard/repo_access.py
|
||||
agent/dashboard/repo_snapshots.py
|
||||
agent/dashboard/review_api.py
|
||||
agent/dashboard/review_chat_api.py
|
||||
agent/dashboard/review_style_jobs.py
|
||||
agent/dashboard/review_styles.py
|
||||
agent/dashboard/routes.py
|
||||
agent/dashboard/schedules.py
|
||||
agent/dashboard/slack_oauth.py
|
||||
agent/dashboard/team_credentials.py
|
||||
agent/dashboard/team_settings.py
|
||||
agent/dashboard/thread_api.py
|
||||
agent/dashboard/user_credentials.py
|
||||
agent/dashboard/user_mappings.py
|
||||
agent/dashboard/workflow_approval.py
|
||||
agent/dashboard/workflow_approval_api.py
|
||||
agent/dispatch.py
|
||||
agent/encryption.py
|
||||
agent/graphs/__init__.py
|
||||
agent/graphs/agent.py
|
||||
agent/graphs/analyzer.py
|
||||
agent/graphs/chat.py
|
||||
agent/graphs/reviewer.py
|
||||
agent/graphs/scheduler.py
|
||||
agent/integrations/__init__.py
|
||||
agent/integrations/corridor_mcp.py
|
||||
agent/integrations/currents_tools.py
|
||||
agent/integrations/datadog_mcp.py
|
||||
agent/integrations/daytona.py
|
||||
agent/integrations/e2b.py
|
||||
agent/integrations/langsmith.py
|
||||
agent/integrations/langsmith_tools.py
|
||||
agent/integrations/local.py
|
||||
agent/integrations/modal.py
|
||||
agent/integrations/notion_mcp.py
|
||||
agent/integrations/runloop.py
|
||||
agent/integrations/stagehand_browser.py
|
||||
agent/middleware/__init__.py
|
||||
agent/middleware/check_message_queue.py
|
||||
agent/middleware/ensure_no_empty_msg.py
|
||||
agent/middleware/exclude_tools.py
|
||||
agent/middleware/model_fallback.py
|
||||
agent/middleware/notify_step_limit.py
|
||||
agent/middleware/plan_mode.py
|
||||
agent/middleware/pr_creation_guard.py
|
||||
agent/middleware/prepare_run.py
|
||||
agent/middleware/refresh_github_proxy.py
|
||||
agent/middleware/refresh_slack_status.py
|
||||
agent/middleware/repair_orphaned_tool_calls.py
|
||||
agent/middleware/sandbox_circuit_breaker.py
|
||||
agent/middleware/sanitize_fireworks_messages.py
|
||||
agent/middleware/sanitize_thinking_blocks.py
|
||||
agent/middleware/sanitize_tool_inputs.py
|
||||
agent/middleware/settle_review_check.py
|
||||
agent/middleware/subdir_agents.py
|
||||
agent/middleware/task_retry.py
|
||||
agent/middleware/timeout_wrapup.py
|
||||
agent/middleware/tool_artifact.py
|
||||
agent/middleware/tool_error_handler.py
|
||||
agent/middleware/workflow_push_guard.py
|
||||
agent/prompt.py
|
||||
agent/providers/__init__.py
|
||||
agent/reconcile.py
|
||||
agent/resources/__init__.py
|
||||
agent/resources/default_prompt.md
|
||||
agent/review/__init__.py
|
||||
agent/review/diff.py
|
||||
agent/review/eval_store.py
|
||||
agent/review/findings.py
|
||||
agent/review/groups.py
|
||||
agent/review/publish.py
|
||||
agent/review/reconcile.py
|
||||
agent/review/style_collector.py
|
||||
agent/review/style_guidance.py
|
||||
agent/review/trace_context.py
|
||||
agent/reviewer.py
|
||||
agent/runtime/__init__.py
|
||||
agent/runtime/constants.py
|
||||
agent/runtime/execution.py
|
||||
agent/runtime/sandbox.py
|
||||
agent/scheduler.py
|
||||
agent/server.py
|
||||
agent/skills/bootstrap-repo-analysis/SKILL.md
|
||||
agent/skills/continual-learning/SKILL.md
|
||||
agent/tools/__init__.py
|
||||
agent/tools/_sandbox_output.py
|
||||
agent/tools/add_finding.py
|
||||
agent/tools/enter_plan_mode.py
|
||||
agent/tools/fetch_review_diff.py
|
||||
agent/tools/fetch_url.py
|
||||
agent/tools/http_request.py
|
||||
agent/tools/linear_comment.py
|
||||
agent/tools/linear_create_issue.py
|
||||
agent/tools/linear_delete_issue.py
|
||||
agent/tools/linear_get_issue.py
|
||||
agent/tools/linear_get_issue_comments.py
|
||||
agent/tools/linear_list_teams.py
|
||||
agent/tools/linear_search_issues.py
|
||||
agent/tools/linear_update_issue.py
|
||||
agent/tools/list_findings.py
|
||||
agent/tools/list_review_findings.py
|
||||
agent/tools/open_pull_request.py
|
||||
agent/tools/publish_review.py
|
||||
agent/tools/read_finding_outcomes.py
|
||||
agent/tools/read_repo_file.py
|
||||
agent/tools/reply_to_finding_thread.py
|
||||
agent/tools/report_platform_issue.py
|
||||
agent/tools/request_pr_review.py
|
||||
agent/tools/resolve_finding_thread.py
|
||||
agent/tools/save_plan.py
|
||||
agent/tools/save_review_style.py
|
||||
agent/tools/schedule_thread_wakeup.py
|
||||
agent/tools/search_repo_code.py
|
||||
agent/tools/slack_add_reaction.py
|
||||
agent/tools/slack_read_thread_messages.py
|
||||
agent/tools/slack_start_new_thread.py
|
||||
agent/tools/slack_thread_reply.py
|
||||
agent/tools/update_finding.py
|
||||
agent/tools/web_search.py
|
||||
agent/utils/agents_md.py
|
||||
agent/utils/analyzer_skills.py
|
||||
agent/utils/api_standards_skill.py
|
||||
agent/utils/auth.py
|
||||
agent/utils/authorship.py
|
||||
agent/utils/comments.py
|
||||
agent/utils/dashboard_handoff.py
|
||||
agent/utils/dashboard_links.py
|
||||
agent/utils/deferred_model.py
|
||||
agent/utils/gateway.py
|
||||
agent/utils/github_app.py
|
||||
agent/utils/github_checks.py
|
||||
agent/utils/github_ci.py
|
||||
agent/utils/github_comments.py
|
||||
agent/utils/github_feedback.py
|
||||
agent/utils/github_http.py
|
||||
agent/utils/github_org_membership.py
|
||||
agent/utils/github_proxy.py
|
||||
agent/utils/github_token.py
|
||||
agent/utils/http.py
|
||||
agent/utils/json_types.py
|
||||
agent/utils/langsmith.py
|
||||
agent/utils/linear.py
|
||||
agent/utils/linear_team_repo_map.py
|
||||
agent/utils/model.py
|
||||
agent/utils/multimodal.py
|
||||
agent/utils/repo.py
|
||||
agent/utils/repo_prep.py
|
||||
agent/utils/reviewer_outcomes.py
|
||||
agent/utils/sandbox.py
|
||||
agent/utils/sandbox_paths.py
|
||||
agent/utils/sandbox_state.py
|
||||
agent/utils/slack.py
|
||||
agent/utils/slack_feedback.py
|
||||
agent/utils/thread_ids.py
|
||||
agent/utils/thread_ops.py
|
||||
agent/utils/tracing.py
|
||||
agent/utils/ttl_cache.py
|
||||
agent/utils/url_safety.py
|
||||
agent/webapp.py
|
||||
agent/webhooks/__init__.py
|
||||
agent/webhooks/common.py
|
||||
agent/webhooks/github.py
|
||||
agent/webhooks/github_routes.py
|
||||
agent/webhooks/linear.py
|
||||
agent/webhooks/linear_routes.py
|
||||
agent/webhooks/slack.py
|
||||
agent/webhooks/slack_routes.py
|
||||
assets/dark.svg
|
||||
assets/light.svg
|
||||
docs/CUSTOMIZATION.md
|
||||
docs/INSTALLATION.md
|
||||
evals/reviewer/README.md
|
||||
evals/reviewer/build_dataset.py
|
||||
evals/reviewer/config.toml
|
||||
evals/reviewer/golden_comments/cal_dot_com.json
|
||||
evals/reviewer/golden_comments/discourse.json
|
||||
evals/reviewer/golden_comments/grafana.json
|
||||
evals/reviewer/golden_comments/keycloak.json
|
||||
evals/reviewer/golden_comments/sentry.json
|
||||
evals/reviewer/judge.py
|
||||
evals/reviewer/run_eval.py
|
||||
evals/reviewer/store_reporter.py
|
||||
evals/reviewer/target.py
|
||||
langgraph.json
|
||||
package.json
|
||||
pyproject.toml
|
||||
scripts/__init__.py
|
||||
scripts/check_pr_merge_status.py
|
||||
scripts/create_sandbox_snapshot.py
|
||||
scripts/list_snapshots.py
|
||||
scripts/purge_wakeup_crons.py
|
||||
tests/agent/test_agent_assembly_context.py
|
||||
tests/agent/test_agent_instructions.py
|
||||
tests/agent/test_agent_schedules.py
|
||||
tests/agent/test_agent_thread_pr_state.py
|
||||
tests/agent/test_agent_usage.py
|
||||
tests/agent/test_agents_md.py
|
||||
tests/agent/test_api_standards_skill.py
|
||||
tests/agent/test_dispatch.py
|
||||
tests/agent/test_import_hygiene.py
|
||||
tests/agent/test_langsmith_trace_url.py
|
||||
tests/agent/test_multimodal.py
|
||||
tests/agent/test_plan_mode.py
|
||||
tests/agent/test_plan_review.py
|
||||
tests/agent/test_prompt_default_repo.py
|
||||
tests/agent/test_repo_prep.py
|
||||
tests/agent/test_task_retry.py
|
||||
tests/agent/test_timeout_wrapup.py
|
||||
tests/agent/test_workflow_push_guard.py
|
||||
tests/analyzer/test_analyzer_cron.py
|
||||
tests/analyzer/test_analyzer_skills.py
|
||||
tests/analyzer/test_eval_jobs.py
|
||||
tests/analyzer/test_eval_store_reporter.py
|
||||
tests/auth/test_auth_sources.py
|
||||
tests/auth/test_authorship.py
|
||||
tests/auth/test_encryption.py
|
||||
tests/auth/test_github_oauth_refresh.py
|
||||
tests/auth/test_github_token_ttl.py
|
||||
tests/auth/test_notion_oauth.py
|
||||
tests/auth/test_slack_oauth.py
|
||||
tests/auth/test_user_credentials.py
|
||||
tests/conftest.py
|
||||
tests/dashboard/test_account_link.py
|
||||
tests/dashboard/test_autofix_state.py
|
||||
tests/dashboard/test_dashboard_admin.py
|
||||
tests/dashboard/test_dashboard_csrf.py
|
||||
tests/dashboard/test_dashboard_links.py
|
||||
tests/dashboard/test_dashboard_oauth_redirect.py
|
||||
tests/dashboard/test_dashboard_org_login_gate.py
|
||||
tests/dashboard/test_dashboard_repo_optional.py
|
||||
tests/dashboard/test_dashboard_repos.py
|
||||
tests/dashboard/test_dashboard_reviews.py
|
||||
tests/dashboard/test_dashboard_run_email.py
|
||||
tests/dashboard/test_dashboard_thread_api.py
|
||||
tests/dashboard/test_dashboard_thread_api_activity.py
|
||||
tests/dashboard/test_dashboard_web_handoff.py
|
||||
tests/dashboard/test_public_repo_org_gate.py
|
||||
tests/dashboard/test_team_credentials.py
|
||||
tests/dashboard/test_team_settings_fable.py
|
||||
tests/dashboard/test_team_settings_grouping.py
|
||||
tests/dashboard/test_team_settings_org_guidelines.py
|
||||
tests/dashboard/test_user_mappings.py
|
||||
tests/dashboard/test_workflow_approval_api.py
|
||||
tests/e2e/.gitignore
|
||||
tests/e2e/README.md
|
||||
tests/e2e/agent_entrypoint.py
|
||||
tests/e2e/dev-mock.sh
|
||||
tests/e2e/e2e_env.py
|
||||
tests/e2e/fake_llm.py
|
||||
tests/e2e/fakes.py
|
||||
tests/e2e/global-setup.ts
|
||||
tests/e2e/harness.py
|
||||
tests/e2e/langgraph.e2e.json
|
||||
tests/e2e/package-lock.json
|
||||
tests/e2e/package.json
|
||||
tests/e2e/patches.py
|
||||
tests/e2e/playwright.config.ts
|
||||
tests/e2e/screenshots/queued-messages-dashboard.png
|
||||
tests/e2e/static/github.html
|
||||
tests/e2e/static/slack.html
|
||||
tests/e2e/tests/dashboard.spec.ts
|
||||
tests/e2e/tests/full_flow.spec.ts
|
||||
tests/e2e/tests/plan_review.spec.ts
|
||||
tests/e2e/tests/sandbox_id.spec.ts
|
||||
tests/e2e/tests/slack_debounce.spec.ts
|
||||
tests/e2e/tests/slack_untagged_reply.spec.ts
|
||||
tests/github/test_github_app.py
|
||||
tests/github/test_github_checks.py
|
||||
tests/github/test_github_ci.py
|
||||
tests/github/test_github_comment_prompts.py
|
||||
tests/github/test_github_feedback.py
|
||||
tests/github/test_github_http.py
|
||||
tests/github/test_github_issue_webhook.py
|
||||
tests/github/test_github_proxy_refresh.py
|
||||
tests/github/test_normalize_repo.py
|
||||
tests/github/test_open_pull_request.py
|
||||
tests/github/test_pr_creation_guard.py
|
||||
tests/github/test_repo_extraction.py
|
||||
tests/middleware/test_check_message_queue.py
|
||||
tests/middleware/test_ensure_no_empty_msg.py
|
||||
tests/middleware/test_model_fallback_middleware.py
|
||||
tests/middleware/test_notify_step_limit_middleware.py
|
||||
tests/middleware/test_prepare_run_middleware.py
|
||||
tests/middleware/test_repair_orphaned_tool_calls.py
|
||||
tests/middleware/test_sanitize_fireworks_messages.py
|
||||
tests/middleware/test_sanitize_thinking_blocks.py
|
||||
tests/middleware/test_sanitize_tool_inputs.py
|
||||
tests/middleware/test_subdir_agents_middleware.py
|
||||
tests/middleware/test_tool_artifact_middleware.py
|
||||
tests/models/test_agent_subagent_models.py
|
||||
tests/models/test_anthropic_effort.py
|
||||
tests/models/test_anthropic_model.py
|
||||
tests/models/test_deferred_model.py
|
||||
tests/models/test_fireworks_model.py
|
||||
tests/models/test_google_model.py
|
||||
tests/models/test_model_fallback_resolution.py
|
||||
tests/reviewer/test_factory_config_isolation.py
|
||||
tests/reviewer/test_fetch_review_diff_tool.py
|
||||
tests/reviewer/test_pr_ready_auto_review.py
|
||||
tests/reviewer/test_recent_comments.py
|
||||
tests/reviewer/test_reconcile_sweep.py
|
||||
tests/reviewer/test_review_api.py
|
||||
tests/reviewer/test_review_chat.py
|
||||
tests/reviewer/test_review_style_collector.py
|
||||
tests/reviewer/test_review_style_sync.py
|
||||
tests/reviewer/test_review_styles_store.py
|
||||
tests/reviewer/test_reviewer.py
|
||||
tests/reviewer/test_reviewer_diff.py
|
||||
tests/reviewer/test_reviewer_eval_judge.py
|
||||
tests/reviewer/test_reviewer_eval_run.py
|
||||
tests/reviewer/test_reviewer_eval_target.py
|
||||
tests/reviewer/test_reviewer_findings.py
|
||||
tests/reviewer/test_reviewer_groups.py
|
||||
tests/reviewer/test_reviewer_outcomes.py
|
||||
tests/reviewer/test_reviewer_publish.py
|
||||
tests/reviewer/test_reviewer_reconcile.py
|
||||
tests/reviewer/test_reviewer_tools.py
|
||||
tests/reviewer/test_reviewer_trace_context.py
|
||||
tests/reviewer/test_reviewer_watch.py
|
||||
tests/sandbox/test_daytona_integration.py
|
||||
tests/sandbox/test_e2b_integration.py
|
||||
tests/sandbox/test_gateway.py
|
||||
tests/sandbox/test_langsmith_sandbox_config.py
|
||||
tests/sandbox/test_langsmith_sandbox_timeout.py
|
||||
tests/sandbox/test_local_integration.py
|
||||
tests/sandbox/test_proxy_auth.py
|
||||
tests/sandbox/test_repo_snapshots.py
|
||||
tests/sandbox/test_sandbox_paths.py
|
||||
tests/sandbox/test_sandbox_recovery.py
|
||||
tests/sandbox/test_sandbox_state.py
|
||||
tests/sandbox/test_stale_sandbox_creating.py
|
||||
tests/slack/test_refresh_slack_status_middleware.py
|
||||
tests/slack/test_slack_add_reaction_tool.py
|
||||
tests/slack/test_slack_assistants_status.py
|
||||
tests/slack/test_slack_context.py
|
||||
tests/slack/test_slack_feedback.py
|
||||
tests/slack/test_slack_start_new_thread_tool.py
|
||||
tests/slack/test_slack_thread_reply_tool.py
|
||||
tests/slack/test_slack_webhook_errors.py
|
||||
tests/test_openai_responses_replay.py
|
||||
tests/test_tool_output_offloading.py
|
||||
tests/tools/test_corridor_mcp.py
|
||||
tests/tools/test_currents_tools.py
|
||||
tests/tools/test_http_security.py
|
||||
tests/tools/test_linear_search_issues.py
|
||||
tests/tools/test_observability_tools.py
|
||||
tests/tools/test_report_platform_issue_tool.py
|
||||
tests/tools/test_schedule_thread_wakeup.py
|
||||
tests/tools/test_stagehand_browser.py
|
||||
tests/webhooks/test_completion_webhook.py
|
||||
tests/webhooks/test_linear_webhook_author.py
|
||||
ui/.cta.json
|
||||
ui/.gitignore
|
||||
ui/.prettierignore
|
||||
ui/.prettierrc
|
||||
ui/AGENTS.md
|
||||
ui/README.md
|
||||
ui/assets/LangChain_Symbol_LightBlue.png
|
||||
ui/assets/ic_launcher_round.png
|
||||
ui/assets/playstore-icon.png
|
||||
ui/components.json
|
||||
ui/eslint.config.js
|
||||
ui/package.json
|
||||
ui/pnpm-lock.yaml
|
||||
ui/pnpm-workspace.yaml
|
||||
ui/public/apple-touch-icon.png
|
||||
ui/public/favicon.png
|
||||
ui/public/gh-open-in-open-swe-dark.svg
|
||||
ui/public/gh-open-in-open-swe-light.svg
|
||||
ui/public/logo-mark.png
|
||||
ui/public/logo512.png
|
||||
ui/public/robots.txt
|
||||
ui/src/client.tsx
|
||||
ui/src/components/AgentInstructionsPanel.tsx
|
||||
ui/src/components/AppShell.tsx
|
||||
ui/src/components/AppSidebar.tsx
|
||||
ui/src/components/InstructionsEditor.tsx
|
||||
ui/src/components/RepoSnapshotsPanel.tsx
|
||||
ui/src/components/ReviewStylesPanel.tsx
|
||||
ui/src/components/SidebarUserMenu.tsx
|
||||
ui/src/components/sidebar-layout.tsx
|
||||
ui/src/components/ui/avatar.tsx
|
||||
ui/src/components/ui/badge.tsx
|
||||
ui/src/components/ui/button.tsx
|
||||
ui/src/components/ui/card.tsx
|
||||
ui/src/components/ui/combobox.tsx
|
||||
ui/src/components/ui/input-group.tsx
|
||||
ui/src/components/ui/input.tsx
|
||||
ui/src/components/ui/label.tsx
|
||||
ui/src/components/ui/select.tsx
|
||||
ui/src/components/ui/separator.tsx
|
||||
ui/src/components/ui/skeleton.tsx
|
||||
ui/src/components/ui/switch.tsx
|
||||
ui/src/components/ui/textarea.tsx
|
||||
ui/src/features/agents/components/AgentGitPanel.tsx
|
||||
ui/src/features/agents/components/AgentPromptBar.tsx
|
||||
ui/src/features/agents/components/AgentRunCard.tsx
|
||||
ui/src/features/agents/components/AgentThreadView.tsx
|
||||
ui/src/features/agents/components/AgentsHome.tsx
|
||||
ui/src/features/agents/components/AgentsSidebar.tsx
|
||||
ui/src/features/agents/components/AgentsThreadsPage.tsx
|
||||
ui/src/features/agents/components/OnboardingDialog.tsx
|
||||
ui/src/features/agents/components/PlanReview.tsx
|
||||
ui/src/features/agents/components/SidebarFilterMenu.tsx
|
||||
ui/src/features/agents/components/WorkflowApprovalCard.tsx
|
||||
ui/src/features/agents/components/chat/CloudPromptBar.tsx
|
||||
ui/src/features/agents/components/chat/CodeBlock.tsx
|
||||
ui/src/features/agents/components/chat/DiffView.tsx
|
||||
ui/src/features/agents/components/chat/Logo.tsx
|
||||
ui/src/features/agents/components/chat/Markdown.tsx
|
||||
ui/src/features/agents/components/chat/ReplyCard.tsx
|
||||
ui/src/features/agents/components/chat/ShellCommand.tsx
|
||||
ui/src/features/agents/components/chat/ToolExecution.test.ts
|
||||
ui/src/features/agents/components/chat/ToolExecution.tsx
|
||||
ui/src/features/agents/components/chat/toolExecutionDisplay.ts
|
||||
ui/src/features/agents/components/messages/AgentMessage.tsx
|
||||
ui/src/features/agents/components/messages/ChunkRenderer.tsx
|
||||
ui/src/features/agents/components/messages/MessageTimestamp.tsx
|
||||
ui/src/features/agents/components/messages/Messages.tsx
|
||||
ui/src/features/agents/components/messages/ReasoningBlock.tsx
|
||||
ui/src/features/agents/components/messages/ThinkingSpinner.tsx
|
||||
ui/src/features/agents/components/messages/TurnChangedFilesCard.tsx
|
||||
ui/src/features/agents/components/messages/UserMessage.tsx
|
||||
ui/src/features/agents/components/messages/WorkSummary.tsx
|
||||
ui/src/features/agents/components/messages/index.ts
|
||||
ui/src/features/agents/components/messages/renderItems.ts
|
||||
ui/src/features/agents/components/messages/summarizeChangedFiles.ts
|
||||
ui/src/features/agents/components/messages/types.ts
|
||||
ui/src/features/agents/components/subagents/SubagentActivity.tsx
|
||||
ui/src/features/agents/components/subagents/SubagentCard.tsx
|
||||
ui/src/features/agents/components/subagents/SubagentGroup.tsx
|
||||
ui/src/features/agents/components/subagents/index.ts
|
||||
ui/src/features/agents/components/z-index.ts
|
||||
ui/src/features/agents/experiments/BranchSelector.tsx
|
||||
ui/src/features/agents/experiments/BubblePrefix.tsx
|
||||
ui/src/features/agents/experiments/ChatView.tsx
|
||||
ui/src/features/agents/experiments/CompactingIndicator.tsx
|
||||
ui/src/features/agents/experiments/ContextIndicator.tsx
|
||||
ui/src/features/agents/experiments/Footer.tsx
|
||||
ui/src/features/agents/experiments/HeaderBar.tsx
|
||||
ui/src/features/agents/experiments/MarkdownTable.tsx
|
||||
ui/src/features/agents/experiments/PanelResizeHandle.tsx
|
||||
ui/src/features/agents/experiments/PromptBar.tsx
|
||||
ui/src/features/agents/experiments/SourceControlPanel.tsx
|
||||
ui/src/features/agents/experiments/SourceControlTile.tsx
|
||||
ui/src/features/agents/experiments/ThreadPicker.tsx
|
||||
ui/src/features/agents/experiments/TodoList.tsx
|
||||
ui/src/features/agents/experiments/index.ts
|
||||
ui/src/features/agents/lib/AgentThreadStreamProvider.tsx
|
||||
ui/src/features/agents/lib/api.ts
|
||||
ui/src/features/agents/lib/gitPanelPreferences.test.ts
|
||||
ui/src/features/agents/lib/gitPanelPreferences.ts
|
||||
ui/src/features/agents/lib/messageTimestamps.ts
|
||||
ui/src/features/agents/lib/provider/useIsInAgentThreadStream.tsx
|
||||
ui/src/features/agents/lib/provider/useLiveMarkdownMessageId.ts
|
||||
ui/src/features/agents/lib/provider/useModelOptions.ts
|
||||
ui/src/features/agents/lib/provider/useSubmitAgentMessage.ts
|
||||
ui/src/features/agents/lib/queries.ts
|
||||
ui/src/features/agents/lib/sidebarFilter.test.ts
|
||||
ui/src/features/agents/lib/sidebarFilter.ts
|
||||
ui/src/features/agents/lib/sidebarPrefs.ts
|
||||
ui/src/features/agents/lib/streamMessagesToUi.ts
|
||||
ui/src/features/agents/lib/toolNames.ts
|
||||
ui/src/features/agents/lib/types.ts
|
||||
ui/src/features/agents/lib/useRunCompletionNotifier.ts
|
||||
ui/src/features/agents/utils/diffStats.ts
|
||||
ui/src/features/agents/utils/diffUtils.test.ts
|
||||
ui/src/features/agents/utils/diffUtils.ts
|
||||
ui/src/features/automations/components/AutomationEditor.tsx
|
||||
ui/src/features/automations/components/AutomationTemplates.tsx
|
||||
ui/src/features/automations/components/AutomationsList.tsx
|
||||
ui/src/features/automations/components/ScheduleTriggerPicker.tsx
|
||||
ui/src/features/automations/lib/automation-templates.ts
|
||||
ui/src/features/automations/lib/cron.ts
|
||||
ui/src/features/reviews/components/PrHeader.tsx
|
||||
ui/src/features/reviews/components/ReviewChat.tsx
|
||||
ui/src/features/reviews/components/ReviewCommentsMenu.tsx
|
||||
ui/src/features/reviews/components/ReviewMainBody.tsx
|
||||
ui/src/features/reviews/components/ReviewSidebar.tsx
|
||||
ui/src/features/reviews/components/ReviewTab.tsx
|
||||
ui/src/features/settings/components/RepoSelector.tsx
|
||||
ui/src/lib/api.ts
|
||||
ui/src/lib/auth-redirect-core.ts
|
||||
ui/src/lib/auth-redirect.test.ts
|
||||
ui/src/lib/auth-redirect.tsx
|
||||
ui/src/lib/hotkeys.ts
|
||||
ui/src/lib/notifications.ts
|
||||
ui/src/lib/plan.ts
|
||||
ui/src/lib/profile.ts
|
||||
ui/src/lib/query.ts
|
||||
ui/src/lib/repo.test.ts
|
||||
ui/src/lib/repo.ts
|
||||
ui/src/lib/reviewImage.test.ts
|
||||
ui/src/lib/session.ts
|
||||
ui/src/lib/theme.ts
|
||||
ui/src/lib/useIsMobile.ts
|
||||
ui/src/lib/utils.ts
|
||||
ui/src/logo.svg
|
||||
ui/src/routeTree.gen.ts
|
||||
ui/src/router.tsx
|
||||
ui/src/routes/$owner.$repo.pull.$number.tsx
|
||||
ui/src/routes/__root.tsx
|
||||
ui/src/routes/admin.tsx
|
||||
ui/src/routes/admin_.evals.tsx
|
||||
ui/src/routes/agents.tsx
|
||||
ui/src/routes/agents/$threadId.tsx
|
||||
ui/src/routes/agents/$threadId_.plan.tsx
|
||||
ui/src/routes/agents/automations/$scheduleId.tsx
|
||||
ui/src/routes/agents/automations/index.tsx
|
||||
ui/src/routes/agents/automations/new.tsx
|
||||
ui/src/routes/agents/index.tsx
|
||||
ui/src/routes/agents/reviews/$owner.$repo.$number.tsx
|
||||
ui/src/routes/agents/reviews/index.tsx
|
||||
ui/src/routes/agents/threads.tsx
|
||||
ui/src/routes/agents_.instructions.tsx
|
||||
ui/src/routes/agents_.snapshots.tsx
|
||||
ui/src/routes/cloud-agents.tsx
|
||||
ui/src/routes/index.tsx
|
||||
ui/src/routes/integrations.tsx
|
||||
ui/src/routes/login.tsx
|
||||
ui/src/routes/my-settings.tsx
|
||||
ui/src/routes/review.tsx
|
||||
ui/src/routes/review_.repositories.$owner.tsx
|
||||
ui/src/routes/review_.styles.tsx
|
||||
ui/src/routes/usage.tsx
|
||||
ui/src/styles.css
|
||||
ui/src/styles/agents.css
|
||||
ui/tsconfig.json
|
||||
ui/vercel.json
|
||||
ui/vite.config.ts
|
||||
uv.lock
|
||||
535
docs/upstream-sync/domain-reorg/upstream-pre-tree.txt
Normal file
535
docs/upstream-sync/domain-reorg/upstream-pre-tree.txt
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
.codespellignore
|
||||
.dockerignore
|
||||
.github/dependabot.yml
|
||||
.github/workflows/ci.yml
|
||||
.github/workflows/pr_lint.yml
|
||||
.github/workflows/promote_main_to_prod.yml
|
||||
.github/workflows/reviewer_eval.yml
|
||||
.gitignore
|
||||
.vscode/settings.json
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
CUSTOMIZATION.md
|
||||
Dockerfile
|
||||
INSTALLATION.md
|
||||
LICENSE
|
||||
Makefile
|
||||
README.md
|
||||
SECURITY.md
|
||||
agent/analyzer.py
|
||||
agent/chat.py
|
||||
agent/completion.py
|
||||
agent/dashboard/__init__.py
|
||||
agent/dashboard/admin.py
|
||||
agent/dashboard/agent_instructions.py
|
||||
agent/dashboard/agent_overrides.py
|
||||
agent/dashboard/agent_usage.py
|
||||
agent/dashboard/analyzer_cron.py
|
||||
agent/dashboard/autofix_state.py
|
||||
agent/dashboard/enabled_repos.py
|
||||
agent/dashboard/eval_jobs.py
|
||||
agent/dashboard/notion_oauth.py
|
||||
agent/dashboard/oauth.py
|
||||
agent/dashboard/options.py
|
||||
agent/dashboard/plan_api.py
|
||||
agent/dashboard/plan_store.py
|
||||
agent/dashboard/pr_diff.py
|
||||
agent/dashboard/profiles.py
|
||||
agent/dashboard/repo_access.py
|
||||
agent/dashboard/repo_snapshots.py
|
||||
agent/dashboard/review_api.py
|
||||
agent/dashboard/review_chat_api.py
|
||||
agent/dashboard/review_style_jobs.py
|
||||
agent/dashboard/review_styles.py
|
||||
agent/dashboard/routes.py
|
||||
agent/dashboard/schedules.py
|
||||
agent/dashboard/slack_oauth.py
|
||||
agent/dashboard/team_credentials.py
|
||||
agent/dashboard/team_settings.py
|
||||
agent/dashboard/thread_api.py
|
||||
agent/dashboard/user_credentials.py
|
||||
agent/dashboard/user_mappings.py
|
||||
agent/dashboard/workflow_approval.py
|
||||
agent/dashboard/workflow_approval_api.py
|
||||
agent/dispatch.py
|
||||
agent/encryption.py
|
||||
agent/integrations/__init__.py
|
||||
agent/integrations/corridor_mcp.py
|
||||
agent/integrations/currents_tools.py
|
||||
agent/integrations/datadog_mcp.py
|
||||
agent/integrations/daytona.py
|
||||
agent/integrations/e2b.py
|
||||
agent/integrations/langsmith.py
|
||||
agent/integrations/langsmith_tools.py
|
||||
agent/integrations/local.py
|
||||
agent/integrations/modal.py
|
||||
agent/integrations/notion_mcp.py
|
||||
agent/integrations/runloop.py
|
||||
agent/integrations/stagehand_browser.py
|
||||
agent/middleware/__init__.py
|
||||
agent/middleware/check_message_queue.py
|
||||
agent/middleware/ensure_no_empty_msg.py
|
||||
agent/middleware/exclude_tools.py
|
||||
agent/middleware/model_fallback.py
|
||||
agent/middleware/notify_step_limit.py
|
||||
agent/middleware/plan_mode.py
|
||||
agent/middleware/pr_creation_guard.py
|
||||
agent/middleware/prepare_run.py
|
||||
agent/middleware/refresh_github_proxy.py
|
||||
agent/middleware/refresh_slack_status.py
|
||||
agent/middleware/repair_orphaned_tool_calls.py
|
||||
agent/middleware/sandbox_circuit_breaker.py
|
||||
agent/middleware/sanitize_fireworks_messages.py
|
||||
agent/middleware/sanitize_openai_responses.py
|
||||
agent/middleware/sanitize_thinking_blocks.py
|
||||
agent/middleware/sanitize_tool_inputs.py
|
||||
agent/middleware/settle_review_check.py
|
||||
agent/middleware/subdir_agents.py
|
||||
agent/middleware/task_retry.py
|
||||
agent/middleware/timeout_wrapup.py
|
||||
agent/middleware/tool_artifact.py
|
||||
agent/middleware/tool_error_handler.py
|
||||
agent/middleware/workflow_push_guard.py
|
||||
agent/prompt.py
|
||||
agent/reconcile.py
|
||||
agent/review_style_collector.py
|
||||
agent/review_style_guidance.py
|
||||
agent/reviewer.py
|
||||
agent/reviewer_diff.py
|
||||
agent/reviewer_eval_store.py
|
||||
agent/reviewer_findings.py
|
||||
agent/reviewer_groups.py
|
||||
agent/reviewer_publish.py
|
||||
agent/reviewer_reconcile.py
|
||||
agent/reviewer_trace_context.py
|
||||
agent/scheduler.py
|
||||
agent/server.py
|
||||
agent/skills/bootstrap-repo-analysis/SKILL.md
|
||||
agent/skills/continual-learning/SKILL.md
|
||||
agent/tools/__init__.py
|
||||
agent/tools/add_finding.py
|
||||
agent/tools/enter_plan_mode.py
|
||||
agent/tools/fetch_url.py
|
||||
agent/tools/http_request.py
|
||||
agent/tools/linear_comment.py
|
||||
agent/tools/linear_create_issue.py
|
||||
agent/tools/linear_delete_issue.py
|
||||
agent/tools/linear_get_issue.py
|
||||
agent/tools/linear_get_issue_comments.py
|
||||
agent/tools/linear_list_teams.py
|
||||
agent/tools/linear_update_issue.py
|
||||
agent/tools/list_findings.py
|
||||
agent/tools/list_review_findings.py
|
||||
agent/tools/open_pull_request.py
|
||||
agent/tools/publish_review.py
|
||||
agent/tools/read_finding_outcomes.py
|
||||
agent/tools/read_repo_file.py
|
||||
agent/tools/reply_to_finding_thread.py
|
||||
agent/tools/report_platform_issue.py
|
||||
agent/tools/request_pr_review.py
|
||||
agent/tools/resolve_finding_thread.py
|
||||
agent/tools/save_plan.py
|
||||
agent/tools/save_review_style.py
|
||||
agent/tools/schedule_thread_wakeup.py
|
||||
agent/tools/search_repo_code.py
|
||||
agent/tools/slack_add_reaction.py
|
||||
agent/tools/slack_read_thread_messages.py
|
||||
agent/tools/slack_start_new_thread.py
|
||||
agent/tools/slack_thread_reply.py
|
||||
agent/tools/update_finding.py
|
||||
agent/tools/web_search.py
|
||||
agent/utils/agents_md.py
|
||||
agent/utils/analyzer_skills.py
|
||||
agent/utils/api_standards_skill.py
|
||||
agent/utils/auth.py
|
||||
agent/utils/authorship.py
|
||||
agent/utils/comments.py
|
||||
agent/utils/dashboard_handoff.py
|
||||
agent/utils/dashboard_links.py
|
||||
agent/utils/deferred_model.py
|
||||
agent/utils/gateway.py
|
||||
agent/utils/github_app.py
|
||||
agent/utils/github_checks.py
|
||||
agent/utils/github_ci.py
|
||||
agent/utils/github_comments.py
|
||||
agent/utils/github_feedback.py
|
||||
agent/utils/github_http.py
|
||||
agent/utils/github_org_membership.py
|
||||
agent/utils/github_proxy.py
|
||||
agent/utils/github_token.py
|
||||
agent/utils/http.py
|
||||
agent/utils/langsmith.py
|
||||
agent/utils/linear.py
|
||||
agent/utils/linear_team_repo_map.py
|
||||
agent/utils/model.py
|
||||
agent/utils/multimodal.py
|
||||
agent/utils/repo.py
|
||||
agent/utils/repo_prep.py
|
||||
agent/utils/reviewer_outcomes.py
|
||||
agent/utils/sandbox.py
|
||||
agent/utils/sandbox_paths.py
|
||||
agent/utils/sandbox_state.py
|
||||
agent/utils/slack.py
|
||||
agent/utils/slack_feedback.py
|
||||
agent/utils/thread_ids.py
|
||||
agent/utils/thread_ops.py
|
||||
agent/utils/tracing.py
|
||||
agent/utils/ttl_cache.py
|
||||
agent/utils/url_safety.py
|
||||
agent/webapp.py
|
||||
agent/webhooks/__init__.py
|
||||
agent/webhooks/github.py
|
||||
agent/webhooks/linear.py
|
||||
agent/webhooks/slack.py
|
||||
default_prompt.md
|
||||
evals/reviewer/README.md
|
||||
evals/reviewer/build_dataset.py
|
||||
evals/reviewer/config.toml
|
||||
evals/reviewer/golden_comments/cal_dot_com.json
|
||||
evals/reviewer/golden_comments/discourse.json
|
||||
evals/reviewer/golden_comments/grafana.json
|
||||
evals/reviewer/golden_comments/keycloak.json
|
||||
evals/reviewer/golden_comments/sentry.json
|
||||
evals/reviewer/judge.py
|
||||
evals/reviewer/run_eval.py
|
||||
evals/reviewer/store_reporter.py
|
||||
evals/reviewer/target.py
|
||||
langgraph.json
|
||||
package.json
|
||||
pyproject.toml
|
||||
scripts/__init__.py
|
||||
scripts/check_pr_merge_status.py
|
||||
scripts/create_sandbox_snapshot.py
|
||||
scripts/list_snapshots.py
|
||||
scripts/purge_wakeup_crons.py
|
||||
static/dark.svg
|
||||
static/light.svg
|
||||
tests/conftest.py
|
||||
tests/e2e/.gitignore
|
||||
tests/e2e/README.md
|
||||
tests/e2e/agent_entrypoint.py
|
||||
tests/e2e/dev-mock.sh
|
||||
tests/e2e/e2e_env.py
|
||||
tests/e2e/fake_llm.py
|
||||
tests/e2e/fakes.py
|
||||
tests/e2e/global-setup.ts
|
||||
tests/e2e/harness.py
|
||||
tests/e2e/langgraph.e2e.json
|
||||
tests/e2e/package-lock.json
|
||||
tests/e2e/package.json
|
||||
tests/e2e/patches.py
|
||||
tests/e2e/playwright.config.ts
|
||||
tests/e2e/screenshots/queued-messages-dashboard.png
|
||||
tests/e2e/static/github.html
|
||||
tests/e2e/static/slack.html
|
||||
tests/e2e/tests/dashboard.spec.ts
|
||||
tests/e2e/tests/full_flow.spec.ts
|
||||
tests/e2e/tests/plan_review.spec.ts
|
||||
tests/e2e/tests/sandbox_id.spec.ts
|
||||
tests/middleware/test_sandbox_recovery.py
|
||||
tests/test_account_link.py
|
||||
tests/test_agent_assembly_context.py
|
||||
tests/test_agent_instructions.py
|
||||
tests/test_agent_schedules.py
|
||||
tests/test_agent_subagent_models.py
|
||||
tests/test_agent_thread_pr_state.py
|
||||
tests/test_agent_usage.py
|
||||
tests/test_agents_md.py
|
||||
tests/test_analyzer_cron.py
|
||||
tests/test_analyzer_skills.py
|
||||
tests/test_anthropic_effort.py
|
||||
tests/test_anthropic_model.py
|
||||
tests/test_api_standards_skill.py
|
||||
tests/test_auth_sources.py
|
||||
tests/test_authorship.py
|
||||
tests/test_autofix_state.py
|
||||
tests/test_check_message_queue.py
|
||||
tests/test_completion_webhook.py
|
||||
tests/test_corridor_mcp.py
|
||||
tests/test_currents_tools.py
|
||||
tests/test_dashboard_admin.py
|
||||
tests/test_dashboard_csrf.py
|
||||
tests/test_dashboard_links.py
|
||||
tests/test_dashboard_oauth_redirect.py
|
||||
tests/test_dashboard_org_login_gate.py
|
||||
tests/test_dashboard_repo_optional.py
|
||||
tests/test_dashboard_repos.py
|
||||
tests/test_dashboard_reviews.py
|
||||
tests/test_dashboard_run_email.py
|
||||
tests/test_dashboard_thread_api.py
|
||||
tests/test_dashboard_thread_api_activity.py
|
||||
tests/test_dashboard_web_handoff.py
|
||||
tests/test_daytona_integration.py
|
||||
tests/test_deferred_model.py
|
||||
tests/test_dispatch.py
|
||||
tests/test_e2b_integration.py
|
||||
tests/test_encryption.py
|
||||
tests/test_ensure_no_empty_msg.py
|
||||
tests/test_eval_jobs.py
|
||||
tests/test_eval_store_reporter.py
|
||||
tests/test_fireworks_model.py
|
||||
tests/test_gateway.py
|
||||
tests/test_github_app.py
|
||||
tests/test_github_checks.py
|
||||
tests/test_github_ci.py
|
||||
tests/test_github_comment_prompts.py
|
||||
tests/test_github_feedback.py
|
||||
tests/test_github_http.py
|
||||
tests/test_github_issue_webhook.py
|
||||
tests/test_github_oauth_refresh.py
|
||||
tests/test_github_proxy_refresh.py
|
||||
tests/test_github_token_ttl.py
|
||||
tests/test_google_model.py
|
||||
tests/test_http_security.py
|
||||
tests/test_import_hygiene.py
|
||||
tests/test_langsmith_sandbox_config.py
|
||||
tests/test_langsmith_sandbox_timeout.py
|
||||
tests/test_langsmith_trace_url.py
|
||||
tests/test_linear_webhook_author.py
|
||||
tests/test_local_integration.py
|
||||
tests/test_model_fallback_middleware.py
|
||||
tests/test_model_fallback_resolution.py
|
||||
tests/test_multimodal.py
|
||||
tests/test_normalize_repo.py
|
||||
tests/test_notify_step_limit_middleware.py
|
||||
tests/test_notion_oauth.py
|
||||
tests/test_observability_tools.py
|
||||
tests/test_open_pull_request.py
|
||||
tests/test_plan_mode.py
|
||||
tests/test_plan_review.py
|
||||
tests/test_pr_creation_guard.py
|
||||
tests/test_pr_ready_auto_review.py
|
||||
tests/test_prepare_run_middleware.py
|
||||
tests/test_prompt_default_repo.py
|
||||
tests/test_proxy_auth.py
|
||||
tests/test_public_repo_org_gate.py
|
||||
tests/test_recent_comments.py
|
||||
tests/test_reconcile_sweep.py
|
||||
tests/test_refresh_slack_status_middleware.py
|
||||
tests/test_repair_orphaned_tool_calls.py
|
||||
tests/test_repo_extraction.py
|
||||
tests/test_repo_prep.py
|
||||
tests/test_repo_snapshots.py
|
||||
tests/test_report_platform_issue_tool.py
|
||||
tests/test_review_api.py
|
||||
tests/test_review_chat.py
|
||||
tests/test_review_style_collector.py
|
||||
tests/test_review_style_sync.py
|
||||
tests/test_review_styles_store.py
|
||||
tests/test_reviewer.py
|
||||
tests/test_reviewer_diff.py
|
||||
tests/test_reviewer_eval_judge.py
|
||||
tests/test_reviewer_eval_run.py
|
||||
tests/test_reviewer_eval_target.py
|
||||
tests/test_reviewer_findings.py
|
||||
tests/test_reviewer_groups.py
|
||||
tests/test_reviewer_outcomes.py
|
||||
tests/test_reviewer_publish.py
|
||||
tests/test_reviewer_reconcile.py
|
||||
tests/test_reviewer_tools.py
|
||||
tests/test_reviewer_trace_context.py
|
||||
tests/test_reviewer_watch.py
|
||||
tests/test_sandbox_paths.py
|
||||
tests/test_sandbox_state.py
|
||||
tests/test_sanitize_fireworks_messages.py
|
||||
tests/test_sanitize_openai_responses.py
|
||||
tests/test_sanitize_thinking_blocks.py
|
||||
tests/test_sanitize_tool_inputs.py
|
||||
tests/test_schedule_thread_wakeup.py
|
||||
tests/test_slack_add_reaction_tool.py
|
||||
tests/test_slack_assistants_status.py
|
||||
tests/test_slack_context.py
|
||||
tests/test_slack_feedback.py
|
||||
tests/test_slack_oauth.py
|
||||
tests/test_slack_start_new_thread_tool.py
|
||||
tests/test_slack_thread_reply_tool.py
|
||||
tests/test_slack_webhook_errors.py
|
||||
tests/test_stagehand_browser.py
|
||||
tests/test_stale_sandbox_creating.py
|
||||
tests/test_subdir_agents_middleware.py
|
||||
tests/test_task_retry.py
|
||||
tests/test_team_credentials.py
|
||||
tests/test_team_settings_fable.py
|
||||
tests/test_team_settings_grouping.py
|
||||
tests/test_team_settings_org_guidelines.py
|
||||
tests/test_timeout_wrapup.py
|
||||
tests/test_tool_artifact_middleware.py
|
||||
tests/test_user_credentials.py
|
||||
tests/test_user_mappings.py
|
||||
tests/test_workflow_approval_api.py
|
||||
tests/test_workflow_push_guard.py
|
||||
ui/.cta.json
|
||||
ui/.gitignore
|
||||
ui/.prettierignore
|
||||
ui/.prettierrc
|
||||
ui/AGENTS.md
|
||||
ui/README.md
|
||||
ui/assets/LangChain_Symbol_LightBlue.png
|
||||
ui/assets/ic_launcher_round.png
|
||||
ui/assets/playstore-icon.png
|
||||
ui/components.json
|
||||
ui/eslint.config.js
|
||||
ui/package.json
|
||||
ui/pnpm-lock.yaml
|
||||
ui/pnpm-workspace.yaml
|
||||
ui/public/apple-touch-icon.png
|
||||
ui/public/favicon.png
|
||||
ui/public/gh-open-in-open-swe-dark.svg
|
||||
ui/public/gh-open-in-open-swe-light.svg
|
||||
ui/public/logo-mark.png
|
||||
ui/public/logo512.png
|
||||
ui/public/robots.txt
|
||||
ui/src/client.tsx
|
||||
ui/src/components/AgentInstructionsPanel.tsx
|
||||
ui/src/components/AppShell.tsx
|
||||
ui/src/components/AppSidebar.tsx
|
||||
ui/src/components/InstructionsEditor.tsx
|
||||
ui/src/components/RepoSnapshotsPanel.tsx
|
||||
ui/src/components/ReviewStylesPanel.tsx
|
||||
ui/src/components/SidebarUserMenu.tsx
|
||||
ui/src/components/agents/AgentGitPanel.tsx
|
||||
ui/src/components/agents/AgentPromptBar.tsx
|
||||
ui/src/components/agents/AgentRunCard.tsx
|
||||
ui/src/components/agents/AgentThreadView.tsx
|
||||
ui/src/components/agents/AgentsHome.tsx
|
||||
ui/src/components/agents/AgentsSidebar.tsx
|
||||
ui/src/components/agents/AgentsThreadsPage.tsx
|
||||
ui/src/components/agents/AutomationEditor.tsx
|
||||
ui/src/components/agents/AutomationTemplates.tsx
|
||||
ui/src/components/agents/AutomationsList.tsx
|
||||
ui/src/components/agents/OnboardingDialog.tsx
|
||||
ui/src/components/agents/PlanReview.tsx
|
||||
ui/src/components/agents/PrHeader.tsx
|
||||
ui/src/components/agents/RepoSelector.tsx
|
||||
ui/src/components/agents/ReviewChat.tsx
|
||||
ui/src/components/agents/ReviewCommentsMenu.tsx
|
||||
ui/src/components/agents/ReviewMainBody.tsx
|
||||
ui/src/components/agents/ReviewSidebar.tsx
|
||||
ui/src/components/agents/ReviewTab.tsx
|
||||
ui/src/components/agents/ScheduleTriggerPicker.tsx
|
||||
ui/src/components/agents/SidebarFilterMenu.tsx
|
||||
ui/src/components/agents/WorkflowApprovalCard.tsx
|
||||
ui/src/components/agents/messages/AgentMessage.tsx
|
||||
ui/src/components/agents/messages/ChunkRenderer.tsx
|
||||
ui/src/components/agents/messages/MessageTimestamp.tsx
|
||||
ui/src/components/agents/messages/Messages.tsx
|
||||
ui/src/components/agents/messages/ReasoningBlock.tsx
|
||||
ui/src/components/agents/messages/ThinkingSpinner.tsx
|
||||
ui/src/components/agents/messages/TurnChangedFilesCard.tsx
|
||||
ui/src/components/agents/messages/UserMessage.tsx
|
||||
ui/src/components/agents/messages/WorkSummary.tsx
|
||||
ui/src/components/agents/messages/index.ts
|
||||
ui/src/components/agents/messages/renderItems.ts
|
||||
ui/src/components/agents/messages/summarizeChangedFiles.ts
|
||||
ui/src/components/agents/messages/types.ts
|
||||
ui/src/components/agents/ported/BranchSelector.tsx
|
||||
ui/src/components/agents/ported/BubblePrefix.tsx
|
||||
ui/src/components/agents/ported/ChatView.tsx
|
||||
ui/src/components/agents/ported/CloudPromptBar.tsx
|
||||
ui/src/components/agents/ported/CodeBlock.tsx
|
||||
ui/src/components/agents/ported/CompactingIndicator.tsx
|
||||
ui/src/components/agents/ported/ContextIndicator.tsx
|
||||
ui/src/components/agents/ported/DiffView.tsx
|
||||
ui/src/components/agents/ported/Footer.tsx
|
||||
ui/src/components/agents/ported/HeaderBar.tsx
|
||||
ui/src/components/agents/ported/Logo.tsx
|
||||
ui/src/components/agents/ported/Markdown.tsx
|
||||
ui/src/components/agents/ported/MarkdownTable.tsx
|
||||
ui/src/components/agents/ported/PanelResizeHandle.tsx
|
||||
ui/src/components/agents/ported/PromptBar.tsx
|
||||
ui/src/components/agents/ported/ReplyCard.tsx
|
||||
ui/src/components/agents/ported/ShellCommand.tsx
|
||||
ui/src/components/agents/ported/SourceControlPanel.tsx
|
||||
ui/src/components/agents/ported/SourceControlTile.tsx
|
||||
ui/src/components/agents/ported/ThreadPicker.tsx
|
||||
ui/src/components/agents/ported/TodoList.tsx
|
||||
ui/src/components/agents/ported/ToolExecution.tsx
|
||||
ui/src/components/agents/ported/index.ts
|
||||
ui/src/components/agents/subagents/SubagentActivity.tsx
|
||||
ui/src/components/agents/subagents/SubagentCard.tsx
|
||||
ui/src/components/agents/subagents/SubagentGroup.tsx
|
||||
ui/src/components/agents/subagents/index.ts
|
||||
ui/src/components/agents/utils/diffStats.ts
|
||||
ui/src/components/agents/utils/diffUtils.test.ts
|
||||
ui/src/components/agents/utils/diffUtils.ts
|
||||
ui/src/components/agents/z-index.ts
|
||||
ui/src/components/sidebar-layout.tsx
|
||||
ui/src/components/ui/avatar.tsx
|
||||
ui/src/components/ui/badge.tsx
|
||||
ui/src/components/ui/button.tsx
|
||||
ui/src/components/ui/card.tsx
|
||||
ui/src/components/ui/combobox.tsx
|
||||
ui/src/components/ui/input-group.tsx
|
||||
ui/src/components/ui/input.tsx
|
||||
ui/src/components/ui/label.tsx
|
||||
ui/src/components/ui/select.tsx
|
||||
ui/src/components/ui/separator.tsx
|
||||
ui/src/components/ui/skeleton.tsx
|
||||
ui/src/components/ui/switch.tsx
|
||||
ui/src/components/ui/textarea.tsx
|
||||
ui/src/lib/agents/AgentThreadStreamProvider.tsx
|
||||
ui/src/lib/agents/api.ts
|
||||
ui/src/lib/agents/automation-templates.ts
|
||||
ui/src/lib/agents/cron.ts
|
||||
ui/src/lib/agents/messageTimestamps.ts
|
||||
ui/src/lib/agents/provider/useIsInAgentThreadStream.tsx
|
||||
ui/src/lib/agents/provider/useLiveMarkdownMessageId.ts
|
||||
ui/src/lib/agents/provider/useModelOptions.ts
|
||||
ui/src/lib/agents/provider/useSubmitAgentMessage.ts
|
||||
ui/src/lib/agents/queries.ts
|
||||
ui/src/lib/agents/sidebarFilter.test.ts
|
||||
ui/src/lib/agents/sidebarFilter.ts
|
||||
ui/src/lib/agents/sidebarPrefs.ts
|
||||
ui/src/lib/agents/streamMessagesToUi.ts
|
||||
ui/src/lib/agents/types.ts
|
||||
ui/src/lib/agents/useRunCompletionNotifier.ts
|
||||
ui/src/lib/api.ts
|
||||
ui/src/lib/auth-redirect-core.ts
|
||||
ui/src/lib/auth-redirect.test.ts
|
||||
ui/src/lib/auth-redirect.tsx
|
||||
ui/src/lib/hotkeys.ts
|
||||
ui/src/lib/notifications.ts
|
||||
ui/src/lib/plan.ts
|
||||
ui/src/lib/profile.ts
|
||||
ui/src/lib/query.ts
|
||||
ui/src/lib/repo.test.ts
|
||||
ui/src/lib/repo.ts
|
||||
ui/src/lib/reviewImage.test.ts
|
||||
ui/src/lib/session.ts
|
||||
ui/src/lib/theme.ts
|
||||
ui/src/lib/useIsMobile.ts
|
||||
ui/src/lib/utils.ts
|
||||
ui/src/logo.svg
|
||||
ui/src/routeTree.gen.ts
|
||||
ui/src/router.tsx
|
||||
ui/src/routes/$owner.$repo.pull.$number.tsx
|
||||
ui/src/routes/__root.tsx
|
||||
ui/src/routes/admin.tsx
|
||||
ui/src/routes/admin_.evals.tsx
|
||||
ui/src/routes/agents.tsx
|
||||
ui/src/routes/agents/$threadId.tsx
|
||||
ui/src/routes/agents/$threadId_.plan.tsx
|
||||
ui/src/routes/agents/automations/$scheduleId.tsx
|
||||
ui/src/routes/agents/automations/index.tsx
|
||||
ui/src/routes/agents/automations/new.tsx
|
||||
ui/src/routes/agents/index.tsx
|
||||
ui/src/routes/agents/reviews/$owner.$repo.$number.tsx
|
||||
ui/src/routes/agents/reviews/index.tsx
|
||||
ui/src/routes/agents/threads.tsx
|
||||
ui/src/routes/agents_.instructions.tsx
|
||||
ui/src/routes/agents_.snapshots.tsx
|
||||
ui/src/routes/cloud-agents.tsx
|
||||
ui/src/routes/index.tsx
|
||||
ui/src/routes/integrations.tsx
|
||||
ui/src/routes/login.tsx
|
||||
ui/src/routes/my-settings.tsx
|
||||
ui/src/routes/review.tsx
|
||||
ui/src/routes/review_.repositories.$owner.tsx
|
||||
ui/src/routes/review_.styles.tsx
|
||||
ui/src/routes/usage.tsx
|
||||
ui/src/styles.css
|
||||
ui/src/styles/agents.css
|
||||
ui/tsconfig.json
|
||||
ui/vercel.json
|
||||
ui/vite.config.ts
|
||||
uv.lock
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
{"sha": "35659177", "pr": 1718, "subject": "fix: sanitize orphaned OpenAI tool results (#1718)", "disposition": "wont-merge", "reason": "N/A — fork has no OpenAI Responses traffic path; middleware present is #155's leaner rewrite lacking the orphan machinery #1718 patches; langchain-openai>=1.3.4 already satisfied; superseded upstream by #1731 (re-evaluate #155 rewrite when triaging #1731). Note: fd2541ce row's '#62 deleted sanitize_openai_responses.py' is inaccurate — the path was added fresh by #155", "branch": "", "local_sha": null, "updated": "2026-07-16T22:30:00Z"}
|
||||
{"sha": "092abafa", "pr": 1717, "subject": "fix: enforce terse Slack tool messages (#1717)", "disposition": "landed", "reason": "cherry-picked (-x) in #197; one test conflict resolved by adopting upstream's test (fork prompt now contains upstream text); follow-up to landed #1681, not superseded by it", "branch": "fix/port-1717-terse-slack-tool-msgs", "local_sha": null, "updated": "2026-07-16T22:30:00Z"}
|
||||
{"sha": "92d63170", "pr": 1720, "subject": "fix: separate review access from automatic reviews (#1720)", "disposition": "landed", "reason": "cherry-picked (-x) in #198 + fork-only auto-fix gates renamed to _is_repo_auto_review_enabled (kept opt-in-gated); opt-in list now gates only AUTOMATIC reviews — manual/re-review/watch/finding-replies open to any App-installed repo. Adam signed off 2026-07-16; /sh-security-review explicitly waived by Adam 2026-07-16", "branch": "fix/port-1720-review-access-split", "local_sha": null, "updated": "2026-07-16T22:30:00Z"}
|
||||
{"sha": "8356eb34", "pr": 1726, "subject": "refactor: organize repository by domain (#1726)", "disposition": "deferred", "reason": "FLAG-HUMAN: 298-file structural reorg (tests/<domain>/, ui/src/features/); chain head — every later upstream commit is written against this layout. Fork policy defers structural refactors (CLAUDE.md); adopting is a dedicated merge exercise. Until then, later picks need path remapping.", "branch": "domain-reorg", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "8356eb34", "pr": 1726, "subject": "refactor: organize repository by domain (#1726)", "disposition": "landed", "reason": "Adopted as a file-move exercise (fork content, upstream layout) on branch refactor/domain-reorg-adoption — gate-approved plan in docs/upstream-sync/domain-reorg/. New layout: agent/{graphs,runtime,api,review,resources}, agent/webhooks/*_routes.py (webapp.py split into api/ + per-source route modules; webapp.py now a shim), tests/<domain>/, ui/src/features/. Kills the per-pick path-remap tax and unblocks 8 deferred rows (#1732/#1761/#1744/#1748/#1742 clean; #1736/#1758/#1760 near-clean).", "branch": "refactor/domain-reorg-adoption", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "83abea26", "pr": 1724, "subject": "fix: accept natural-language Slack plan approvals (#1724)", "disposition": "deferred", "reason": "natural-language Slack plan approvals; touches fork-diverged plan-mode + Slack webhook stack (#130); post-reorg test paths need remap", "branch": "plan-approval", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "129ddcf9", "pr": 1728, "subject": "chore: include ripgrep in sandbox image (#1728)", "disposition": "landed", "reason": "1-line Dockerfile add (ripgrep); dev image lacks it; trivial pick", "branch": "chore/upstream-easy-picks", "local_sha": null, "updated": "2026-07-16T19:15:42Z"}
|
||||
{"sha": "ddbe457b", "pr": 1727, "subject": "fix: restore GPT-5.5 as default model (#1727)", "disposition": "deferred", "reason": "restores GPT-5.5 default in options/team_settings; fork picker is Bedrock/Fireworks-only — rides the #1708 OpenAI-models product decision (27b0ddeb)", "branch": "model-picker", "local_sha": null, "updated": "2026-07-16T18:46:38Z"}
|
||||
|
|
@ -98,28 +98,28 @@
|
|||
{"sha": "5136079d", "pr": 1725, "subject": "chore: disable todos for GPT-5.6 Sol (#1725)", "disposition": "wont-merge", "reason": "superseded — #1733 (136d28e6) rewrites the same todo-exclusion block to a global default-off with env opt-in; per-model GPT-5.6 Sol list moot (fork picker has no OpenAI models)", "branch": "", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "09eaf94c", "pr": 1730, "subject": "fix: let admins interrupt runaway agents (#1730)", "disposition": "landed", "reason": "admin interrupt for runaway agents (dashboard route + UI); useful ops control; UI half on post-reorg ui/src/features — remap to ui/src/components/agents", "branch": "feat/admin-thread-interrupt", "local_sha": null, "updated": "2026-07-16T19:36:07Z"}
|
||||
{"sha": "30832d29", "pr": 1731, "subject": "fix: preserve OpenAI Responses tool history (#1731)", "disposition": "deferred", "reason": "deletes SanitizeOpenAIResponsesMiddleware in favor of replay-history preservation in utils/model.py; supersedes deferred #1718 (35659177) — triage the pair together against fork-diverged middleware + model.py", "branch": "openai-sanitize", "local_sha": null, "updated": "2026-07-16T18:46:38Z"}
|
||||
{"sha": "1ea0e600", "pr": 1736, "subject": "fix: bind cached GitHub tokens to users (#1736)", "disposition": "deferred", "reason": "FLAG-HUMAN: security fix — binds per-thread cached GitHub tokens to a user principal (closes cross-user token-reuse leak). Fork has github_token.py but not webhooks/common.py — hand-map; auth surface: GPT-4.1 cross-review + /sh-security-review on landing. Priority pick.", "branch": "github-token-binding", "local_sha": null, "updated": "2026-07-16T18:46:37Z"}
|
||||
{"sha": "1ea0e600", "pr": 1736, "subject": "fix: bind cached GitHub tokens to users (#1736)", "disposition": "deferred", "reason": "FLAG-HUMAN: security fix — binds per-thread cached GitHub tokens to a user principal (closes cross-user token-reuse leak). Fork has github_token.py but not webhooks/common.py — hand-map; auth surface: GPT-4.1 cross-review + /sh-security-review on landing. Priority pick. Re-triage (post-reorg 8356eb34 landed): biggest beneficiary — agent/webhooks/common.py now exists, so the pick is near-clean against it; auth gates (GPT-4.1 cross-review + /sh-security-review) still apply.", "branch": "github-token-binding", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "3fcb27ce", "pr": 1737, "subject": "fix: bound reviewer diff fetching (#1737)", "disposition": "deferred", "reason": "bounds reviewer diff fetching + new fetch_review_diff tool; reviewer.py fork-diverged — reconcile like #1713 (71e3b818)", "branch": "reviewer-misc", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "d714586c", "pr": 1732, "subject": "fix: normalize dashboard label rendering (#1732)", "disposition": "deferred", "reason": "dashboard tool-label normalization; post-reorg ui/src/features paths — remap", "branch": "dashboard-ui", "local_sha": null, "updated": "2026-07-16T18:47:07Z"}
|
||||
{"sha": "d714586c", "pr": 1732, "subject": "fix: normalize dashboard label rendering (#1732)", "disposition": "deferred", "reason": "dashboard tool-label normalization; post-reorg ui/src/features paths — remap. Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick; ui/src/features/ now matches upstream.", "branch": "dashboard-ui", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "ef68c09b", "pr": 1734, "subject": "fix: handle Slack DMs as mentions (#1734)", "disposition": "deferred", "reason": "treat Slack DMs as mentions; touches fork-diverged Slack webhook + e2e harness; post-reorg test paths", "branch": "slack-tooling", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "bfa67a7a", "pr": 1711, "subject": "chore(deps): bump soupsieve from 2.8.3 to 2.8.4 (#1711)", "disposition": "wont-merge", "reason": "already in dev — uv.lock resolves soupsieve 2.8.4", "branch": "", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "26828ff9", "pr": 1745, "subject": "chore: upgrade deepagents to 0.7.0a7 (#1745)", "disposition": "deferred", "reason": "deepagents 0.6.12 -> 0.7.0a7 alpha + [tool.uv] override-dependencies to bypass sandbox integrations <0.7.0 bound; fork is built on create_deep_agent — dedicated validation (unit + e2e) before adopting an alpha", "branch": "deps", "local_sha": null, "updated": "2026-07-16T18:46:38Z"}
|
||||
{"sha": "ef0ed5af", "pr": 1741, "subject": "fix: centralize SSRF-safe image fetches (#1741)", "disposition": "deferred", "reason": "security hardening — centralizes image fetches through url_safety.py (SSRF); fork has url_safety.py/multimodal.py (diverged) — hand-reconcile; untrusted-input surface: /sh-security-review on landing", "branch": "ssrf-hardening", "local_sha": null, "updated": "2026-07-16T18:46:37Z"}
|
||||
{"sha": "136d28e6", "pr": 1733, "subject": "fix: disable todos by default (#1733)", "disposition": "deferred", "reason": "global write_todos/TodoListMiddleware default-off with OPEN_SWE_ENABLE_TODOS opt-in; edits fork-customized prompt.py — small hand-merge; supersedes #1725", "branch": "prompt-tweaks", "local_sha": null, "updated": "2026-07-16T18:47:07Z"}
|
||||
{"sha": "3e8089c3", "pr": 1744, "subject": "fix: collapse git panel by default (#1744)", "disposition": "deferred", "reason": "collapse git panel by default (localStorage pref); post-reorg UI paths — remap to ui/src/components/agents", "branch": "dashboard-ui", "local_sha": null, "updated": "2026-07-16T18:47:08Z"}
|
||||
{"sha": "3e8089c3", "pr": 1744, "subject": "fix: collapse git panel by default (#1744)", "disposition": "deferred", "reason": "collapse git panel by default (localStorage pref); post-reorg UI paths — remap to ui/src/components/agents. Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick; ui/src/features/ now matches upstream.", "branch": "dashboard-ui", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "5077e2c7", "pr": 1735, "subject": "feat(open-swe): untagged two-party Slack replies + debounced interrupts (#1735)", "disposition": "deferred", "reason": "untagged two-party Slack replies + debounced interrupts (~620 LOC incl. e2e); rides fork-diverged Slack webhook stack; own branch + e2e validation", "branch": "slack-untagged-replies", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "f7d94ad3", "pr": 1721, "subject": "docs: add e2b to sandbox provider lists in AGENTS.md and CLAUDE.md (#1721)", "disposition": "wont-merge", "reason": "N/A — edits upstream AGENTS.md/CLAUDE.md, both fully fork-rewritten; E2B provider itself deferred (48217b68) — add a doc line if/when E2B lands", "branch": "", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "8b26819f", "pr": 1719, "subject": "fix: offload web tool results to sandbox (#1719)", "disposition": "deferred", "reason": "offloads large web tool results to sandbox files; touches fork-relevant web_search/http tools; check interplay with fork sandbox lifecycle", "branch": "tool-offloading", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "c34e04f4", "pr": 1742, "subject": "fix: defensive copy in get_reviewer_agent and get_chat_agent [closes #1584] (#1742)", "disposition": "deferred", "reason": "defensive copy of config in get_reviewer_agent/get_chat_agent; small correctness fix; dev has chat.py + reviewer.py (diverged) — likely near-clean pick", "branch": "small-tools", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "c34e04f4", "pr": 1742, "subject": "fix: defensive copy in get_reviewer_agent and get_chat_agent [closes #1584] (#1742)", "disposition": "deferred", "reason": "defensive copy of config in get_reviewer_agent/get_chat_agent; small correctness fix; dev has chat.py + reviewer.py (diverged) — likely near-clean pick. Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick; reviewer.py/chat.py factories unmoved.", "branch": "small-tools", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "4773b336", "pr": 1746, "subject": "chore: point basedpyright at uv's .venv (#1746)", "disposition": "wont-merge", "reason": "tooling — fork does not use basedpyright (lint stack is ruff); nothing to point at .venv", "branch": "", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "79df6b2f", "pr": 1748, "subject": "feat: add Linear issue search tool (#1748)", "disposition": "deferred", "reason": "additive linear_search_issues tool + utils/linear.py search helper; fork ships Linear tools — straightforward port; post-reorg test path remap", "branch": "small-tools", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "79df6b2f", "pr": 1748, "subject": "feat: add Linear issue search tool (#1748)", "disposition": "deferred", "reason": "additive linear_search_issues tool + utils/linear.py search helper; fork ships Linear tools — straightforward port; post-reorg test path remap. Re-triage (post-reorg 8356eb34 landed): test-path blocker removed — clean pick; tests/<domain>/ now matches upstream.", "branch": "small-tools", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "b7c5dbd6", "pr": 1747, "subject": "fix: simplify Slack run links (#1747)", "disposition": "deferred", "reason": "simplifies Slack run links; heavy churn on fork-diverged Slack context/prompt tests", "branch": "slack-tooling", "local_sha": null, "updated": "2026-07-16T18:46:52Z"}
|
||||
{"sha": "c69459ad", "pr": 1751, "subject": "fix: prefer LangSmith tools for trace links (#1751)", "disposition": "landed", "reason": "1-line prompt: prefer LangSmith tools for trace links; trivial but edits fork-customized prompt.py", "branch": "chore/upstream-easy-picks", "local_sha": null, "updated": "2026-07-16T19:15:42Z"}
|
||||
{"sha": "5cb2e2bb", "pr": 1750, "subject": "fix: add trace link to error banner (#1750)", "disposition": "landed", "reason": "adds trace link to error banner (13 lines); remap AgentThreadView.tsx path (fork: ui/src/components/agents/)", "branch": "chore/upstream-easy-picks", "local_sha": null, "updated": "2026-07-16T19:15:42Z"}
|
||||
{"sha": "697adaa7", "pr": 1752, "subject": "fix: update PyJWT to 2.13.0 (#1752)", "disposition": "wont-merge", "reason": "already in dev — uv.lock resolves PyJWT 2.13.0", "branch": "", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "22383033", "pr": 1758, "subject": "feat: inject extra JSON fields into sandbox create via env var (#1758)", "disposition": "deferred", "reason": "additive: extra JSON fields into sandbox create via env var (integrations/langsmith.py); fork langsmith.py diverged (proxy config) — small reconcile", "branch": "sandbox-config", "local_sha": null, "updated": "2026-07-16T18:47:08Z"}
|
||||
{"sha": "22383033", "pr": 1758, "subject": "feat: inject extra JSON fields into sandbox create via env var (#1758)", "disposition": "deferred", "reason": "additive: extra JSON fields into sandbox create via env var (integrations/langsmith.py); fork langsmith.py diverged (proxy config) — small reconcile. Re-triage (post-reorg 8356eb34 landed): near-clean — path-remap tax gone; remaining work is the additive reconcile against fork's diverged langsmith.py (integrations/ unmoved by reorg).", "branch": "sandbox-config", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "714ea4a2", "pr": 1759, "subject": "fix: clear basedpyright standard-mode type errors (#1759)", "disposition": "wont-merge", "reason": "tooling — basedpyright type-error cleanup across 123 post-reorg files; fork uses ruff and the pre-reorg layout", "branch": "", "local_sha": null, "updated": "2026-07-16T18:46:21Z"}
|
||||
{"sha": "e826864d", "pr": 1760, "subject": "feat: optional separate LangSmith key/endpoint for sandboxes (#1760)", "disposition": "deferred", "reason": "optional separate LangSmith key/endpoint for sandboxes; additive to langsmith.py + proxy auth; useful for fork LangSmith sandbox usage", "branch": "sandbox-config", "local_sha": null, "updated": "2026-07-16T18:47:08Z"}
|
||||
{"sha": "dd5b7bec", "pr": 1761, "subject": "fix: capitalize dashboard tool labels (#1761)", "disposition": "deferred", "reason": "capitalize dashboard tool labels; post-reorg UI paths; stacks on #1732 (d714586c)", "branch": "dashboard-ui", "local_sha": null, "updated": "2026-07-16T18:47:08Z"}
|
||||
{"sha": "e826864d", "pr": 1760, "subject": "feat: optional separate LangSmith key/endpoint for sandboxes (#1760)", "disposition": "deferred", "reason": "optional separate LangSmith key/endpoint for sandboxes; additive to langsmith.py + proxy auth; useful for fork LangSmith sandbox usage. Re-triage (post-reorg 8356eb34 landed): near-clean — path-remap tax gone; remaining work is the additive reconcile against fork's diverged langsmith.py + proxy auth (integrations/ unmoved).", "branch": "sandbox-config", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "dd5b7bec", "pr": 1761, "subject": "fix: capitalize dashboard tool labels (#1761)", "disposition": "deferred", "reason": "capitalize dashboard tool labels; post-reorg UI paths; stacks on #1732 (d714586c). Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick after #1732.", "branch": "dashboard-ui", "local_sha": null, "updated": "2026-07-17T00:00:00Z"}
|
||||
{"sha": "dccf6437", "pr": 1769, "subject": "fix: tighten sandbox config test types (#1769)", "disposition": "wont-merge", "reason": "test-only change in post-reorg path tests/sandbox/ — fork doesn't have this file (domain reorg #1726 deferred)", "branch": "", "local_sha": null, "updated": "2026-07-17T12:42:37Z"}
|
||||
{"sha": "c9a193e2", "pr": 1766, "subject": "chore(deps): bump mcp from 1.27.2 to 1.28.1 (#1766)", "disposition": "wont-merge", "reason": "indirect dependency bump (mcp); fork's own Dependabot handles these", "branch": "", "local_sha": null, "updated": "2026-07-17T12:42:37Z"}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
|||
| `71e3b818` | #1713 | fix: align reviewer eval with published findings (#1713) | Landed | cherry-picked (-x) in #201; reviewer.py + publish_review.py hand-reconciled into fork structure (kept sandbox/skills/middleware stack, kebab-case workflow, Bedrock eval default); publish_review cap param removed (tool-facing only) | fix/port-1713-reviewer-eval-alignment |
|
||||
| `092abafa` | #1717 | fix: enforce terse Slack tool messages (#1717) | Landed | cherry-picked (-x) in #197; one test conflict resolved by adopting upstream's test (fork prompt now contains upstream text); follow-up to landed #1681, not superseded by it | fix/port-1717-terse-slack-tool-msgs |
|
||||
| `92d63170` | #1720 | fix: separate review access from automatic reviews (#1720) | Landed | cherry-picked (-x) in #198 + fork-only auto-fix gates renamed to _is_repo_auto_review_enabled (kept opt-in-gated); opt-in list now gates only AUTOMATIC reviews — manual/re-review/watch/finding-replies open to any App-installed repo. Adam signed off 2026-07-16; /sh-security-review explicitly waived by Adam 2026-07-16 | fix/port-1720-review-access-split |
|
||||
| `8356eb34` | #1726 | refactor: organize repository by domain (#1726) | Landed | Adopted as a file-move exercise (fork content, upstream layout) on branch refactor/domain-reorg-adoption — gate-approved plan in docs/upstream-sync/domain-reorg/. New layout: agent/{graphs,runtime,api,review,resources}, agent/webhooks/*_routes.py (webapp.py split into api/ + per-source route modules; webapp.py now a shim), tests/<domain>/, ui/src/features/. Kills the per-pick path-remap tax and unblocks 8 deferred rows (#1732/#1761/#1744/#1748/#1742 clean; #1736/#1758/#1760 near-clean). | refactor/domain-reorg-adoption |
|
||||
| `129ddcf9` | #1728 | chore: include ripgrep in sandbox image (#1728) | Landed | 1-line Dockerfile add (ripgrep); dev image lacks it; trivial pick | chore/upstream-easy-picks |
|
||||
| `1ea03a43` | #1729 | feat: include Cargo in sandbox image (#1729) | Landed | 2-line Dockerfile add (Cargo); adopt with #1728 | chore/upstream-easy-picks |
|
||||
| `09eaf94c` | #1730 | fix: let admins interrupt runaway agents (#1730) | Landed | admin interrupt for runaway agents (dashboard route + UI); useful ops control; UI half on post-reorg ui/src/features — remap to ui/src/components/agents | feat/admin-thread-interrupt |
|
||||
|
|
@ -114,25 +115,24 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
|||
| `714ea4a2` | #1759 | fix: clear basedpyright standard-mode type errors (#1759) | Won't merge | tooling — basedpyright type-error cleanup across 123 post-reorg files; fork uses ruff and the pre-reorg layout | |
|
||||
| `dccf6437` | #1769 | fix: tighten sandbox config test types (#1769) | Won't merge | test-only change in post-reorg path tests/sandbox/ — fork doesn't have this file (domain reorg #1726 deferred) | |
|
||||
| `c9a193e2` | #1766 | chore(deps): bump mcp from 1.27.2 to 1.28.1 (#1766) | Won't merge | indirect dependency bump (mcp); fork's own Dependabot handles these | |
|
||||
| `8356eb34` | #1726 | refactor: organize repository by domain (#1726) | Deferred | FLAG-HUMAN: 298-file structural reorg (tests/<domain>/, ui/src/features/); chain head — every later upstream commit is written against this layout. Fork policy defers structural refactors (CLAUDE.md); adopting is a dedicated merge exercise. Until then, later picks need path remapping. | domain-reorg |
|
||||
| `83abea26` | #1724 | fix: accept natural-language Slack plan approvals (#1724) | Deferred | natural-language Slack plan approvals; touches fork-diverged plan-mode + Slack webhook stack (#130); post-reorg test paths need remap | plan-approval |
|
||||
| `ddbe457b` | #1727 | fix: restore GPT-5.5 as default model (#1727) | Deferred | restores GPT-5.5 default in options/team_settings; fork picker is Bedrock/Fireworks-only — rides the #1708 OpenAI-models product decision (27b0ddeb) | model-picker |
|
||||
| `30832d29` | #1731 | fix: preserve OpenAI Responses tool history (#1731) | Deferred | deletes SanitizeOpenAIResponsesMiddleware in favor of replay-history preservation in utils/model.py; supersedes deferred #1718 (35659177) — triage the pair together against fork-diverged middleware + model.py | openai-sanitize |
|
||||
| `1ea0e600` | #1736 | fix: bind cached GitHub tokens to users (#1736) | Deferred | FLAG-HUMAN: security fix — binds per-thread cached GitHub tokens to a user principal (closes cross-user token-reuse leak). Fork has github_token.py but not webhooks/common.py — hand-map; auth surface: GPT-4.1 cross-review + /sh-security-review on landing. Priority pick. | github-token-binding |
|
||||
| `1ea0e600` | #1736 | fix: bind cached GitHub tokens to users (#1736) | Deferred | FLAG-HUMAN: security fix — binds per-thread cached GitHub tokens to a user principal (closes cross-user token-reuse leak). Fork has github_token.py but not webhooks/common.py — hand-map; auth surface: GPT-4.1 cross-review + /sh-security-review on landing. Priority pick. Re-triage (post-reorg 8356eb34 landed): biggest beneficiary — agent/webhooks/common.py now exists, so the pick is near-clean against it; auth gates (GPT-4.1 cross-review + /sh-security-review) still apply. | github-token-binding |
|
||||
| `3fcb27ce` | #1737 | fix: bound reviewer diff fetching (#1737) | Deferred | bounds reviewer diff fetching + new fetch_review_diff tool; reviewer.py fork-diverged — reconcile like #1713 (71e3b818) | reviewer-misc |
|
||||
| `d714586c` | #1732 | fix: normalize dashboard label rendering (#1732) | Deferred | dashboard tool-label normalization; post-reorg ui/src/features paths — remap | dashboard-ui |
|
||||
| `d714586c` | #1732 | fix: normalize dashboard label rendering (#1732) | Deferred | dashboard tool-label normalization; post-reorg ui/src/features paths — remap. Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick; ui/src/features/ now matches upstream. | dashboard-ui |
|
||||
| `ef68c09b` | #1734 | fix: handle Slack DMs as mentions (#1734) | Deferred | treat Slack DMs as mentions; touches fork-diverged Slack webhook + e2e harness; post-reorg test paths | slack-tooling |
|
||||
| `26828ff9` | #1745 | chore: upgrade deepagents to 0.7.0a7 (#1745) | Deferred | deepagents 0.6.12 -> 0.7.0a7 alpha + [tool.uv] override-dependencies to bypass sandbox integrations <0.7.0 bound; fork is built on create_deep_agent — dedicated validation (unit + e2e) before adopting an alpha | deps |
|
||||
| `ef0ed5af` | #1741 | fix: centralize SSRF-safe image fetches (#1741) | Deferred | security hardening — centralizes image fetches through url_safety.py (SSRF); fork has url_safety.py/multimodal.py (diverged) — hand-reconcile; untrusted-input surface: /sh-security-review on landing | ssrf-hardening |
|
||||
| `136d28e6` | #1733 | fix: disable todos by default (#1733) | Deferred | global write_todos/TodoListMiddleware default-off with OPEN_SWE_ENABLE_TODOS opt-in; edits fork-customized prompt.py — small hand-merge; supersedes #1725 | prompt-tweaks |
|
||||
| `3e8089c3` | #1744 | fix: collapse git panel by default (#1744) | Deferred | collapse git panel by default (localStorage pref); post-reorg UI paths — remap to ui/src/components/agents | dashboard-ui |
|
||||
| `3e8089c3` | #1744 | fix: collapse git panel by default (#1744) | Deferred | collapse git panel by default (localStorage pref); post-reorg UI paths — remap to ui/src/components/agents. Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick; ui/src/features/ now matches upstream. | dashboard-ui |
|
||||
| `5077e2c7` | #1735 | feat(open-swe): untagged two-party Slack replies + debounced interrupts (#1735) | Deferred | untagged two-party Slack replies + debounced interrupts (~620 LOC incl. e2e); rides fork-diverged Slack webhook stack; own branch + e2e validation | slack-untagged-replies |
|
||||
| `8b26819f` | #1719 | fix: offload web tool results to sandbox (#1719) | Deferred | offloads large web tool results to sandbox files; touches fork-relevant web_search/http tools; check interplay with fork sandbox lifecycle | tool-offloading |
|
||||
| `c34e04f4` | #1742 | fix: defensive copy in get_reviewer_agent and get_chat_agent [closes #1584] (#1742) | Deferred | defensive copy of config in get_reviewer_agent/get_chat_agent; small correctness fix; dev has chat.py + reviewer.py (diverged) — likely near-clean pick | small-tools |
|
||||
| `79df6b2f` | #1748 | feat: add Linear issue search tool (#1748) | Deferred | additive linear_search_issues tool + utils/linear.py search helper; fork ships Linear tools — straightforward port; post-reorg test path remap | small-tools |
|
||||
| `c34e04f4` | #1742 | fix: defensive copy in get_reviewer_agent and get_chat_agent [closes #1584] (#1742) | Deferred | defensive copy of config in get_reviewer_agent/get_chat_agent; small correctness fix; dev has chat.py + reviewer.py (diverged) — likely near-clean pick. Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick; reviewer.py/chat.py factories unmoved. | small-tools |
|
||||
| `79df6b2f` | #1748 | feat: add Linear issue search tool (#1748) | Deferred | additive linear_search_issues tool + utils/linear.py search helper; fork ships Linear tools — straightforward port; post-reorg test path remap. Re-triage (post-reorg 8356eb34 landed): test-path blocker removed — clean pick; tests/<domain>/ now matches upstream. | small-tools |
|
||||
| `b7c5dbd6` | #1747 | fix: simplify Slack run links (#1747) | Deferred | simplifies Slack run links; heavy churn on fork-diverged Slack context/prompt tests | slack-tooling |
|
||||
| `22383033` | #1758 | feat: inject extra JSON fields into sandbox create via env var (#1758) | Deferred | additive: extra JSON fields into sandbox create via env var (integrations/langsmith.py); fork langsmith.py diverged (proxy config) — small reconcile | sandbox-config |
|
||||
| `e826864d` | #1760 | feat: optional separate LangSmith key/endpoint for sandboxes (#1760) | Deferred | optional separate LangSmith key/endpoint for sandboxes; additive to langsmith.py + proxy auth; useful for fork LangSmith sandbox usage | sandbox-config |
|
||||
| `dd5b7bec` | #1761 | fix: capitalize dashboard tool labels (#1761) | Deferred | capitalize dashboard tool labels; post-reorg UI paths; stacks on #1732 (d714586c) | dashboard-ui |
|
||||
| `22383033` | #1758 | feat: inject extra JSON fields into sandbox create via env var (#1758) | Deferred | additive: extra JSON fields into sandbox create via env var (integrations/langsmith.py); fork langsmith.py diverged (proxy config) — small reconcile. Re-triage (post-reorg 8356eb34 landed): near-clean — path-remap tax gone; remaining work is the additive reconcile against fork's diverged langsmith.py (integrations/ unmoved by reorg). | sandbox-config |
|
||||
| `e826864d` | #1760 | feat: optional separate LangSmith key/endpoint for sandboxes (#1760) | Deferred | optional separate LangSmith key/endpoint for sandboxes; additive to langsmith.py + proxy auth; useful for fork LangSmith sandbox usage. Re-triage (post-reorg 8356eb34 landed): near-clean — path-remap tax gone; remaining work is the additive reconcile against fork's diverged langsmith.py + proxy auth (integrations/ unmoved). | sandbox-config |
|
||||
| `dd5b7bec` | #1761 | fix: capitalize dashboard tool labels (#1761) | Deferred | capitalize dashboard tool labels; post-reorg UI paths; stacks on #1732 (d714586c). Re-triage (post-reorg 8356eb34 landed): path-remap blocker removed — clean pick after #1732. | dashboard-ui |
|
||||
|
||||
_Maintenance: after a `git sync`, add new `dev..upstream/main` SHAs as **Untriaged** (edit `triage.jsonl`) and bump "Last synced". A successful `git cherry-pick -x` auto-moves the row to **Landed** via the `post-commit` journal + `make triage-reconcile`._
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from uuid import UUID
|
|||
from langchain_anthropic import ChatAnthropic
|
||||
from langsmith.schemas import Example, Run
|
||||
|
||||
from agent.reviewer_findings import REVIEW_FINDING_CAP
|
||||
from agent.review.findings import REVIEW_FINDING_CAP
|
||||
|
||||
JUDGE_MODEL = "claude-opus-4-5"
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ from langgraph_sdk import get_client
|
|||
from langsmith import Client, aevaluate
|
||||
from langsmith.schemas import Example
|
||||
|
||||
from agent.reviewer_eval_store import _EXPERIMENT_URL_RE, _LOG_TAIL_CHARS
|
||||
from agent.reviewer_findings import REVIEW_FINDING_CAP
|
||||
from agent.review.eval_store import _EXPERIMENT_URL_RE, _LOG_TAIL_CHARS
|
||||
from agent.review.findings import REVIEW_FINDING_CAP
|
||||
from evals.reviewer.judge import aggregate_pr, judge_match
|
||||
from evals.reviewer.store_reporter import StoreReporter, is_enabled
|
||||
from evals.reviewer.target import (
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from typing import Any
|
|||
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
from agent.reviewer_eval_store import (
|
||||
from agent.review.eval_store import (
|
||||
_HEARTBEAT_INTERVAL_SECONDS,
|
||||
EVALS_NAMESPACE,
|
||||
REVIEWER_EVAL_KEY,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from typing import Any, Literal, cast
|
|||
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
from agent.reviewer_findings import (
|
||||
from agent.review.findings import (
|
||||
REVIEW_FINDING_CAP,
|
||||
REVIEWER_EVAL_PUBLICATION_KEY,
|
||||
Finding,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
"python_version": "3.12",
|
||||
"api_version": "0.10.0rc3",
|
||||
"graphs": {
|
||||
"agent": "agent.server:traced_agent",
|
||||
"reviewer": "agent.reviewer:traced_reviewer_agent",
|
||||
"analyzer": "agent.analyzer:traced_analyzer",
|
||||
"chat": "agent.chat:traced_chat_agent",
|
||||
"scheduler": "agent.scheduler:get_scheduler",
|
||||
"ci_monitor": "agent.ci_monitor:get_ci_monitor"
|
||||
"agent": "agent.graphs.agent:traced_agent",
|
||||
"reviewer": "agent.graphs.reviewer:traced_reviewer_agent",
|
||||
"analyzer": "agent.graphs.analyzer:traced_analyzer",
|
||||
"chat": "agent.graphs.chat:traced_chat_agent",
|
||||
"scheduler": "agent.graphs.scheduler:get_scheduler",
|
||||
"ci_monitor": "agent.graphs.ci_monitor:get_ci_monitor"
|
||||
},
|
||||
"dependencies": [
|
||||
"."
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from agent import webapp
|
||||
from agent.webhooks import common as webhook_common
|
||||
|
||||
|
||||
def _pr_payload(*, state: str, merged: bool = False, draft: bool = False) -> dict[str, Any]:
|
||||
|
|
@ -22,23 +22,25 @@ def _pr_payload(*, state: str, merged: bool = False, draft: bool = False) -> dic
|
|||
|
||||
|
||||
def test_pr_state_from_payload_merged() -> None:
|
||||
assert webapp._pr_state_from_payload(_pr_payload(state="closed", merged=True)) == "merged"
|
||||
assert (
|
||||
webhook_common._pr_state_from_payload(_pr_payload(state="closed", merged=True)) == "merged"
|
||||
)
|
||||
|
||||
|
||||
def test_pr_state_from_payload_closed() -> None:
|
||||
assert webapp._pr_state_from_payload(_pr_payload(state="closed")) == "closed"
|
||||
assert webhook_common._pr_state_from_payload(_pr_payload(state="closed")) == "closed"
|
||||
|
||||
|
||||
def test_pr_state_from_payload_draft() -> None:
|
||||
assert webapp._pr_state_from_payload(_pr_payload(state="open", draft=True)) == "draft"
|
||||
assert webhook_common._pr_state_from_payload(_pr_payload(state="open", draft=True)) == "draft"
|
||||
|
||||
|
||||
def test_pr_state_from_payload_open() -> None:
|
||||
assert webapp._pr_state_from_payload(_pr_payload(state="open")) == "open"
|
||||
assert webhook_common._pr_state_from_payload(_pr_payload(state="open")) == "open"
|
||||
|
||||
|
||||
def test_pr_state_from_payload_missing_pull_request() -> None:
|
||||
assert webapp._pr_state_from_payload({}) is None
|
||||
assert webhook_common._pr_state_from_payload({}) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -54,8 +56,8 @@ async def test_update_agent_thread_pr_state_updates_matching_thread() -> None:
|
|||
)
|
||||
fake_client.threads.update = AsyncMock()
|
||||
|
||||
with patch("agent.webapp.get_client", return_value=fake_client):
|
||||
await webapp.update_agent_thread_pr_state(_pr_payload(state="closed"))
|
||||
with patch("agent.webhooks.common.get_client", return_value=fake_client):
|
||||
await webhook_common.update_agent_thread_pr_state(_pr_payload(state="closed"))
|
||||
|
||||
fake_client.threads.search.assert_awaited_once()
|
||||
fake_client.threads.update.assert_awaited_once()
|
||||
|
|
@ -71,8 +73,8 @@ async def test_update_agent_thread_pr_state_skips_reviewer_threads() -> None:
|
|||
)
|
||||
fake_client.threads.update = AsyncMock()
|
||||
|
||||
with patch("agent.webapp.get_client", return_value=fake_client):
|
||||
await webapp.update_agent_thread_pr_state(_pr_payload(state="closed"))
|
||||
with patch("agent.webhooks.common.get_client", return_value=fake_client):
|
||||
await webhook_common.update_agent_thread_pr_state(_pr_payload(state="closed"))
|
||||
|
||||
fake_client.threads.update.assert_not_called()
|
||||
|
||||
|
|
@ -85,7 +87,7 @@ async def test_update_agent_thread_pr_state_noop_when_state_unchanged() -> None:
|
|||
)
|
||||
fake_client.threads.update = AsyncMock()
|
||||
|
||||
with patch("agent.webapp.get_client", return_value=fake_client):
|
||||
await webapp.update_agent_thread_pr_state(_pr_payload(state="closed", merged=True))
|
||||
with patch("agent.webhooks.common.get_client", return_value=fake_client):
|
||||
await webhook_common.update_agent_thread_pr_state(_pr_payload(state="closed", merged=True))
|
||||
|
||||
fake_client.threads.update.assert_not_called()
|
||||
|
|
@ -191,7 +191,7 @@ async def test_save_plan_reads_markdown_file_from_sandbox(
|
|||
|
||||
|
||||
def test_plan_routes_registered() -> None:
|
||||
from agent.webapp import app
|
||||
from agent.api.app import app
|
||||
|
||||
paths = set()
|
||||
for route in app.routes:
|
||||
|
|
@ -746,7 +746,7 @@ async def test_approve_plan_hands_edited_plan_to_agent(
|
|||
|
||||
|
||||
def test_plan_update_route_registered() -> None:
|
||||
from agent.webapp import app
|
||||
from agent.api.app import app
|
||||
|
||||
paths = set()
|
||||
for route in app.routes:
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue