mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-248623 split AWB task folders from Pi sessions
Task folders are Agent Workbench UI state, so move their persistence and Pi control handling out of the core Pi sessions module. Pi now exposes a generic control-request extension point, and the AWB plugin contributes task-folder handling through its own module and descriptor. Keep Pi-owned integration details in Pi-owned artifacts: task-folder tools are separated from the generic bridge extension code, Pi settings strings live in the AWB Pi bundle, and provider references use the PI_AGENT_SESSION_PROVIDER constant instead of repeating the raw provider id. This changelist also carries the session activity report model through providers, presentation code, tests, and specs so row activity and chrome activity can diverge without keeping task-folder or provider-specific state in core session actions. GitOrigin-RevId: 3d35a81cb78599fb0115b0eb876f48ab58579924
This commit is contained in:
committed by
intellij-monorepo-bot
parent
09ff8a47d5
commit
17519f0daa
Generated
+2
@@ -81,6 +81,8 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-jbcentral/intellij.agent.workbench.sessions.jbcentral.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-jbcentral/intellij.agent.workbench.sessions.jbcentral.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-launch-config/backend/intellij.agent.workbench.sessions.launch.config.backend.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-launch-config/backend/intellij.agent.workbench.sessions.launch.config.backend.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-launch-config/backend/intellij.agent.workbench.sessions.launch.config.backend.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-launch-config/backend/intellij.agent.workbench.sessions.launch.config.backend.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-task-folders/intellij.agent.workbench.sessions.task.folders.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-task-folders/intellij.agent.workbench.sessions.task.folders.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-task-folders/intellij.agent.workbench.sessions.task.folders.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-task-folders/intellij.agent.workbench.sessions.task.folders.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions/intellij.agent.workbench.sessions.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-toolwindow/intellij.agent.workbench.sessions.toolwindow.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-toolwindow/intellij.agent.workbench.sessions.toolwindow.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/agent-workbench/sessions-toolwindow/intellij.agent.workbench.sessions.toolwindow.tests.iml" filepath="$PROJECT_DIR$/plugins/agent-workbench/sessions-toolwindow/intellij.agent.workbench.sessions.toolwindow.tests.iml" />
|
||||
|
||||
@@ -1014,6 +1014,7 @@ plugins/agent-workbench/sessions-actions
|
||||
plugins/agent-workbench/sessions-cost
|
||||
plugins/agent-workbench/sessions-jbcentral
|
||||
plugins/agent-workbench/sessions-launch-config/backend
|
||||
plugins/agent-workbench/sessions-task-folders
|
||||
plugins/agent-workbench/sessions-toolwindow
|
||||
plugins/agent-workbench/settings
|
||||
plugins/agent-workbench/ui
|
||||
|
||||
@@ -103,7 +103,7 @@ internal class AgentChatVirtualFile internal constructor(
|
||||
get() = resolveAgentChatThreadPresentation(this).title
|
||||
|
||||
override val threadActivity: AgentThreadActivity
|
||||
get() = resolveAgentChatThreadPresentation(this).activity
|
||||
get() = resolveAgentChatThreadPresentation(this).activityReport.rowActivity
|
||||
|
||||
var pendingCreatedAtMs: Long? = null
|
||||
private set
|
||||
|
||||
@@ -783,7 +783,7 @@ class AgentChatEditorServiceTest {
|
||||
val updatedTabs = publishThreadPresentation(
|
||||
file = file,
|
||||
title = "Renamed by source update",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
assertThat(updatedTabs).isEqualTo(1)
|
||||
|
||||
@@ -794,7 +794,7 @@ class AgentChatEditorServiceTest {
|
||||
val unchangedTabs = publishThreadPresentation(
|
||||
file = file,
|
||||
title = "Renamed by source update",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
assertThat(unchangedTabs).isEqualTo(0)
|
||||
}
|
||||
@@ -873,7 +873,7 @@ class AgentChatEditorServiceTest {
|
||||
file = file,
|
||||
path = "${file.projectPath}/",
|
||||
title = "Renamed by normalized source update",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
assertThat(updatedTabs).isEqualTo(1)
|
||||
|
||||
@@ -885,7 +885,7 @@ class AgentChatEditorServiceTest {
|
||||
file = file,
|
||||
path = "${file.projectPath}/",
|
||||
title = "Renamed by normalized source update",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
assertThat(unchangedTabs).isEqualTo(0)
|
||||
}
|
||||
@@ -963,7 +963,7 @@ class AgentChatEditorServiceTest {
|
||||
val updatedTabs = publishThreadPresentation(
|
||||
file = openedChatFiles().first { it.subAgentId == null },
|
||||
title = "Renamed parent",
|
||||
activity = null,
|
||||
activityReport = null,
|
||||
)
|
||||
assertThat(updatedTabs).isEqualTo(1)
|
||||
|
||||
@@ -992,7 +992,7 @@ class AgentChatEditorServiceTest {
|
||||
val updatedTabs = publishThreadPresentation(
|
||||
file = openedChatFiles().first { it.subAgentId == null },
|
||||
title = "Renamed parent",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
assertThat(updatedTabs).isEqualTo(2)
|
||||
|
||||
@@ -1099,7 +1099,7 @@ class AgentChatEditorServiceTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
threadId = targetThreadId,
|
||||
title = sharedTitle,
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
)).isEqualTo(0)
|
||||
|
||||
val rebindReport = rebindOpenPendingCodexTabs(
|
||||
@@ -1479,7 +1479,7 @@ class AgentChatEditorServiceTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
threadId = targetThreadId,
|
||||
title = sharedTitle,
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
)).isEqualTo(0)
|
||||
|
||||
val rebindReport = rebindOpenConcreteCodexTabs(
|
||||
@@ -1964,7 +1964,7 @@ class AgentChatEditorServiceTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
threadId = "thread-1",
|
||||
title = "Main thread",
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
)
|
||||
assertThat(presentationModel.resolve(rootPresentationKey)).isNotNull
|
||||
val matchingTabKeys = beforeCleanup
|
||||
@@ -2024,7 +2024,7 @@ class AgentChatEditorServiceTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
threadId = "thread-1",
|
||||
title = "Main thread",
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
)
|
||||
|
||||
closeAndForgetAgentChatsForThread(
|
||||
@@ -2544,23 +2544,7 @@ private suspend fun publishThreadPresentation(
|
||||
file: AgentChatVirtualFile,
|
||||
path: String = file.projectPath,
|
||||
title: String,
|
||||
activity: AgentThreadActivity?,
|
||||
): Int {
|
||||
val provider = checkNotNull(file.provider)
|
||||
return publishThreadPresentation(
|
||||
path = path,
|
||||
provider = provider,
|
||||
threadId = file.sessionId,
|
||||
title = title,
|
||||
activity = activity,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun publishThreadPresentation(
|
||||
file: AgentChatVirtualFile,
|
||||
path: String = file.projectPath,
|
||||
title: String,
|
||||
activityReport: AgentThreadActivityReport,
|
||||
activityReport: AgentThreadActivityReport?,
|
||||
): Int {
|
||||
val provider = checkNotNull(file.provider)
|
||||
return publishThreadPresentation(
|
||||
@@ -2577,31 +2561,13 @@ private suspend fun publishThreadPresentation(
|
||||
provider: AgentSessionProvider,
|
||||
threadId: String,
|
||||
title: String,
|
||||
activity: AgentThreadActivity?,
|
||||
activityReport: AgentThreadActivityReport?,
|
||||
): Int {
|
||||
val changeSet = service<AgentSessionThreadPresentationModel>().updateThread(
|
||||
path = path,
|
||||
provider = provider,
|
||||
threadId = threadId,
|
||||
title = title,
|
||||
activity = activity,
|
||||
)
|
||||
return AgentChatOpenTabPresentationInvalidator.invalidate(changeSet)
|
||||
}
|
||||
|
||||
private suspend fun publishThreadPresentation(
|
||||
path: String,
|
||||
provider: AgentSessionProvider,
|
||||
threadId: String,
|
||||
title: String,
|
||||
activityReport: AgentThreadActivityReport,
|
||||
): Int {
|
||||
val changeSet = service<AgentSessionThreadPresentationModel>().updateThread(
|
||||
path = path,
|
||||
provider = provider,
|
||||
threadId = threadId,
|
||||
title = title,
|
||||
activity = null,
|
||||
activityReport = activityReport,
|
||||
)
|
||||
return AgentChatOpenTabPresentationInvalidator.invalidate(changeSet)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.agent.workbench.chat
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.buildAgentThreadIdentity
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
@@ -879,7 +880,7 @@ class AgentChatFileEditorLifecycleTest {
|
||||
title = "Restored thread",
|
||||
updatedAt = 1L,
|
||||
archived = true,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
)
|
||||
),
|
||||
|
||||
@@ -1055,10 +1055,10 @@ class AgentChatFileEditorProviderTest {
|
||||
val otherPathKey = presentationKey("/work/project-b", AgentSessionProvider.from("codex"), "thread-3")
|
||||
model.replaceForTests(
|
||||
mapOf(
|
||||
refreshedKey to AgentSessionThreadPresentation(title = "Old title", activity = AgentThreadActivity.READY),
|
||||
removedKey to AgentSessionThreadPresentation(title = "Removed title", activity = AgentThreadActivity.UNREAD),
|
||||
otherProviderKey to AgentSessionThreadPresentation(title = "Claude title", activity = AgentThreadActivity.PROCESSING),
|
||||
otherPathKey to AgentSessionThreadPresentation(title = "Other path", activity = AgentThreadActivity.READY),
|
||||
refreshedKey to AgentSessionThreadPresentation(title = "Old title", activityReport = AgentThreadActivityReport(AgentThreadActivity.READY)),
|
||||
removedKey to AgentSessionThreadPresentation(title = "Removed title", activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD)),
|
||||
otherProviderKey to AgentSessionThreadPresentation(title = "Claude title", activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING)),
|
||||
otherPathKey to AgentSessionThreadPresentation(title = "Other path", activityReport = AgentThreadActivityReport(AgentThreadActivity.READY)),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1084,9 +1084,9 @@ class AgentChatFileEditorProviderTest {
|
||||
)
|
||||
assertThat(model.resolve(removedKey)).isNull()
|
||||
assertThat(model.resolve(otherProviderKey))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Claude title", activity = AgentThreadActivity.PROCESSING))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Claude title", activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING)))
|
||||
assertThat(model.resolve(otherPathKey))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Other path", activity = AgentThreadActivity.READY))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Other path", activityReport = AgentThreadActivityReport(AgentThreadActivity.READY)))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1099,7 +1099,7 @@ class AgentChatFileEditorProviderTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
threadId = "thread-1",
|
||||
title = "Existing title",
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
)
|
||||
|
||||
val changeSet = model.updateActivityHints(
|
||||
@@ -1108,14 +1108,14 @@ class AgentChatFileEditorProviderTest {
|
||||
com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadActivityPresentationUpdate(
|
||||
path = "/work/project-a",
|
||||
threadId = "thread-1",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(changeSet.changedKeys).containsExactly(key)
|
||||
assertThat(model.resolve(key))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Existing title", activity = AgentThreadActivity.UNREAD))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Existing title", activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1143,7 +1143,7 @@ class AgentChatFileEditorProviderTest {
|
||||
assertThat(file).isNotNull
|
||||
assertThat(model.resolve(key)).isNull()
|
||||
assertThat(resolveAgentChatThreadPresentation(checkNotNull(file)))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Restored thread", activity = AgentThreadActivity.UNREAD))
|
||||
.isEqualTo(AgentSessionThreadPresentation(title = "Restored thread", activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD)))
|
||||
}
|
||||
finally {
|
||||
tabsService.forget(snapshot.tabKey)
|
||||
@@ -1172,7 +1172,7 @@ class AgentChatFileEditorProviderTest {
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
threadId = snapshot.runtime.threadId,
|
||||
title = "Forget me",
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
assertThat(model.resolve(key)).isNotNull
|
||||
|
||||
@@ -1475,7 +1475,7 @@ private fun threadModel(
|
||||
title = title,
|
||||
updatedAt = 1L,
|
||||
archived = false,
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
provider = provider,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.intellij.agent.workbench.chat
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.buildAgentThreadIdentity
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem
|
||||
@@ -933,7 +934,7 @@ private class OpenTabDispatchPiForkSource : AgentSessionSource, AgentSessionThre
|
||||
title = "Forked Pi thread",
|
||||
updatedAt = 5_000L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
provider = AgentSessionProvider.from("pi"),
|
||||
),
|
||||
launchSpecOverride = AgentSessionTerminalLaunchSpec(
|
||||
|
||||
@@ -210,7 +210,6 @@ f:com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
- f:subAgents:java.util.List
|
||||
- f:title:java.lang.String
|
||||
- f:updatedAt:J
|
||||
- b:<init>(java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivity,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.session.AgentSessionCost,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- b:<init>(java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionCost,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- f:component1():java.lang.String
|
||||
- f:component2():java.lang.String
|
||||
@@ -224,9 +223,7 @@ f:com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
- f:copy-JxZNnm8(java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionCost):com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
- bs:copy-JxZNnm8$default(com.intellij.platform.ai.agent.core.session.AgentSessionThread,java.lang.String,java.lang.String,J,Z,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.String,java.util.List,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionCost,I,java.lang.Object):com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
- equals(java.lang.Object):Z
|
||||
- f:getActivity():com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
- f:getProvider-dBzBBrw():java.lang.String
|
||||
- f:getSummaryActivity():com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
- hashCode():I
|
||||
f:com.intellij.platform.ai.agent.core.session.AgentSessionThreadOutline
|
||||
- f:items:java.util.List
|
||||
|
||||
@@ -50,36 +50,7 @@ data class AgentSessionThread(
|
||||
@JvmField val subAgents: List<AgentSubAgent> = emptyList(),
|
||||
@JvmField val originBranch: String? = null,
|
||||
@JvmField val cost: AgentSessionCost? = null,
|
||||
) {
|
||||
constructor(
|
||||
id: String,
|
||||
title: String,
|
||||
updatedAt: Long,
|
||||
archived: Boolean,
|
||||
activity: AgentThreadActivity,
|
||||
provider: AgentSessionProvider,
|
||||
subAgents: List<AgentSubAgent> = emptyList(),
|
||||
originBranch: String? = null,
|
||||
summaryActivity: AgentThreadActivity? = activity,
|
||||
cost: AgentSessionCost? = null,
|
||||
) : this(
|
||||
id = id,
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = archived,
|
||||
activityReport = AgentThreadActivityReport(rowActivity = activity, chromeActivity = summaryActivity),
|
||||
provider = provider,
|
||||
subAgents = subAgents,
|
||||
originBranch = originBranch,
|
||||
cost = cost,
|
||||
)
|
||||
|
||||
val activity: AgentThreadActivity
|
||||
get() = activityReport.rowActivity
|
||||
|
||||
val summaryActivity: AgentThreadActivity?
|
||||
get() = activityReport.chromeActivity
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Provider-neutral outline for a persisted agent thread.
|
||||
|
||||
+7
-5
@@ -277,7 +277,7 @@ internal class ClaudeSessionSource internal constructor(
|
||||
title = sourcePrompt.threadTitle,
|
||||
updatedAt = System.currentTimeMillis(),
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
provider = CLAUDE_AGENT_SESSION_PROVIDER,
|
||||
),
|
||||
launchSpecOverride = launchSpec,
|
||||
@@ -347,10 +347,12 @@ private fun ClaudeBackendThread.toAgentSessionThread(
|
||||
archived = archived,
|
||||
provider = CLAUDE_AGENT_SESSION_PROVIDER,
|
||||
originBranch = gitBranch,
|
||||
activity = effectiveActivity(
|
||||
readTracker = readTracker,
|
||||
completedUnreadUpdatedAtByThreadId = completedUnreadUpdatedAtByThreadId,
|
||||
observedUpdatedAtByThreadId = observedUpdatedAtByThreadId,
|
||||
activityReport = AgentThreadActivityReport(
|
||||
effectiveActivity(
|
||||
readTracker = readTracker,
|
||||
completedUnreadUpdatedAtByThreadId = completedUnreadUpdatedAtByThreadId,
|
||||
observedUpdatedAtByThreadId = observedUpdatedAtByThreadId,
|
||||
)
|
||||
),
|
||||
cost = if (includeCost) usageSnapshots.aggregateAgentSessionUsageCost(calculateCost) else null,
|
||||
)
|
||||
|
||||
+21
-21
@@ -54,7 +54,7 @@ class ClaudeSessionSourceTest {
|
||||
}
|
||||
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result).allMatch { it.activity == AgentThreadActivity.READY }
|
||||
assertThat(result).allMatch { it.activityReport.rowActivity == AgentThreadActivity.READY }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,12 +105,12 @@ class ClaudeSessionSourceTest {
|
||||
val source = ClaudeSessionSource(backend = dynamicRefreshBackend { currentThreads })
|
||||
|
||||
runBlocking(Dispatchers.Default) {
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
|
||||
// updatedAt increases but thread was never opened → stays READY.
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L))
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
@@ -127,12 +127,12 @@ class ClaudeSessionSourceTest {
|
||||
|
||||
// User opens the thread.
|
||||
source.markThreadAsRead("s1", 1000L)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
|
||||
// Agent replies while user is elsewhere → UNREAD.
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L))
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.UNREAD)
|
||||
}
|
||||
}
|
||||
@@ -150,12 +150,12 @@ class ClaudeSessionSourceTest {
|
||||
|
||||
// Agent replies → UNREAD.
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L))
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.UNREAD)
|
||||
|
||||
// User returns → mark as read → READY.
|
||||
source.markThreadAsRead("s1", 2000L)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@ class ClaudeSessionSourceTest {
|
||||
currentThreads = listOf(
|
||||
ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L, activity = ClaudeSessionActivity.PROCESSING),
|
||||
)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
@@ -195,7 +195,7 @@ class ClaudeSessionSourceTest {
|
||||
currentThreads = listOf(
|
||||
ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L, activity = ClaudeSessionActivity.NEEDS_INPUT),
|
||||
)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.NEEDS_INPUT)
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ class ClaudeSessionSourceTest {
|
||||
source.markThreadAsRead("s1", 1000L)
|
||||
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L))
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
@@ -240,18 +240,18 @@ class ClaudeSessionSourceTest {
|
||||
currentThreads = listOf(
|
||||
ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L, activity = ClaudeSessionActivity.PROCESSING)
|
||||
)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
|
||||
// Completion should still be surfaced as Done/UNREAD even if the thread remains selected.
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 3000L))
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.UNREAD)
|
||||
|
||||
// User switches away → clear active. A later update still becomes UNREAD.
|
||||
source.setActiveThreadId(null)
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 4000L))
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.UNREAD)
|
||||
}
|
||||
}
|
||||
@@ -264,18 +264,18 @@ class ClaudeSessionSourceTest {
|
||||
val source = ClaudeSessionSource(backend = dynamicRefreshBackend { currentThreads })
|
||||
|
||||
runBlocking(Dispatchers.Default) {
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
|
||||
currentThreads = listOf(ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L))
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.UNREAD)
|
||||
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.UNREAD)
|
||||
|
||||
source.markThreadAsRead("s1", 2000L)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
@@ -288,13 +288,13 @@ class ClaudeSessionSourceTest {
|
||||
val source = ClaudeSessionSource(backend = dynamicRefreshBackend { currentThreads })
|
||||
|
||||
runBlocking(Dispatchers.Default) {
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activity)
|
||||
assertThat(source.listThreads(path = "/any", openProject = null).single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
|
||||
currentThreads = listOf(
|
||||
ClaudeBackendThread(id = "s1", title = "Session 1", updatedAt = 2000L, awaitingAssistantTurn = true)
|
||||
)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activity)
|
||||
assertThat(source.refreshThreads(refreshRequest()).partialThreadsByPath.getValue("/any").single().activityReport.rowActivity)
|
||||
.isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
@@ -410,7 +410,7 @@ class ClaudeSessionSourceTest {
|
||||
|
||||
assertThat(result.map { it.id }).containsExactly("archived")
|
||||
assertThat(result.single().archived).isTrue()
|
||||
assertThat(result.single().activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(result.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -555,7 +555,7 @@ class ClaudeSessionSourceTest {
|
||||
assertThat(UUID.fromString(forkResult.thread.id).toString()).isEqualTo(forkResult.thread.id)
|
||||
assertThat(forkResult.thread.title).isEqualTo("Source Claude thread")
|
||||
assertThat(forkResult.thread.provider).isEqualTo(AgentSessionProvider.from("claude"))
|
||||
assertThat(forkResult.thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(forkResult.thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
val launchSpec = requireNotNull(forkResult.launchSpecOverride)
|
||||
assertThat(launchSpec.command).containsExactly(
|
||||
"claude-test",
|
||||
|
||||
+5
-2
@@ -22,6 +22,7 @@ import com.intellij.platform.ai.agent.codex.sessions.backend.rollout.CodexRollou
|
||||
import com.intellij.platform.ai.agent.codex.sessions.backend.toAgentSessionRefreshHints
|
||||
import com.intellij.platform.ai.agent.codex.sessions.backend.toAgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCost
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItem
|
||||
@@ -598,8 +599,10 @@ private fun toAgentSessionThread(
|
||||
)
|
||||
},
|
||||
originBranch = thread.gitBranch,
|
||||
activity = activity.toAgentThreadActivity(),
|
||||
summaryActivity = summaryActivity?.toAgentThreadActivity(),
|
||||
activityReport = AgentThreadActivityReport(
|
||||
rowActivity = activity.toAgentThreadActivity(),
|
||||
chromeActivity = summaryActivity?.toAgentThreadActivity(),
|
||||
),
|
||||
cost = cost,
|
||||
)
|
||||
}
|
||||
|
||||
+5
-5
@@ -72,7 +72,7 @@ class CodexSessionSourceRolloutIntegrationTest {
|
||||
val listedThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
|
||||
assertThat(listedThreads).hasSize(1)
|
||||
assertThat(listedThreads.single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(listedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ class CodexSessionSourceRolloutIntegrationTest {
|
||||
val listedThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
|
||||
assertThat(listedThreads).hasSize(1)
|
||||
assertThat(listedThreads.single().activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(listedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ class CodexSessionSourceRolloutIntegrationTest {
|
||||
)
|
||||
|
||||
assertThat(refreshResult.completeThreadsByPath[projectPath]).hasSize(1)
|
||||
assertThat(refreshResult.completeThreadsByPath.getValue(projectPath).single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(refreshResult.completeThreadsByPath.getValue(projectPath).single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ class CodexSessionSourceRolloutIntegrationTest {
|
||||
val listedThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
|
||||
assertThat(listedThreads).hasSize(1)
|
||||
assertThat(listedThreads.single().activity).isEqualTo(AgentThreadActivity.NEEDS_INPUT)
|
||||
assertThat(listedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.NEEDS_INPUT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ class CodexSessionSourceRolloutIntegrationTest {
|
||||
val listedThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
|
||||
assertThat(listedThreads).hasSize(1)
|
||||
assertThat(listedThreads.single().activity).isEqualTo(AgentThreadActivity.NEEDS_INPUT)
|
||||
assertThat(listedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.NEEDS_INPUT)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -1081,7 +1081,7 @@ class CodexSessionSourceTest {
|
||||
assertThat(archivedThreads).hasSize(1)
|
||||
assertThat(archivedThreads.single().id).isEqualTo("archived-1")
|
||||
assertThat(archivedThreads.single().archived).isTrue()
|
||||
assertThat(archivedThreads.single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(archivedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1143,7 +1143,7 @@ class CodexSessionSourceTest {
|
||||
val loadedCosts = source.loadThreadCosts(PROJECT_PATH, archivedThreads)
|
||||
|
||||
assertThat(archivedThreads).hasSize(1)
|
||||
assertThat(archivedThreads.single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(archivedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(archivedThreads.single().cost).isNull()
|
||||
assertThat(loadedCosts.getValue(archivedThreadId)).isEqualTo(
|
||||
AgentSessionCost(
|
||||
@@ -1377,7 +1377,7 @@ class CodexSessionSourceTest {
|
||||
|
||||
assertThat(observedAppServerSeeds).isEmpty()
|
||||
assertThat(observedRolloutSeeds).isEmpty()
|
||||
assertThat(threads.single().activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(threads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1427,7 +1427,7 @@ class CodexSessionSourceTest {
|
||||
|
||||
assertThat(observedAppServerSeeds).isEmpty()
|
||||
assertThat(observedRolloutSeeds).isEmpty()
|
||||
assertThat(prefetched.getValue(PROJECT_PATH).single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(prefetched.getValue(PROJECT_PATH).single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@ import tools.jackson.core.JsonParser
|
||||
import tools.jackson.core.JsonToken
|
||||
import tools.jackson.core.json.JsonFactory
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCost
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentSessionProjectPath
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentSessionTitle
|
||||
@@ -339,7 +340,7 @@ internal class JunieSessionSource(
|
||||
threadId = thread.id,
|
||||
title = thread.title,
|
||||
updatedAt = thread.updatedAt,
|
||||
activity = thread.activity,
|
||||
activity = thread.activityReport.rowActivity,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -497,7 +498,7 @@ private fun JunieSessionIndexEntry.toAgentSessionThread(
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = archived == true,
|
||||
activity = effectiveActivity(readTracker, eventsAnalysis),
|
||||
activityReport = AgentThreadActivityReport(effectiveActivity(readTracker, eventsAnalysis)),
|
||||
provider = JUNIE_AGENT_SESSION_PROVIDER,
|
||||
)
|
||||
}
|
||||
|
||||
+8
-8
@@ -61,7 +61,7 @@ class JunieSessionSourceTest {
|
||||
assertThat(threads[0].title).isEqualTo("Review changes")
|
||||
assertThat(threads[0].updatedAt).isEqualTo(2000L)
|
||||
assertThat(threads[0].archived).isFalse()
|
||||
assertThat(threads[0].activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(threads[0].activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(threads[0].provider).isEqualTo(AgentSessionProvider.from("junie"))
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ class JunieSessionSourceTest {
|
||||
assertThat(threads.map { it.id }).containsExactly("session-archived")
|
||||
assertThat(threads.single().title).isEqualTo("Archived")
|
||||
assertThat(threads.single().archived).isTrue()
|
||||
assertThat(threads.single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(threads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(threads.single().provider).isEqualTo(AgentSessionProvider.from("junie"))
|
||||
}
|
||||
}
|
||||
@@ -248,12 +248,12 @@ class JunieSessionSourceTest {
|
||||
source.markThreadAsRead("session-read-state", 2000L)
|
||||
|
||||
val unreadThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
assertThat(unreadThreads.single().activity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(unreadThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
|
||||
source.markThreadAsRead("session-read-state", 3000L)
|
||||
|
||||
val readyThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
assertThat(readyThreads.single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(readyThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,15 +342,15 @@ class JunieSessionSourceTest {
|
||||
val source = JunieSessionSource(sessionIndexPathProvider = { index })
|
||||
|
||||
val listedThreads = source.listThreads(projectDir.toString(), openProject = null)
|
||||
assertThat(listedThreads.single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(listedThreads.single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
|
||||
val refreshResult = source.refreshThreads(
|
||||
threadScopedRequest(projectDir, "session-running-status")
|
||||
)
|
||||
|
||||
val refreshedThread = refreshResult.partialThreadsByPath.getValue(projectDir.toString()).single()
|
||||
assertThat(refreshedThread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(refreshedThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ class JunieSessionSourceTest {
|
||||
)
|
||||
|
||||
val refreshedThread = refreshResult.partialThreadsByPath.getValue(projectDir.toString()).single()
|
||||
assertThat(refreshedThread.activity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(refreshedThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ai.agent.opencode.sessions
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentSessionProjectPath
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentSessionTitle
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -119,7 +120,7 @@ private fun OpenCodeSessionIndexEntry.toAgentSessionThread(readTracker: Map<Stri
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = archived,
|
||||
activity = resolveReadTrackedActivity(readTracker = readTracker, threadId = sessionId, updatedAt = updatedAt),
|
||||
activityReport = AgentThreadActivityReport(resolveReadTrackedActivity(readTracker = readTracker, threadId = sessionId, updatedAt = updatedAt)),
|
||||
provider = OPENCODE_AGENT_SESSION_PROVIDER,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ class OpenCodeSessionSourceTest {
|
||||
assertThat(threads.map { it.title }).containsExactly("New thread", "Worktree session", "Older thread")
|
||||
assertThat(threads.map { it.provider }).containsOnly(AgentSessionProvider.from("opencode"))
|
||||
assertThat(threads.map { it.archived }).containsOnly(false)
|
||||
assertThat(threads.map { it.activity }).containsOnly(AgentThreadActivity.READY)
|
||||
assertThat(threads.map { it.activityReport.rowActivity }).containsOnly(AgentThreadActivity.READY)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -39,7 +39,6 @@ jvm_library(
|
||||
"//plugins/agent-workbench/lib-agent/core",
|
||||
"//plugins/agent-workbench/lib-agent/json",
|
||||
"//plugins/agent-workbench/prompt/core",
|
||||
"//plugins/agent-workbench/sessions",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core",
|
||||
"//libraries/jackson3/jackson:libraries-jackson3",
|
||||
"//libraries/netty/buffer",
|
||||
@@ -78,7 +77,6 @@ jvm_library(
|
||||
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/json:json_test_lib",
|
||||
"//plugins/agent-workbench/prompt/core:core_test_lib",
|
||||
"//plugins/agent-workbench/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core_test_lib",
|
||||
"//libraries/jackson3/jackson:libraries-jackson3_test_lib",
|
||||
"//libraries/netty/buffer:buffer_test_lib",
|
||||
@@ -175,6 +173,10 @@ jvm_library(
|
||||
"//plugins/agent-workbench/prompt/core:core_test_lib",
|
||||
"//plugins/agent-workbench/sessions",
|
||||
"//plugins/agent-workbench/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders_test_lib",
|
||||
"//plugins/agent-workbench/pi/awb",
|
||||
"//plugins/agent-workbench/pi/awb:awb_test_lib",
|
||||
"//plugins/agent-workbench/settings",
|
||||
"//plugins/agent-workbench/settings:settings_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core",
|
||||
|
||||
@@ -6,8 +6,10 @@ The wire format is JSON with a string `type` field. Those strings are part of th
|
||||
IDE-side routing is intentionally explicit:
|
||||
|
||||
- `PiExtensionControlBridge` owns the WebSocket lifecycle, authentication, connected-session registry, pending request correlation, and session update events.
|
||||
- `PiExtensionControlProtocol` owns the typed message enum, payload parsing, and shared response builders.
|
||||
- `PiTaskFolderControlHandler` owns task-folder and task-folder metadata requests backed by `AgentTaskFolderService`.
|
||||
- `PiExtensionControlProtocol` owns the typed built-in message enum, payload parsing, extension request envelope, and shared response builders.
|
||||
- `PiControlRequestHandler` extensions own domain-specific extension requests. The Agent Workbench Pi module registers
|
||||
`PiTaskFolderControlHandler` for `taskFolderRequest` frames backed by `AgentTaskFolderService`.
|
||||
|
||||
Do not add reflection-based dispatch here. New message families should get a focused handler and be routed from `PiControlMessageType` by explicit `when` branches.
|
||||
Do not add reflection-based dispatch here. New built-in Pi transport commands should be routed from `PiControlMessageType` by explicit `when` branches;
|
||||
domain-specific Agent Workbench requests should get focused `PiControlRequestHandler` implementations.
|
||||
This keeps PI transport concerns separate from task-folder domain state and avoids growing the bridge into a single control object.
|
||||
|
||||
-1
@@ -26,7 +26,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.json" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.prompt.core" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.sessions.core" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.jackson3" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.netty.buffer" />
|
||||
|
||||
+2
@@ -25,6 +25,8 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.json" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.prompt.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.task.folders" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.pi.awb" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.settings" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.sessions.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.jackson3" scope="TEST" />
|
||||
|
||||
+4
@@ -21,6 +21,10 @@
|
||||
qualifiedName="com.intellij.agent.workbench.pi.jbCentralModelContributor"
|
||||
interface="com.intellij.platform.ai.agent.pi.sessions.PiJbCentralModelContributor"
|
||||
dynamic="true"/>
|
||||
<extensionPoint
|
||||
qualifiedName="com.intellij.agent.workbench.pi.controlRequestHandler"
|
||||
interface="com.intellij.platform.ai.agent.pi.sessions.PiControlRequestHandler"
|
||||
dynamic="true"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
import {type ExtensionAPI, type ExtensionContext} from "@earendil-works/pi-coding-agent";
|
||||
import {registerTaskFolderTools, startControlBridge} from "./control.ts";
|
||||
import {startControlBridge} from "./control.ts";
|
||||
import {registerJbCentralProvider} from "./jbcentral.ts";
|
||||
import {parseModelCatalogMetadata} from "./modelCatalog.ts";
|
||||
import {registerOmlxProviders} from "./omlx.ts";
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
resolveStartupActivity,
|
||||
} from "./status.ts";
|
||||
import {subscribeShiftEnterTerminalInput} from "./terminalInput.ts";
|
||||
import {registerTaskFolderTools} from "./taskFolders.ts";
|
||||
import {applyCurrentTheme, startStateWatcher} from "./theme.ts";
|
||||
|
||||
const MODEL_CATALOG_ENV = "AGENT_WORKBENCH_PI_MODEL_CATALOG";
|
||||
|
||||
+8
-279
@@ -1,5 +1,4 @@
|
||||
import {Type} from "@earendil-works/pi-ai";
|
||||
import {defineTool, type ExtensionAPI, type ExtensionContext} from "@earendil-works/pi-coding-agent";
|
||||
import {type ExtensionContext} from "@earendil-works/pi-coding-agent";
|
||||
import * as crypto from "node:crypto";
|
||||
import * as http from "node:http";
|
||||
import {type Duplex} from "node:stream";
|
||||
@@ -11,9 +10,9 @@ const STATUS_TOKEN_ENV = "AGENT_WORKBENCH_PI_STATUS_TOKEN";
|
||||
const CONTROL_ENDPOINT = process.env[CONTROL_ENDPOINT_ENV];
|
||||
const STATUS_TOKEN = process.env[STATUS_TOKEN_ENV];
|
||||
|
||||
type AgentWorkbenchControlBridge = {
|
||||
export type AgentWorkbenchControlBridge = {
|
||||
setContext: (ctx: ExtensionContext) => void;
|
||||
requestTaskFolder: <T = unknown>(operation: string, args?: Record<string, unknown>) => Promise<T>;
|
||||
request: <T = unknown>(type: string, payload?: Record<string, unknown>) => Promise<T>;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
@@ -23,10 +22,9 @@ type AgentWorkbenchControlMessageType =
|
||||
| "response"
|
||||
| "navigateTree"
|
||||
| "forkFromEntry"
|
||||
| "taskFolderRequest";
|
||||
| (string & {});
|
||||
|
||||
type AgentWorkbenchControlSessionMessageType = "hello" | "sessionState";
|
||||
type AgentWorkbenchControlRequestType = "taskFolderRequest";
|
||||
|
||||
type AgentWorkbenchControlCommand = {
|
||||
type?: AgentWorkbenchControlMessageType;
|
||||
@@ -38,8 +36,6 @@ type AgentWorkbenchControlCommand = {
|
||||
ok?: boolean;
|
||||
cancelled?: boolean;
|
||||
error?: string;
|
||||
operation?: string;
|
||||
arguments?: Record<string, unknown>;
|
||||
result?: unknown;
|
||||
};
|
||||
|
||||
@@ -50,24 +46,6 @@ type AgentWorkbenchControlThread = {
|
||||
activity: string;
|
||||
};
|
||||
|
||||
type AgentWorkbenchTaskFolder = {
|
||||
path: string;
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
metadata?: Record<string, string>;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
type AgentWorkbenchTaskFolderThread = {
|
||||
path: string;
|
||||
provider: string;
|
||||
threadId: string;
|
||||
folderId: string;
|
||||
assignedAt?: number;
|
||||
};
|
||||
|
||||
type AgentWorkbenchControlContext = ExtensionContext & {
|
||||
navigateTree?: (entryId: string) => Promise<void> | void;
|
||||
fork?: (
|
||||
@@ -150,8 +128,8 @@ export function startControlBridge(ctx: ExtensionContext): AgentWorkbenchControl
|
||||
};
|
||||
|
||||
const sendRequest = (
|
||||
type: AgentWorkbenchControlRequestType,
|
||||
payload: Partial<AgentWorkbenchControlCommand> = {},
|
||||
type: string,
|
||||
payload: Record<string, unknown> = {},
|
||||
): Promise<AgentWorkbenchControlCommand> => {
|
||||
const sessionId = currentCtx.sessionManager.getSessionId();
|
||||
const cwd = currentCtx.cwd;
|
||||
@@ -269,8 +247,8 @@ export function startControlBridge(ctx: ExtensionContext): AgentWorkbenchControl
|
||||
currentCtx = nextCtx;
|
||||
sendSessionMessage("sessionState");
|
||||
},
|
||||
requestTaskFolder: async <T = unknown>(operation: string, args: Record<string, unknown> = {}): Promise<T> => {
|
||||
const response = requireOk(await sendRequest("taskFolderRequest", {operation, arguments: args}));
|
||||
request: async <T = unknown>(type: string, payload: Record<string, unknown> = {}): Promise<T> => {
|
||||
const response = requireOk(await sendRequest(type, payload));
|
||||
return response.result as T;
|
||||
},
|
||||
close: () => {
|
||||
@@ -284,255 +262,6 @@ export function startControlBridge(ctx: ExtensionContext): AgentWorkbenchControl
|
||||
};
|
||||
}
|
||||
|
||||
type AgentWorkbenchTaskFolderToolDefinition = {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
promptSnippet?: string;
|
||||
promptGuidelines?: string[];
|
||||
parameters: ReturnType<typeof Type.Object>;
|
||||
operation: string;
|
||||
arguments?: (params: Record<string, unknown>) => Record<string, unknown>;
|
||||
confirm?: (params: Record<string, unknown>, ctx: ExtensionContext) => Promise<boolean>;
|
||||
resultText: (result: unknown) => string;
|
||||
};
|
||||
|
||||
export function registerTaskFolderTools(
|
||||
pi: ExtensionAPI,
|
||||
bridgeProvider: () => AgentWorkbenchControlBridge | undefined,
|
||||
): void {
|
||||
for (const definition of TASK_FOLDER_TOOL_DEFINITIONS) {
|
||||
pi.registerTool(defineTool({
|
||||
name: definition.name,
|
||||
label: definition.label,
|
||||
description: definition.description,
|
||||
promptSnippet: definition.promptSnippet,
|
||||
promptGuidelines: definition.promptGuidelines,
|
||||
parameters: definition.parameters,
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const normalizedParams = params as Record<string, unknown>;
|
||||
const bridge = bridgeProvider();
|
||||
if (bridge === undefined) {
|
||||
throw new Error("Agent Workbench control bridge is unavailable");
|
||||
}
|
||||
if (definition.confirm !== undefined && !(await definition.confirm(normalizedParams, ctx))) {
|
||||
return {
|
||||
content: [{type: "text", text: "Cancelled task folder operation."}],
|
||||
details: {cancelled: true},
|
||||
};
|
||||
}
|
||||
const result = await bridge.requestTaskFolder(definition.operation, definition.arguments?.(normalizedParams) ?? normalizedParams);
|
||||
return {
|
||||
content: [{type: "text", text: definition.resultText(result)}],
|
||||
details: result,
|
||||
};
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const TASK_FOLDER_METADATA_DESCRIPTION = "String metadata. Conventional keys are 'issue' and 'review'; custom keys are allowed.";
|
||||
|
||||
const TASK_FOLDER_TOOL_DEFINITIONS: AgentWorkbenchTaskFolderToolDefinition[] = [
|
||||
{
|
||||
name: "agent_workbench_get_current_task_folder",
|
||||
label: "Get Current Task Folder",
|
||||
description: "Get the Agent Workbench task folder assigned to the current Pi session, if any.",
|
||||
promptSnippet: "Inspect the task folder assigned to the current Pi thread",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_get_current_task_folder before updating a task folder when you are unsure whether this Pi thread is already assigned.",
|
||||
],
|
||||
parameters: Type.Object({}),
|
||||
operation: "getCurrent",
|
||||
resultText: (result) => {
|
||||
const folder = resultFolder(result);
|
||||
return folder === undefined ? "No task folder is assigned to this thread." : `Current task folder: '${folder.name}'.`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_list_task_folders",
|
||||
label: "List Task Folders",
|
||||
description: "List Agent Workbench task folders for the current project.",
|
||||
promptSnippet: "List Agent Workbench task folders for the current project",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_list_task_folders when you need a task folder id or need to choose an existing task folder.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
includeDone: Type.Optional(Type.Boolean({description: "Whether to include done task folders"})),
|
||||
}),
|
||||
operation: "listFolders",
|
||||
resultText: (result) => `Found ${resultFolders(result).length} task folder(s).`,
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_list_task_folder_threads",
|
||||
label: "List Task Folder Threads",
|
||||
description: "List threads assigned to an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "List threads assigned to an Agent Workbench task folder",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
}),
|
||||
operation: "listThreads",
|
||||
resultText: (result) => `Found ${resultThreads(result).length} assigned thread(s).`,
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_create_task_folder",
|
||||
label: "Create Task Folder",
|
||||
description: "Create an Agent Workbench task folder for the current project and assign the current Pi session to it.",
|
||||
promptSnippet: "Create an Agent Workbench task folder and assign this Pi thread to it",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_create_task_folder when the user asks to create a task folder or start work in a new task folder.",
|
||||
"Use metadata key 'issue' for issue tracker ids and 'review' for review ids; do not use separate issue parameters.",
|
||||
"If the current Pi thread already has a task folder, update it with metadata or rename tools instead of creating another folder.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
name: Type.String({description: "Task folder name"}),
|
||||
metadata: Type.Optional(Type.Record(Type.String(), Type.String({description: TASK_FOLDER_METADATA_DESCRIPTION}))),
|
||||
}),
|
||||
operation: "createAndAssign",
|
||||
resultText: (result) => {
|
||||
const folder = resultFolder(result);
|
||||
if (folder === undefined) {
|
||||
return "Task folder request completed.";
|
||||
}
|
||||
return resultBoolean(result, "created") === false
|
||||
? `This thread is already assigned to task folder '${folder.name}'. Use metadata or rename tools for updates.`
|
||||
: `Created task folder '${folder.name}' and assigned this thread.`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_assign_current_thread_to_task_folder",
|
||||
label: "Assign Current Thread",
|
||||
description: "Assign the current Pi session to an existing Agent Workbench task folder in the current project.",
|
||||
promptSnippet: "Assign this Pi thread to an existing task folder",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.String({description: "Task folder id"}),
|
||||
}),
|
||||
operation: "assignCurrentThread",
|
||||
resultText: mutationResultText("Assigned current thread to task folder."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_remove_current_thread_from_task_folder",
|
||||
label: "Remove Current Thread",
|
||||
description: "Remove the current Pi session from its Agent Workbench task folder.",
|
||||
promptSnippet: "Remove this Pi thread from its current task folder",
|
||||
parameters: Type.Object({}),
|
||||
operation: "unassignCurrentThread",
|
||||
resultText: mutationResultText("Removed current thread from task folder."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_rename_task_folder",
|
||||
label: "Rename Task Folder",
|
||||
description: "Rename an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "Rename an Agent Workbench task folder",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
name: Type.String({description: "New task folder name"}),
|
||||
}),
|
||||
operation: "rename",
|
||||
resultText: (result) => {
|
||||
const folder = resultFolder(result);
|
||||
return folder === undefined ? "Task folder rename completed." : `Renamed task folder to '${folder.name}'.`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_set_task_folder_metadata",
|
||||
label: "Set Task Folder Metadata",
|
||||
description: "Set a string metadata key on an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "Set task folder metadata such as issue or review",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_set_task_folder_metadata to associate an issue id with an existing task folder using key 'issue'.",
|
||||
"Use agent_workbench_set_task_folder_metadata to associate a review id with an existing task folder using key 'review'.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
key: Type.String({description: "Metadata key. Conventional keys are 'issue' and 'review'."}),
|
||||
value: Type.String({description: "Metadata value"}),
|
||||
}),
|
||||
operation: "setMetadata",
|
||||
resultText: mutationResultText("Updated task folder metadata."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_delete_task_folder_metadata",
|
||||
label: "Delete Task Folder Metadata",
|
||||
description: "Delete a metadata key from an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "Delete task folder metadata",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
key: Type.String({description: "Metadata key to delete"}),
|
||||
}),
|
||||
operation: "deleteMetadata",
|
||||
resultText: mutationResultText("Deleted task folder metadata."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_mark_task_folder_done",
|
||||
label: "Mark Task Folder Done",
|
||||
description: "Mark an Agent Workbench task folder done after archiving assigned threads. Defaults to the current task folder.",
|
||||
promptSnippet: "Mark a task folder done and archive assigned threads",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
}),
|
||||
operation: "markDone",
|
||||
confirm: (_params, ctx) => confirmTaskFolderOperation(ctx, "Mark Task Folder Done", "Archive assigned threads and mark this task folder done?"),
|
||||
resultText: (result) => {
|
||||
const archived = resultNumber(result, "archivedCount");
|
||||
const requested = resultNumber(result, "requestedCount");
|
||||
return `Marked task folder done. Archived ${archived} of ${requested} assigned thread(s).`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_delete_task_folder",
|
||||
label: "Delete Task Folder",
|
||||
description: "Delete an Agent Workbench task folder and remove its assignments. Threads are not archived.",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
}),
|
||||
operation: "delete",
|
||||
confirm: (_params, ctx) => confirmTaskFolderOperation(ctx, "Delete Task Folder", "Delete this task folder and remove its thread assignments? Threads will not be archived."),
|
||||
resultText: mutationResultText("Deleted task folder."),
|
||||
},
|
||||
];
|
||||
|
||||
async function confirmTaskFolderOperation(ctx: ExtensionContext, title: string, message: string): Promise<boolean> {
|
||||
if (!ctx.hasUI) {
|
||||
throw new Error("Task folder operation requires interactive confirmation");
|
||||
}
|
||||
return ctx.ui.confirm(title, message);
|
||||
}
|
||||
|
||||
function mutationResultText(changedText: string): (result: unknown) => string {
|
||||
return (result) => resultBoolean(result, "changed") ? changedText : "Task folder was already up to date.";
|
||||
}
|
||||
|
||||
function resultFolder(result: unknown): AgentWorkbenchTaskFolder | undefined {
|
||||
if (!isRecord(result)) return undefined;
|
||||
const folder = result.folder;
|
||||
return isRecord(folder) && typeof folder.name === "string" ? folder as AgentWorkbenchTaskFolder : undefined;
|
||||
}
|
||||
|
||||
function resultFolders(result: unknown): AgentWorkbenchTaskFolder[] {
|
||||
if (!isRecord(result) || !Array.isArray(result.folders)) return [];
|
||||
return result.folders.filter(isRecord) as AgentWorkbenchTaskFolder[];
|
||||
}
|
||||
|
||||
function resultThreads(result: unknown): AgentWorkbenchTaskFolderThread[] {
|
||||
if (!isRecord(result) || !Array.isArray(result.threads)) return [];
|
||||
return result.threads.filter(isRecord) as AgentWorkbenchTaskFolderThread[];
|
||||
}
|
||||
|
||||
function resultBoolean(result: unknown, key: string): boolean | undefined {
|
||||
if (!isRecord(result)) return undefined;
|
||||
return typeof result[key] === "boolean" ? result[key] : undefined;
|
||||
}
|
||||
|
||||
function resultNumber(result: unknown, key: string): number {
|
||||
if (!isRecord(result)) return 0;
|
||||
return typeof result[key] === "number" ? result[key] : 0;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function resolveCapabilities(ctx: ExtensionContext): { navigateTree: boolean; fork: boolean } {
|
||||
const controlCtx = ctx as AgentWorkbenchControlContext;
|
||||
return {
|
||||
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
import {Type} from "@earendil-works/pi-ai";
|
||||
import {defineTool, type ExtensionAPI, type ExtensionContext} from "@earendil-works/pi-coding-agent";
|
||||
import {type AgentWorkbenchControlBridge} from "./control.ts";
|
||||
|
||||
const TASK_FOLDER_REQUEST_TYPE = "taskFolderRequest";
|
||||
|
||||
type AgentWorkbenchTaskFolder = {
|
||||
path: string;
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
metadata?: Record<string, string>;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
type AgentWorkbenchTaskFolderThread = {
|
||||
path: string;
|
||||
provider: string;
|
||||
threadId: string;
|
||||
folderId: string;
|
||||
assignedAt?: number;
|
||||
};
|
||||
|
||||
type AgentWorkbenchTaskFolderToolDefinition = {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
promptSnippet?: string;
|
||||
promptGuidelines?: string[];
|
||||
parameters: ReturnType<typeof Type.Object>;
|
||||
operation: string;
|
||||
arguments?: (params: Record<string, unknown>) => Record<string, unknown>;
|
||||
confirm?: (params: Record<string, unknown>, ctx: ExtensionContext) => Promise<boolean>;
|
||||
resultText: (result: unknown) => string;
|
||||
};
|
||||
|
||||
export function registerTaskFolderTools(
|
||||
pi: ExtensionAPI,
|
||||
bridgeProvider: () => AgentWorkbenchControlBridge | undefined,
|
||||
): void {
|
||||
for (const definition of TASK_FOLDER_TOOL_DEFINITIONS) {
|
||||
pi.registerTool(defineTool({
|
||||
name: definition.name,
|
||||
label: definition.label,
|
||||
description: definition.description,
|
||||
promptSnippet: definition.promptSnippet,
|
||||
promptGuidelines: definition.promptGuidelines,
|
||||
parameters: definition.parameters,
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const normalizedParams = params as Record<string, unknown>;
|
||||
const bridge = bridgeProvider();
|
||||
if (bridge === undefined) {
|
||||
throw new Error("Agent Workbench control bridge is unavailable");
|
||||
}
|
||||
if (definition.confirm !== undefined && !(await definition.confirm(normalizedParams, ctx))) {
|
||||
return {
|
||||
content: [{type: "text", text: "Cancelled task folder operation."}],
|
||||
details: {cancelled: true},
|
||||
};
|
||||
}
|
||||
const args = definition.arguments?.(normalizedParams) ?? normalizedParams;
|
||||
const result = await bridge.request(TASK_FOLDER_REQUEST_TYPE, {operation: definition.operation, arguments: args});
|
||||
return {
|
||||
content: [{type: "text", text: definition.resultText(result)}],
|
||||
details: result,
|
||||
};
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const TASK_FOLDER_METADATA_DESCRIPTION = "String metadata. Conventional keys are 'issue' and 'review'; custom keys are allowed.";
|
||||
|
||||
const TASK_FOLDER_TOOL_DEFINITIONS: AgentWorkbenchTaskFolderToolDefinition[] = [
|
||||
{
|
||||
name: "agent_workbench_get_current_task_folder",
|
||||
label: "Get Current Task Folder",
|
||||
description: "Get the Agent Workbench task folder assigned to the current Pi session, if any.",
|
||||
promptSnippet: "Inspect the task folder assigned to the current Pi thread",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_get_current_task_folder before updating a task folder when you are unsure whether this Pi thread is already assigned.",
|
||||
],
|
||||
parameters: Type.Object({}),
|
||||
operation: "getCurrent",
|
||||
resultText: (result) => {
|
||||
const folder = resultFolder(result);
|
||||
return folder === undefined ? "No task folder is assigned to this thread." : `Current task folder: '${folder.name}'.`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_list_task_folders",
|
||||
label: "List Task Folders",
|
||||
description: "List Agent Workbench task folders for the current project.",
|
||||
promptSnippet: "List Agent Workbench task folders for the current project",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_list_task_folders when you need a task folder id or need to choose an existing task folder.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
includeDone: Type.Optional(Type.Boolean({description: "Whether to include done task folders"})),
|
||||
}),
|
||||
operation: "listFolders",
|
||||
resultText: (result) => `Found ${resultFolders(result).length} task folder(s).`,
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_list_task_folder_threads",
|
||||
label: "List Task Folder Threads",
|
||||
description: "List threads assigned to an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "List threads assigned to an Agent Workbench task folder",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
}),
|
||||
operation: "listThreads",
|
||||
resultText: (result) => `Found ${resultThreads(result).length} assigned thread(s).`,
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_create_task_folder",
|
||||
label: "Create Task Folder",
|
||||
description: "Create an Agent Workbench task folder for the current project and assign the current Pi session to it.",
|
||||
promptSnippet: "Create an Agent Workbench task folder and assign this Pi thread to it",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_create_task_folder when the user asks to create a task folder or start work in a new task folder.",
|
||||
"Use metadata key 'issue' for issue tracker ids and 'review' for review ids; do not use separate issue parameters.",
|
||||
"If the current Pi thread already has a task folder, update it with metadata or rename tools instead of creating another folder.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
name: Type.String({description: "Task folder name"}),
|
||||
metadata: Type.Optional(Type.Record(Type.String(), Type.String({description: TASK_FOLDER_METADATA_DESCRIPTION}))),
|
||||
}),
|
||||
operation: "createAndAssign",
|
||||
resultText: (result) => {
|
||||
const folder = resultFolder(result);
|
||||
if (folder === undefined) {
|
||||
return "Task folder request completed.";
|
||||
}
|
||||
return resultBoolean(result, "created") === false
|
||||
? `This thread is already assigned to task folder '${folder.name}'. Use metadata or rename tools for updates.`
|
||||
: `Created task folder '${folder.name}' and assigned this thread.`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_assign_current_thread_to_task_folder",
|
||||
label: "Assign Current Thread",
|
||||
description: "Assign the current Pi session to an existing Agent Workbench task folder in the current project.",
|
||||
promptSnippet: "Assign this Pi thread to an existing task folder",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.String({description: "Task folder id"}),
|
||||
}),
|
||||
operation: "assignCurrentThread",
|
||||
resultText: mutationResultText("Assigned current thread to task folder."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_remove_current_thread_from_task_folder",
|
||||
label: "Remove Current Thread",
|
||||
description: "Remove the current Pi session from its Agent Workbench task folder.",
|
||||
promptSnippet: "Remove this Pi thread from its current task folder",
|
||||
parameters: Type.Object({}),
|
||||
operation: "unassignCurrentThread",
|
||||
resultText: mutationResultText("Removed current thread from task folder."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_rename_task_folder",
|
||||
label: "Rename Task Folder",
|
||||
description: "Rename an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "Rename an Agent Workbench task folder",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
name: Type.String({description: "New task folder name"}),
|
||||
}),
|
||||
operation: "rename",
|
||||
resultText: (result) => {
|
||||
const folder = resultFolder(result);
|
||||
return folder === undefined ? "Task folder rename completed." : `Renamed task folder to '${folder.name}'.`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_set_task_folder_metadata",
|
||||
label: "Set Task Folder Metadata",
|
||||
description: "Set a string metadata key on an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "Set task folder metadata such as issue or review",
|
||||
promptGuidelines: [
|
||||
"Use agent_workbench_set_task_folder_metadata to associate an issue id with an existing task folder using key 'issue'.",
|
||||
"Use agent_workbench_set_task_folder_metadata to associate a review id with an existing task folder using key 'review'.",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
key: Type.String({description: "Metadata key. Conventional keys are 'issue' and 'review'."}),
|
||||
value: Type.String({description: "Metadata value"}),
|
||||
}),
|
||||
operation: "setMetadata",
|
||||
resultText: mutationResultText("Updated task folder metadata."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_delete_task_folder_metadata",
|
||||
label: "Delete Task Folder Metadata",
|
||||
description: "Delete a metadata key from an Agent Workbench task folder. Defaults to the current task folder.",
|
||||
promptSnippet: "Delete task folder metadata",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
key: Type.String({description: "Metadata key to delete"}),
|
||||
}),
|
||||
operation: "deleteMetadata",
|
||||
resultText: mutationResultText("Deleted task folder metadata."),
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_mark_task_folder_done",
|
||||
label: "Mark Task Folder Done",
|
||||
description: "Mark an Agent Workbench task folder done after archiving assigned threads. Defaults to the current task folder.",
|
||||
promptSnippet: "Mark a task folder done and archive assigned threads",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
}),
|
||||
operation: "markDone",
|
||||
confirm: (_params, ctx) => confirmTaskFolderOperation(ctx, "Mark Task Folder Done", "Archive assigned threads and mark this task folder done?"),
|
||||
resultText: (result) => {
|
||||
const archived = resultNumber(result, "archivedCount");
|
||||
const requested = resultNumber(result, "requestedCount");
|
||||
return `Marked task folder done. Archived ${archived} of ${requested} assigned thread(s).`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "agent_workbench_delete_task_folder",
|
||||
label: "Delete Task Folder",
|
||||
description: "Delete an Agent Workbench task folder and remove its assignments. Threads are not archived.",
|
||||
parameters: Type.Object({
|
||||
folderId: Type.Optional(Type.String({description: "Task folder id; defaults to the current task folder"})),
|
||||
}),
|
||||
operation: "delete",
|
||||
confirm: (_params, ctx) => confirmTaskFolderOperation(ctx, "Delete Task Folder", "Delete this task folder and remove its thread assignments? Threads will not be archived."),
|
||||
resultText: mutationResultText("Deleted task folder."),
|
||||
},
|
||||
];
|
||||
|
||||
async function confirmTaskFolderOperation(ctx: ExtensionContext, title: string, message: string): Promise<boolean> {
|
||||
if (!ctx.hasUI) {
|
||||
throw new Error("Task folder operation requires interactive confirmation");
|
||||
}
|
||||
return ctx.ui.confirm(title, message);
|
||||
}
|
||||
|
||||
function mutationResultText(changedText: string): (result: unknown) => string {
|
||||
return (result) => resultBoolean(result, "changed") ? changedText : "Task folder was already up to date.";
|
||||
}
|
||||
|
||||
function resultFolder(result: unknown): AgentWorkbenchTaskFolder | undefined {
|
||||
if (!isRecord(result)) return undefined;
|
||||
const folder = result.folder;
|
||||
return isRecord(folder) && typeof folder.name === "string" ? folder as AgentWorkbenchTaskFolder : undefined;
|
||||
}
|
||||
|
||||
function resultFolders(result: unknown): AgentWorkbenchTaskFolder[] {
|
||||
if (!isRecord(result) || !Array.isArray(result.folders)) return [];
|
||||
return result.folders.filter(isRecord) as AgentWorkbenchTaskFolder[];
|
||||
}
|
||||
|
||||
function resultThreads(result: unknown): AgentWorkbenchTaskFolderThread[] {
|
||||
if (!isRecord(result) || !Array.isArray(result.threads)) return [];
|
||||
return result.threads.filter(isRecord) as AgentWorkbenchTaskFolderThread[];
|
||||
}
|
||||
|
||||
function resultBoolean(result: unknown, key: string): boolean | undefined {
|
||||
if (!isRecord(result)) return undefined;
|
||||
return typeof result[key] === "boolean" ? result[key] : undefined;
|
||||
}
|
||||
|
||||
function resultNumber(result: unknown, key: string): number {
|
||||
if (!isRecord(result)) return 0;
|
||||
return typeof result[key] === "number" ? result[key] : 0;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
+3
-1
@@ -2,5 +2,7 @@
|
||||
package com.intellij.platform.ai.agent.pi.sessions
|
||||
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
internal val PI_AGENT_SESSION_PROVIDER: AgentSessionProvider = AgentSessionProvider.from("pi")
|
||||
@ApiStatus.Internal
|
||||
val PI_AGENT_SESSION_PROVIDER: AgentSessionProvider = AgentSessionProvider.from("pi")
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ai.agent.pi.sessions
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface PiControlRequestHandler {
|
||||
val messageType: String
|
||||
|
||||
fun handle(
|
||||
context: PiControlSessionContext,
|
||||
request: PiControlExtensionRequest,
|
||||
requestId: String,
|
||||
sendResponse: (String) -> Unit,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val EP_NAME: ExtensionPointName<PiControlRequestHandler> =
|
||||
ExtensionPointName("com.intellij.agent.workbench.pi.controlRequestHandler")
|
||||
}
|
||||
}
|
||||
+12
-8
@@ -30,7 +30,6 @@ private val CONTROL_LOG = logger<PiExtensionControlBridge>()
|
||||
private val PI_CONTROL_CONNECTION_KEY: Key<PiControlConnection> = Key.create("agent.workbench.pi.control.connection")
|
||||
|
||||
internal object PiExtensionControlBridge {
|
||||
private val taskFolderControlHandler = PiTaskFolderControlHandler()
|
||||
private val connectionsBySessionId = ConcurrentHashMap<String, PiControlConnection>()
|
||||
private val controlUpdates = MutableSharedFlow<AgentSessionSourceUpdateEvent>(
|
||||
extraBufferCapacity = 64,
|
||||
@@ -87,11 +86,12 @@ internal object PiExtensionControlBridge {
|
||||
PiControlMessageType.HELLO -> handleHello(webSocketClient, payload)
|
||||
PiControlMessageType.SESSION_STATE -> handleSessionState(webSocketClient, payload)
|
||||
PiControlMessageType.RESPONSE -> handleResponse(webSocketClient, payload)
|
||||
PiControlMessageType.TASK_FOLDER_REQUEST -> handleTaskFolderRequest(webSocketClient, payload)
|
||||
PiControlMessageType.NAVIGATE_TREE,
|
||||
PiControlMessageType.FORK_FROM_ENTRY,
|
||||
null,
|
||||
-> sendProtocolError(webSocketClient, requestId = payload.requestId, error = "Unsupported control message type")
|
||||
null -> if (!handleExtensionRequest(webSocketClient, payload)) {
|
||||
sendProtocolError(webSocketClient, requestId = payload.requestId, error = "Unsupported control message type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,20 +171,24 @@ internal object PiExtensionControlBridge {
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleTaskFolderRequest(client: WebSocketClient, payload: PiControlPayload) {
|
||||
private fun handleExtensionRequest(client: WebSocketClient, payload: PiControlPayload): Boolean {
|
||||
val messageType = payload.typeName?.trim()?.takeIf { it.isNotEmpty() } ?: return false
|
||||
val handler = PiControlRequestHandler.EP_NAME.extensionList.firstOrNull { candidate -> candidate.messageType == messageType }
|
||||
?: return false
|
||||
val connection = client.getUserData(PI_CONTROL_CONNECTION_KEY)
|
||||
val requestId = payload.requestId?.trim()?.takeIf { it.isNotEmpty() }
|
||||
if (connection == null || requestId == null) {
|
||||
sendProtocolError(client, requestId = requestId, error = "Task folder request requires an authenticated control connection")
|
||||
return
|
||||
sendProtocolError(client, requestId = requestId, error = "Control request requires an authenticated control connection")
|
||||
return true
|
||||
}
|
||||
|
||||
taskFolderControlHandler.handle(
|
||||
handler.handle(
|
||||
context = PiControlSessionContext(projectPath = connection.projectPath, sessionId = connection.sessionId),
|
||||
payload = payload,
|
||||
request = PiControlExtensionRequest(operation = payload.operation, arguments = payload.arguments),
|
||||
requestId = requestId,
|
||||
sendResponse = { response -> sendControlText(client = client, text = response) },
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun sendCommand(path: String, threadId: String, itemId: String, type: PiControlMessageType): PiControlResponse? {
|
||||
|
||||
+42
-12
@@ -2,6 +2,7 @@
|
||||
package com.intellij.platform.ai.agent.pi.sessions
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
import com.intellij.platform.ai.agent.json.createJsonGenerator
|
||||
import com.intellij.platform.ai.agent.json.createJsonParser
|
||||
@@ -11,6 +12,7 @@ import com.intellij.platform.ai.agent.json.readJsonStringOrNull
|
||||
import tools.jackson.core.JsonParser
|
||||
import tools.jackson.core.JsonToken
|
||||
import tools.jackson.core.json.JsonFactory
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.io.StringWriter
|
||||
|
||||
internal enum class PiControlMessageType(@JvmField val wireName: String) {
|
||||
@@ -18,8 +20,7 @@ internal enum class PiControlMessageType(@JvmField val wireName: String) {
|
||||
SESSION_STATE("sessionState"),
|
||||
RESPONSE("response"),
|
||||
NAVIGATE_TREE("navigateTree"),
|
||||
FORK_FROM_ENTRY("forkFromEntry"),
|
||||
TASK_FOLDER_REQUEST("taskFolderRequest");
|
||||
FORK_FROM_ENTRY("forkFromEntry");
|
||||
|
||||
companion object {
|
||||
private val byWireName: Map<String, PiControlMessageType> = entries.associateBy(PiControlMessageType::wireName)
|
||||
@@ -30,11 +31,18 @@ internal enum class PiControlMessageType(@JvmField val wireName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
internal data class PiControlSessionContext(
|
||||
@ApiStatus.Internal
|
||||
data class PiControlSessionContext(
|
||||
@JvmField val projectPath: String,
|
||||
@JvmField val sessionId: String,
|
||||
)
|
||||
|
||||
@ApiStatus.Internal
|
||||
data class PiControlExtensionRequest(
|
||||
@JvmField val operation: String? = null,
|
||||
@JvmField val arguments: PiControlRequestArguments? = null,
|
||||
)
|
||||
|
||||
internal data class PiControlCapabilities(
|
||||
@JvmField val navigateTree: Boolean,
|
||||
@JvmField val fork: Boolean,
|
||||
@@ -55,6 +63,7 @@ internal data class PiControlResponse(
|
||||
|
||||
internal data class PiControlPayload(
|
||||
@JvmField val type: PiControlMessageType? = null,
|
||||
@JvmField val typeName: String? = null,
|
||||
@JvmField val requestId: String? = null,
|
||||
@JvmField val token: String? = null,
|
||||
@JvmField val sessionId: String? = null,
|
||||
@@ -65,10 +74,11 @@ internal data class PiControlPayload(
|
||||
@JvmField val thread: PiControlThreadPayload? = null,
|
||||
@JvmField val capabilities: PiControlCapabilities? = null,
|
||||
@JvmField val operation: String? = null,
|
||||
@JvmField val arguments: PiTaskFolderControlArguments? = null,
|
||||
@JvmField val arguments: PiControlRequestArguments? = null,
|
||||
)
|
||||
|
||||
internal data class PiTaskFolderControlArguments(
|
||||
@ApiStatus.Internal
|
||||
data class PiControlRequestArguments(
|
||||
@JvmField val folderId: String? = null,
|
||||
@JvmField val name: String? = null,
|
||||
@JvmField val key: String? = null,
|
||||
@@ -90,7 +100,7 @@ internal data class PiControlThreadPayload(
|
||||
title = title?.trim()?.takeIf { it.isNotEmpty() } ?: threadId,
|
||||
updatedAt = updatedAt ?: System.currentTimeMillis(),
|
||||
archived = false,
|
||||
activity = activity ?: AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(activity ?: AgentThreadActivity.READY),
|
||||
provider = PI_AGENT_SESSION_PROVIDER,
|
||||
)
|
||||
}
|
||||
@@ -129,7 +139,8 @@ internal fun buildPiControlHelloAcknowledgement(requestId: String?, sessionId: S
|
||||
}
|
||||
}
|
||||
|
||||
internal fun buildPiControlErrorResponse(requestId: String?, error: String): String {
|
||||
@ApiStatus.Internal
|
||||
fun buildPiControlErrorResponse(requestId: String?, error: String): String {
|
||||
return buildPiControlJsonObject { generator ->
|
||||
generator.writeStringProperty("type", PiControlMessageType.RESPONSE.wireName)
|
||||
requestId?.let { generator.writeStringProperty("requestId", it) }
|
||||
@@ -138,6 +149,19 @@ internal fun buildPiControlErrorResponse(requestId: String?, error: String): Str
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun buildPiControlResultResponse(requestId: String, writeResult: (tools.jackson.core.JsonGenerator) -> Unit): String {
|
||||
return buildPiControlJsonObject { generator ->
|
||||
generator.writeStringProperty("type", PiControlMessageType.RESPONSE.wireName)
|
||||
generator.writeStringProperty("requestId", requestId)
|
||||
generator.writeBooleanProperty("ok", true)
|
||||
generator.writeName("result")
|
||||
generator.writeStartObject()
|
||||
writeResult(generator)
|
||||
generator.writeEndObject()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun buildPiControlJsonObject(builder: (tools.jackson.core.JsonGenerator) -> Unit): String {
|
||||
val writer = StringWriter()
|
||||
PI_CONTROL_JSON_FACTORY.createJsonGenerator(writer).use { generator ->
|
||||
@@ -150,6 +174,7 @@ internal fun buildPiControlJsonObject(builder: (tools.jackson.core.JsonGenerator
|
||||
|
||||
private fun readControlPayload(parser: JsonParser): PiControlPayload {
|
||||
var type: PiControlMessageType? = null
|
||||
var typeName: String? = null
|
||||
var requestId: String? = null
|
||||
var token: String? = null
|
||||
var sessionId: String? = null
|
||||
@@ -160,10 +185,14 @@ private fun readControlPayload(parser: JsonParser): PiControlPayload {
|
||||
var thread: PiControlThreadPayload? = null
|
||||
var capabilities: PiControlCapabilities? = null
|
||||
var operation: String? = null
|
||||
var arguments: PiTaskFolderControlArguments? = null
|
||||
var arguments: PiControlRequestArguments? = null
|
||||
forEachJsonObjectField(parser) { fieldName ->
|
||||
when (fieldName) {
|
||||
"type" -> type = PiControlMessageType.fromWireName(readJsonStringOrNull(parser))
|
||||
"type" -> {
|
||||
val wireName = readJsonStringOrNull(parser)
|
||||
typeName = wireName
|
||||
type = PiControlMessageType.fromWireName(wireName)
|
||||
}
|
||||
"requestId" -> requestId = readJsonStringOrNull(parser)
|
||||
"token" -> token = readJsonStringOrNull(parser)
|
||||
"sessionId" -> sessionId = readJsonStringOrNull(parser)
|
||||
@@ -174,13 +203,14 @@ private fun readControlPayload(parser: JsonParser): PiControlPayload {
|
||||
"thread" -> thread = readControlThreadPayload(parser)
|
||||
"capabilities" -> capabilities = readControlCapabilities(parser)
|
||||
"operation" -> operation = readJsonStringOrNull(parser)
|
||||
"arguments" -> arguments = readTaskFolderControlArguments(parser)
|
||||
"arguments" -> arguments = readControlRequestArguments(parser)
|
||||
else -> parser.skipChildren()
|
||||
}
|
||||
true
|
||||
}
|
||||
return PiControlPayload(
|
||||
type = type,
|
||||
typeName = typeName,
|
||||
requestId = requestId,
|
||||
token = token,
|
||||
sessionId = sessionId,
|
||||
@@ -195,7 +225,7 @@ private fun readControlPayload(parser: JsonParser): PiControlPayload {
|
||||
)
|
||||
}
|
||||
|
||||
private fun readTaskFolderControlArguments(parser: JsonParser): PiTaskFolderControlArguments? {
|
||||
private fun readControlRequestArguments(parser: JsonParser): PiControlRequestArguments? {
|
||||
if (parser.currentToken() != JsonToken.START_OBJECT) {
|
||||
parser.skipChildren()
|
||||
return null
|
||||
@@ -218,7 +248,7 @@ private fun readTaskFolderControlArguments(parser: JsonParser): PiTaskFolderCont
|
||||
}
|
||||
true
|
||||
}
|
||||
return PiTaskFolderControlArguments(
|
||||
return PiControlRequestArguments(
|
||||
folderId = folderId,
|
||||
name = name,
|
||||
key = key,
|
||||
|
||||
@@ -8,6 +8,7 @@ import tools.jackson.core.JsonParser
|
||||
import tools.jackson.core.JsonToken
|
||||
import tools.jackson.core.json.JsonFactory
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentSessionProjectPath
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentSessionTitle
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
|
||||
@@ -1431,7 +1432,7 @@ private fun PiSessionIndexEntry.toAgentSessionThread(readTracker: Map<String, Lo
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = archived,
|
||||
activity = activity ?: resolveCompletedPiActivity(readTracker = readTracker),
|
||||
activityReport = AgentThreadActivityReport(activity ?: resolveCompletedPiActivity(readTracker = readTracker)),
|
||||
provider = PI_AGENT_SESSION_PROVIDER,
|
||||
)
|
||||
}
|
||||
@@ -1446,10 +1447,12 @@ private fun PiSessionIndexEntry.toAgentSessionThread(
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = archived,
|
||||
activity = activity ?: resolveCompletedPiActivity(
|
||||
readTracker = readTracker,
|
||||
completedUnreadUpdatedAtByThreadId = completedUnreadUpdatedAtByThreadId,
|
||||
observedUpdatedAtByThreadId = observedUpdatedAtByThreadId,
|
||||
activityReport = AgentThreadActivityReport(
|
||||
activity ?: resolveCompletedPiActivity(
|
||||
readTracker = readTracker,
|
||||
completedUnreadUpdatedAtByThreadId = completedUnreadUpdatedAtByThreadId,
|
||||
observedUpdatedAtByThreadId = observedUpdatedAtByThreadId,
|
||||
)
|
||||
),
|
||||
provider = PI_AGENT_SESSION_PROVIDER,
|
||||
)
|
||||
|
||||
@@ -325,6 +325,7 @@ private val PI_EXTENSION_RESOURCE_FILE_NAMES: List<String> = listOf(
|
||||
"omlx.ts",
|
||||
"status.ts",
|
||||
"terminalInput.ts",
|
||||
"taskFolders.ts",
|
||||
"theme.ts",
|
||||
)
|
||||
private const val PI_EXTENSION_FILE_NAME_HASH_PREFIX_LENGTH: Int = 16
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ class PiAgentSessionProviderDescriptorTest {
|
||||
|
||||
@Test
|
||||
fun exposesPiProviderMetadata() {
|
||||
assertThat(PI_AGENT_SESSION_PROVIDER).isEqualTo(AgentSessionProvider.from("pi"))
|
||||
assertThat(descriptor.sessionSource.provider).isEqualTo(PI_AGENT_SESSION_PROVIDER)
|
||||
assertThat(descriptor.displayPriority).isEqualTo(3)
|
||||
assertThat(descriptor.displayNameKey).isEqualTo("toolwindow.provider.pi")
|
||||
assertThat(descriptor.newSessionLabelKey).isEqualTo("toolwindow.action.new.session.pi")
|
||||
@@ -1063,7 +1063,7 @@ private fun knownSelection(provider: String, modelId: String, reasoning: Boolean
|
||||
private fun emptySource(): AgentSessionSource {
|
||||
return object : AgentSessionSource {
|
||||
override val provider: AgentSessionProvider
|
||||
get() = AgentSessionProvider.from("pi")
|
||||
get() = PI_AGENT_SESSION_PROVIDER
|
||||
|
||||
override suspend fun listThreads(path: String, openProject: Project?): List<AgentSessionThread> = emptyList()
|
||||
|
||||
|
||||
+4
-3
@@ -5,8 +5,8 @@ import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.json.createJsonParser
|
||||
import com.intellij.platform.ai.agent.json.forEachJsonObjectField
|
||||
import com.intellij.platform.ai.agent.json.readJsonStringOrNull
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderService
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionSourceUpdate
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.service
|
||||
@@ -116,7 +116,7 @@ class PiExtensionControlWebSocketHandlerTest {
|
||||
assertThat(forkedThread?.id).isEqualTo(forkedSessionId)
|
||||
assertThat(forkedThread?.title).isEqualTo("Forked thread")
|
||||
assertThat(forkedThread?.updatedAt).isEqualTo(9_000L)
|
||||
assertThat(forkedThread?.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(forkedThread?.activityReport?.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(PiExtensionControlBridge.canNavigateThreadOutlineItem(projectDir.toString(), sessionId, "entry-next")).isFalse()
|
||||
assertThat(PiExtensionControlBridge.canNavigateThreadOutlineItem(projectDir.toString(), forkedSessionId, "entry-next")).isTrue()
|
||||
assertThat(PiExtensionStatusBridge.authenticateLaunchToken(launchEnvironment.token, sessionId)).isNull()
|
||||
@@ -402,6 +402,7 @@ class PiExtensionControlWebSocketHandlerTest {
|
||||
}
|
||||
|
||||
private fun registerControlHandler(disposable: Disposable) {
|
||||
ExtensionTestUtil.maskExtensions(PiControlRequestHandler.EP_NAME, listOf(PiTaskFolderControlHandler()), disposable)
|
||||
ExtensionTestUtil.maskExtensions(HttpRequestHandler.EP_NAME, listOf(PiExtensionControlWebSocketHandler()), disposable)
|
||||
}
|
||||
|
||||
|
||||
+18
-19
@@ -6,7 +6,6 @@ import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCost
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionOutlineItemKind
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.sessions.core.cost.AgentSessionUsageSnapshot
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionActivityEvidence
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionSourceUpdate
|
||||
@@ -71,8 +70,8 @@ class PiSessionSourceTest {
|
||||
assertThat(threads[0].title).isEqualTo("Named Pi session")
|
||||
assertThat(threads[0].updatedAt).isEqualTo(3_000L)
|
||||
assertThat(threads[0].archived).isFalse()
|
||||
assertThat(threads[0].activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(threads[0].provider).isEqualTo(AgentSessionProvider.from("pi"))
|
||||
assertThat(threads[0].activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(threads[0].provider).isEqualTo(PI_AGENT_SESSION_PROVIDER)
|
||||
assertThat(threads[1].title).isEqualTo("Old task")
|
||||
}
|
||||
}
|
||||
@@ -467,7 +466,7 @@ class PiSessionSourceTest {
|
||||
val outline = source.loadThreadOutline(projectDir.toString(), "session-outline", null)
|
||||
|
||||
assertThat(outline).isNotNull
|
||||
assertThat(outline!!.provider).isEqualTo(AgentSessionProvider.from("pi"))
|
||||
assertThat(outline!!.provider).isEqualTo(PI_AGENT_SESSION_PROVIDER)
|
||||
assertThat(outline.threadId).isEqualTo("session-outline")
|
||||
assertThat(outline.title).isEqualTo("Named outline")
|
||||
assertThat(outline.updatedAt).isEqualTo(3_000L)
|
||||
@@ -524,7 +523,7 @@ class PiSessionSourceTest {
|
||||
)
|
||||
|
||||
val forkedThread = checkNotNull(forkResult?.thread)
|
||||
assertThat(forkedThread.provider).isEqualTo(AgentSessionProvider.from("pi"))
|
||||
assertThat(forkedThread.provider).isEqualTo(PI_AGENT_SESSION_PROVIDER)
|
||||
assertThat(forkedThread.id).isNotEqualTo("session-local-fork")
|
||||
assertThat(forkedThread.title).isEqualTo("First task")
|
||||
val forkedOutline = source.loadThreadOutline(projectDir.toString(), forkedThread.id, null)
|
||||
@@ -617,15 +616,15 @@ class PiSessionSourceTest {
|
||||
val store = PiSessionStore(sessionDirResolver = { sessionDir }, timeProvider = { now })
|
||||
val source = PiSessionSource(sessionStore = store)
|
||||
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(store.archiveThread(projectDir.toString(), "session-archive-processing")).isTrue()
|
||||
val archivedThread = source.listArchivedThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(archivedThread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(archivedThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
|
||||
now = 5_000L
|
||||
assertThat(store.unarchiveThread(projectDir.toString(), "session-archive-processing")).isTrue()
|
||||
val activeThread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(activeThread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(activeThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,10 +692,10 @@ class PiSessionSourceTest {
|
||||
val source = sourceFor(sessionDir)
|
||||
|
||||
source.markThreadAsRead("session-read", 2_000L)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
|
||||
source.markThreadAsRead("session-read", 3_000L)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(source.listThreads(projectDir.toString(), openProject = null).single().activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,7 +714,7 @@ class PiSessionSourceTest {
|
||||
|
||||
val thread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
|
||||
assertThat(thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,7 +739,7 @@ class PiSessionSourceTest {
|
||||
|
||||
val thread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
|
||||
assertThat(thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,7 +759,7 @@ class PiSessionSourceTest {
|
||||
|
||||
val thread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
|
||||
assertThat(thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,7 +779,7 @@ class PiSessionSourceTest {
|
||||
|
||||
val thread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
|
||||
assertThat(thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,17 +797,17 @@ class PiSessionSourceTest {
|
||||
val source = sourceFor(sessionDir)
|
||||
|
||||
val workingThread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(workingThread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(workingThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
appendPiSessionEntry(
|
||||
sessionFile,
|
||||
piAssistantMessageEntry(id = "assistant-completed-observed", parentId = "user-completed-observed", timestamp = 4_000L),
|
||||
)
|
||||
|
||||
val completedThread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(completedThread.activity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(completedThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
source.markThreadAsRead("session-completed-observed", 4_000L)
|
||||
val readThread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(readThread.activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(readThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,14 +826,14 @@ class PiSessionSourceTest {
|
||||
source.setActiveThreadId("session-active-completed")
|
||||
|
||||
val workingThread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(workingThread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(workingThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
appendPiSessionEntry(
|
||||
sessionFile,
|
||||
piAssistantMessageEntry(id = "assistant-active-completed", parentId = "user-active-completed", timestamp = 4_000L),
|
||||
)
|
||||
|
||||
val completedThread = source.listThreads(projectDir.toString(), openProject = null).single()
|
||||
assertThat(completedThread.activity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(completedThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -200,6 +200,7 @@ class PiThemeSupportTest {
|
||||
"from \"./jbcentral.ts\"",
|
||||
"from \"./omlx.ts\"",
|
||||
"from \"./status.ts\"",
|
||||
"from \"./taskFolders.ts\"",
|
||||
"from \"./theme.ts\"",
|
||||
"controlBridge = startControlBridge(ctx)",
|
||||
)
|
||||
@@ -208,13 +209,14 @@ class PiThemeSupportTest {
|
||||
@Test
|
||||
fun bundledExtensionRegistersTaskFolderToolForPi() {
|
||||
val entrypoint = readBundledPiExtensionText("agent-workbench-extension.ts")
|
||||
val control = readBundledPiExtensionText("control.ts")
|
||||
val taskFolders = readBundledPiExtensionText("taskFolders.ts")
|
||||
|
||||
assertThat(entrypoint).contains(
|
||||
"import {registerTaskFolderTools, startControlBridge} from \"./control.ts\";",
|
||||
"import {startControlBridge} from \"./control.ts\";",
|
||||
"import {registerTaskFolderTools} from \"./taskFolders.ts\";",
|
||||
"registerTaskFolderTools(pi, () => controlBridge);",
|
||||
)
|
||||
assertThat(control).contains(
|
||||
assertThat(taskFolders).contains(
|
||||
"const TASK_FOLDER_TOOL_DEFINITIONS",
|
||||
"pi.registerTool(defineTool({",
|
||||
"name: \"agent_workbench_get_current_task_folder\"",
|
||||
@@ -227,7 +229,7 @@ class PiThemeSupportTest {
|
||||
"name: Type.String({description: \"Task folder name\"})",
|
||||
"metadata: Type.Optional(Type.Record(Type.String(), Type.String({description: TASK_FOLDER_METADATA_DESCRIPTION})))",
|
||||
"Use metadata key 'issue' for issue tracker ids and 'review' for review ids; do not use separate issue parameters.",
|
||||
"const result = await bridge.requestTaskFolder(definition.operation, definition.arguments?.(normalizedParams) ?? normalizedParams);",
|
||||
"const result = await bridge.request(TASK_FOLDER_REQUEST_TYPE, {operation: definition.operation, arguments: args});",
|
||||
"details: result",
|
||||
)
|
||||
}
|
||||
|
||||
-2
@@ -4,7 +4,6 @@ package com.intellij.platform.ai.agent.terminal.sessions
|
||||
// @spec community/plugins/agent-workbench/spec/sessions/agent-terminal-sessions.spec.md
|
||||
// @spec community/plugins/agent-workbench/spec/core/agent-state-storage.spec.md
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionTerminalRestoreContext
|
||||
@@ -191,7 +190,6 @@ private fun PersistedTerminalSessionState.toAgentSessionThread(): AgentSessionTh
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = archived,
|
||||
activity = AgentThreadActivity.READY,
|
||||
provider = TERMINAL_AGENT_SESSION_PROVIDER,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadActivityPresent
|
||||
- f:path:java.lang.String
|
||||
- f:threadId:java.lang.String
|
||||
- f:updatedAt:java.lang.Long
|
||||
- <init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity):V
|
||||
- <init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):V
|
||||
- b:<init>(java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- f:component1():java.lang.String
|
||||
@@ -21,7 +20,6 @@ f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
|
||||
- f:activityReport:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
- f:title:java.lang.String
|
||||
- f:updatedAt:java.lang.Long
|
||||
- <init>(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity):V
|
||||
- <init>(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):V
|
||||
- b:<init>(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- f:component1():java.lang.String
|
||||
@@ -30,7 +28,6 @@ f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
|
||||
- f:copy(java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
|
||||
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentation
|
||||
- equals(java.lang.Object):Z
|
||||
- f:getActivity():com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
- hashCode():I
|
||||
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- sf:Companion:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet$Companion
|
||||
@@ -80,8 +77,8 @@ f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationMod
|
||||
- f:updateActivityHints-9c6lQVA(java.lang.String,java.util.Collection):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- f:updatePresentationHints-9c6lQVA(java.lang.String,java.util.Collection):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- f:updateProviderSnapshot-3n3rsyc(java.lang.String,java.util.Set,java.util.Map):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- f:updateThread-AlqNjB4(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- bs:updateThread-AlqNjB4$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationModel,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivity,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- f:updateThread-RmkVQ7I(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
- bs:updateThread-RmkVQ7I$default(com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationModel,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.AgentThreadActivityReport,java.lang.Long,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationChangeSet
|
||||
f:com.intellij.platform.ai.agent.sessions.core.AgentSessionThreadPresentationPatchUpdate
|
||||
- f:activityReport:com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
- f:path:java.lang.String
|
||||
@@ -159,24 +156,6 @@ f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$SubAgent
|
||||
- getThreadId():java.lang.String
|
||||
- getTitle():java.lang.String
|
||||
- hashCode():I
|
||||
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$TaskFolder
|
||||
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
- f:folderId:java.lang.String
|
||||
- f:isDone:Z
|
||||
- f:metadata:java.util.Map
|
||||
- f:name:java.lang.String
|
||||
- f:path:java.lang.String
|
||||
- <init>(java.lang.String,java.lang.String,java.lang.String,Z,java.util.Map):V
|
||||
- b:<init>(java.lang.String,java.lang.String,java.lang.String,Z,java.util.Map,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
- f:component1():java.lang.String
|
||||
- f:component2():java.lang.String
|
||||
- f:component3():java.lang.String
|
||||
- f:component4():Z
|
||||
- f:component5():java.util.Map
|
||||
- f:copy(java.lang.String,java.lang.String,java.lang.String,Z,java.util.Map):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$TaskFolder
|
||||
- bs:copy$default(com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$TaskFolder,java.lang.String,java.lang.String,java.lang.String,Z,java.util.Map,I,java.lang.Object):com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$TaskFolder
|
||||
- equals(java.lang.Object):Z
|
||||
- hashCode():I
|
||||
f:com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Thread
|
||||
- com.intellij.platform.ai.agent.sessions.core.SessionActionTarget$Conversation
|
||||
- b:<init>(java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.platform.ai.agent.core.session.AgentSessionThread,I,kotlin.jvm.internal.DefaultConstructorMarker):V
|
||||
|
||||
+3
-26
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ai.agent.sessions.core
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
@@ -42,18 +41,7 @@ data class AgentSessionThreadPresentation(
|
||||
@JvmField val title: @NlsSafe String,
|
||||
@JvmField val activityReport: AgentThreadActivityReport,
|
||||
@JvmField val updatedAt: Long? = null,
|
||||
) {
|
||||
constructor(
|
||||
title: @NlsSafe String,
|
||||
activity: AgentThreadActivity,
|
||||
) : this(
|
||||
title = title,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
)
|
||||
|
||||
val activity: AgentThreadActivity
|
||||
get() = activityReport.rowActivity
|
||||
}
|
||||
)
|
||||
|
||||
data class AgentSessionThreadPresentationChangeSet(
|
||||
@JvmField val changedKeys: Set<AgentSessionThreadPresentationKey>,
|
||||
@@ -75,17 +63,7 @@ data class AgentSessionThreadActivityPresentationUpdate(
|
||||
@JvmField val threadId: String,
|
||||
@JvmField val activityReport: AgentThreadActivityReport,
|
||||
@JvmField val updatedAt: Long? = null,
|
||||
) {
|
||||
constructor(
|
||||
path: String,
|
||||
threadId: String,
|
||||
activity: AgentThreadActivity,
|
||||
) : this(
|
||||
path = path,
|
||||
threadId = threadId,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
class AgentSessionThreadPresentationModel {
|
||||
@@ -105,8 +83,7 @@ class AgentSessionThreadPresentationModel {
|
||||
provider: AgentSessionProvider,
|
||||
threadId: String,
|
||||
title: String,
|
||||
activity: AgentThreadActivity?,
|
||||
activityReport: AgentThreadActivityReport? = activity?.let(::AgentThreadActivityReport),
|
||||
activityReport: AgentThreadActivityReport? = null,
|
||||
updatedAt: Long? = null,
|
||||
): AgentSessionThreadPresentationChangeSet {
|
||||
val key = AgentSessionThreadPresentationKey.create(projectPath = path, provider = provider, threadId = threadId)
|
||||
|
||||
@@ -15,14 +15,6 @@ sealed interface SessionActionTarget {
|
||||
@JvmField val path: String,
|
||||
) : SessionActionTarget
|
||||
|
||||
data class TaskFolder(
|
||||
@JvmField val path: String,
|
||||
@JvmField val folderId: String,
|
||||
@JvmField val name: String,
|
||||
@JvmField val isDone: Boolean,
|
||||
@JvmField val metadata: Map<String, String> = emptyMap(),
|
||||
) : SessionActionTarget
|
||||
|
||||
sealed interface Conversation : SessionActionTarget {
|
||||
val path: String
|
||||
val provider: AgentSessionProvider
|
||||
|
||||
@@ -26,8 +26,10 @@ jvm_library(
|
||||
"//platform/util",
|
||||
"//plugins/agent-workbench/lib-agent/core",
|
||||
"//plugins/agent-workbench/lib-agent/providers/pi/sessions",
|
||||
"//plugins/agent-workbench/sessions",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders",
|
||||
"//plugins/agent-workbench/settings",
|
||||
"//libraries/jackson3/jackson:libraries-jackson3",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -48,8 +50,10 @@ jvm_library(
|
||||
"//platform/util:util_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/providers/pi/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/sessions-core:ai-agent-sessions-core_test_lib",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders_test_lib",
|
||||
"//plugins/agent-workbench/settings:settings_test_lib",
|
||||
"//libraries/jackson3/jackson:libraries-jackson3_test_lib",
|
||||
],
|
||||
)
|
||||
### auto-generated section `build intellij.agent.workbench.pi.awb` end
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.pi.sessions" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.sessions.core" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.task.folders" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.settings" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.jackson3" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -3,12 +3,14 @@
|
||||
<dependencies>
|
||||
<module name="intellij.platform.ai.agent.core"/>
|
||||
<module name="intellij.platform.ai.agent.pi.sessions"/>
|
||||
<module name="intellij.agent.workbench.sessions"/>
|
||||
<module name="intellij.platform.ai.agent.sessions.core"/>
|
||||
<module name="intellij.agent.workbench.sessions.task.folders"/>
|
||||
<module name="intellij.agent.workbench.settings"/>
|
||||
</dependencies>
|
||||
<!-- endregion -->
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij.agent.workbench">
|
||||
<settingsContributor implementation="com.intellij.platform.ai.agent.pi.sessions.PiProviderSettingsContributor"/>
|
||||
<pi.controlRequestHandler implementation="com.intellij.platform.ai.agent.pi.sessions.PiTaskFolderControlHandler"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
settings.agent.workbench.provider.pi.omlx.models=Use local oMLX models
|
||||
settings.agent.workbench.provider.pi.omlx.models.description=When enabled, Agent Workbench discovers local oMLX models and registers the selected model for Pi sessions.
|
||||
settings.agent.workbench.provider.pi.jbcentral.models=Use JetBrains Central models
|
||||
settings.agent.workbench.provider.pi.jbcentral.models.description=When enabled, Agent Workbench detects JetBrains Central wiring and registers it for Pi sessions.
|
||||
@@ -2,12 +2,9 @@
|
||||
package com.intellij.platform.ai.agent.pi.sessions
|
||||
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.agent.workbench.sessions.AgentSessionsBundle
|
||||
import com.intellij.agent.workbench.settings.AgentWorkbenchCheckboxSetting
|
||||
import com.intellij.agent.workbench.settings.AgentWorkbenchSettingsContributor
|
||||
|
||||
private val PI_AGENT_SESSION_PROVIDER: AgentSessionProvider = AgentSessionProvider.from("pi")
|
||||
|
||||
internal class PiProviderSettingsContributor : AgentWorkbenchSettingsContributor {
|
||||
override fun providerCheckboxSettings(provider: AgentSessionProvider): List<AgentWorkbenchCheckboxSetting> {
|
||||
if (provider != PI_AGENT_SESSION_PROVIDER) {
|
||||
@@ -15,14 +12,14 @@ internal class PiProviderSettingsContributor : AgentWorkbenchSettingsContributor
|
||||
}
|
||||
return listOf(
|
||||
AgentWorkbenchCheckboxSetting(
|
||||
text = AgentSessionsBundle.message("settings.agent.workbench.provider.pi.omlx.models"),
|
||||
description = AgentSessionsBundle.message("settings.agent.workbench.provider.pi.omlx.models.description"),
|
||||
text = PiSessionsBundle.message("settings.agent.workbench.provider.pi.omlx.models"),
|
||||
description = PiSessionsBundle.message("settings.agent.workbench.provider.pi.omlx.models.description"),
|
||||
isSelected = PiOmlxSupportSettings::isEnabled,
|
||||
setSelected = PiOmlxSupportSettings::setEnabled,
|
||||
),
|
||||
AgentWorkbenchCheckboxSetting(
|
||||
text = AgentSessionsBundle.message("settings.agent.workbench.provider.pi.jbcentral.models"),
|
||||
description = AgentSessionsBundle.message("settings.agent.workbench.provider.pi.jbcentral.models.description"),
|
||||
text = PiSessionsBundle.message("settings.agent.workbench.provider.pi.jbcentral.models"),
|
||||
description = PiSessionsBundle.message("settings.agent.workbench.provider.pi.jbcentral.models.description"),
|
||||
isSelected = PiJbCentralSupportSettings::isEnabled,
|
||||
setSelected = PiJbCentralSupportSettings::setEnabled,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ai.agent.pi.sessions
|
||||
|
||||
import com.intellij.DynamicBundle
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.PropertyKey
|
||||
|
||||
const val PI_SESSIONS_BUNDLE: @NonNls String = "messages.PiSessionsBundle"
|
||||
|
||||
internal object PiSessionsBundle {
|
||||
private val BUNDLE = DynamicBundle(PiSessionsBundle::class.java, PI_SESSIONS_BUNDLE)
|
||||
|
||||
fun message(key: @PropertyKey(resourceBundle = PI_SESSIONS_BUNDLE) String, vararg params: Any): @Nls String {
|
||||
return BUNDLE.getMessage(key, *params)
|
||||
}
|
||||
}
|
||||
|
||||
+73
-44
@@ -1,29 +1,43 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ai.agent.pi.sessions
|
||||
|
||||
import com.intellij.agent.workbench.sessions.model.ArchiveThreadTarget
|
||||
import com.intellij.agent.workbench.sessions.service.AgentSessionArchiveService
|
||||
import com.intellij.agent.workbench.sessions.statistics.AgentWorkbenchEntryPoint
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderService
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviders
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import tools.jackson.core.JsonGenerator
|
||||
|
||||
internal class PiTaskFolderControlHandler(
|
||||
private val taskFolderServiceProvider: () -> AgentTaskFolderService = { service<AgentTaskFolderService>() },
|
||||
private val archiveServiceProvider: () -> AgentSessionArchiveService = { service<AgentSessionArchiveService>() },
|
||||
) {
|
||||
fun handle(
|
||||
@ApiStatus.Internal
|
||||
class PiTaskFolderControlHandler : PiControlRequestHandler {
|
||||
private val taskFolderServiceProvider: () -> AgentTaskFolderService
|
||||
|
||||
constructor() : this(
|
||||
taskFolderServiceProvider = { service<AgentTaskFolderService>() },
|
||||
)
|
||||
|
||||
internal constructor(
|
||||
taskFolderServiceProvider: () -> AgentTaskFolderService,
|
||||
) {
|
||||
this.taskFolderServiceProvider = taskFolderServiceProvider
|
||||
}
|
||||
|
||||
override val messageType: String = PI_TASK_FOLDER_CONTROL_MESSAGE_TYPE
|
||||
|
||||
override fun handle(
|
||||
context: PiControlSessionContext,
|
||||
payload: PiControlPayload,
|
||||
request: PiControlExtensionRequest,
|
||||
requestId: String,
|
||||
sendResponse: (String) -> Unit,
|
||||
) {
|
||||
val service = taskFolderServiceProvider()
|
||||
val arguments = payload.arguments ?: PiTaskFolderControlArguments()
|
||||
when (payload.operation?.trim()) {
|
||||
val arguments = request.arguments ?: PiControlRequestArguments()
|
||||
when (request.operation?.trim()) {
|
||||
OP_GET_CURRENT -> {
|
||||
val folder = service.getFolderForThread(context.projectPath, PI_AGENT_SESSION_PROVIDER, context.sessionId)
|
||||
sendResponse(buildTaskFolderResponse(requestId = requestId, folder = folder))
|
||||
@@ -107,7 +121,7 @@ internal class PiTaskFolderControlHandler(
|
||||
sendResponse(buildPiControlErrorResponse(requestId, "Task folder is not available"))
|
||||
}
|
||||
else {
|
||||
markDone(service, archiveServiceProvider, context, folder, requestId, sendResponse)
|
||||
markDone(service, context, folder, requestId, sendResponse)
|
||||
}
|
||||
}
|
||||
OP_DELETE -> {
|
||||
@@ -128,7 +142,7 @@ internal class PiTaskFolderControlHandler(
|
||||
private fun createAndAssignCurrentThread(
|
||||
service: AgentTaskFolderService,
|
||||
context: PiControlSessionContext,
|
||||
arguments: PiTaskFolderControlArguments,
|
||||
arguments: PiControlRequestArguments,
|
||||
requestId: String,
|
||||
sendResponse: (String) -> Unit,
|
||||
) {
|
||||
@@ -154,7 +168,6 @@ private fun createAndAssignCurrentThread(
|
||||
|
||||
private fun markDone(
|
||||
service: AgentTaskFolderService,
|
||||
archiveServiceProvider: () -> AgentSessionArchiveService,
|
||||
context: PiControlSessionContext,
|
||||
folder: AgentTaskFolder,
|
||||
requestId: String,
|
||||
@@ -169,8 +182,8 @@ private fun markDone(
|
||||
return
|
||||
}
|
||||
val targets = service.listFolderThreadAssignments(folder.id)
|
||||
.map { assignment -> ArchiveThreadTarget.Thread(assignment.path, assignment.provider, assignment.threadId) }
|
||||
.distinctBy { target -> ArchiveTargetKey(target.path, target.provider.value, target.threadId) }
|
||||
.map { assignment -> TaskFolderArchiveTarget(assignment.path, assignment.provider, assignment.threadId) }
|
||||
.distinct()
|
||||
if (targets.isEmpty()) {
|
||||
val changed = service.setFolderStatus(folder.id, AgentTaskFolderStatus.DONE)
|
||||
sendResponse(
|
||||
@@ -184,23 +197,38 @@ private fun markDone(
|
||||
)
|
||||
return
|
||||
}
|
||||
val archiveService = archiveServiceProvider()
|
||||
if (!targets.all { target -> archiveService.canArchiveProvider(target.provider) }) {
|
||||
if (!targets.all { target -> AgentSessionProviders.find(target.provider)?.supportsArchiveThread == true }) {
|
||||
sendResponse(buildPiControlErrorResponse(requestId, "Task folder contains threads that cannot be archived"))
|
||||
return
|
||||
}
|
||||
archiveService.archiveThreads(targets, AgentWorkbenchEntryPoint.TREE_POPUP) { result ->
|
||||
val changed = result.allRequestedArchived && service.setFolderStatus(folder.id, AgentTaskFolderStatus.DONE)
|
||||
sendResponse(
|
||||
buildTaskFolderDoneResponse(
|
||||
requestId = requestId,
|
||||
changed = changed,
|
||||
folder = refreshedFolder(service, context, folder.id) ?: folder,
|
||||
requestedCount = result.requestedCount,
|
||||
archivedCount = result.archivedCount,
|
||||
)
|
||||
val result = archiveTargets(targets)
|
||||
val changed = result.allRequestedArchived && service.setFolderStatus(folder.id, AgentTaskFolderStatus.DONE)
|
||||
sendResponse(
|
||||
buildTaskFolderDoneResponse(
|
||||
requestId = requestId,
|
||||
changed = changed,
|
||||
folder = refreshedFolder(service, context, folder.id) ?: folder,
|
||||
requestedCount = result.requestedCount,
|
||||
archivedCount = result.archivedCount,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun archiveTargets(targets: List<TaskFolderArchiveTarget>): TaskFolderArchiveResult {
|
||||
var archivedCount = 0
|
||||
targets.forEach { target ->
|
||||
val descriptor = AgentSessionProviders.find(target.provider)
|
||||
if (descriptor?.supportsArchiveThread != true) return@forEach
|
||||
val archived = try {
|
||||
runBlockingCancellable { descriptor.archiveThread(path = target.path, threadId = target.threadId) }
|
||||
}
|
||||
catch (t: Throwable) {
|
||||
if (t is CancellationException) throw t
|
||||
false
|
||||
}
|
||||
if (archived) archivedCount++
|
||||
}
|
||||
return TaskFolderArchiveResult(requestedCount = targets.size, archivedCount = archivedCount)
|
||||
}
|
||||
|
||||
private fun buildTaskFolderCreatedResponse(requestId: String, folder: AgentTaskFolder, created: Boolean, assigned: Boolean): String {
|
||||
@@ -262,15 +290,7 @@ private fun buildTaskFolderDoneResponse(
|
||||
}
|
||||
|
||||
private fun buildTaskFolderResultResponse(requestId: String, writeResult: (JsonGenerator) -> Unit): String {
|
||||
return buildPiControlJsonObject { generator ->
|
||||
generator.writeStringProperty("type", PiControlMessageType.RESPONSE.wireName)
|
||||
generator.writeStringProperty("requestId", requestId)
|
||||
generator.writeBooleanProperty("ok", true)
|
||||
generator.writeName("result")
|
||||
generator.writeStartObject()
|
||||
writeResult(generator)
|
||||
generator.writeEndObject()
|
||||
}
|
||||
return buildPiControlResultResponse(requestId, writeResult)
|
||||
}
|
||||
|
||||
private fun writeTaskFolder(generator: JsonGenerator, folder: AgentTaskFolder?) {
|
||||
@@ -305,7 +325,7 @@ private fun writeTaskFolderAssignment(generator: JsonGenerator, assignment: Agen
|
||||
private fun resolveTaskFolder(
|
||||
service: AgentTaskFolderService,
|
||||
context: PiControlSessionContext,
|
||||
arguments: PiTaskFolderControlArguments,
|
||||
arguments: PiControlRequestArguments,
|
||||
): AgentTaskFolder? {
|
||||
val explicitFolderId = arguments.folderId?.trim()?.takeIf { it.isNotEmpty() }
|
||||
if (explicitFolderId != null) {
|
||||
@@ -318,12 +338,20 @@ private fun refreshedFolder(service: AgentTaskFolderService, context: PiControlS
|
||||
return service.snapshot(includeDone = true).folder(context.projectPath, folderId)
|
||||
}
|
||||
|
||||
private data class ArchiveTargetKey(
|
||||
private data class TaskFolderArchiveTarget(
|
||||
@JvmField val path: String,
|
||||
@JvmField val providerId: String,
|
||||
val provider: AgentSessionProvider,
|
||||
@JvmField val threadId: String,
|
||||
)
|
||||
|
||||
private data class TaskFolderArchiveResult(
|
||||
@JvmField val requestedCount: Int,
|
||||
@JvmField val archivedCount: Int,
|
||||
) {
|
||||
val allRequestedArchived: Boolean
|
||||
get() = requestedCount > 0 && requestedCount == archivedCount
|
||||
}
|
||||
|
||||
private const val OP_GET_CURRENT: String = "getCurrent"
|
||||
private const val OP_LIST_FOLDERS: String = "listFolders"
|
||||
private const val OP_LIST_THREADS: String = "listThreads"
|
||||
@@ -335,3 +363,4 @@ private const val OP_SET_METADATA: String = "setMetadata"
|
||||
private const val OP_DELETE_METADATA: String = "deleteMetadata"
|
||||
private const val OP_MARK_DONE: String = "markDone"
|
||||
private const val OP_DELETE: String = "delete"
|
||||
private const val PI_TASK_FOLDER_CONTROL_MESSAGE_TYPE: String = "taskFolderRequest"
|
||||
@@ -27,6 +27,7 @@
|
||||
- name: intellij.agent.workbench.prompt.vcs.ui
|
||||
- name: intellij.agent.workbench.prompt.testrunner
|
||||
- name: intellij.agent.workbench.sessions
|
||||
- name: intellij.agent.workbench.sessions.task.folders
|
||||
- name: intellij.agent.workbench.sessions.jbcentral
|
||||
- name: intellij.agent.workbench.sessions.actions
|
||||
- name: intellij.agent.workbench.sessions.toolwindow
|
||||
|
||||
@@ -272,6 +272,8 @@ jvm_library(
|
||||
"//plugins/agent-workbench/lib-agent/providers/codex/common:ai-agent-codex-common-tests_test_lib",
|
||||
"//plugins/agent-workbench/sessions-launch-config/backend:agent-workbench-sessions-launch-config-backend-tests",
|
||||
"//plugins/agent-workbench/sessions-launch-config/backend:agent-workbench-sessions-launch-config-backend-tests_test_lib",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders-tests",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders-tests_test_lib",
|
||||
],
|
||||
)
|
||||
### auto-generated section `build intellij.agent.workbench.plugin.tests` end
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.cost.tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.codex.common.tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.launch.config.backend.tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.task.folders.tests" scope="TEST" />
|
||||
</component>
|
||||
<component name="TestModuleProperties" production-module="intellij.agent.workbench.plugin" />
|
||||
</module>
|
||||
@@ -80,6 +80,9 @@
|
||||
- name: lib/modules/intellij.agent.workbench.sessions.launch.config.backend.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.sessions.launch.config.backend
|
||||
- name: lib/modules/intellij.agent.workbench.sessions.task.folders.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.sessions.task.folders
|
||||
- name: lib/modules/intellij.agent.workbench.sessions.toolwindow.jar
|
||||
contentModules:
|
||||
- name: intellij.agent.workbench.sessions.toolwindow
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<module name="intellij.agent.workbench.prompt.vcs.ui"/>
|
||||
<module name="intellij.agent.workbench.prompt.testrunner"/>
|
||||
<module name="intellij.agent.workbench.sessions"/>
|
||||
<module name="intellij.agent.workbench.sessions.task.folders"/>
|
||||
<module name="intellij.agent.workbench.sessions.jbcentral"/>
|
||||
<module name="intellij.agent.workbench.ui"/>
|
||||
<module name="intellij.agent.workbench.sessions.actions"/>
|
||||
|
||||
@@ -218,7 +218,7 @@ internal fun formatExistingTaskEntries(
|
||||
nowLabel = nowLabel,
|
||||
unknownLabel = unknownLabel,
|
||||
),
|
||||
activity = thread.activity,
|
||||
activity = thread.activityReport.rowActivity,
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.agent.workbench.prompt.ui
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptExistingThreadsSnapshot
|
||||
@@ -311,7 +312,7 @@ class AgentPromptExistingTaskControllerTest {
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = false,
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
)
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@
|
||||
package com.intellij.agent.workbench.prompt.ui
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -543,7 +544,7 @@ class AgentPromptPaletteSessionControllerTest {
|
||||
title = "${provider.value} thread",
|
||||
updatedAt = 100,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
provider = provider,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.platform.ai.agent.common.session.isClaudeMenuCommandPrompt
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -682,7 +683,7 @@ class AgentPromptPaletteSubmitControllerTest {
|
||||
updatedAt = 100,
|
||||
archived = false,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
subAgents = emptyList(),
|
||||
)
|
||||
),
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
load("@community//build:tests-options.bzl", "jps_test")
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_library")
|
||||
|
||||
### auto-generated section `build intellij.agent.workbench.sessions.task.folders` start
|
||||
|
||||
jvm_library(
|
||||
name = "agent-workbench-sessions-task-folders",
|
||||
srcs = glob(
|
||||
[
|
||||
"src/**/*.kt",
|
||||
"src/**/*.java",
|
||||
"src/**/*.form",
|
||||
],
|
||||
allow_empty = True,
|
||||
),
|
||||
module_name = "intellij.agent.workbench.sessions.task.folders",
|
||||
resource_strip_prefix = "resources",
|
||||
resources = glob(
|
||||
["resources/**/*"],
|
||||
),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
# do not sort,
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//libraries/kotlinx/serialization/core",
|
||||
"//platform/core-api:core",
|
||||
"//platform/projectModel-api:projectModel",
|
||||
"//platform/util",
|
||||
"//platform/util-ex",
|
||||
"//plugins/agent-workbench/lib-agent/core",
|
||||
],
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "agent-workbench-sessions-task-folders_test_lib",
|
||||
testonly = True,
|
||||
srcs = glob(
|
||||
[],
|
||||
allow_empty = True,
|
||||
),
|
||||
module_name = "intellij.agent.workbench.sessions.task.folders",
|
||||
visibility = ["//visibility:public"],
|
||||
runtime_deps = [
|
||||
# do not sort,
|
||||
":agent-workbench-sessions-task-folders",
|
||||
"//libraries/kotlinx/serialization/core:core_test_lib",
|
||||
"//platform/core-api:core_test_lib",
|
||||
"//platform/projectModel-api:projectModel_test_lib",
|
||||
"//platform/util:util_test_lib",
|
||||
"//platform/util-ex:util-ex_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
|
||||
],
|
||||
)
|
||||
### auto-generated section `build intellij.agent.workbench.sessions.task.folders` end
|
||||
|
||||
### auto-generated section `iml intellij.agent.workbench.sessions.task.folders` start
|
||||
exports_files(
|
||||
[
|
||||
"intellij.agent.workbench.sessions.task.folders.iml",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
### auto-generated section `iml intellij.agent.workbench.sessions.task.folders` end
|
||||
|
||||
### auto-generated section `build intellij.agent.workbench.sessions.task.folders.tests` start
|
||||
|
||||
jvm_library(
|
||||
name = "agent-workbench-sessions-task-folders-tests",
|
||||
srcs = glob(
|
||||
[],
|
||||
allow_empty = True,
|
||||
),
|
||||
module_name = "intellij.agent.workbench.sessions.task.folders.tests",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "agent-workbench-sessions-task-folders-tests_test_lib",
|
||||
testonly = True,
|
||||
srcs = glob(
|
||||
[
|
||||
"testSrc/**/*.kt",
|
||||
"testSrc/**/*.java",
|
||||
"testSrc/**/*.form",
|
||||
],
|
||||
allow_empty = True,
|
||||
),
|
||||
associates = [
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders_test_lib",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
runtime_deps = [":agent-workbench-sessions-task-folders-tests"],
|
||||
deps = [
|
||||
# do not sort,
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//libraries/kotlinx/serialization/core",
|
||||
"//libraries/kotlinx/serialization/core:core_test_lib",
|
||||
"//platform/core-api:core",
|
||||
"//platform/core-api:core_test_lib",
|
||||
"//platform/projectModel-api:projectModel",
|
||||
"//platform/projectModel-api:projectModel_test_lib",
|
||||
"//platform/util",
|
||||
"//platform/util:util_test_lib",
|
||||
"//platform/util-ex",
|
||||
"//platform/util-ex:util-ex_test_lib",
|
||||
"//plugins/agent-workbench/lib-agent/core",
|
||||
"//plugins/agent-workbench/lib-agent/core:core_test_lib",
|
||||
"//libraries/junit5",
|
||||
"//libraries/junit5:junit5_test_lib",
|
||||
"//platform/testFramework",
|
||||
"//platform/testFramework:testFramework_test_lib",
|
||||
"//platform/testFramework/junit5",
|
||||
"//platform/testFramework/junit5:junit5_test_lib",
|
||||
"//libraries/assertj-core",
|
||||
"//libraries/assertj-core:assertj-core_test_lib",
|
||||
],
|
||||
)
|
||||
### auto-generated section `build intellij.agent.workbench.sessions.task.folders.tests` end
|
||||
|
||||
### auto-generated section `iml intellij.agent.workbench.sessions.task.folders.tests` start
|
||||
exports_files(
|
||||
[
|
||||
"intellij.agent.workbench.sessions.task.folders.tests.iml",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
### auto-generated section `iml intellij.agent.workbench.sessions.task.folders.tests` end
|
||||
|
||||
### auto-generated section `test intellij.agent.workbench.sessions.task.folders.tests` start
|
||||
|
||||
jps_test(
|
||||
name = "agent-workbench-sessions-task-folders-tests_test",
|
||||
runtime_deps = [":agent-workbench-sessions-task-folders-tests_test_lib"],
|
||||
)
|
||||
### auto-generated section `test intellij.agent.workbench.sessions.task.folders.tests` end
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="kotlin-language" name="Kotlin">
|
||||
<configuration version="5" platform="JVM 25" allPlatforms="JVM [25]" useProjectSettings="false">
|
||||
<compilerSettings>
|
||||
<option name="additionalArguments" value="-Xjvm-default=all -XXLanguage:+AllowEagerSupertypeAccessibilityChecks -progressive" />
|
||||
</compilerSettings>
|
||||
<compilerArguments>
|
||||
<stringArguments>
|
||||
<stringArg name="jvmTarget" arg="25" />
|
||||
<stringArg name="apiVersion" arg="2.4" />
|
||||
<stringArg name="languageVersion" arg="2.4" />
|
||||
</stringArguments>
|
||||
<arrayArguments>
|
||||
<arrayArg name="pluginClasspaths">
|
||||
<args>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</args>
|
||||
</arrayArg>
|
||||
</arrayArguments>
|
||||
</compilerArguments>
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.agent.workbench.sessions.task.folders" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.ex" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" />
|
||||
</component>
|
||||
</module>
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="kotlin-language" name="Kotlin">
|
||||
<configuration version="5" platform="JVM 25" allPlatforms="JVM [25]" useProjectSettings="false">
|
||||
<compilerSettings>
|
||||
<option name="additionalArguments" value="-Xjvm-default=all -XXLanguage:+AllowEagerSupertypeAccessibilityChecks -progressive" />
|
||||
</compilerSettings>
|
||||
<compilerArguments>
|
||||
<stringArguments>
|
||||
<stringArg name="jvmTarget" arg="25" />
|
||||
<stringArg name="apiVersion" arg="2.4" />
|
||||
<stringArg name="languageVersion" arg="2.4" />
|
||||
</stringArguments>
|
||||
<arrayArguments>
|
||||
<arrayArg name="pluginClasspaths">
|
||||
<args>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</args>
|
||||
</arrayArg>
|
||||
</arrayArguments>
|
||||
</compilerArguments>
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/testSrc">
|
||||
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" packagePrefix="com.intellij.agent.workbench.sessions.task.folders" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" scope="TEST" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.serialization.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.ex" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.assertj.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.task.folders" scope="TEST" />
|
||||
</component>
|
||||
<component name="TestModuleProperties" production-module="intellij.agent.workbench.sessions.task.folders" />
|
||||
</module>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<idea-plugin>
|
||||
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
|
||||
<dependencies>
|
||||
<module name="intellij.platform.ai.agent.core"/>
|
||||
</dependencies>
|
||||
<!-- endregion -->
|
||||
</idea-plugin>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ai.agent.sessions.core.folders
|
||||
package com.intellij.agent.workbench.sessions.task.folders
|
||||
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
+1
-3
@@ -1,9 +1,7 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.agent.workbench.sessions
|
||||
package com.intellij.agent.workbench.sessions.task.folders
|
||||
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.util.io.Ksuid
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@@ -48,6 +48,7 @@ jvm_library(
|
||||
"//platform/editor-ui-api:editor-ui",
|
||||
"//plugins/agent-workbench/chat",
|
||||
"//plugins/agent-workbench/sessions",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders",
|
||||
"//plugins/agent-workbench/sessions-jbcentral:agent-workbench-sessions-jbcentral",
|
||||
"//plugins/agent-workbench/sessions-actions:agent-workbench-sessions-actions",
|
||||
"//libraries/jackson3/jackson:libraries-jackson3",
|
||||
@@ -101,6 +102,7 @@ jvm_library(
|
||||
"//plugins/agent-workbench/lib-agent/providers/claude/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/chat:chat_test_lib",
|
||||
"//plugins/agent-workbench/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders_test_lib",
|
||||
"//plugins/agent-workbench/sessions-jbcentral:agent-workbench-sessions-jbcentral_test_lib",
|
||||
"//plugins/agent-workbench/sessions-actions:agent-workbench-sessions-actions_test_lib",
|
||||
"//libraries/jackson3/jackson:libraries-jackson3_test_lib",
|
||||
@@ -222,6 +224,8 @@ jvm_library(
|
||||
"//plugins/agent-workbench/chat:chat_test_lib",
|
||||
"//plugins/agent-workbench/sessions",
|
||||
"//plugins/agent-workbench/sessions:sessions_test_lib",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders",
|
||||
"//plugins/agent-workbench/sessions-task-folders:agent-workbench-sessions-task-folders_test_lib",
|
||||
"//plugins/agent-workbench/sessions-jbcentral:agent-workbench-sessions-jbcentral",
|
||||
"//plugins/agent-workbench/sessions-jbcentral:agent-workbench-sessions-jbcentral_test_lib",
|
||||
"//plugins/agent-workbench/sessions-actions:agent-workbench-sessions-actions",
|
||||
|
||||
+1
@@ -59,6 +59,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.claude.sessions" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.chat" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.task.folders" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.jbcentral" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.actions" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.jackson3" />
|
||||
|
||||
+1
@@ -57,6 +57,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ai.agent.claude.sessions" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.chat" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.task.folders" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.jbcentral" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.agent.workbench.sessions.actions" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.jackson3" scope="TEST" />
|
||||
|
||||
+1
@@ -8,6 +8,7 @@
|
||||
<module name="intellij.platform.ai.agent.core"/>
|
||||
<module name="intellij.agent.workbench.prompt.core"/>
|
||||
<module name="intellij.agent.workbench.sessions"/>
|
||||
<module name="intellij.agent.workbench.sessions.task.folders"/>
|
||||
<module name="intellij.agent.workbench.sessions.actions"/>
|
||||
<module name="intellij.platform.ai.agent.sessions.core"/>
|
||||
<module name="intellij.agent.workbench.sessions.jbcentral"/>
|
||||
|
||||
+12
-3
@@ -44,7 +44,8 @@ internal object AgentSessionsTreePopupDataKeys {
|
||||
|
||||
internal data class AgentSessionsTreePopupActionContext(
|
||||
@JvmField val project: Project,
|
||||
val target: SessionActionTarget,
|
||||
val target: SessionActionTarget? = null,
|
||||
@JvmField val taskFolderTarget: AgentTaskFolderActionTarget? = null,
|
||||
@JvmField val archiveTargets: List<ArchiveThreadTarget>,
|
||||
@JvmField val unarchiveTargets: List<ArchiveThreadTarget> = emptyList(),
|
||||
@JvmField val selectedThreadTargets: List<SessionActionTarget.Thread> = emptyList(),
|
||||
@@ -255,7 +256,15 @@ internal class AgentSessionsTreePopupNewThreadGroup @JvmOverloads constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun newThreadPathFromTarget(target: SessionActionTarget): String? {
|
||||
internal data class AgentTaskFolderActionTarget(
|
||||
@JvmField val path: String,
|
||||
@JvmField val folderId: String,
|
||||
@JvmField val name: String,
|
||||
@JvmField val isDone: Boolean,
|
||||
@JvmField val metadata: Map<String, String> = emptyMap(),
|
||||
)
|
||||
|
||||
private fun newThreadPathFromTarget(target: SessionActionTarget?): String? {
|
||||
return when (target) {
|
||||
is SessionActionTarget.Project -> target.path
|
||||
is SessionActionTarget.Worktree -> target.path
|
||||
@@ -263,7 +272,7 @@ private fun newThreadPathFromTarget(target: SessionActionTarget): String? {
|
||||
}
|
||||
}
|
||||
|
||||
private fun morePopupLabel(target: SessionActionTarget): @Nls String {
|
||||
private fun morePopupLabel(target: SessionActionTarget?): @Nls String {
|
||||
return when (target) {
|
||||
is SessionActionTarget.MoreProjects -> AgentSessionsBundle.message("toolwindow.action.more.count", target.hiddenCount)
|
||||
is SessionActionTarget.MoreThreads ->
|
||||
|
||||
+28
-28
@@ -7,6 +7,9 @@ import com.intellij.agent.workbench.sessions.service.AgentSessionArchiveRequestR
|
||||
import com.intellij.agent.workbench.sessions.service.AgentSessionArchiveService
|
||||
import com.intellij.agent.workbench.sessions.statistics.AgentWorkbenchEntryPoint
|
||||
import com.intellij.agent.workbench.prompt.core.AgentPromptLaunchProfile
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderService
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.openapi.actionSystem.ActionGroup
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
@@ -24,9 +27,6 @@ import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.util.ui.JBUI
|
||||
@@ -93,17 +93,17 @@ internal class AgentSessionsTreePopupRenameTaskFolderAction : DumbAwareAction()
|
||||
private val resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext? =
|
||||
::resolveAgentSessionsTreePopupActionContext
|
||||
private val promptForName: (Project, String) -> String? = ::showRenameTaskFolderDialog
|
||||
private val renameFolder: (SessionActionTarget.TaskFolder, String) -> Unit = { target, name ->
|
||||
private val renameFolder: (AgentTaskFolderActionTarget, String) -> Unit = { target, name ->
|
||||
service<AgentTaskFolderService>().renameFolder(target.folderId, name)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = resolveContext(e)?.target is SessionActionTarget.TaskFolder
|
||||
e.presentation.isEnabledAndVisible = resolveContext(e)?.taskFolderTarget != null
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val context = resolveContext(e) ?: return
|
||||
val target = context.target as? SessionActionTarget.TaskFolder ?: return
|
||||
val target = context.taskFolderTarget ?: return
|
||||
val name = promptForName(context.project, target.name) ?: return
|
||||
renameFolder(target, name)
|
||||
}
|
||||
@@ -114,18 +114,18 @@ internal class AgentSessionsTreePopupRenameTaskFolderAction : DumbAwareAction()
|
||||
internal class AgentSessionsTreePopupDeleteTaskFolderAction : DumbAwareAction() {
|
||||
private val resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext? =
|
||||
::resolveAgentSessionsTreePopupActionContext
|
||||
private val confirmDelete: (Project, SessionActionTarget.TaskFolder) -> Boolean = ::confirmDeleteTaskFolder
|
||||
private val deleteFolder: (SessionActionTarget.TaskFolder) -> Unit = { target ->
|
||||
private val confirmDelete: (Project, AgentTaskFolderActionTarget) -> Boolean = ::confirmDeleteTaskFolder
|
||||
private val deleteFolder: (AgentTaskFolderActionTarget) -> Unit = { target ->
|
||||
service<AgentTaskFolderService>().deleteFolder(target.folderId)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = resolveContext(e)?.target is SessionActionTarget.TaskFolder
|
||||
e.presentation.isEnabledAndVisible = resolveContext(e)?.taskFolderTarget != null
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val context = resolveContext(e) ?: return
|
||||
val target = context.target as? SessionActionTarget.TaskFolder ?: return
|
||||
val target = context.taskFolderTarget ?: return
|
||||
if (confirmDelete(context.project, target)) {
|
||||
deleteFolder(target)
|
||||
}
|
||||
@@ -205,8 +205,8 @@ internal class AgentSessionsTreePopupRemoveFromTaskFolderAction : DumbAwareActio
|
||||
|
||||
internal class AgentSessionsTreePopupSetTaskFolderMetadataAction : DumbAwareAction {
|
||||
private val resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext?
|
||||
private val promptForMetadata: (Project, SessionActionTarget.TaskFolder) -> TaskFolderMetadataEdit?
|
||||
private val setMetadata: (SessionActionTarget.TaskFolder, String, String) -> Unit
|
||||
private val promptForMetadata: (Project, AgentTaskFolderActionTarget) -> TaskFolderMetadataEdit?
|
||||
private val setMetadata: (AgentTaskFolderActionTarget, String, String) -> Unit
|
||||
|
||||
@Suppress("unused")
|
||||
constructor() {
|
||||
@@ -217,8 +217,8 @@ internal class AgentSessionsTreePopupSetTaskFolderMetadataAction : DumbAwareActi
|
||||
|
||||
internal constructor(
|
||||
resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext?,
|
||||
promptForMetadata: (Project, SessionActionTarget.TaskFolder) -> TaskFolderMetadataEdit?,
|
||||
setMetadata: (SessionActionTarget.TaskFolder, String, String) -> Unit,
|
||||
promptForMetadata: (Project, AgentTaskFolderActionTarget) -> TaskFolderMetadataEdit?,
|
||||
setMetadata: (AgentTaskFolderActionTarget, String, String) -> Unit,
|
||||
) {
|
||||
this.resolveContext = resolveContext
|
||||
this.promptForMetadata = promptForMetadata
|
||||
@@ -226,12 +226,12 @@ internal class AgentSessionsTreePopupSetTaskFolderMetadataAction : DumbAwareActi
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = resolveContext(e)?.target is SessionActionTarget.TaskFolder
|
||||
e.presentation.isEnabledAndVisible = resolveContext(e)?.taskFolderTarget != null
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val context = resolveContext(e) ?: return
|
||||
val target = context.target as? SessionActionTarget.TaskFolder ?: return
|
||||
val target = context.taskFolderTarget ?: return
|
||||
val metadata = promptForMetadata(context.project, target)?.let(::resolveTaskFolderMetadataUpdate) ?: return
|
||||
setMetadata(target, metadata.key, metadata.value)
|
||||
}
|
||||
@@ -264,19 +264,19 @@ internal fun resolveTaskFolderMetadataUpdate(edit: TaskFolderMetadataEdit): Task
|
||||
internal class AgentSessionsTreePopupDeleteTaskFolderMetadataAction : DumbAwareAction() {
|
||||
private val resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext? =
|
||||
::resolveAgentSessionsTreePopupActionContext
|
||||
private val promptForKey: (Project, SessionActionTarget.TaskFolder) -> String? = ::showDeleteTaskFolderMetadataDialog
|
||||
private val deleteMetadata: (SessionActionTarget.TaskFolder, String) -> Unit = { target, key ->
|
||||
private val promptForKey: (Project, AgentTaskFolderActionTarget) -> String? = ::showDeleteTaskFolderMetadataDialog
|
||||
private val deleteMetadata: (AgentTaskFolderActionTarget, String) -> Unit = { target, key ->
|
||||
service<AgentTaskFolderService>().deleteMetadata(target.folderId, key)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val target = resolveContext(e)?.target as? SessionActionTarget.TaskFolder
|
||||
val target = resolveContext(e)?.taskFolderTarget
|
||||
e.presentation.isEnabledAndVisible = target?.metadata?.isNotEmpty() == true
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val context = resolveContext(e) ?: return
|
||||
val target = context.target as? SessionActionTarget.TaskFolder ?: return
|
||||
val target = context.taskFolderTarget ?: return
|
||||
val key = promptForKey(context.project, target) ?: return
|
||||
deleteMetadata(target, key)
|
||||
}
|
||||
@@ -288,7 +288,7 @@ internal class AgentSessionsTreePopupMarkTaskFolderDoneAction : DumbAwareAction
|
||||
private val resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext?
|
||||
private val canArchiveProvider: (AgentSessionProvider) -> Boolean
|
||||
private val archiveThreads: (List<ArchiveThreadTarget>, AgentWorkbenchEntryPoint, (AgentSessionArchiveRequestResult) -> Unit) -> Unit
|
||||
private val setFolderDone: (SessionActionTarget.TaskFolder) -> Unit
|
||||
private val setFolderDone: (AgentTaskFolderActionTarget) -> Unit
|
||||
|
||||
@Suppress("unused")
|
||||
constructor() {
|
||||
@@ -305,7 +305,7 @@ internal class AgentSessionsTreePopupMarkTaskFolderDoneAction : DumbAwareAction
|
||||
resolveContext: (AnActionEvent) -> AgentSessionsTreePopupActionContext?,
|
||||
canArchiveProvider: (AgentSessionProvider) -> Boolean,
|
||||
archiveThreads: (List<ArchiveThreadTarget>, AgentWorkbenchEntryPoint, (AgentSessionArchiveRequestResult) -> Unit) -> Unit,
|
||||
setFolderDone: (SessionActionTarget.TaskFolder) -> Unit,
|
||||
setFolderDone: (AgentTaskFolderActionTarget) -> Unit,
|
||||
) {
|
||||
this.resolveContext = resolveContext
|
||||
this.canArchiveProvider = canArchiveProvider
|
||||
@@ -315,7 +315,7 @@ internal class AgentSessionsTreePopupMarkTaskFolderDoneAction : DumbAwareAction
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val context = resolveContext(e)
|
||||
val target = context?.target as? SessionActionTarget.TaskFolder
|
||||
val target = context?.taskFolderTarget
|
||||
if (target == null || target.isDone) {
|
||||
e.presentation.isEnabledAndVisible = false
|
||||
return
|
||||
@@ -327,7 +327,7 @@ internal class AgentSessionsTreePopupMarkTaskFolderDoneAction : DumbAwareAction
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val context = resolveContext(e) ?: return
|
||||
val target = context.target as? SessionActionTarget.TaskFolder ?: return
|
||||
val target = context.taskFolderTarget ?: return
|
||||
if (target.isDone) return
|
||||
val archiveTargets = context.taskFolderArchiveTargets
|
||||
if (archiveTargets.isEmpty()) {
|
||||
@@ -426,7 +426,7 @@ private fun showTaskFolderNameDialog(
|
||||
return Messages.showInputDialog(project, message, title, Messages.getQuestionIcon(), initialValue, NonBlankInputValidator())
|
||||
}
|
||||
|
||||
private fun confirmDeleteTaskFolder(project: Project, target: SessionActionTarget.TaskFolder): Boolean {
|
||||
private fun confirmDeleteTaskFolder(project: Project, target: AgentTaskFolderActionTarget): Boolean {
|
||||
return Messages.showYesNoDialog(
|
||||
project,
|
||||
AgentSessionsBundle.message("toolwindow.task.folder.delete.dialog.message", target.name),
|
||||
@@ -435,12 +435,12 @@ private fun confirmDeleteTaskFolder(project: Project, target: SessionActionTarge
|
||||
) == Messages.YES
|
||||
}
|
||||
|
||||
private fun showSetTaskFolderMetadataDialog(project: Project, target: SessionActionTarget.TaskFolder): TaskFolderMetadataEdit? {
|
||||
private fun showSetTaskFolderMetadataDialog(project: Project, target: AgentTaskFolderActionTarget): TaskFolderMetadataEdit? {
|
||||
val dialog = TaskFolderMetadataDialog(project, target)
|
||||
return if (dialog.showAndGet()) dialog.metadataEdit() else null
|
||||
}
|
||||
|
||||
private fun showDeleteTaskFolderMetadataDialog(project: Project, target: SessionActionTarget.TaskFolder): String? {
|
||||
private fun showDeleteTaskFolderMetadataDialog(project: Project, target: AgentTaskFolderActionTarget): String? {
|
||||
return Messages.showInputDialog(
|
||||
project,
|
||||
AgentSessionsBundle.message("toolwindow.task.folder.metadata.delete.dialog.message"),
|
||||
@@ -515,7 +515,7 @@ private class CreateTaskFolderDialog(
|
||||
|
||||
private class TaskFolderMetadataDialog(
|
||||
project: Project,
|
||||
private val target: SessionActionTarget.TaskFolder,
|
||||
private val target: AgentTaskFolderActionTarget,
|
||||
) : DialogWrapper(project) {
|
||||
private val keyCombo = ComboBox<@NlsSafe String>().apply {
|
||||
model = DefaultComboBoxModel(taskFolderMetadataKeyOptions(target.metadata))
|
||||
|
||||
+3
-3
@@ -13,6 +13,8 @@ import com.intellij.agent.workbench.sessions.resolveAgentSessionLaunchProfileSel
|
||||
import com.intellij.agent.workbench.sessions.service.AgentSessionLaunchService
|
||||
import com.intellij.agent.workbench.sessions.state.AgentSessionUiPreferencesStateService
|
||||
import com.intellij.agent.workbench.sessions.statistics.AgentWorkbenchEntryPoint
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderService
|
||||
import com.intellij.openapi.actionSystem.ActionGroup
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
@@ -23,8 +25,6 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionLaunchMode
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviderDescriptor
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.AgentSessionProviders
|
||||
import com.intellij.platform.ai.agent.sessions.core.providers.builtInLaunchProfileId
|
||||
@@ -204,7 +204,7 @@ internal fun buildTaskFolderAgentPrompt(
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
private fun taskFolderAgentPathFromTarget(target: SessionActionTarget): String? {
|
||||
private fun taskFolderAgentPathFromTarget(target: SessionActionTarget?): String? {
|
||||
return when (target) {
|
||||
is SessionActionTarget.Project -> target.path
|
||||
is SessionActionTarget.Worktree -> target.path
|
||||
|
||||
+18
-12
@@ -4,6 +4,7 @@ package com.intellij.agent.workbench.sessions.toolwindow.actions
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
import com.intellij.agent.workbench.sessions.model.ArchiveThreadTarget
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.SessionTreeId
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.SessionTreeNode
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.pathForMoreThreadsNode
|
||||
@@ -11,7 +12,6 @@ import com.intellij.agent.workbench.sessions.toolwindow.tree.pathForTaskFolderNo
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.pathForThreadNode
|
||||
import com.intellij.agent.workbench.sessions.util.isAgentSessionNewSessionId
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
|
||||
internal fun createAgentSessionsTreePopupActionContext(
|
||||
project: Project,
|
||||
@@ -23,10 +23,13 @@ internal fun createAgentSessionsTreePopupActionContext(
|
||||
taskFolderArchiveTargets: List<ArchiveThreadTarget> = emptyList(),
|
||||
newThreadActionAvailable: Boolean = true,
|
||||
): AgentSessionsTreePopupActionContext? {
|
||||
val target = resolveSessionActionTarget(nodeId, node) ?: return null
|
||||
val target = resolveSessionActionTarget(nodeId, node)
|
||||
val taskFolderTarget = resolveTaskFolderActionTarget(nodeId, node)
|
||||
if (target == null && taskFolderTarget == null) return null
|
||||
return AgentSessionsTreePopupActionContext(
|
||||
project = project,
|
||||
target = target,
|
||||
taskFolderTarget = taskFolderTarget,
|
||||
archiveTargets = archiveTargets,
|
||||
unarchiveTargets = unarchiveTargets,
|
||||
selectedThreadTargets = selectedThreadTargets,
|
||||
@@ -56,16 +59,7 @@ internal fun resolveSessionActionTarget(nodeId: SessionTreeId, node: SessionTree
|
||||
)
|
||||
}
|
||||
|
||||
is SessionTreeNode.TaskFolder -> {
|
||||
val path = pathForTaskFolderNode(nodeId) ?: return null
|
||||
SessionActionTarget.TaskFolder(
|
||||
path = normalizeAgentWorkbenchPath(path),
|
||||
folderId = node.folder.id,
|
||||
name = node.folder.name,
|
||||
isDone = node.folder.status == AgentTaskFolderStatus.DONE,
|
||||
metadata = node.folder.metadata,
|
||||
)
|
||||
}
|
||||
is SessionTreeNode.TaskFolder -> null
|
||||
|
||||
is SessionTreeNode.Thread -> {
|
||||
if (isAgentSessionNewSessionId(node.thread.id)) return null
|
||||
@@ -125,3 +119,15 @@ internal fun resolveSessionActionTarget(nodeId: SessionTreeId, node: SessionTree
|
||||
-> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveTaskFolderActionTarget(nodeId: SessionTreeId, node: SessionTreeNode): AgentTaskFolderActionTarget? {
|
||||
if (node !is SessionTreeNode.TaskFolder) return null
|
||||
val path = pathForTaskFolderNode(nodeId) ?: return null
|
||||
return AgentTaskFolderActionTarget(
|
||||
path = normalizeAgentWorkbenchPath(path),
|
||||
folderId = node.folder.id,
|
||||
name = node.folder.name,
|
||||
isDone = node.folder.status == AgentTaskFolderStatus.DONE,
|
||||
metadata = node.folder.metadata,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.agent.workbench.sessions.toolwindow.tree
|
||||
import com.intellij.agent.workbench.chat.AgentChatOpenTabsPresentationState
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabSnapshot
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.parseAgentThreadIdentity
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
@@ -83,7 +84,7 @@ private fun buildPendingThread(
|
||||
title = AgentSessionsBundle.message("toolwindow.action.new.thread"),
|
||||
updatedAt = pendingTab.pendingFirstInputAtMs ?: pendingTab.pendingCreatedAtMs ?: 0L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
provider = provider,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ import com.intellij.agent.workbench.sessions.model.hasAnyProviderSnapshot
|
||||
import com.intellij.agent.workbench.sessions.state.DEFAULT_VISIBLE_THREAD_COUNT
|
||||
import com.intellij.agent.workbench.sessions.state.SessionTreeUiState
|
||||
import com.intellij.agent.workbench.sessions.util.isAgentSessionNewSessionId
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderSnapshot
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderSnapshot
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
|
||||
internal data class SessionTreeModel(
|
||||
|
||||
@@ -182,7 +182,7 @@ private fun AgentSessionsActivitySummary.rowsDebugText(): String {
|
||||
return rows.take(MAX_ACTIVITY_DEBUG_ROWS).joinToString(prefix = "[", postfix = postfix) { bucketedRow ->
|
||||
val row = bucketedRow.row
|
||||
"${bucketedRow.bucket}:${row.path}:${row.thread.provider.value}:${row.thread.id}:" +
|
||||
"activity=${row.thread.activity}:summaryActivity=${row.thread.summaryActivity}:updatedAt=${row.thread.updatedAt}"
|
||||
"rowActivity=${row.thread.activityReport.rowActivity}:chromeActivity=${row.thread.activityReport.chromeActivity}:updatedAt=${row.thread.updatedAt}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.agent.workbench.sessions.toolwindow.ui
|
||||
// @spec community/plugins/agent-workbench/spec/sessions/agent-sessions-tree.spec.md
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.chromePresentationActivity
|
||||
import com.intellij.platform.ai.agent.common.statusColor
|
||||
import com.intellij.platform.ai.agent.common.statusMessageKey
|
||||
import com.intellij.agent.workbench.sessions.AgentSessionsBundle
|
||||
@@ -277,7 +278,7 @@ private class AgentSessionsActivityOpenThreadAction(
|
||||
) : DumbAwareAction(
|
||||
agentSessionsActivityPopupRowText(row, now),
|
||||
null,
|
||||
agentSessionThreadStatusIcon(row.thread.provider, row.thread.activity),
|
||||
agentSessionThreadStatusIcon(row.thread.provider, row.thread.activityReport),
|
||||
) {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
|
||||
|
||||
@@ -304,7 +305,7 @@ internal fun agentSessionsActivityPopupRowText(
|
||||
val timeLabel = row.thread.updatedAt.takeIf { it > 0 }?.let { timestamp ->
|
||||
formatRelativeTimeShort(timestamp, now)
|
||||
} ?: AgentSessionsBundle.message("toolwindow.time.unknown")
|
||||
val statusLabel = AgentSessionsBundle.message(row.thread.activity.statusMessageKey())
|
||||
val statusLabel = AgentSessionsBundle.message(row.thread.activityReport.chromePresentationActivity().statusMessageKey())
|
||||
return AgentSessionsBundle.message("toolwindow.activity.popup.row", title, statusLabel, row.locationLabel, timeLabel)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ import com.intellij.ui.tree.StructureTreeModel
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import com.intellij.util.ui.EdtInvocationManager
|
||||
import com.intellij.util.ui.tree.TreeUtil
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderService
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderThreadAssignment
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Graphics
|
||||
import java.util.Collections
|
||||
|
||||
@@ -399,8 +399,8 @@ internal class SessionTreeCellRenderer(
|
||||
}
|
||||
|
||||
private fun threadCompositeIcon(treeNode: SessionTreeNode.Thread): Icon {
|
||||
return providerIconProvider?.let { agentSessionThreadStatusIcon(it(treeNode.thread.provider), treeNode.thread.activity) }
|
||||
?: agentSessionThreadStatusIcon(treeNode.thread.provider, treeNode.thread.activity)
|
||||
return providerIconProvider?.let { agentSessionThreadStatusIcon(it(treeNode.thread.provider), treeNode.thread.activityReport.rowActivity) }
|
||||
?: agentSessionThreadStatusIcon(treeNode.thread.provider, treeNode.thread.activityReport.rowActivity)
|
||||
}
|
||||
|
||||
private fun projectCompositeIcon(project: AgentProjectSessions): Icon {
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.ui.hover.HoverListener
|
||||
import com.intellij.ui.hover.TreeHoverListener
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ import com.intellij.agent.workbench.sessions.toolwindow.tree.buildSessionTreeMod
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.diffSessionTreeModels
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.overlayPendingAgentChatTabs
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.resolveSelectedSessionTreeId
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderService
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderSnapshot
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderService
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderSnapshot
|
||||
import com.intellij.openapi.application.UI
|
||||
import com.intellij.openapi.components.serviceAsync
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
|
||||
+1
-1
@@ -15,8 +15,8 @@ import com.intellij.ide.dnd.DnDEvent
|
||||
import com.intellij.ide.dnd.DnDSupport
|
||||
import com.intellij.ide.dnd.DnDTargetChecker
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.ui.awt.RelativeRectangle
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import java.awt.Rectangle
|
||||
|
||||
@@ -36,15 +36,15 @@ internal fun buildSessionTreeThreadRowPresentation(
|
||||
treeNode: SessionTreeNode.Thread,
|
||||
now: Long,
|
||||
): SessionTreeThreadRowPresentation {
|
||||
val activityColor = treeNode.thread.activity.statusColor()
|
||||
val rowActivity = treeNode.thread.activityReport.rowActivity
|
||||
val activityColor = rowActivity.statusColor()
|
||||
val timeLabel = treeNode.thread.updatedAt.takeIf { it > 0 }?.let { timestamp ->
|
||||
formatRelativeTimeShort(timestamp, now)
|
||||
} ?: AgentSessionsBundle.message("toolwindow.time.unknown")
|
||||
val activity = treeNode.thread.activity
|
||||
val statusLabel = threadActivityDisplayName(activity)
|
||||
val statusLabel = threadActivityDisplayName(rowActivity)
|
||||
val costLabel = treeNode.thread.cost.takeIf { AgentSessionCostPresentationSettings.isEnabled() }?.toDisplayLabel()
|
||||
val trailingMetadataLabel = listOfNotNull(
|
||||
statusLabel.takeIf { activity.hasVisibleTrailingStatus() },
|
||||
statusLabel.takeIf { rowActivity.hasVisibleTrailingStatus() },
|
||||
costLabel,
|
||||
).joinToString(separator = " · ").takeIf { it.isNotEmpty() }
|
||||
val originBranch = treeNode.thread.originBranch
|
||||
|
||||
+9
-10
@@ -322,7 +322,7 @@ class AgentSessionsActivitySummaryTest {
|
||||
title = "Parent ready",
|
||||
updatedAt = 100,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
subAgents = listOf(AgentSubAgent(id = "child-done", name = "Child done", activity = AgentThreadActivity.UNREAD)),
|
||||
),
|
||||
@@ -348,7 +348,7 @@ class AgentSessionsActivitySummaryTest {
|
||||
isOpen = true,
|
||||
providerLoadStates = loadedProviderStates(AgentSessionProvider.from("codex")),
|
||||
threads = listOf(
|
||||
thread("sub-agent-done", AgentThreadActivity.UNREAD, 100, summaryActivity = null),
|
||||
thread("sub-agent-done", AgentThreadActivity.UNREAD, 100, chromeActivity = null),
|
||||
),
|
||||
)
|
||||
),
|
||||
@@ -561,13 +561,13 @@ class AgentSessionsActivitySummaryTest {
|
||||
|
||||
assertThat(
|
||||
tracker.collectNotifications(
|
||||
summary(thread("done", AgentThreadActivity.UNREAD, 200, summaryActivity = null)),
|
||||
summary(thread("done", AgentThreadActivity.UNREAD, 200, chromeActivity = null)),
|
||||
isLoadedState = true,
|
||||
)
|
||||
).isEmpty()
|
||||
assertThat(
|
||||
tracker.collectNotifications(
|
||||
summary(thread("done", AgentThreadActivity.UNREAD, 300, summaryActivity = AgentThreadActivity.UNREAD)),
|
||||
summary(thread("done", AgentThreadActivity.UNREAD, 300, chromeActivity = AgentThreadActivity.UNREAD)),
|
||||
isLoadedState = true,
|
||||
)
|
||||
).hasSize(1)
|
||||
@@ -580,19 +580,19 @@ class AgentSessionsActivitySummaryTest {
|
||||
|
||||
assertThat(
|
||||
tracker.collectNotifications(
|
||||
summary(thread("needs-input", AgentThreadActivity.NEEDS_INPUT, 200, summaryActivity = AgentThreadActivity.READY)),
|
||||
summary(thread("needs-input", AgentThreadActivity.NEEDS_INPUT, 200, chromeActivity = AgentThreadActivity.READY)),
|
||||
isLoadedState = true,
|
||||
)
|
||||
).isEmpty()
|
||||
assertThat(
|
||||
tracker.collectNotifications(
|
||||
summary(thread("needs-input", AgentThreadActivity.NEEDS_INPUT, 300, summaryActivity = null)),
|
||||
summary(thread("needs-input", AgentThreadActivity.NEEDS_INPUT, 300, chromeActivity = null)),
|
||||
isLoadedState = true,
|
||||
)
|
||||
).isEmpty()
|
||||
assertThat(
|
||||
tracker.collectNotifications(
|
||||
summary(thread("needs-input", AgentThreadActivity.NEEDS_INPUT, 400, summaryActivity = AgentThreadActivity.NEEDS_INPUT)),
|
||||
summary(thread("needs-input", AgentThreadActivity.NEEDS_INPUT, 400, chromeActivity = AgentThreadActivity.NEEDS_INPUT)),
|
||||
isLoadedState = true,
|
||||
)
|
||||
).hasSize(1)
|
||||
@@ -791,16 +791,15 @@ class AgentSessionsActivitySummaryTest {
|
||||
activity: AgentThreadActivity,
|
||||
updatedAt: Long,
|
||||
title: String = id,
|
||||
summaryActivity: AgentThreadActivity? = activity,
|
||||
chromeActivity: AgentThreadActivity? = activity,
|
||||
): AgentSessionThread {
|
||||
return AgentSessionThread(
|
||||
id = id,
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = false,
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(rowActivity = activity, chromeActivity = chromeActivity),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
summaryActivity = summaryActivity,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -76,13 +76,14 @@ class AgentSessionsCodexActivityRenderingIntegrationTest {
|
||||
service.state.value.projects.firstOrNull { it.path == PROJECT_PATH }
|
||||
?.threads
|
||||
?.singleOrNull()
|
||||
?.activity == AgentThreadActivity.PROCESSING
|
||||
?.activityReport
|
||||
?.rowActivity == AgentThreadActivity.PROCESSING
|
||||
}
|
||||
|
||||
val state = service.state.value
|
||||
val project = state.projects.single { it.path == PROJECT_PATH }
|
||||
val thread = project.threads.single()
|
||||
assertThat(thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
|
||||
val model = buildSessionTreeModel(
|
||||
projects = state.projects,
|
||||
@@ -92,7 +93,7 @@ class AgentSessionsCodexActivityRenderingIntegrationTest {
|
||||
)
|
||||
val threadId = SessionTreeId.Thread(project.path, AgentSessionProvider.from("codex"), thread.id)
|
||||
val processingNode = model.entriesById.getValue(threadId).node as SessionTreeNode.Thread
|
||||
assertThat(processingNode.thread.activity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
assertThat(processingNode.thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.PROCESSING)
|
||||
|
||||
val tree = createTree()
|
||||
val processingRenderer = createRenderer { id -> model.entriesById[id]?.node }
|
||||
@@ -118,15 +119,14 @@ class AgentSessionsCodexActivityRenderingIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun treeRowRendersActualActivityWhenSummaryActivityDoesNotContribute() {
|
||||
fun treeRowRendersActualActivityWhenChromeActivityDoesNotContribute() {
|
||||
val thread = AgentSessionThread(
|
||||
id = "sub-agent-only",
|
||||
title = "Sub-agent only",
|
||||
updatedAt = 1_000L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(rowActivity = AgentThreadActivity.UNREAD, chromeActivity = null),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
summaryActivity = null,
|
||||
)
|
||||
val project = AgentProjectSessions(
|
||||
path = PROJECT_PATH,
|
||||
@@ -143,8 +143,8 @@ class AgentSessionsCodexActivityRenderingIntegrationTest {
|
||||
)
|
||||
val threadId = SessionTreeId.Thread(project.path, AgentSessionProvider.from("codex"), thread.id)
|
||||
val unreadNode = model.entriesById.getValue(threadId).node as SessionTreeNode.Thread
|
||||
assertThat(unreadNode.thread.activity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(unreadNode.thread.summaryActivity).isNull()
|
||||
assertThat(unreadNode.thread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.UNREAD)
|
||||
assertThat(unreadNode.thread.activityReport.chromeActivity).isNull()
|
||||
|
||||
val tree = createTree()
|
||||
val unreadRenderer = createRenderer { id -> model.entriesById[id]?.node }
|
||||
@@ -181,8 +181,7 @@ class AgentSessionsCodexActivityRenderingIntegrationTest {
|
||||
title = "Thread 1",
|
||||
updatedAt = 1_000L,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
summaryActivity = null,
|
||||
activityReport = AgentThreadActivityReport(rowActivity = AgentThreadActivity.PROCESSING, chromeActivity = null),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,6 +2,7 @@
|
||||
package com.intellij.agent.workbench.sessions.toolwindow
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.agent.workbench.ui.AgentWorkbenchActionIds
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -326,9 +327,8 @@ class AgentSessionsMainToolbarActivityGroupTest {
|
||||
title = id,
|
||||
updatedAt = updatedAt,
|
||||
archived = false,
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(rowActivity = activity, chromeActivity = activity),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
summaryActivity = activity,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -756,7 +756,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "How much time",
|
||||
updatedAt = 0L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
)
|
||||
val providerBaseIcon = EmptyIcon.create(12, 12)
|
||||
val threadId = SessionTreeId.Thread(project.path, thread.provider, thread.id)
|
||||
@@ -795,7 +795,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Need input",
|
||||
updatedAt = 14L * 24L * 60L * 60L * 1000L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.NEEDS_INPUT,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.NEEDS_INPUT),
|
||||
)
|
||||
val threadId = SessionTreeId.Thread(project.path, thread.provider, thread.id)
|
||||
val renderer = SessionTreeCellRenderer(
|
||||
@@ -830,7 +830,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Processing",
|
||||
updatedAt = 14L * 24L * 60L * 60L * 1000L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
)
|
||||
val threadId = SessionTreeId.Thread(project.path, thread.provider, thread.id)
|
||||
val renderer = SessionTreeCellRenderer(
|
||||
@@ -867,7 +867,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Ready thread",
|
||||
updatedAt = 14L * 24L * 60L * 60L * 1000L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
)
|
||||
val threadId = SessionTreeId.Thread(project.path, thread.provider, thread.id)
|
||||
val renderer = SessionTreeCellRenderer(
|
||||
@@ -895,7 +895,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "How much time",
|
||||
updatedAt = 0L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
)
|
||||
val readyThreadId = SessionTreeId.Thread(project.path, readyThread.provider, readyThread.id)
|
||||
val renderer = SessionTreeCellRenderer(
|
||||
@@ -993,7 +993,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Thread 1",
|
||||
updatedAt = 0L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
subAgents = listOf(
|
||||
AgentSubAgent(id = "sub-ready", name = "Ready sub-agent"),
|
||||
AgentSubAgent(id = "sub-done", name = "Done sub-agent", activity = AgentThreadActivity.UNREAD),
|
||||
@@ -1154,7 +1154,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Status thread",
|
||||
updatedAt = 14L * 24L * 60L * 60L * 1000L,
|
||||
archived = false,
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
)
|
||||
val tooltip = buildSessionTreeThreadTooltipHtml(
|
||||
treeNode = SessionTreeNode.Thread(project, thread),
|
||||
@@ -1394,7 +1394,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Needs input",
|
||||
updatedAt = 0L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.NEEDS_INPUT,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.NEEDS_INPUT),
|
||||
cost = AgentSessionCost(amountUsd = BigDecimal("0.42"), kind = AgentSessionCostKind.ESTIMATED),
|
||||
)
|
||||
val project = AgentProjectSessions(path = "/work/project-a", name = "Project A", isOpen = true)
|
||||
@@ -1418,7 +1418,7 @@ class AgentSessionsSwingTreeCellRendererTest {
|
||||
title = "Done",
|
||||
updatedAt = 0L,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.UNREAD,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.UNREAD),
|
||||
cost = AgentSessionCost(amountUsd = BigDecimal("0.42"), kind = AgentSessionCostKind.ESTIMATED),
|
||||
)
|
||||
val project = AgentProjectSessions(path = "/work/project-a", name = "Project A", isOpen = true)
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@
|
||||
package com.intellij.agent.workbench.sessions.toolwindow
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
import com.intellij.agent.workbench.sessions.model.ProjectBuildSystemBadge
|
||||
@@ -173,7 +174,7 @@ class AgentSessionsTreeModelDiffTest {
|
||||
title = "Thread 1",
|
||||
updatedAt = 100,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.READY,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.READY),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
)
|
||||
),
|
||||
@@ -196,7 +197,7 @@ class AgentSessionsTreeModelDiffTest {
|
||||
title = "Thread 1",
|
||||
updatedAt = 100,
|
||||
archived = false,
|
||||
activity = AgentThreadActivity.PROCESSING,
|
||||
activityReport = AgentThreadActivityReport(AgentThreadActivity.PROCESSING),
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
)
|
||||
),
|
||||
|
||||
+11
-10
@@ -15,6 +15,7 @@ import com.intellij.platform.ai.agent.sessions.core.providers.builtInLaunchProfi
|
||||
import com.intellij.agent.workbench.sessions.statistics.AgentWorkbenchEntryPoint
|
||||
import com.intellij.agent.workbench.sessions.model.ArchiveThreadTarget
|
||||
import com.intellij.agent.workbench.sessions.service.AgentSessionProviderAvailabilityService
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.actions.AgentTaskFolderActionTarget
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.actions.AgentSessionsTreePopupActionContext
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.actions.AgentSessionsTreePopupArchiveThreadAction
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.actions.AgentSessionsTreePopupCopyThreadIdAction
|
||||
@@ -54,9 +55,9 @@ import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.agent.workbench.sessions.service.AgentSessionArchiveRequestResult
|
||||
import com.intellij.platform.ai.agent.sessions.core.SessionActionTarget
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import com.intellij.testFramework.TestActionEvent
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
@@ -807,7 +808,7 @@ class AgentSessionsTreePopupActionsTest {
|
||||
@Test
|
||||
fun markTaskFolderDoneArchivesAssignedTargetsBeforeChangingStatus() {
|
||||
val provider = AgentSessionProvider.from("codex")
|
||||
val folderTarget = SessionActionTarget.TaskFolder(
|
||||
val folderTarget = AgentTaskFolderActionTarget(
|
||||
path = "/work/project-a",
|
||||
folderId = "folder1",
|
||||
name = "Research",
|
||||
@@ -816,7 +817,7 @@ class AgentSessionsTreePopupActionsTest {
|
||||
val loadedArchiveTarget = ArchiveThreadTarget.Thread(path = "/work/project-a", provider = provider, threadId = "loaded-thread")
|
||||
val unloadedArchiveTarget = ArchiveThreadTarget.Thread(path = "/work/project-a", provider = provider, threadId = "unloaded-thread")
|
||||
var archivedTargets: List<ArchiveThreadTarget>? = null
|
||||
var doneTarget: SessionActionTarget.TaskFolder? = null
|
||||
var doneTarget: AgentTaskFolderActionTarget? = null
|
||||
val action = AgentSessionsTreePopupMarkTaskFolderDoneAction(
|
||||
resolveContext = { event -> resolveAgentSessionsTreePopupActionContext(event) },
|
||||
canArchiveProvider = { true },
|
||||
@@ -828,7 +829,7 @@ class AgentSessionsTreePopupActionsTest {
|
||||
)
|
||||
val context = AgentSessionsTreePopupActionContext(
|
||||
project = ProjectManager.getInstance().defaultProject,
|
||||
target = folderTarget,
|
||||
taskFolderTarget = folderTarget,
|
||||
archiveTargets = emptyList(),
|
||||
taskFolderArchiveTargets = listOf(loadedArchiveTarget, unloadedArchiveTarget),
|
||||
)
|
||||
@@ -865,13 +866,13 @@ class AgentSessionsTreePopupActionsTest {
|
||||
@Test
|
||||
fun markTaskFolderDoneKeepsFolderInProgressWhenAssignedArchiveIsPartial() {
|
||||
val provider = AgentSessionProvider.from("codex")
|
||||
val folderTarget = SessionActionTarget.TaskFolder(
|
||||
val folderTarget = AgentTaskFolderActionTarget(
|
||||
path = "/work/project-a",
|
||||
folderId = "folder1",
|
||||
name = "Research",
|
||||
isDone = false,
|
||||
)
|
||||
var doneTarget: SessionActionTarget.TaskFolder? = null
|
||||
var doneTarget: AgentTaskFolderActionTarget? = null
|
||||
val action = AgentSessionsTreePopupMarkTaskFolderDoneAction(
|
||||
resolveContext = { event -> resolveAgentSessionsTreePopupActionContext(event) },
|
||||
canArchiveProvider = { true },
|
||||
@@ -882,7 +883,7 @@ class AgentSessionsTreePopupActionsTest {
|
||||
)
|
||||
val context = AgentSessionsTreePopupActionContext(
|
||||
project = ProjectManager.getInstance().defaultProject,
|
||||
target = folderTarget,
|
||||
taskFolderTarget = folderTarget,
|
||||
archiveTargets = emptyList(),
|
||||
taskFolderArchiveTargets = listOf(
|
||||
ArchiveThreadTarget.Thread(path = "/work/project-a", provider = provider, threadId = "loaded-thread"),
|
||||
@@ -907,7 +908,7 @@ class AgentSessionsTreePopupActionsTest {
|
||||
)
|
||||
val context = AgentSessionsTreePopupActionContext(
|
||||
project = ProjectManager.getInstance().defaultProject,
|
||||
target = SessionActionTarget.TaskFolder(
|
||||
taskFolderTarget = AgentTaskFolderActionTarget(
|
||||
path = "/work/project-a",
|
||||
folderId = "folder1",
|
||||
name = "Research",
|
||||
|
||||
+5
-5
@@ -17,10 +17,10 @@ import com.intellij.agent.workbench.sessions.toolwindow.tree.buildSessionTreeMod
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.overlayPendingAgentChatTabs
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.tree.sessionTreeNodeSearchText
|
||||
import com.intellij.agent.workbench.sessions.toolwindow.ui.SessionTreeStrictSubstringComparator
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolder
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderSnapshot
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderStatus
|
||||
import com.intellij.platform.ai.agent.sessions.core.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolder
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderSnapshot
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderStatus
|
||||
import com.intellij.agent.workbench.sessions.task.folders.AgentTaskFolderThreadAssignment
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -483,7 +483,7 @@ class AgentSessionsTreeSnapshotTest {
|
||||
assertThat(pendingThread.id).isEqualTo("new-pending")
|
||||
assertThat(pendingThread.title).isEqualTo(AgentSessionsBundle.message("toolwindow.action.new.thread"))
|
||||
assertThat(pendingThread.updatedAt).isEqualTo(700L)
|
||||
assertThat(pendingThread.activity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(pendingThread.activityReport.rowActivity).isEqualTo(AgentThreadActivity.READY)
|
||||
assertThat(pendingThread.provider).isEqualTo(AgentSessionProvider.from("codex"))
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@ import com.intellij.agent.workbench.chat.AgentChatOpenTabsPresentationState
|
||||
import com.intellij.agent.workbench.chat.AgentChatPendingTabSnapshot
|
||||
import com.intellij.agent.workbench.chat.AgentChatTabSelection
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.buildAgentThreadIdentity
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionProvider
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
@@ -532,7 +533,7 @@ private fun stateWithThread(
|
||||
updatedAt = updatedAt,
|
||||
archived = false,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -575,7 +576,7 @@ private fun thread(
|
||||
updatedAt = updatedAt,
|
||||
archived = false,
|
||||
provider = AgentSessionProvider.from("codex"),
|
||||
activity = activity,
|
||||
activityReport = AgentThreadActivityReport(activity),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -205,10 +205,6 @@ settings.agent.workbench.session.cost.description=When enabled, Agent Threads sh
|
||||
settings.agent.workbench.jbcentral.quota.status.bar.widget=Show JBCentral quota in status bar
|
||||
settings.agent.workbench.jbcentral.quota.status.bar.widget.description=When enabled, Agent Workbench shows the remaining JBCentral quota in the IDE status bar.
|
||||
settings.agent.workbench.providers.name=Providers
|
||||
settings.agent.workbench.provider.pi.omlx.models=Use local oMLX models
|
||||
settings.agent.workbench.provider.pi.omlx.models.description=When enabled, Agent Workbench discovers local oMLX models and registers the selected model for Pi sessions.
|
||||
settings.agent.workbench.provider.pi.jbcentral.models=Use JetBrains Central models
|
||||
settings.agent.workbench.provider.pi.jbcentral.models.description=When enabled, Agent Workbench detects JetBrains Central wiring and registers it for Pi sessions.
|
||||
action.AgentWorkbenchSessions.ToggleSessionCost.text=Show Session Cost
|
||||
action.AgentWorkbenchSessions.ToggleSessionCost.description=Show per-session cost in Agent Threads
|
||||
toolwindow.session.cost.hint.title=Show session cost in Agent Threads?
|
||||
|
||||
@@ -449,8 +449,8 @@ private fun markThreadAsRead(
|
||||
changed = true
|
||||
thread.copy(
|
||||
activityReport = AgentThreadActivityReport(
|
||||
rowActivity = thread.activity.takeUnless { it == AgentThreadActivity.UNREAD } ?: AgentThreadActivity.READY,
|
||||
chromeActivity = thread.summaryActivity?.takeUnless { it == AgentThreadActivity.UNREAD } ?: AgentThreadActivity.READY,
|
||||
rowActivity = thread.activityReport.rowActivity.takeUnless { it == AgentThreadActivity.UNREAD } ?: AgentThreadActivity.READY,
|
||||
chromeActivity = thread.activityReport.chromeActivity?.takeUnless { it == AgentThreadActivity.UNREAD } ?: AgentThreadActivity.READY,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -524,7 +524,7 @@ class AgentSessionLaunchService internal constructor(
|
||||
promptLaunchResolved?.invoke(AgentPromptLaunchResult.failure(AgentPromptLaunchError.TARGET_THREAD_NOT_FOUND))
|
||||
return@launchDropAction
|
||||
}
|
||||
if (effectiveInitialMessagePlan?.isBlockedForExistingThreadPlanMode(refreshedThread.activity) == true) {
|
||||
if (effectiveInitialMessagePlan?.isBlockedForExistingThreadPlanMode(refreshedThread.activityReport.rowActivity) == true) {
|
||||
promptLaunchResolved?.invoke(AgentPromptLaunchResult.failure(AgentPromptLaunchError.TARGET_THREAD_BUSY_FOR_PLAN_MODE))
|
||||
return@launchDropAction
|
||||
}
|
||||
@@ -648,7 +648,7 @@ class AgentSessionLaunchService internal constructor(
|
||||
provider = thread.provider,
|
||||
threadId = thread.id,
|
||||
title = thread.title,
|
||||
activity = thread.activity,
|
||||
activity = thread.activityReport.rowActivity,
|
||||
updatedAt = thread.updatedAt,
|
||||
)
|
||||
val matchResult = PendingAgentChatTabMatcher.match(
|
||||
@@ -1407,7 +1407,7 @@ class AgentSessionLaunchService internal constructor(
|
||||
?: return@run reportPromptLaunchResolved(AgentPromptLaunchResult.failure(AgentPromptLaunchError.TARGET_THREAD_NOT_FOUND))
|
||||
val effectiveInitialMessageRequest = request.initialMessageRequest
|
||||
val initialMessagePlan = bridge.buildInitialMessagePlan(effectiveInitialMessageRequest)
|
||||
if (initialMessagePlan.isBlockedForExistingThreadPlanMode(targetThread.activity)) {
|
||||
if (initialMessagePlan.isBlockedForExistingThreadPlanMode(targetThread.activityReport.rowActivity)) {
|
||||
return@run reportPromptLaunchResolved(AgentPromptLaunchResult.failure(AgentPromptLaunchError.TARGET_THREAD_BUSY_FOR_PLAN_MODE))
|
||||
}
|
||||
uiPreferencesState.updateProviderOptionsOnLaunch(
|
||||
@@ -2252,7 +2252,7 @@ private suspend fun openChatInProject(
|
||||
threadId = chatOpenPlan.runtimeThreadId,
|
||||
threadTitle = chatOpenPlan.threadTitle,
|
||||
subAgentId = chatOpenPlan.subAgentId,
|
||||
threadActivity = thread.activity,
|
||||
threadActivity = thread.activityReport.rowActivity,
|
||||
launchMode = serializeAgentChatLaunchMode(launchMode),
|
||||
launchProfileId = launchProfileId,
|
||||
initialMessageDispatchPlan = effectiveInitialMessageDispatchPlan,
|
||||
|
||||
@@ -69,7 +69,7 @@ class AgentSessionReadService private constructor(
|
||||
threadIdentity = buildAgentSessionIdentity(provider = thread.provider, sessionId = thread.id),
|
||||
threadId = thread.id,
|
||||
threadTitle = thread.title,
|
||||
threadActivity = thread.activity,
|
||||
threadActivity = thread.activityReport.rowActivity,
|
||||
threadUpdatedAt = thread.updatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -838,8 +838,8 @@ private fun applyThreadPresentationHintsForPath(
|
||||
LOG.debug {
|
||||
"Applying ${provider.value} presentation hint path=$path threadId=${thread.id} " +
|
||||
"titleChanged=${resolvedUpdate.title != thread.title} " +
|
||||
"activity=${thread.activity}->${resolvedUpdate.activityReport.rowActivity} " +
|
||||
"summaryActivity=${thread.summaryActivity}->${resolvedUpdate.activityReport.chromeActivity} " +
|
||||
"rowActivity=${thread.activityReport.rowActivity}->${resolvedUpdate.activityReport.rowActivity} " +
|
||||
"chromeActivity=${thread.activityReport.chromeActivity}->${resolvedUpdate.activityReport.chromeActivity} " +
|
||||
"updatedAt=${thread.updatedAt}->${resolvedUpdate.updatedAt}"
|
||||
}
|
||||
presentationUpdates += AgentSessionThreadPresentationPatchUpdate(
|
||||
|
||||
@@ -179,7 +179,6 @@ class AgentSessionRenameService internal constructor(
|
||||
provider = target.provider,
|
||||
threadId = target.threadId,
|
||||
title = normalizedRequestedName,
|
||||
activity = null,
|
||||
activityReport = activityReport,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ internal class AgentSessionThreadRebindSupport(
|
||||
provider = provider,
|
||||
threadId = thread.id,
|
||||
title = thread.title,
|
||||
activity = thread.activity,
|
||||
activity = thread.activityReport.rowActivity,
|
||||
updatedAt = thread.updatedAt,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,5 +5,5 @@ import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionThread
|
||||
|
||||
internal fun AgentSessionThread.hasUnreadActivitySignal(): Boolean {
|
||||
return activity == AgentThreadActivity.UNREAD || summaryActivity == AgentThreadActivity.UNREAD
|
||||
return activityReport.rowActivity == AgentThreadActivity.UNREAD || activityReport.chromeActivity == AgentThreadActivity.UNREAD
|
||||
}
|
||||
|
||||
+3
-3
@@ -240,7 +240,7 @@ private fun shouldReuseCachedCost(
|
||||
if (cacheEntry.updatedAt != visibleThread.updatedAt) {
|
||||
return false
|
||||
}
|
||||
if (!visibleThread.activity.isWorking) {
|
||||
if (!visibleThread.rowActivity.isWorking) {
|
||||
return true
|
||||
}
|
||||
return nowMs - cacheEntry.refreshedAtMs < workingThreadCostCacheTtlMs
|
||||
@@ -265,8 +265,8 @@ private data class VisibleThreadSnapshot(
|
||||
val cost: AgentSessionCost?
|
||||
get() = thread.cost
|
||||
|
||||
val activity: AgentThreadActivity
|
||||
get() = thread.activity
|
||||
val rowActivity: AgentThreadActivity
|
||||
get() = thread.activityReport.rowActivity
|
||||
|
||||
val cacheKey: ThreadCacheKey
|
||||
get() = ThreadCacheKey(path = path, provider = provider, threadId = threadId)
|
||||
|
||||
@@ -255,7 +255,7 @@ private fun createPowerSaveModeFlow(serviceScope: CoroutineScope): StateFlow<Boo
|
||||
|
||||
private fun AgentSessionsState.hasSleepPreventingWork(): Boolean {
|
||||
return projects.any { project ->
|
||||
project.threads.any { thread -> thread.activity.isWorking } ||
|
||||
project.worktrees.any { worktree -> worktree.threads.any { thread -> thread.activity.isWorking } }
|
||||
project.threads.any { thread -> thread.activityReport.rowActivity.isWorking } ||
|
||||
project.worktrees.any { worktree -> worktree.threads.any { thread -> thread.activityReport.rowActivity.isWorking } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.agent.workbench.sessions.state
|
||||
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivity
|
||||
import com.intellij.platform.ai.agent.core.AgentThreadActivityReport
|
||||
import com.intellij.platform.ai.agent.core.normalizeAgentWorkbenchPath
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCost
|
||||
import com.intellij.platform.ai.agent.core.session.AgentSessionCostKind
|
||||
@@ -202,7 +203,10 @@ private fun AgentSessionWarmStateService.WarmPathSnapshotState.toSnapshot(): Age
|
||||
title = threadDisplayTitle(threadId = thread.id, title = thread.title),
|
||||
updatedAt = thread.updatedAt,
|
||||
archived = false,
|
||||
activity = parseWarmStateThreadActivity(thread.activity),
|
||||
activityReport = AgentThreadActivityReport(
|
||||
rowActivity = parseWarmStateThreadActivity(thread.activity),
|
||||
chromeActivity = parseWarmStateThreadSummaryActivity(thread.summaryActivity),
|
||||
),
|
||||
provider = provider,
|
||||
subAgents = thread.subAgents.map { subAgent ->
|
||||
AgentSubAgent(
|
||||
@@ -212,7 +216,6 @@ private fun AgentSessionWarmStateService.WarmPathSnapshotState.toSnapshot(): Age
|
||||
)
|
||||
},
|
||||
originBranch = thread.originBranch,
|
||||
summaryActivity = parseWarmStateThreadSummaryActivity(thread.summaryActivity),
|
||||
cost = thread.cost?.toCost(),
|
||||
)
|
||||
},
|
||||
@@ -231,13 +234,13 @@ private fun AgentSessionWarmPathSnapshot.toState(): AgentSessionWarmStateService
|
||||
id = thread.id,
|
||||
title = thread.title,
|
||||
updatedAt = thread.updatedAt,
|
||||
activity = thread.activity.name,
|
||||
activity = thread.activityReport.rowActivity.name,
|
||||
provider = thread.provider.value,
|
||||
subAgents = thread.subAgents.map { subAgent ->
|
||||
AgentSessionWarmStateService.WarmSubAgentState(id = subAgent.id, name = subAgent.name, activity = subAgent.activity.name)
|
||||
},
|
||||
originBranch = thread.originBranch,
|
||||
summaryActivity = thread.summaryActivity?.name,
|
||||
summaryActivity = thread.activityReport.chromeActivity?.name,
|
||||
cost = thread.cost?.toState(),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -357,7 +357,8 @@ fun thread(
|
||||
provider: AgentSessionProvider,
|
||||
title: String = id,
|
||||
activity: AgentThreadActivity = AgentThreadActivity.READY,
|
||||
summaryActivity: AgentThreadActivity? = activity,
|
||||
chromeActivity: AgentThreadActivity? = activity,
|
||||
activityReport: AgentThreadActivityReport = AgentThreadActivityReport(rowActivity = activity, chromeActivity = chromeActivity),
|
||||
subAgents: List<AgentSubAgent> = emptyList(),
|
||||
cost: AgentSessionCost? = null,
|
||||
): AgentSessionThread {
|
||||
@@ -366,9 +367,8 @@ fun thread(
|
||||
title = title,
|
||||
updatedAt = updatedAt,
|
||||
archived = false,
|
||||
activityReport = activityReport,
|
||||
provider = provider,
|
||||
activity = activity,
|
||||
summaryActivity = summaryActivity,
|
||||
subAgents = subAgents,
|
||||
cost = cost,
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user