Skip to content
Draft
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,18 @@ See [AGENTS.md](AGENTS.md) for architecture and conventions.
Licensed under the [Apache License, Version 2.0](LICENSE).

Copyright 2025-2026 Risa Labs Inc.


### Durable bookmark library contracts (unreleased)

`BookmarkLibraryProvider` exposes revision-checked saved-record mutations and independent
favorite membership. `BookmarkOpeningProvider` is the shared, window-scoped host opening
route used by the shelf, library and search. Definitions match the corresponding host
`plugin-bookmark-types` module in [BossConsole PR #759](https://github.com/risa-labs-inc/BossConsole/pull/759).

The SDK supplies types, not host implementations. A plugin requiring the library must
check `context.getPluginAPI(BookmarkOpeningProvider::class.java)` before initializing
storage or registering its library providers, and refuse activation when unavailable.
When this SDK change is released, consumers should pin that actual release and declare
its `minApiVersion`; no future SDK or BOSS release number is assigned by this change.
The existing release workflow publishes the next SDK release after a maintainer merges it.
112 changes: 112 additions & 0 deletions api/boss-plugin-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -7111,6 +7111,118 @@ public final class ai/rever/boss/plugin/bookmark/BookmarkCollection$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public abstract interface class ai/rever/boss/plugin/bookmark/BookmarkLibraryProvider {
public abstract fun createCollection (Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun deleteBookmark (Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun deleteCollection (Ljava/lang/String;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow;
public abstract fun moveBookmark (Ljava/lang/String;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun reload (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun renameCollection (Ljava/lang/String;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun saveBookmark (Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun setFavorite (Ljava/lang/String;ZJLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun undo (Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class ai/rever/boss/plugin/bookmark/BookmarkLibraryState {
public static final field $stable I
public fun <init> ()V
public fun <init> (Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;)V
public synthetic fun <init> (Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/util/Set;
public final fun component3 ()Z
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()J
public final fun component6 ()Z
public final fun component7 ()Ljava/util/Set;
public final fun copy (Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;)Lai/rever/boss/plugin/bookmark/BookmarkLibraryState;
public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkLibraryState;Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;ILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkLibraryState;
public fun equals (Ljava/lang/Object;)Z
public final fun getCollections ()Ljava/util/List;
public final fun getDefaultFavorite ()Z
public final fun getError ()Ljava/lang/String;
public final fun getFavoriteBookmarkIds ()Ljava/util/Set;
public final fun getReady ()Z
public final fun getRevision ()J
public final fun getUnfiledCollectionIds ()Ljava/util/Set;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class ai/rever/boss/plugin/bookmark/BookmarkMutationResult {
public static final field $stable I
public fun <init> (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Ljava/lang/String;
public final fun component6 ()Ljava/lang/String;
public final fun copy (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lai/rever/boss/plugin/bookmark/BookmarkMutationResult;
public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkMutationResult;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkMutationResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getBookmarkId ()Ljava/lang/String;
public final fun getCollectionId ()Ljava/lang/String;
public final fun getDuplicateBookmarkId ()Ljava/lang/String;
public final fun getMessage ()Ljava/lang/String;
public final fun getSuccess ()Z
public final fun getUndoToken ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class ai/rever/boss/plugin/bookmark/BookmarkOpenResult {
public static final field $stable I
public fun <init> (ZLjava/lang/String;Ljava/lang/String;Z)V
public synthetic fun <init> (ZLjava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Z
public final fun copy (ZLjava/lang/String;Ljava/lang/String;Z)Lai/rever/boss/plugin/bookmark/BookmarkOpenResult;
public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkOpenResult;ZLjava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkOpenResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getMessage ()Ljava/lang/String;
public final fun getReused ()Z
public final fun getSuccess ()Z
public final fun getTabId ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class ai/rever/boss/plugin/bookmark/BookmarkOpeningProvider {
public abstract fun openBookmark (Lai/rever/boss/plugin/bookmark/Bookmark;Ljava/lang/String;Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class ai/rever/boss/plugin/bookmark/BookmarkSaveRequest {
public static final field $stable I
public fun <init> (Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lai/rever/boss/plugin/workspace/TabConfig;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Z
public final fun component5 ()J
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Z
public final fun component8 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;)Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest;
public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest;Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest;
public fun equals (Ljava/lang/Object;)Z
public final fun getAllowCopy ()Z
public final fun getBookmarkId ()Ljava/lang/String;
public final fun getCollectionId ()Ljava/lang/String;
public final fun getExpectedRevision ()J
public final fun getFavorite ()Z
public final fun getName ()Ljava/lang/String;
public final fun getTabConfig ()Lai/rever/boss/plugin/workspace/TabConfig;
public final fun getWorkspaceName ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class ai/rever/boss/plugin/bookmark/FavoriteWorkspace {
public static final field $stable I
public static final field Companion Lai/rever/boss/plugin/bookmark/FavoriteWorkspace$Companion;
Expand Down
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@ group = "ai.rever.boss.plugin.bundled"
// CLI selection, rather than asking a consumer to duplicate routing policy. New types only:
// ApiClassLoader/minApiVersion, no host release.
//
// Unreleased: adds durable bookmark library and shared opening contracts.
// SDK availability does not imply a host supplies BookmarkOpeningProvider;
// consumers must check that capability before activating/migrating a library.
version = "1.0.93"

java {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package ai.rever.boss.plugin.bookmark

import ai.rever.boss.plugin.workspace.TabConfig
import kotlinx.coroutines.flow.StateFlow

/**
* Optional durable library capability. Older providers remain readable through their existing API.
*
* The SDK makes these types available; it does not install the matching host behavior.
* Consumers must verify [BookmarkOpeningProvider] is registered before activating a
* library that requires the shared opening route. Keep these definitions binary-compatible
* with the host's plugin-bookmark-types module, which is loaded parent-first.
*/
interface BookmarkLibraryProvider {
val state: StateFlow<BookmarkLibraryState>

suspend fun saveBookmark(request: BookmarkSaveRequest): BookmarkMutationResult

suspend fun setFavorite(
bookmarkId: String,
favorite: Boolean,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun moveBookmark(
bookmarkId: String,
collectionId: String,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun deleteBookmark(
bookmarkId: String,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun undo(
token: String,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun createCollection(
name: String,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun renameCollection(
collectionId: String,
name: String,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun deleteCollection(
collectionId: String,
moveToCollectionId: String?,
expectedRevision: Long,
): BookmarkMutationResult

suspend fun reload()
}

data class BookmarkLibraryState(
val collections: List<BookmarkCollection> = emptyList(),
val favoriteBookmarkIds: Set<String> = emptySet(),
val ready: Boolean = false,
val error: String? = null,
val revision: Long = 0,
val defaultFavorite: Boolean = true,
/** Storage containers shown as root bookmarks rather than user-created folders. */
val unfiledCollectionIds: Set<String> = emptySet(),
)

data class BookmarkSaveRequest(
val collectionId: String,
val tabConfig: TabConfig,
val name: String,
val favorite: Boolean,
val expectedRevision: Long,
val bookmarkId: String? = null,
val allowCopy: Boolean = false,
val workspaceName: String = "",
)

data class BookmarkMutationResult(
val success: Boolean,
val bookmarkId: String? = null,
val collectionId: String? = null,
val duplicateBookmarkId: String? = null,
val undoToken: String? = null,
val message: String? = null,
)

/** One host opening route for the sidebar, library and search. No saved command is executed on reuse. */
interface BookmarkOpeningProvider {
suspend fun openBookmark(
bookmark: Bookmark,
windowId: String,
panelId: String?,
forceNewTab: Boolean,
): BookmarkOpenResult
}

data class BookmarkOpenResult(
val success: Boolean,
val message: String? = null,
val tabId: String? = null,
val reused: Boolean = false,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ai.rever.boss.plugin.bookmark

import kotlin.test.Test
import kotlin.test.assertEquals
import java.lang.reflect.Modifier

/** Host and SDK copies must both provide fields emitted into consuming Compose plugins. */
class BookmarkLibraryLinkageTest {
@Test fun `new bookmark models expose public static Compose stability fields`() {
listOf(BookmarkLibraryState::class.java, BookmarkSaveRequest::class.java,
BookmarkMutationResult::class.java, BookmarkOpenResult::class.java).forEach { type ->
val field = type.getField("\$stable")
assertEquals(Int::class.javaPrimitiveType, field.type, type.name)
assertEquals(true, Modifier.isStatic(field.modifiers), type.name)
}
}
}