Seed workspace creation from task source context

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-12 23:23:36 -07:00
co-authored by Orca
parent 0eac0e384f
commit 7db57daa14
3 changed files with 85 additions and 4 deletions
@@ -57,7 +57,7 @@ Done means: users can tell where Tasks are being fetched from, and changing exec
## 4. New Workspace Modal, Start Work, And Workspace Creation
- [x] Split “start from task/issue/PR” source context from workspace run target in New Workspace modal state.
- [~] When opening the New Workspace modal from Tasks, preselect the logical project and a reasonable run host without treating the source repo as the only execution target.
- [x] When opening the New Workspace modal from Tasks, preselect the logical project and a reasonable run host without treating the source repo as the only execution target.
- [x] Ensure pending worktree/workspace creation persists both the source task context and selected run host.
- [x] Make “create workspace on host” only show hosts where the project has a ProjectHostSetup.
- [x] Keep “add project to host” as the path for making a project available on another host; do not present workspace creation as if Orca is responsible for bootstrapping repos/dependencies.
@@ -189,6 +189,7 @@ Done means: reviewers and future agents can understand the vision, implementatio
- [x] 2026-06-12: Added Linear/Jira Settings account-scope diagnostics showing whether provider credentials and account checks are owned by the local desktop client or the selected remote server; verified with provider account-scope tests and web typecheck.
- [x] 2026-06-12: Added repo-backed Tasks empty-state copy that distinguishes missing project sources from no matching GitHub/GitLab work, then verified with focused empty-state tests and web typecheck.
- [x] 2026-06-12: Scoped Jira Tasks drawer/list cache reconciliation by Jira task source context and site id so same-key issues from different hosts/accounts cannot replace each other; verified with focused cache selector tests, targeted oxlint, and full typecheck.
- [x] 2026-06-12: Seeded New Workspace run-target selection from the incoming task source context so Linear/Jira starts preselect the logical project/source host instead of the ambient active repo; verified with a focused composer host-boundary test, targeted oxlint, and full typecheck.
- [x] 2026-06-12: Fed Tasks source-context diagnostics from the shared execution-host registry so the source chip now reports SSH availability and blocked/incompatible remote-server hosts; verified with summary tests and web typecheck.
- [x] 2026-06-12: Extended Linear/Jira Tasks source diagnostics to include the account-owning host plus blocked remote-server availability, keeping account source separate from workspace run host; verified with summary tests and web typecheck.
- [x] 2026-06-12: Started the user-facing Host copy audit by replacing stale “remote project” and “Remote SSH” UI labels with project-on-host / SSH-host language; verified with web typecheck.
@@ -1,6 +1,7 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { resolveInitialWorkspaceRunSeed } from './useComposerState'
const HOOK_SOURCE = readFileSync(join(__dirname, 'useComposerState.ts'), 'utf8')
@@ -38,4 +39,45 @@ describe('useComposerState host-context boundaries', () => {
expect(section).toContain('repoId: runRepo.id')
expect(section).not.toContain('repoId: repoForItem.id')
})
it('seeds initial workspace run target from the task source context', () => {
expect(
resolveInitialWorkspaceRunSeed({
initialTaskSourceContext: {
projectId: 'logical-project',
hostId: 'ssh:builder',
projectHostSetupId: 'setup-builder'
}
})
).toEqual({
projectId: 'logical-project',
hostId: 'ssh:builder',
projectHostSetupId: 'setup-builder'
})
expect(
resolveInitialWorkspaceRunSeed({
draftProjectId: 'draft-project',
draftHostId: 'local',
draftProjectHostSetupId: 'setup-local',
initialTaskSourceContext: {
projectId: 'logical-project',
hostId: 'ssh:builder',
projectHostSetupId: 'setup-builder'
}
})
).toEqual({
projectId: 'draft-project',
hostId: 'local',
projectHostSetupId: 'setup-local'
})
const section = sourceBetween(HOOK_SOURCE, 'const initialRunSeed', 'const [internalRepoId')
expect(section).toContain('resolveInitialWorkspaceRunSeed')
expect(section).toContain('initialTaskSourceContext')
expect(section).toContain('projectId: initialRunSeed.projectId')
expect(section).toContain('hostId: initialRunSeed.hostId')
expect(section).toContain('projectHostSetupId: initialRunSeed.projectHostSetupId')
})
})
+41 -3
View File
@@ -103,6 +103,7 @@ import {
type NewWorkspaceProjectOption
} from '@/lib/new-workspace-project-options'
import { buildExecutionHostRegistry } from '../../../shared/execution-host-registry'
import { normalizeExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host'
import { getHostDisplayLabelOverrides } from '../../../shared/host-setting-overrides'
import { queueNewWorkspaceTerminalFocus } from '@/lib/new-workspace-terminal-focus'
import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner'
@@ -281,6 +282,34 @@ export type UseComposerStateResult = {
createDisabled: boolean
}
export type InitialWorkspaceRunSeedInput = {
draftProjectId?: string | null
draftHostId?: string | null
draftProjectHostSetupId?: string | null
initialTaskSourceContext?: Pick<
TaskSourceContext,
'projectId' | 'hostId' | 'projectHostSetupId'
> | null
}
export function resolveInitialWorkspaceRunSeed({
draftProjectId,
draftHostId,
draftProjectHostSetupId,
initialTaskSourceContext
}: InitialWorkspaceRunSeedInput): {
projectId: string | null
hostId: ExecutionHostId | null
projectHostSetupId: string | null
} {
return {
projectId: draftProjectId ?? initialTaskSourceContext?.projectId ?? null,
hostId: normalizeExecutionHostId(draftHostId ?? initialTaskSourceContext?.hostId),
projectHostSetupId:
draftProjectHostSetupId ?? initialTaskSourceContext?.projectHostSetupId ?? null
}
}
// Why: both the full-page TaskPage composer and the Cmd+J modal can be
// mounted simultaneously. Without instance scoping, a single native file
// drop fires every subscriber and duplicates attachments/prompt edits across
@@ -363,6 +392,15 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
const draftProjectHostSetupId = persistDraft
? (newWorkspaceDraft?.projectHostSetupId ?? null)
: null
// Why: Tasks can start work from Linear/Jira source contexts that are not
// repo-backed. Seed the run target from the logical project/source host so
// the modal does not silently fall back to the ambient active repo.
const initialRunSeed = resolveInitialWorkspaceRunSeed({
draftProjectId,
draftHostId,
draftProjectHostSetupId,
initialTaskSourceContext
})
const resolvedInitialWorkspaceStatus = useMemo(
() =>
initialWorkspaceStatus && isWorkspaceStatusId(initialWorkspaceStatus, workspaceStatuses)
@@ -378,9 +416,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
draftRepoId,
initialRepoId,
activeRepoId,
projectId: draftProjectId,
hostId: draftHostId,
projectHostSetupId: draftProjectHostSetupId,
projectId: initialRunSeed.projectId,
hostId: initialRunSeed.hostId,
projectHostSetupId: initialRunSeed.projectHostSetupId,
focusedHostScope: workspaceHostScope
})