Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ ci:
- ".github/workflows/**"
- ".github/**"

task:
- changed-files:
- any-glob-to-any-file:
- ".github/workflows/**"

platform:
- changed-files:
- any-glob-to-any-file:
Expand Down
40 changes: 40 additions & 0 deletions .github/scripts/pr-priority-triage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { POLICY } from "./label-policy-check.js"

export const TRIAGE_MARKER = "<!-- pawwork-pr-priority-triage-v1 -->"

export const PRIORITY_LABELS = ["P0", "P1", "P2", "P3"]
export const TYPE_LABELS = POLICY.types

const LOW_RISK_GLOBS = [
"docs/**",
Expand All @@ -11,6 +14,8 @@ const LOW_RISK_GLOBS = [
]

const USER_PATH_GLOBS = ["packages/app/src/**", "packages/desktop-electron/src/**"]
const RELEASE_BUMP_GLOBS = ["packages/desktop-electron/package.json", "bun.lock"]
const RELEASE_BUMP_REQUIRED_PATH = "packages/desktop-electron/package.json"

function escapeRegex(value) {
return value.replace(/[.+^${}()|[\]\\]/g, "\\$&")
Expand Down Expand Up @@ -114,3 +119,38 @@ export function planPriorityLabels(paths, labels = []) {
removeLabels: existingPriorities.filter((label) => label !== desiredPriority),
}
}

export function classifyPullRequestType(paths) {
Comment thread
Astro-Han marked this conversation as resolved.
const normalized = paths.map((path) => path.replace(/\\/g, "/"))

if (
normalized.length > 0 &&
normalized.includes(RELEASE_BUMP_REQUIRED_PATH) &&
normalized.every((path) => matchesAny(path, RELEASE_BUMP_GLOBS))
) {
return "task"
}

return undefined
}

/**
* @param {string[]} paths
* @param {string[]} labels
*/
export function planPullRequestLabels(paths, labels = []) {
const priorityPlan = planPriorityLabels(paths, labels)
const labelSet = new Set(labels)
const existingTypes = TYPE_LABELS.filter((label) => labelSet.has(label))
const inferredType = existingTypes.length === 0 ? classifyPullRequestType(paths) : undefined
const addLabels = [...priorityPlan.addLabels]

if (inferredType && !labelSet.has(inferredType)) {
addLabels.push(inferredType)
}

return {
...priorityPlan,
addLabels,
}
}
6 changes: 3 additions & 3 deletions .github/workflows/pr-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: pr-triage

on:
pull_request_target:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, labeled, unlabeled]
branches: [dev]

concurrency:
Expand Down Expand Up @@ -41,7 +41,7 @@ jobs:
const policy = await import(
pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, ".github/scripts/label-policy-check.js")).href,
)
const { buildPriorityReview, planPriorityLabels, TRIAGE_MARKER } = triage
const { buildPriorityReview, planPullRequestLabels, TRIAGE_MARKER } = triage
const pull_number = Number(process.env.PR_NUMBER)
const owner = context.repo.owner
const repo = context.repo.repo
Expand All @@ -60,7 +60,7 @@ jobs:
issue_number: pull_number,
per_page: 100,
})
const labelPlan = planPriorityLabels(paths, currentLabels.map((label) => label.name))
const labelPlan = planPullRequestLabels(paths, currentLabels.map((label) => label.name))

if (labelPlan.addLabels.length > 0) {
await github.rest.issues.addLabels({
Expand Down
8 changes: 7 additions & 1 deletion packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useSpring } from "@opencode-ai/ui/motion-spring"
import { isWorkInFlightStatus } from "@opencode-ai/ui/util/session-status"
import { useNavigate, useParams } from "@solidjs/router"
import { createEffect, on, Component, For, Show, createMemo, createSignal } from "solid-js"
import { createStore } from "solid-js/store"
import { useLocal } from "@/context/local"
Expand Down Expand Up @@ -38,7 +39,8 @@ import { pickAttachments } from "./prompt-input/pick-attachments"
import { ACCEPTED_FILE_TYPES } from "./prompt-input/files"
import { promptLength } from "./prompt-input/history"
import type { PromptStore } from "./prompt-input/store-types"
import { createPromptSubmit, type FollowupDraft } from "./prompt-input/submit"
import type { FollowupDraft } from "./prompt-input/followup-draft"
import { createPromptSubmit } from "./prompt-input/submit"
import { PromptPopover } from "./prompt-input/slash-popover"
import { PromptContextItems } from "./prompt-input/context-items"
import { PromptImageAttachments } from "./prompt-input/image-attachments"
Expand Down Expand Up @@ -392,6 +394,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
return permission.isAutoAccepting(id, sdk.directory)
})
const navigate = useNavigate()
const routeParams = useParams()

const { abort, handleSubmit } = createPromptSubmit({
sessionID: activeSessionID,
Expand Down Expand Up @@ -419,6 +423,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onQueue: props.onQueue,
onAbort: props.onAbort,
onSubmit: props.onSubmit,
navigate,
routeParams: () => routeParams,
})

createEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { createPinnedDraftOwner } from "./pinned-draft"
import { buildRequestParts } from "./build-request-parts"
import { captureCommentMentions } from "./mention-metadata"

let detectSubmitOwnership: typeof import("./submit").detectSubmitOwnership
let detectSubmitOwnership: any

beforeAll(async () => {
// submit.ts pulls in router/sdk/etc. at module scope; mock bare minimum.
Expand Down
16 changes: 16 additions & 0 deletions packages/app/src/components/prompt-input/followup-draft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ContextItem, Prompt } from "@/context/prompt"

export type FollowupDraft = {
sessionID: string
sessionDirectory: string
prompt: Prompt
context: (ContextItem & { key: string })[]
agent: string
model: { providerID: string; modelID: string }
locale?: string
variant?: string
}

export function followupCommandText(draft: FollowupDraft) {
return draft.prompt.map((part) => ("content" in part ? part.content : "")).join("")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"
import { createPortableDraftOwner } from "./portable-draft"
import { createPinnedDraftOwner } from "./pinned-draft"

// Submit module pulls in router/sdk/etc.; mock the bare minimum so a pure-function
// import does not blow up on solid-js server-side guards.
let detectSubmitOwnership: typeof import("./submit").detectSubmitOwnership
type SubmitOwnership = import("./submit").SubmitOwnership
let detectSubmitOwnership: any
type SubmitOwnership = any

beforeAll(async () => {
mock.module("@solidjs/router", () => ({
Expand Down
55 changes: 52 additions & 3 deletions packages/app/src/components/prompt-input/submit.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"
import type { Prompt } from "@/context/prompt"
import type {
createPromptSubmit as createPromptSubmitType,
sendFollowupDraft as sendFollowupDraftType,
} from "./submit"

let createPromptSubmit: typeof import("./submit").createPromptSubmit
let sendFollowupDraft: typeof import("./submit").sendFollowupDraft
type PromptSubmitInput = Parameters<typeof createPromptSubmitType>[0]
type PromptSubmit = ReturnType<typeof createPromptSubmitType>

let createPromptSubmit: (input: PromptSubmitInput) => PromptSubmit
let sendFollowupDraft: typeof sendFollowupDraftType

const createdClients: string[] = []
const createdSessions: string[] = []
Expand Down Expand Up @@ -166,7 +173,7 @@ beforeAll(async () => {
directory: "/repo/main",
client: rootClient,
url: "http://localhost:4096",
createClient(opts: any) {
createClient(opts: { directory: string }) {
return clientFor(opts.directory)
},
}
Expand Down Expand Up @@ -281,6 +288,8 @@ describe("prompt submit worktree selection", () => {
params = { id: "session-route" }
const aborts: string[] = []
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-visible",
isNewSession: () => false,
info: () => ({ id: "session-visible" }),
Expand Down Expand Up @@ -313,6 +322,8 @@ describe("prompt submit worktree selection", () => {
const aborts: string[] = []
const submits: string[] = []
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-visible",
isNewSession: () => false,
info: () => ({ id: "session-visible" }),
Expand Down Expand Up @@ -349,6 +360,8 @@ describe("prompt submit worktree selection", () => {
commandDefinitions.push({ name: "summarize" })
promptValue = [{ type: "text", content: "/summarize this", start: 0, end: 15 }]
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-existing",
isNewSession: () => false,
info: () => ({ id: "session-existing" }),
Expand Down Expand Up @@ -378,6 +391,8 @@ describe("prompt submit worktree selection", () => {
commandsReady = false
promptValue = [{ type: "text", content: "/bin/ls", start: 0, end: 7 }]
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-existing",
isNewSession: () => false,
info: () => ({ id: "session-existing" }),
Expand Down Expand Up @@ -407,6 +422,8 @@ describe("prompt submit worktree selection", () => {
const aborts: string[] = []
const submits: string[] = []
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-visible",
isNewSession: () => false,
info: () => ({ id: "session-visible" }),
Expand Down Expand Up @@ -440,6 +457,8 @@ describe("prompt submit worktree selection", () => {
params = { id: "session-visible" }
promptValue = [{ type: "text", content: "", start: 0, end: 0 }]
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-visible",
isNewSession: () => false,
info: () => ({ id: "session-visible" }),
Expand All @@ -465,6 +484,8 @@ describe("prompt submit worktree selection", () => {

test("reads the latest worktree accessor value per submit", async () => {
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => undefined,
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -502,6 +523,8 @@ describe("prompt submit worktree selection", () => {

test("applies auto-accept to newly created sessions", async () => {
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => undefined,
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -532,6 +555,8 @@ describe("prompt submit worktree selection", () => {
variant = "high"

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-1" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -565,6 +590,8 @@ describe("prompt submit worktree selection", () => {
params = { id: "session-route" }

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
sessionID: () => "session-visible",
isNewSession: () => false,
info: () => ({ id: "session-visible" }),
Expand Down Expand Up @@ -592,6 +619,8 @@ describe("prompt submit worktree selection", () => {

test("seeds new sessions before optimistic prompts are added", async () => {
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => undefined,
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -624,6 +653,8 @@ describe("prompt submit worktree selection", () => {
promptValue = [{ type: "text", content: "run tests", start: 0, end: 9 }]
promptAsyncFailure = new Error("send failed")
const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => undefined,
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -653,6 +684,8 @@ describe("prompt submit worktree selection", () => {
currentIntl = "pt-BR"

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -680,6 +713,8 @@ describe("prompt submit worktree selection", () => {
const queued: Array<Record<string, unknown>> = []

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -709,6 +744,8 @@ describe("prompt submit worktree selection", () => {
promptValue = [{ type: "text", content: "/summarize this", start: 0, end: 15 }]

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -770,6 +807,8 @@ describe("prompt submit worktree selection", () => {
}

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => undefined,
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -807,6 +846,8 @@ describe("Path D — marked TextPart routes through session.command", () => {
}]

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -840,6 +881,8 @@ describe("Path D — marked TextPart routes through session.command", () => {
]

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -874,6 +917,8 @@ describe("Path D — marked TextPart routes through session.command", () => {
}]

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -910,6 +955,8 @@ describe("Legacy fallback boundary (no marked TextPart)", () => {
]

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down Expand Up @@ -941,6 +988,8 @@ describe("Legacy fallback boundary (no marked TextPart)", () => {
]

const submit = createPromptSubmit({
navigate: (path) => navigateImpl(path),
routeParams: () => params,
info: () => ({ id: "session-existing" }),
imageAttachments: () => [],
commentCount: () => 0,
Expand Down
Loading
Loading