Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8bca511
chore(sdk): regen client for POST /session/:sessionID/tool/respond
Astro-Han May 19, 2026
10909a7
feat(question): switch dock to message-stream selector and POST /tool…
Astro-Han May 19, 2026
1dd8940
feat(question): default PAWWORK_QUESTION_TOOL_EXTERNAL_RESULT on
Astro-Han May 19, 2026
075801f
refactor(llm): use ExternalResult.hasPending for silent-timeout guard
Astro-Han May 19, 2026
181c928
refactor(opencode): delete legacy Question namespace, routes, blockers
Astro-Han May 19, 2026
931186c
refactor(app): delete question-recovery, fallback, refetch, reconcile
Astro-Han May 19, 2026
e70a743
refactor(session): collapse session.abort(mode) to single signature
Astro-Han May 19, 2026
ac24538
chore(core): delete PAWWORK_QUESTION_TOOL_EXTERNAL_RESULT flag
Astro-Han May 19, 2026
2b92a47
test(e2e): drive question dock via real tool runner, drop legacy seed…
Astro-Han May 19, 2026
0f8ff34
fix(question): swallow void-promise rejections and skip empty snapshots
Astro-Han May 19, 2026
0a90193
fix(question): dock walks session tree so parent page shows child age…
Astro-Han May 19, 2026
6ed20d8
fix(question): notify on background question via message.part.updated
Astro-Han May 19, 2026
4b439d6
fix(question): align notification suppression with dock tree walk, pr…
Astro-Han May 19, 2026
06e09a9
docs(question): note the running-only transition assumption in dedup …
Astro-Han May 19, 2026
974241b
fix(question): hydrate pending external-result questions on bootstrap
Astro-Han May 19, 2026
ff61a4a
chore: remove pawwork-keep-ours merge driver
Astro-Han May 20, 2026
d9d314f
chore: merge dev into PR B (#741 #765 #768 + diagnostics)
Astro-Han May 20, 2026
e255aa4
fix(question): close abort vs respond race in external-result registry
Astro-Han May 20, 2026
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
27 changes: 0 additions & 27 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,3 @@
# `core.autocrlf` flips line endings on checkout and breaks the snapshot
# fixture in `packages/opencode/test/server/models-snapshot-fixture.ts`.
packages/opencode/src/provider/models-snapshot.js text eol=lf

# PawWork UI carve-out (issue #440 slice #01).
# These paths are permanently forked from upstream opencode and must never
# be overwritten by an upstream sync. When both sides modify a file on these
# paths, git invokes the named driver instead of the default three-way merge.
#
# The driver is registered with:
# git config merge.pawwork-keep-ours.driver "true"
# which exits 0 and leaves the worktree file (= HEAD = our version) intact.
# NOTE: the driver only fires when both sides change a file; a one-sided
# upstream change will still clean-merge. Always review the upstream-sync
# PR diff to confirm no carve-out paths were silently updated.
# Run `packages/ui/script/verify-merge-driver.sh` on a fresh clone to confirm
# the driver is active before performing an upstream sync.
packages/ui/** merge=pawwork-keep-ours
packages/app/src/app.tsx merge=pawwork-keep-ours
packages/app/src/components/** merge=pawwork-keep-ours
packages/app/src/context/** merge=pawwork-keep-ours
packages/app/src/index.css merge=pawwork-keep-ours
packages/app/src/pages/** merge=pawwork-keep-ours
packages/app/src/styles/** merge=pawwork-keep-ours
packages/app/src/theme/** merge=pawwork-keep-ours
packages/app/src/i18n/** merge=pawwork-keep-ours
packages/app/src/utils/** merge=pawwork-keep-ours
packages/app/index.html merge=pawwork-keep-ours
packages/desktop-electron/src/renderer/index.html merge=pawwork-keep-ours
packages/desktop-electron/src/renderer/loading.html merge=pawwork-keep-ours
47 changes: 28 additions & 19 deletions packages/app/e2e/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,32 +749,44 @@ export async function seedSessionQuestion(
"After calling the tool, wait for the user response.",
].join("\n")

// Poll session messages for a running `question` tool part with
// externalResultReady=true; the new tool flow drives the dock via
// session messages, not a separate Question.ask state machine.
const result = await seed({
sdk,
sessionID: input.sessionID,
prompt: text,
timeout: 30_000,
probe: async () => {
const list = await sdk.question.list().then((x) => x.data ?? [])
return list.find((item) => item.sessionID === input.sessionID && item.questions[0]?.header === first.header)
const messages = await sdk.session.messages({ sessionID: input.sessionID, limit: 50 }).then((x) => x.data ?? [])
for (const message of messages) {
for (const part of message.parts) {
if (part.type !== "tool") continue
if (part.tool !== "question") continue
if (!("state" in part) || !part.state || typeof part.state !== "object") continue
const state = part.state as { status?: string; input?: { questions?: Array<{ header?: string }> }; metadata?: { externalResultReady?: boolean } }
if (state.status !== "running") continue
if (state.metadata?.externalResultReady !== true) continue
const header = state.input?.questions?.[0]?.header
if (header !== first.header) continue
return { id: `${part.messageID}:${part.callID}`, messageID: part.messageID, callID: part.callID }
}
}
return undefined
},
})

if (!result) {
const [questions, status] = await Promise.all([
sdk.question.list().then((x) => x.data ?? []).catch((error) => ({ error: String(error) })),
sdk.session.status().then((x) => x.data ?? {}).catch((error) => ({ error: String(error) })),
])
const status = await sdk.session.status().then((x) => x.data ?? {}).catch((error) => ({ error: String(error) }))
throw new Error(
`Timed out seeding question request: ${JSON.stringify({
sessionID: input.sessionID,
wantedHeader: first.header,
questions,
status,
})}`,
)
}
return { id: result.id }
return result
}

export async function seedSessionTask(
Expand Down Expand Up @@ -836,19 +848,16 @@ export async function seedSessionTask(
}

export async function clearSessionDockSeed(sdk: ReturnType<typeof createSdk>, sessionID: string) {
const [questions, permissions] = await Promise.all([
sdk.question.list().then((x) => x.data ?? []),
sdk.permission.list().then((x) => x.data ?? []),
])

await Promise.all([
...questions
.filter((item) => item.sessionID === sessionID)
.map((item) => sdk.question.reject({ requestID: item.id }).catch(() => undefined)),
...permissions
const permissions = await sdk.permission.list().then((x) => x.data ?? [])
await Promise.all(
permissions
.filter((item) => item.sessionID === sessionID)
.map((item) => sdk.permission.reply({ requestID: item.id, reply: "reject" }).catch(() => undefined)),
])
)

// The question tool resolves naturally when its session is aborted (the
// ExternalResult Deferred is rejected). Cleanup runs from withDockSession
// which removes the session — no extra dismiss call needed.

return true
}
Expand Down
Loading