summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-01 01:46:13 +0900
committerAdam Malczewski <[email protected]>2026-06-01 01:46:13 +0900
commit8b9533c22a47bbf6f916667e2c25d8e8e419da37 (patch)
tree715a6a3d6f43781395e7dc7c8cdb519cef46a870 /packages/frontend/src/lib
parent1853dd1d40308deb829bc621beb79c5d39b9c57f (diff)
downloaddispatch-8b9533c22a47bbf6f916667e2c25d8e8e419da37.tar.gz
dispatch-8b9533c22a47bbf6f916667e2c25d8e8e419da37.zip
feat(tabs): tab-to-tab agent communication via short handles
Add send_to_tab / read_tab tools so an agent can message or read another tab by a git-style short handle (shortest unique prefix of the tab UUID, min 4 chars), shown in the tab bar. - core/db/tabs: resolveTabPrefix + shortestUniquePrefix (open tabs only, LIKE-sanitized prefix matching) - new tools read-tab.ts / send-to-tab.ts (+ tests) decoupled from the DB TabRow via a minimal ResolvedTabRef projection - agent-manager: unified deliverMessage routing (busy -> queue, idle -> new turn) shared by POST /chat and send_to_tab; agent->agent auto-wake budget (MAX_AGENT_AUTO_WAKES) to bound ping-pong loops - summon/loader: send_to_tab + read_tab as grantable tools - frontend: shortHandleFor + handle badge in TabBar; perm toggles - notes: tab-comm / user-agents / todo-redesign plans - chore: biome format fixes (debug-logger, summon.test) Refs notes/plan-tab-comm.md
Diffstat (limited to 'packages/frontend/src/lib')
-rw-r--r--packages/frontend/src/lib/components/TabBar.svelte2
-rw-r--r--packages/frontend/src/lib/components/ToolPermissions.svelte10
-rw-r--r--packages/frontend/src/lib/settings.svelte.ts4
-rw-r--r--packages/frontend/src/lib/tabs.svelte.ts24
4 files changed, 40 insertions, 0 deletions
diff --git a/packages/frontend/src/lib/components/TabBar.svelte b/packages/frontend/src/lib/components/TabBar.svelte
index feb1e5b..3cbd849 100644
--- a/packages/frontend/src/lib/components/TabBar.svelte
+++ b/packages/frontend/src/lib/components/TabBar.svelte
@@ -56,6 +56,7 @@ const activeUserTabId = $derived(
>
<span class="flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full shrink-0 {statusColor(tab.agentStatus)}"></span>
+ <span class="font-mono text-[10px] px-1 py-0.5 rounded bg-base-300 text-base-content/60 shrink-0" title="Tab ID — agents address this tab by this handle">{tabStore.shortHandleFor(tab.id)}</span>
<span class="max-w-32 truncate text-xs">{tab.title}</span>
</span>
<button
@@ -89,6 +90,7 @@ const activeUserTabId = $derived(
>
<span class="flex items-center gap-1">
<span class="w-1 h-1 rounded-full shrink-0 {statusColor(tab.agentStatus)}"></span>
+ <span class="font-mono text-[10px] px-1 rounded bg-base-300 text-base-content/60 shrink-0" title="Tab ID — agents address this tab by this handle">{tabStore.shortHandleFor(tab.id)}</span>
<span class="max-w-28 truncate text-xs">{tab.title}</span>
</span>
{#if tab.persistent}
diff --git a/packages/frontend/src/lib/components/ToolPermissions.svelte b/packages/frontend/src/lib/components/ToolPermissions.svelte
index d64eaf9..0caf0fa 100644
--- a/packages/frontend/src/lib/components/ToolPermissions.svelte
+++ b/packages/frontend/src/lib/components/ToolPermissions.svelte
@@ -28,6 +28,16 @@ const toolPermissions: ToolPermission[] = [
description: "Allow the AI to open new independent top-level tabs",
},
{
+ id: "send_to_tab",
+ label: "Message other tabs",
+ description: "Allow the AI to send messages to other tabs by their ID",
+ },
+ {
+ id: "read_tab",
+ label: "Read other tabs",
+ description: "Allow the AI to read other tabs' latest responses by their ID",
+ },
+ {
id: "web_search",
label: "Web search",
description: "Allow the AI to search the web via Firecrawl",
diff --git a/packages/frontend/src/lib/settings.svelte.ts b/packages/frontend/src/lib/settings.svelte.ts
index 11eb79c..123008d 100644
--- a/packages/frontend/src/lib/settings.svelte.ts
+++ b/packages/frontend/src/lib/settings.svelte.ts
@@ -9,6 +9,8 @@ let toolPerms = $state<Record<string, boolean>>({
bash: false,
summon: false,
user_agent: false,
+ send_to_tab: false,
+ read_tab: false,
external_directory: false,
web_search: false,
youtube_transcribe: false,
@@ -19,6 +21,8 @@ let savedToolPerms = $state<Record<string, boolean>>({
bash: false,
summon: false,
user_agent: false,
+ send_to_tab: false,
+ read_tab: false,
external_directory: false,
web_search: false,
youtube_transcribe: false,
diff --git a/packages/frontend/src/lib/tabs.svelte.ts b/packages/frontend/src/lib/tabs.svelte.ts
index e303c80..317de8d 100644
--- a/packages/frontend/src/lib/tabs.svelte.ts
+++ b/packages/frontend/src/lib/tabs.svelte.ts
@@ -232,6 +232,29 @@ export function createTabStore() {
return tabs.find((t) => t.id === id);
}
+ /**
+ * Minimum display length of a tab handle (git-style short id). Mirrors
+ * `MIN_TAB_PREFIX_LENGTH` in core's `db/tabs.ts` so the handle the user sees
+ * is always resolvable by the backend's `resolveTabPrefix`.
+ */
+ const MIN_HANDLE_LENGTH = 4;
+
+ /**
+ * Compute the shortest unique prefix (≥ MIN_HANDLE_LENGTH chars) of `tabId`
+ * among all currently-open tabs — the displayed "handle" agents use to
+ * address each other. Purely DERIVED from the UUIDs already in `tabs`; never
+ * stored. Grows by one char only when another open tab shares the prefix, and
+ * shrinks back when that sibling closes.
+ */
+ function shortHandleFor(tabId: string): string {
+ const others = tabs.map((t) => t.id).filter((id) => id !== tabId);
+ for (let len = MIN_HANDLE_LENGTH; len < tabId.length; len++) {
+ const candidate = tabId.slice(0, len);
+ if (!others.some((id) => id.startsWith(candidate))) return candidate;
+ }
+ return tabId;
+ }
+
async function createNewTab(): Promise<Tab> {
const id = generateId();
const title = "New Tab";
@@ -1926,6 +1949,7 @@ export function createTabStore() {
get configReloaded() {
return configReloaded;
},
+ shortHandleFor,
createNewTab,
switchTab,
closeTab,