-
-
Notifications
You must be signed in to change notification settings - Fork 368
feat(ai-chat): agent workspace phases 1 to 3, assistant mode, approval floor, session isolation #2384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
J2TeamNNL
wants to merge
16
commits into
TableProApp:main
Choose a base branch
from
J2TeamNNL:feat/agent-workspace
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
feat(ai-chat): agent workspace phases 1 to 3, assistant mode, approval floor, session isolation #2384
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
f678781
feat(coordinator): add assistant mode to the connection window
J2TeamNNL 965fc20
feat(ai-chat): hold assistant mode at confirm writes and scope tool a…
J2TeamNNL a9a35ad
feat(ai-chat): give each chat session its own mode, transcript, provi…
J2TeamNNL 3001d26
feat(ai-chat): hold sessions in an app-scoped registry so several run…
J2TeamNNL b2435e4
feat(ai-chat): fill the assistant result pane with proposed SQL, step…
J2TeamNNL 183c909
feat(welcome): start or reopen an AI session from the welcome window
J2TeamNNL be5b966
feat(mcp): let a session call an outside MCP server under approval an…
J2TeamNNL 90c3b58
fix(hig): name the Browse and Assistant segments for VoiceOver
J2TeamNNL b723316
Merge upstream/main into feat/agent-workspace
J2TeamNNL 79f7c78
test(plugins): seed the duplicate type id instead of relying on a loa…
J2TeamNNL 3352572
ci(tests): quarantine CompareSyncUITests, which opens a modal licence…
J2TeamNNL 4837570
Merge upstream/main into feat/agent-workspace
J2TeamNNL a298684
docs: note macOS/Xcode-only build for cloud agents
cursoragent 73a954d
Merge upstream/main into feat/agent-workspace
cursoragent d54dff5
Merge upstream/main into feat/agent-workspace
cursoragent 6e548a0
Merge pull request #4 from J2TeamNNL/cursor/document-cloud-env-limita…
J2TeamNNL File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # AGENTS.md | ||
|
|
||
| ## Cursor Cloud specific instructions | ||
|
|
||
| TablePro is a native macOS/iOS application (SwiftUI + AppKit) built with Xcode and XcodeGen. It **cannot be built, tested, or run on the Linux Cloud Agent VM**: | ||
|
|
||
| - Building requires macOS 14+, Xcode 26+, and `xcodebuild`, none of which exist on (or can be installed on) Linux. See "How to Build" in `README.md` and the build/test/lint commands in `CLAUDE.md`. | ||
| - The app and its SwiftPM packages (`Packages/TableProCore`, `Packages/TableProOracle`) import Apple-only frameworks (AppKit, SwiftUI, CloudKit) and declare only `.macOS`/`.iOS` platforms, so `swift build` / `swift test` do not work on Linux either. | ||
| - `Libs/` and `Libs/ios/` are prebuilt macOS/iOS binaries fetched by `scripts/download-libs.sh`; they are not usable without Xcode. | ||
|
|
||
| Because there is no Linux dependency-install/update step for this codebase, no Cloud Agent update script is configured. Do lint/build/test/run work on a macOS host with Xcode, following `README.md` and `CLAUDE.md`. | ||
|
|
||
| Note: an experimental native Linux client (Rust) exists only on the `linux` branch under `linux/` and is a prototype ("nothing to install yet" per `README.md`). It is separate from this branch's macOS/iOS codebase. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // | ||
| // AssistantSafeModeFloor.swift | ||
| // TablePro | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| /// Assistant mode promises that a write the assistant proposes waits for a human. On a connection | ||
| /// left at `.silent`, which is the default in both the initializer and the decoder, that promise | ||
| /// was false: `requiresConfirmation` is `false` there, so the approval path returned `.approved` | ||
| /// for a `.write` tool with no user interaction at all. | ||
| /// | ||
| /// The floor is applied where the level is read, not by mutating the stored connection. Nothing is | ||
| /// written to `ConnectionStorage`, so nothing syncs, nothing has to be restored after a crash, and | ||
| /// the user's own level is still their own level the moment they leave the mode. | ||
| internal enum AssistantSafeModeFloor { | ||
| /// Confirm Writes. High enough that every proposed write stops for a human, low enough that it | ||
| /// adds no authentication step the user did not ask for. | ||
| internal static let floor: SafeModeLevel = .alert | ||
|
|
||
| /// Pure, so the rule is testable without a window, a connection record or UserDefaults. | ||
| /// | ||
| /// Both floors are composed here, in one place. An administrator's | ||
| /// `com.TablePro.policy.minimumSafeModeLevel` is a floor with exactly the same shape, and every | ||
| /// other execution path already applies it through `ExecutionGateProvider`. The chat tools hand | ||
| /// the gate `.confirmationPreCleared`, so the gate's confirmation arm is skipped for them and | ||
| /// this is the only place a managed Alert floor can still be enforced on an AI-proposed write. | ||
| /// Reading the raw level here left a managed "confirm every write" as a no-op for the assistant. | ||
| internal static func effectiveLevel( | ||
| stored: SafeModeLevel, | ||
| assistantModeActive: Bool, | ||
| policy: any ManagedPolicyReading = ManagedPolicyReader.shared | ||
| ) -> SafeModeLevel { | ||
| let managed = ManagedPolicyResolver.effectiveSafeModeLevel( | ||
| connectionLevel: stored, | ||
| policy: policy | ||
| ) | ||
| guard assistantModeActive else { return managed } | ||
| return managed.raised(toFloor: floor) | ||
| } | ||
|
|
||
| /// Whether a floor, rather than the user's own choice, is what is asking for the confirmation. | ||
| /// The approval path needs this separately from the level itself: a grant the user made for | ||
| /// their own level must not silently switch off a floor they did not set. | ||
| internal static func floorRaisedLevel( | ||
| stored: SafeModeLevel, | ||
| assistantModeActive: Bool, | ||
| policy: any ManagedPolicyReading = ManagedPolicyReader.shared | ||
| ) -> Bool { | ||
| effectiveLevel(stored: stored, assistantModeActive: assistantModeActive, policy: policy) != stored | ||
| } | ||
|
|
||
| /// `WorkspaceContentModeStore` is the single record of which surface a connection is on, and it | ||
| /// is written on every mode change, so it answers this without a second registry to keep in | ||
| /// step. A connection no window is hosting still reads whatever it was last left in, which errs | ||
| /// toward the floor being on: there is no session to gate in that case, and a floor that is on | ||
| /// when it need not be costs a confirmation, while one that is off when it should be on costs | ||
| /// the user their data. | ||
| @MainActor | ||
| internal static func isActive( | ||
| for connectionId: UUID, | ||
| store: WorkspaceContentModeStore = .shared | ||
| ) -> Bool { | ||
| store.mode(connectionId: connectionId) == .assistant | ||
| } | ||
|
|
||
| @MainActor | ||
| internal static func effectiveLevel( | ||
| live: SafeModeLevel, | ||
| connectionId: UUID, | ||
| store: WorkspaceContentModeStore = .shared, | ||
| policy: any ManagedPolicyReading = ManagedPolicyReader.shared | ||
| ) -> SafeModeLevel { | ||
| effectiveLevel( | ||
| stored: live, | ||
| assistantModeActive: isActive(for: connectionId, store: store), | ||
| policy: policy | ||
| ) | ||
| } | ||
|
|
||
| @MainActor | ||
| internal static func floorRaisedLevel( | ||
| live: SafeModeLevel, | ||
| connectionId: UUID, | ||
| store: WorkspaceContentModeStore = .shared, | ||
| policy: any ManagedPolicyReading = ManagedPolicyReader.shared | ||
| ) -> Bool { | ||
| floorRaisedLevel( | ||
| stored: live, | ||
| assistantModeActive: isActive(for: connectionId, store: store), | ||
| policy: policy | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On launch, this unstructured task does not actually ensure restoration finishes before a window or welcome action calls
session(for:). If that happens whilestore.load()is suspended, the registry creates a new default session and later appends the stored session as well, leaving duplicate sessions for the same conversation in the rail and persisting both. Gate session creation on restoration completion or perform restoration before exposing the registry to UI actions.Useful? React with 👍 / 👎.