@omercnet/paseo-tell-agent 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omer Cohen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,69 @@
1
+ import type { PaseoAgent, PaseoAgentListResult, PaseoApi } from "@getpaseo/client";
2
+
3
+ export type AgentEntry = PaseoAgentListResult["entries"][number];
4
+ type AgentSnapshot = PaseoAgent;
5
+
6
+ export const AGENT_PAGE_LIMIT = 200;
7
+ export const MAX_AGENT_PAGES = 10;
8
+
9
+ export type AgentDirectoryPage = {
10
+ entries: AgentEntry[];
11
+ truncated: boolean;
12
+ };
13
+
14
+ export async function loadAgents(paseo: PaseoApi): Promise<AgentDirectoryPage> {
15
+ const entries: AgentEntry[] = [];
16
+ let cursor: string | undefined;
17
+ for (let page = 0; page < MAX_AGENT_PAGES; page += 1) {
18
+ const result = await paseo.agents.list({
19
+ sort: [{ key: "updated_at", direction: "desc" }],
20
+ page: { limit: AGENT_PAGE_LIMIT, ...(cursor ? { cursor } : {}) },
21
+ });
22
+ entries.push(...result.entries);
23
+ cursor = result.pageInfo.hasMore ? (result.pageInfo.nextCursor ?? undefined) : undefined;
24
+ if (!cursor) return { entries, truncated: false };
25
+ }
26
+ return { entries, truncated: true };
27
+ }
28
+
29
+ export function title(entry: AgentEntry): string {
30
+ const explicit = entry.agent.title?.trim();
31
+ return explicit && explicit.length > 0 ? explicit : entry.agent.id.slice(0, 7);
32
+ }
33
+
34
+ export function placement(entry: AgentEntry): string {
35
+ const { project } = entry;
36
+ const workspace = project.workspaceName?.trim();
37
+ const scope =
38
+ workspace && workspace !== project.projectName
39
+ ? `${project.projectName} / ${workspace}`
40
+ : project.projectName;
41
+ const branch = project.checkout.isGit ? project.checkout.currentBranch : null;
42
+ return branch ? `${scope} · ${branch}` : scope;
43
+ }
44
+
45
+ export function stateLabel(agent: AgentSnapshot): string {
46
+ const permissionCount = agent.pendingPermissions.length;
47
+ if (permissionCount > 0) {
48
+ return permissionCount === 1 ? "permission" : `${permissionCount} permissions`;
49
+ }
50
+ if (agent.status === "error") return "error";
51
+ if (agent.requiresAttention) return agent.attentionReason ?? "attention";
52
+ return agent.status === "initializing" ? "starting" : agent.status;
53
+ }
54
+
55
+ export function matches(entry: AgentEntry, needle: string): boolean {
56
+ if (needle.length === 0) return true;
57
+ return [
58
+ entry.agent.title ?? "",
59
+ entry.agent.id,
60
+ entry.agent.provider,
61
+ entry.agent.model ?? "",
62
+ entry.agent.cwd,
63
+ entry.project.projectName,
64
+ entry.project.workspaceName ?? "",
65
+ ]
66
+ .join(" ")
67
+ .toLowerCase()
68
+ .includes(needle);
69
+ }
@@ -0,0 +1,363 @@
1
+ import {
2
+ type PluginAgentPanelProps,
3
+ type PluginButtonContentProps,
4
+ type PluginClientContext,
5
+ usePaseo,
6
+ } from "@getpaseo/plugin/client";
7
+ import { Icon, useToast } from "@getpaseo/plugin/client/react-native";
8
+ import { useMutation, useQuery } from "@tanstack/react-query";
9
+ import { useEffect, useMemo, useRef, useState } from "react";
10
+ import { Pressable, ScrollView, Text, TextInput, View } from "react-native";
11
+ import {
12
+ AGENT_PAGE_LIMIT,
13
+ type AgentEntry,
14
+ loadAgents,
15
+ MAX_AGENT_PAGES,
16
+ placement,
17
+ stateLabel,
18
+ title,
19
+ } from "./agents";
20
+ import { formatTellInstruction, messageTargets } from "./messaging";
21
+ import { handleTellCommand } from "./tell-command";
22
+
23
+ const VISIBLE_TARGETS = 8;
24
+
25
+ type MessageAgentFormProps = {
26
+ sourceAgentId: string;
27
+ theme: PluginButtonContentProps["theme"];
28
+ host: PluginButtonContentProps["host"];
29
+ close?: () => void;
30
+ };
31
+
32
+ function MessageAgentForm({ sourceAgentId, theme, host, close }: MessageAgentFormProps) {
33
+ const paseo = usePaseo();
34
+ const toast = useToast();
35
+ const [query, setQuery] = useState("");
36
+ const [targetId, setTargetId] = useState<string | null>(null);
37
+ const [message, setMessage] = useState("");
38
+ const messageInputRef = useRef<TextInput>(null);
39
+ const directory = useQuery({
40
+ queryKey: ["tell-agent", "message-targets", host.id],
41
+ queryFn: () => loadAgents(paseo),
42
+ });
43
+ const entries = directory.data?.entries ?? [];
44
+ const target = targetId ? entries.find((entry) => entry.agent.id === targetId) : undefined;
45
+ const matchingTargets = useMemo(
46
+ () => messageTargets(entries, sourceAgentId, query),
47
+ [entries, query, sourceAgentId],
48
+ );
49
+ const visibleTargets = matchingTargets.slice(0, VISIBLE_TARGETS);
50
+ useEffect(() => {
51
+ if (!targetId) return;
52
+ const focusTimer = setTimeout(() => messageInputRef.current?.focus(), 0);
53
+ return () => clearTimeout(focusTimer);
54
+ }, [targetId]);
55
+ const send = useMutation({
56
+ mutationFn: async ({ entry, text }: { entry: AgentEntry; text: string }) => {
57
+ await paseo.agents.ref(sourceAgentId).send(formatTellInstruction(entry.agent.id, text));
58
+ return entry;
59
+ },
60
+ onSuccess(entry) {
61
+ toast.show(`Asked current agent to tell ${title(entry)}`, { variant: "success" });
62
+ if (close) close();
63
+ else {
64
+ setTargetId(null);
65
+ setMessage("");
66
+ }
67
+ },
68
+ });
69
+ const styles = useMemo(
70
+ () => ({
71
+ body: { gap: 12 },
72
+ heading: { color: theme.colors.foreground, fontSize: 18, fontWeight: "600" as const },
73
+ detail: { color: theme.colors.foregroundMuted, fontSize: 13, lineHeight: 18 },
74
+ error: { color: theme.colors.statusDanger, fontSize: 13, lineHeight: 18 },
75
+ warning: { color: theme.colors.statusWarning, fontSize: 13, lineHeight: 18 },
76
+ input: {
77
+ minHeight: 38,
78
+ borderWidth: 1,
79
+ borderColor: theme.colors.border,
80
+ borderRadius: 8,
81
+ paddingHorizontal: 10,
82
+ paddingVertical: 8,
83
+ color: theme.colors.foreground,
84
+ backgroundColor: theme.colors.surface1,
85
+ },
86
+ message: {
87
+ minHeight: 92,
88
+ borderWidth: 1,
89
+ borderColor: theme.colors.border,
90
+ borderRadius: 8,
91
+ paddingHorizontal: 10,
92
+ paddingVertical: 8,
93
+ color: theme.colors.foreground,
94
+ backgroundColor: theme.colors.surface1,
95
+ textAlignVertical: "top" as const,
96
+ },
97
+ results: { gap: 6 },
98
+ row: {
99
+ minHeight: 48,
100
+ flexDirection: "row" as const,
101
+ alignItems: "center" as const,
102
+ gap: 9,
103
+ paddingHorizontal: 10,
104
+ paddingVertical: 8,
105
+ borderWidth: 1,
106
+ borderColor: theme.colors.border,
107
+ borderRadius: 8,
108
+ backgroundColor: theme.colors.surface1,
109
+ },
110
+ rowSelected: {
111
+ borderColor: theme.colors.accent,
112
+ backgroundColor: theme.colors.surface2,
113
+ },
114
+ rowText: { flex: 1, minWidth: 0, gap: 2 },
115
+ rowTitle: { color: theme.colors.foreground, fontWeight: "600" as const, fontSize: 13 },
116
+ rowDetail: { color: theme.colors.foregroundMuted, fontSize: 12 },
117
+ state: { color: theme.colors.foregroundMuted, fontSize: 11 },
118
+ actions: { flexDirection: "row" as const, justifyContent: "flex-end" as const, gap: 8 },
119
+ button: {
120
+ minHeight: 36,
121
+ justifyContent: "center" as const,
122
+ paddingHorizontal: 12,
123
+ borderRadius: 8,
124
+ borderWidth: 1,
125
+ borderColor: theme.colors.border,
126
+ backgroundColor: theme.colors.surface1,
127
+ },
128
+ primary: { borderColor: theme.colors.accent, backgroundColor: theme.colors.accent },
129
+ buttonText: { color: theme.colors.foreground, fontWeight: "600" as const },
130
+ primaryText: { color: theme.colors.accentForeground, fontWeight: "600" as const },
131
+ disabled: { opacity: 0.4 },
132
+ }),
133
+ [theme],
134
+ );
135
+ const canSend = Boolean(target && message.trim()) && !send.isPending;
136
+
137
+ return (
138
+ <View style={styles.body}>
139
+ <Text style={styles.heading}>Tell another agent</Text>
140
+ <Text style={styles.detail}>
141
+ Search active sessions on {host.label}. Your current agent receives the instruction and can
142
+ add context before contacting the selected agent.
143
+ </Text>
144
+ <TextInput
145
+ accessibilityLabel="Search message targets"
146
+ autoCapitalize="none"
147
+ autoCorrect={false}
148
+ onChangeText={setQuery}
149
+ placeholder="Search agent, workspace, project, or model"
150
+ placeholderTextColor={theme.colors.foregroundMuted}
151
+ value={query}
152
+ style={styles.input}
153
+ />
154
+ {directory.isPending ? <Text style={styles.detail}>Loading agents…</Text> : null}
155
+ {directory.error ? (
156
+ <Text accessibilityRole="alert" style={styles.error}>
157
+ {directory.error.message}
158
+ </Text>
159
+ ) : null}
160
+ {!directory.isPending && !directory.error ? (
161
+ <View style={styles.results}>
162
+ {visibleTargets.map((entry) => {
163
+ const selected = entry.agent.id === targetId;
164
+ return (
165
+ <Pressable
166
+ key={entry.agent.id}
167
+ accessibilityRole="button"
168
+ accessibilityLabel={`Select ${title(entry)} in ${placement(entry)}`}
169
+ accessibilityState={{ selected }}
170
+ onPress={() => setTargetId(entry.agent.id)}
171
+ style={({ pressed }) => [
172
+ styles.row,
173
+ selected && styles.rowSelected,
174
+ pressed && styles.disabled,
175
+ ]}
176
+ >
177
+ <Icon
178
+ name={selected ? "CircleCheck" : "MessageSquareMore"}
179
+ size={16}
180
+ color={selected ? theme.colors.accent : theme.colors.foregroundMuted}
181
+ />
182
+ <View style={styles.rowText}>
183
+ <Text numberOfLines={1} style={styles.rowTitle}>
184
+ {title(entry)}
185
+ </Text>
186
+ <Text numberOfLines={1} style={styles.rowDetail}>
187
+ {placement(entry)}
188
+ </Text>
189
+ </View>
190
+ <Text style={styles.state}>{stateLabel(entry.agent)}</Text>
191
+ </Pressable>
192
+ );
193
+ })}
194
+ {matchingTargets.length === 0 ? (
195
+ <Text style={styles.detail}>No active agents match this search.</Text>
196
+ ) : null}
197
+ {matchingTargets.length > VISIBLE_TARGETS ? (
198
+ <Text style={styles.detail}>
199
+ Showing {VISIBLE_TARGETS} of {matchingTargets.length}. Refine the search to narrow it.
200
+ </Text>
201
+ ) : null}
202
+ {directory.data?.truncated ? (
203
+ <Text style={styles.warning}>
204
+ Only the first {AGENT_PAGE_LIMIT * MAX_AGENT_PAGES} agents were searched.
205
+ </Text>
206
+ ) : null}
207
+ </View>
208
+ ) : null}
209
+ {target ? (
210
+ <TextInput
211
+ ref={messageInputRef}
212
+ accessibilityLabel={`Instruction for ${title(target)}`}
213
+ multiline
214
+ onChangeText={setMessage}
215
+ placeholder="What should your current agent tell this agent?"
216
+ placeholderTextColor={theme.colors.foregroundMuted}
217
+ value={message}
218
+ style={styles.message}
219
+ />
220
+ ) : null}
221
+ {send.error ? (
222
+ <Text accessibilityRole="alert" style={styles.error}>
223
+ {send.error.message}
224
+ </Text>
225
+ ) : null}
226
+ <View style={styles.actions}>
227
+ <Pressable
228
+ accessibilityRole="button"
229
+ disabled={send.isPending}
230
+ onPress={() => {
231
+ if (close) close();
232
+ else {
233
+ setTargetId(null);
234
+ setMessage("");
235
+ }
236
+ }}
237
+ style={({ pressed }) => [styles.button, pressed && styles.disabled]}
238
+ >
239
+ <Text style={styles.buttonText}>{close ? "Cancel" : "Clear"}</Text>
240
+ </Pressable>
241
+ <Pressable
242
+ accessibilityRole="button"
243
+ accessibilityLabel={
244
+ target ? `Ask current agent to tell ${title(target)}` : "Select a target agent"
245
+ }
246
+ disabled={!canSend}
247
+ onPress={() => {
248
+ if (target && message.trim()) send.mutate({ entry: target, text: message });
249
+ }}
250
+ style={({ pressed }) => [
251
+ styles.button,
252
+ styles.primary,
253
+ (!canSend || pressed) && styles.disabled,
254
+ ]}
255
+ >
256
+ <Text style={styles.primaryText}>
257
+ {send.isPending ? "Sending…" : "Ask current agent"}
258
+ </Text>
259
+ </Pressable>
260
+ </View>
261
+ </View>
262
+ );
263
+ }
264
+
265
+ export function MessageAgentPopover(props: PluginButtonContentProps) {
266
+ if (props.context !== "agent") return null;
267
+ return <MessageAgentForm {...props} sourceAgentId={props.agentId} />;
268
+ }
269
+
270
+ export function TellAgentPanel({ theme, host, layout, agentId }: PluginAgentPanelProps) {
271
+ const contentStyle = useMemo(
272
+ () => ({
273
+ flexGrow: 1,
274
+ padding: layout.compact ? 14 : 20,
275
+ backgroundColor: theme.colors.surface0,
276
+ }),
277
+ [layout.compact, theme.colors.surface0],
278
+ );
279
+ return (
280
+ <ScrollView contentContainerStyle={contentStyle} keyboardShouldPersistTaps="handled">
281
+ <MessageAgentForm sourceAgentId={agentId} theme={theme} host={host} />
282
+ </ScrollView>
283
+ );
284
+ }
285
+
286
+ export function contributeAgentMessaging(client: PluginClientContext) {
287
+ const removePanel = client.addWorkspacePanel({
288
+ id: "tell-agent",
289
+ title: "Tell agent",
290
+ icon: "MessagesSquare",
291
+ context: "agent",
292
+ locations: ["workspace", "explorer"],
293
+ Component: TellAgentPanel,
294
+ });
295
+ const pills = new Map<string, { workspaceId: string; remove(): void }>();
296
+ let stopped = false;
297
+
298
+ function removePill(agentId: string) {
299
+ pills.get(agentId)?.remove();
300
+ pills.delete(agentId);
301
+ }
302
+
303
+ function syncAgent(agent: {
304
+ id: string;
305
+ workspaceId?: string | null;
306
+ status: string;
307
+ archivedAt?: string | null;
308
+ }) {
309
+ if (!agent.workspaceId || agent.archivedAt || agent.status === "closed") {
310
+ removePill(agent.id);
311
+ return;
312
+ }
313
+ const next = { id: agent.id, workspaceId: agent.workspaceId };
314
+ const current = pills.get(agent.id);
315
+ if (current?.workspaceId === next.workspaceId) return;
316
+ removePill(agent.id);
317
+ const pill = client.addComposerPill({
318
+ id: "message-agent",
319
+ workspaceId: next.workspaceId,
320
+ agentId: next.id,
321
+ button: {
322
+ title: "Message another agent",
323
+ label: "Tell agent",
324
+ icon: "MessagesSquare",
325
+ behavior: { kind: "popover", Content: MessageAgentPopover },
326
+ },
327
+ });
328
+ pills.set(agent.id, { workspaceId: next.workspaceId, remove: pill.remove });
329
+ }
330
+
331
+ const unsubscribe = client.paseo.agents.subscribe((update) => {
332
+ if (update.kind === "remove") {
333
+ removePill(update.agentId);
334
+ return;
335
+ }
336
+ syncAgent(update.agent);
337
+ });
338
+
339
+ void loadAgents(client.paseo)
340
+ .then(({ entries }) => {
341
+ if (stopped) return;
342
+ for (const { agent } of entries) syncAgent(agent);
343
+ })
344
+ .catch(() => undefined);
345
+
346
+ const removeTellCommand = client.addSlashCommand({
347
+ name: "tell",
348
+ description: "Open the agent picker or message another session",
349
+ argumentHint: "[agent or workspace :: message]",
350
+ context: "agent",
351
+ onSubmit: handleTellCommand,
352
+ });
353
+
354
+ return () => {
355
+ if (stopped) return;
356
+ stopped = true;
357
+ removeTellCommand();
358
+ removePanel();
359
+ unsubscribe();
360
+ for (const pill of pills.values()) pill.remove();
361
+ pills.clear();
362
+ };
363
+ }
@@ -0,0 +1,63 @@
1
+ import { type AgentEntry, matches, title } from "./agents";
2
+
3
+ export type TellArguments = { target: string; message: string };
4
+
5
+ export type TargetResolution =
6
+ | { kind: "match"; entry: AgentEntry }
7
+ | { kind: "none" }
8
+ | { kind: "ambiguous"; entries: AgentEntry[] };
9
+
10
+ export function isMessageable(entry: AgentEntry, sourceAgentId: string): boolean {
11
+ const agent = entry.agent;
12
+ return agent.id !== sourceAgentId && !agent.archivedAt && agent.status !== "closed";
13
+ }
14
+
15
+ export function messageTargets(
16
+ entries: readonly AgentEntry[],
17
+ sourceAgentId: string,
18
+ query: string,
19
+ ): AgentEntry[] {
20
+ const needle = query.trim().replace(/^@/, "").toLowerCase();
21
+ return entries
22
+ .filter((entry) => isMessageable(entry, sourceAgentId) && matches(entry, needle))
23
+ .sort((left, right) => {
24
+ const leftTitle = title(left).toLowerCase();
25
+ const rightTitle = title(right).toLowerCase();
26
+ const leftExact = left.agent.id.toLowerCase() === needle || leftTitle === needle;
27
+ const rightExact = right.agent.id.toLowerCase() === needle || rightTitle === needle;
28
+ if (leftExact !== rightExact) return leftExact ? -1 : 1;
29
+ return Date.parse(right.agent.updatedAt) - Date.parse(left.agent.updatedAt);
30
+ });
31
+ }
32
+
33
+ export function resolveMessageTarget(
34
+ entries: readonly AgentEntry[],
35
+ sourceAgentId: string,
36
+ query: string,
37
+ ): TargetResolution {
38
+ const needle = query.trim().replace(/^@/, "").toLowerCase();
39
+ if (!needle) return { kind: "none" };
40
+ const candidates = entries.filter((entry) => isMessageable(entry, sourceAgentId));
41
+ const idMatches = candidates.filter((entry) => entry.agent.id.toLowerCase().startsWith(needle));
42
+ if (idMatches.length === 1) return { kind: "match", entry: idMatches[0] as AgentEntry };
43
+ if (idMatches.length > 1) return { kind: "ambiguous", entries: idMatches };
44
+ const titleMatches = candidates.filter((entry) => title(entry).toLowerCase() === needle);
45
+ if (titleMatches.length === 1) return { kind: "match", entry: titleMatches[0] as AgentEntry };
46
+ if (titleMatches.length > 1) return { kind: "ambiguous", entries: titleMatches };
47
+ const fuzzyMatches = messageTargets(candidates, sourceAgentId, needle);
48
+ if (fuzzyMatches.length === 1) return { kind: "match", entry: fuzzyMatches[0] as AgentEntry };
49
+ if (fuzzyMatches.length > 1) return { kind: "ambiguous", entries: fuzzyMatches };
50
+ return { kind: "none" };
51
+ }
52
+
53
+ export function parseTellArguments(args: string): TellArguments | null {
54
+ const separator = args.indexOf("::");
55
+ if (separator < 1) return null;
56
+ const target = args.slice(0, separator).trim();
57
+ const message = args.slice(separator + 2).trim();
58
+ return target && message ? { target, message } : null;
59
+ }
60
+
61
+ export function formatTellInstruction(targetAgentId: string, message: string): string {
62
+ return `tell ${targetAgentId}: ${message.trim()}`;
63
+ }
@@ -0,0 +1,33 @@
1
+ import type { PluginAgentCommandContext } from "@getpaseo/plugin/client";
2
+ import { loadAgents, placement, title } from "./agents";
3
+ import { formatTellInstruction, parseTellArguments, resolveMessageTarget } from "./messaging";
4
+
5
+ export async function handleTellCommand({
6
+ args,
7
+ agent,
8
+ paseo,
9
+ openPanel,
10
+ }: PluginAgentCommandContext & { args: string }) {
11
+ if (!args.trim()) {
12
+ openPanel("tell-agent");
13
+ return;
14
+ }
15
+ const parsed = parseTellArguments(args);
16
+ if (!parsed) {
17
+ throw new Error("Usage: /tell <agent or workspace> :: <message>");
18
+ }
19
+ const { entries } = await loadAgents(paseo);
20
+ const resolution = resolveMessageTarget(entries, agent.id, parsed.target);
21
+ if (resolution.kind === "none") {
22
+ throw new Error(`No active agent matches “${parsed.target}”.`);
23
+ }
24
+ if (resolution.kind === "ambiguous") {
25
+ const examples = resolution.entries
26
+ .slice(0, 3)
27
+ .map((entry) => `${title(entry)} (${placement(entry)})`)
28
+ .join(", ");
29
+ throw new Error(`More than one agent matches “${parsed.target}”: ${examples}. Refine it.`);
30
+ }
31
+ const target = resolution.entry;
32
+ await paseo.agents.ref(agent.id).send(formatTellInstruction(target.agent.id, parsed.message));
33
+ }
@@ -0,0 +1,6 @@
1
+ import type { PluginClientContext } from "@getpaseo/plugin/client";
2
+ import { contributeAgentMessaging } from "./client/message-agent";
3
+
4
+ export default function contribute(client: PluginClientContext) {
5
+ return contributeAgentMessaging(client);
6
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@omercnet/paseo-tell-agent",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Tell agents in other Paseo workspaces.",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "packageManager": "bun@1.4.0",
11
+ "engines": {
12
+ "bun": ">=1.4.0"
13
+ },
14
+ "author": "Omer Cohen",
15
+ "keywords": [
16
+ "paseo",
17
+ "paseo-plugin",
18
+ "tell-agent",
19
+ "coding-agents",
20
+ "orchestration"
21
+ ],
22
+ "files": [
23
+ "index.client.tsx",
24
+ "client",
25
+ "paseo-plugin.json",
26
+ "LICENSE"
27
+ ],
28
+ "scripts": {
29
+ "check": "biome check .",
30
+ "check:write": "biome check --write .",
31
+ "test": "bun test",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "devDependencies": {
35
+ "@biomejs/biome": "^2.5.10",
36
+ "@getpaseo/client": "0.8.0",
37
+ "@getpaseo/plugin": "0.8.0",
38
+ "@getpaseo/protocol": "0.8.0",
39
+ "@tanstack/react-query": "^5.102.3",
40
+ "@types/bun": "^1.4.0",
41
+ "@types/react": "^19.2.18",
42
+ "react": "19.1.0",
43
+ "react-native": "0.81.5",
44
+ "typescript": "^7.0.2"
45
+ }
46
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "id": "tell-agent",
3
+ "requirements": { "paseo": "^0.8.0" }
4
+ }