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
1 change: 1 addition & 0 deletions apps/ios/ADE/Models/RemoteModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ enum LaneIcon: String, Codable, Equatable {

struct LaneSummary: Codable, Identifiable, Equatable {
var id: String
var projectId: String? = nil
var name: String
var description: String?
var laneType: String
Expand Down
6 changes: 5 additions & 1 deletion apps/ios/ADE/Services/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ final class DatabaseService {

private struct LaneRow {
let id: String
let projectId: String?
let name: String
let description: String?
let laneType: String
Expand Down Expand Up @@ -469,7 +470,8 @@ final class DatabaseService {
ps.ahead,
ps.behind,
ps.remote_behind,
ps.rebase_in_progress
ps.rebase_in_progress,
l.project_id
from lanes l
left join lane_state_snapshots s on s.lane_id = l.id
left join lane_state_snapshots ps on ps.lane_id = l.parent_lane_id
Expand All @@ -486,6 +488,7 @@ final class DatabaseService {
}) { statement in
LaneRow(
id: stringValue(statement, index: 0) ?? "",
projectId: stringValue(statement, index: 26),
name: stringValue(statement, index: 1) ?? "",
description: stringValue(statement, index: 2),
laneType: stringValue(statement, index: 3) ?? "worktree",
Expand Down Expand Up @@ -545,6 +548,7 @@ final class DatabaseService {
var visited = Set<String>()
return LaneSummary(
id: row.id,
projectId: row.projectId,
name: row.name,
description: row.description,
laneType: row.laneType,
Expand Down
97 changes: 81 additions & 16 deletions apps/ios/ADE/Services/SyncService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,28 @@ enum TerminalStreamEvent {
case exit(code: Int?)
}

struct WorkStartShellSessionRequest: Equatable {
var laneId: String
var provider = "shell"
var title = "Shell"
var cols = 48
var rows = 24
var targetProjectId: String?
var targetProjectRootPath: String?
}

func workStartShellSessionRequest(
laneId: String,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil
) -> WorkStartShellSessionRequest {
WorkStartShellSessionRequest(
laneId: laneId,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath
)
}

@MainActor
final class SyncService: ObservableObject {
@Published private(set) var connectionState: RemoteConnectionState = .disconnected
Expand Down Expand Up @@ -2789,6 +2811,7 @@ final class SyncService: ObservableObject {
let hostId: String?
let projectId: String?
let projectRootPath: String?
let fallbackToActiveProjectScope: Bool?
}

init(database: DatabaseService = DatabaseService()) {
Expand Down Expand Up @@ -5469,7 +5492,8 @@ final class SyncService: ObservableObject {
cols: Int? = nil,
rows: Int? = nil,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil
targetProjectRootPath: String? = nil,
fallbackToActiveProjectScope: Bool = true
) async throws -> StartCliSessionResult {
var args: [String: Any] = [
"laneId": laneId,
Expand Down Expand Up @@ -5504,10 +5528,33 @@ final class SyncService: ObservableObject {
args: args,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath,
fallbackToActiveProjectScope: fallbackToActiveProjectScope,
as: StartCliSessionResult.self
)
}

func startShellSession(
laneId: String,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil
) async throws -> StartCliSessionResult {
let request = workStartShellSessionRequest(
laneId: laneId,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath
)
return try await startCliSession(
laneId: request.laneId,
provider: request.provider,
title: request.title,
cols: request.cols,
rows: request.rows,
targetProjectId: request.targetProjectId,
targetProjectRootPath: request.targetProjectRootPath,
fallbackToActiveProjectScope: false
)
}

func stopWorkRuntime(sessionId: String) async throws {
// Scope to the session's project so a stop issued from a cross-project
// quick look reaches the right runtime.
Expand Down Expand Up @@ -8812,14 +8859,15 @@ final class SyncService: ObservableObject {
terminalBufferRevision += 1
}

func pendingOperationsForTesting() -> [(id: String, kind: String, action: String, projectId: String?, projectRootPath: String?)] {
func pendingOperationsForTesting() -> [(id: String, kind: String, action: String, projectId: String?, projectRootPath: String?, fallbackToActiveProjectScope: Bool?)] {
loadPendingOperations().map { operation in
(
id: operation.id,
kind: operation.kind,
action: operation.action,
projectId: operation.projectId,
projectRootPath: operation.projectRootPath
projectRootPath: operation.projectRootPath,
fallbackToActiveProjectScope: operation.fallbackToActiveProjectScope
)
}
}
Expand Down Expand Up @@ -9865,6 +9913,7 @@ final class SyncService: ObservableObject {
timeoutNanoseconds: UInt64? = nil,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil,
fallbackToActiveProjectScope: Bool = true,
as type: T.Type
) async throws -> T {
let response = try await sendCommand(
Expand All @@ -9873,7 +9922,8 @@ final class SyncService: ObservableObject {
disconnectOnTimeout: disconnectOnTimeout,
timeoutNanoseconds: timeoutNanoseconds,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath
targetProjectRootPath: targetProjectRootPath,
fallbackToActiveProjectScope: fallbackToActiveProjectScope
)
if let payload = response as? [String: Any], payload["queued"] as? Bool == true {
throw QueuedRemoteCommandError(action: action)
Expand Down Expand Up @@ -10027,7 +10077,8 @@ final class SyncService: ObservableObject {
args: [String: Any],
id: String? = nil,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil
targetProjectRootPath: String? = nil,
fallbackToActiveProjectScope: Bool = true
) throws {
guard JSONSerialization.isValidJSONObject(args) else {
throw NSError(domain: "ADE", code: 11, userInfo: [NSLocalizedDescriptionKey: "Invalid queued operation payload."])
Expand All @@ -10041,8 +10092,9 @@ final class SyncService: ObservableObject {
payload: payload,
queuedAt: syncDateFormatter.string(from: Date()),
hostId: activeHostStorageKey(),
projectId: targetProjectId ?? activeProjectId,
projectRootPath: targetProjectRootPath ?? activeProjectRootPath
projectId: fallbackToActiveProjectScope ? (targetProjectId ?? activeProjectId) : targetProjectId,
projectRootPath: fallbackToActiveProjectScope ? (targetProjectRootPath ?? activeProjectRootPath) : targetProjectRootPath,
fallbackToActiveProjectScope: fallbackToActiveProjectScope
))
savePendingOperations(queued)
if canSendLiveRequests() {
Expand Down Expand Up @@ -10149,7 +10201,8 @@ final class SyncService: ObservableObject {
args: args,
commandId: operation.id,
targetProjectId: operation.projectId,
targetProjectRootPath: operation.projectRootPath
targetProjectRootPath: operation.projectRootPath,
fallbackToActiveProjectScope: operation.fallbackToActiveProjectScope ?? true
)
case "file":
guard queueableFileActions.contains(operation.action) else {
Expand Down Expand Up @@ -10205,8 +10258,12 @@ final class SyncService: ObservableObject {
timeoutMessage: String = SyncRequestTimeout.message,
timeoutNanoseconds: UInt64? = nil,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil
targetProjectRootPath: String? = nil,
fallbackToActiveProjectScope: Bool = true
) async throws -> Any {
if !fallbackToActiveProjectScope && syncNormalizedCommandScopeValue(targetProjectId) == nil {
throw NSError(domain: "ADE", code: 26, userInfo: [NSLocalizedDescriptionKey: "This action needs the lane's project scope. Refresh lanes and try again."])
}
guard canSendLiveRequests() else {
throw NSError(domain: "ADE", code: 14, userInfo: [NSLocalizedDescriptionKey: "The machine is offline."])
}
Expand All @@ -10215,9 +10272,10 @@ final class SyncService: ObservableObject {
// `targetProjectId` lets a command create-in-place in a NON-active project
// (mobile hub composer): the host routes the command to that project's scope
// via the command-payload projectId without switching the phone's active
// sync project. Defaults to the active project for every existing caller.
let resolvedProjectId = targetProjectId ?? self.activeProjectId
let resolvedProjectRootPath = targetProjectRootPath ?? self.activeProjectRootPath
// sync project. Most callers default to the active project; shell launches
// opt out when the selected lane does not carry trustworthy project scope.
let resolvedProjectId = fallbackToActiveProjectScope ? (targetProjectId ?? self.activeProjectId) : targetProjectId
let resolvedProjectRootPath = fallbackToActiveProjectScope ? (targetProjectRootPath ?? self.activeProjectRootPath) : targetProjectRootPath
Comment thread
arul28 marked this conversation as resolved.
let raw = try await awaitResponse(
requestId: requestId,
disconnectOnTimeout: disconnectOnTimeout,
Expand Down Expand Up @@ -10246,8 +10304,12 @@ final class SyncService: ObservableObject {
timeoutMessage: String = SyncRequestTimeout.message,
timeoutNanoseconds: UInt64? = nil,
targetProjectId: String? = nil,
targetProjectRootPath: String? = nil
targetProjectRootPath: String? = nil,
fallbackToActiveProjectScope: Bool = true
) async throws -> Any {
if !fallbackToActiveProjectScope && syncNormalizedCommandScopeValue(targetProjectId) == nil {
throw NSError(domain: "ADE", code: 26, userInfo: [NSLocalizedDescriptionKey: "This action needs the lane's project scope. Refresh lanes and try again."])
}
let commandId = makeRequestId()
if canSendLiveRequests() {
do {
Expand All @@ -10259,7 +10321,8 @@ final class SyncService: ObservableObject {
timeoutMessage: timeoutMessage,
timeoutNanoseconds: timeoutNanoseconds,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath
targetProjectRootPath: targetProjectRootPath,
fallbackToActiveProjectScope: fallbackToActiveProjectScope
)
} catch {
let stillLive = canSendLiveRequests()
Expand All @@ -10274,7 +10337,8 @@ final class SyncService: ObservableObject {
args: args,
id: commandId,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath
targetProjectRootPath: targetProjectRootPath,
fallbackToActiveProjectScope: fallbackToActiveProjectScope
)
if stillLive, isSyncRequestTimeoutError(error) {
verifyTransportAliveAfterRequestTimeout(error as NSError)
Expand All @@ -10295,7 +10359,8 @@ final class SyncService: ObservableObject {
action: action,
args: args,
targetProjectId: targetProjectId,
targetProjectRootPath: targetProjectRootPath
targetProjectRootPath: targetProjectRootPath,
fallbackToActiveProjectScope: fallbackToActiveProjectScope
)
return ["queued": true]
}
Expand Down
Loading