@gpambrozio/paseo-skills 0.2.0 → 0.3.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/CHANGELOG.md +23 -0
- package/README.md +7 -4
- package/client/agents.ts +104 -0
- package/client/browser.tsx +633 -0
- package/client/invoke.ts +36 -0
- package/client/panel.tsx +11 -518
- package/client/pill.tsx +57 -29
- package/client/popover.tsx +104 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Every version
|
|
|
7
7
|
as `@gpambrozio/paseo-skills` and tagged here, so a version is something to install and a line to
|
|
8
8
|
read before you move.
|
|
9
9
|
|
|
10
|
+
## [0.3.0] — 2026-09-26
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **The Skills pill opens a popover above the composer instead of a new tab.** It lists the same
|
|
15
|
+
skills the Skills tab does, with the same search. Pick one to see a short summary of it, type any
|
|
16
|
+
arguments and press **Invoke** — it is sent exactly as it is from the tab, and the popover closes
|
|
17
|
+
so you can watch the agent start. **← All skills** goes back to the list, and **Open tab** at the
|
|
18
|
+
top right opens the full Skills tab as the pill used to, where the whole skill is shown. The pill
|
|
19
|
+
still shows how many skills there are.
|
|
20
|
+
|
|
21
|
+
## [0.2.1] — 2026-09-23
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **The Skills pill reaches agents created after the plugin loaded.** Paseo 0.9 changed
|
|
26
|
+
`agents.subscribe()` to a local listener that no longer asks the daemon for agent data, so the
|
|
27
|
+
pill only covered agents that existed at load; a new session had none until the plugin was
|
|
28
|
+
reloaded. On 0.9 clients the pill now opens its own agent observation
|
|
29
|
+
(`agents.list({ subscribe: {} })`) and follows its snapshots and updates; an 0.8 client keeps the
|
|
30
|
+
previous listen-and-seed behaviour, because sending `subscribe` from there would replace the
|
|
31
|
+
app's own agent subscription.
|
|
32
|
+
|
|
10
33
|
## [0.2.0] — 2026-09-22
|
|
11
34
|
|
|
12
35
|
### Added
|
package/README.md
CHANGED
|
@@ -49,9 +49,10 @@ plugin.
|
|
|
49
49
|
|
|
50
50
|
## Use
|
|
51
51
|
|
|
52
|
-
Press the **Skills** pill above an agent's composer — its badge counts what
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
Press the **Skills** pill above an agent's composer — its badge counts what it will list. It opens
|
|
53
|
+
a popover over the composer with the agent's skills: pick one to read it and invoke it with
|
|
54
|
+
arguments, or press **Open tab** at its top right for the full Skills panel. The Command Center
|
|
55
|
+
reaches the same panel: focus a workspace tab holding an agent, press ⌘K, and pick **Skills**.
|
|
55
56
|
|
|
56
57
|
## Demo
|
|
57
58
|
|
|
@@ -87,7 +88,9 @@ A failed reload stays failed; Paseo does not restore the previous code.
|
|
|
87
88
|
| `server/resolve/skill-entry.ts` | Entry types, skill id construction, first-wins dedupe. |
|
|
88
89
|
| `server/resolve/frontmatter.ts` | `SKILL.md` frontmatter parsing. |
|
|
89
90
|
| `server/resolve/reported.ts` | Splits session-reported entries discovery did not find. |
|
|
90
|
-
| `client/
|
|
91
|
+
| `client/browser.tsx` | List, search, detail, invoke — drawn by the panel and the popover. |
|
|
92
|
+
| `client/panel.tsx` | The panel: the browser as a whole tab. |
|
|
93
|
+
| `client/popover.tsx` | The pill's popover: the browser, plus a button to the panel. |
|
|
91
94
|
| `client/pill.tsx` | The composer pill and the registration loop that owns it. |
|
|
92
95
|
| `client/skills-query.tsx` | The `skills.list` query the panel and the pill share. |
|
|
93
96
|
|
package/client/agents.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Following the host's agents across Paseo 0.8 and 0.9 clients.
|
|
5
|
+
*
|
|
6
|
+
* Since 0.9, `agents.subscribe()` only adds a local listener to observations
|
|
7
|
+
* that the same API instance opened with `agents.list({ subscribe: {} })`; on
|
|
8
|
+
* its own it never hears anything. The observation delivers a snapshot first
|
|
9
|
+
* and again after every reconnect, then the updates in between.
|
|
10
|
+
*
|
|
11
|
+
* A 0.8 client has no observations, and must not send `subscribe` either: the
|
|
12
|
+
* daemon keeps one agents subscription slot per legacy connection, last query
|
|
13
|
+
* wins, so a plugin asking for one would replace the app's own. The choice is
|
|
14
|
+
* therefore made before any request, from the API's shape.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type Paseo = PluginClientContext["paseo"];
|
|
18
|
+
type AgentListOptions = NonNullable<Parameters<Paseo["agents"]["list"]>[0]>;
|
|
19
|
+
export type AgentList = Awaited<ReturnType<Paseo["agents"]["list"]>>;
|
|
20
|
+
export type AgentUpdate = Parameters<Parameters<Paseo["agents"]["subscribe"]>[0]>[0];
|
|
21
|
+
|
|
22
|
+
/** The 0.9 observation handle; the 0.8 typings this plugin builds against predate it. */
|
|
23
|
+
type AgentObservation = {
|
|
24
|
+
subscribe(observer: {
|
|
25
|
+
snapshot(snapshot: AgentList): void;
|
|
26
|
+
update(message: { type: string; payload?: unknown }): void;
|
|
27
|
+
error?(error: unknown): void;
|
|
28
|
+
}): () => void;
|
|
29
|
+
release(): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
type ObservingAgents = {
|
|
32
|
+
list(
|
|
33
|
+
options: AgentListOptions & { subscribe: {}; signal: AbortSignal },
|
|
34
|
+
): Promise<AgentList & { subscription?: AgentObservation }>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const RETRY_MIN_MS = 2_000;
|
|
38
|
+
const RETRY_MAX_MS = 60_000;
|
|
39
|
+
|
|
40
|
+
/** `observeEvents` shipped together with observations (0.9.0-beta.1). */
|
|
41
|
+
export function canObserveAgents(paseo: Paseo): boolean {
|
|
42
|
+
return typeof (paseo as { observeEvents?: unknown }).observeEvents === "function";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* On a 0.9 client, keeps an agent observation open for the caller's lifetime:
|
|
47
|
+
* `snapshot` replaces the caller's view, `update` applies one change. Paseo
|
|
48
|
+
* releases an observation that fails (a re-request after reconnect, say), so it
|
|
49
|
+
* is reopened with backoff rather than left silent. On a 0.8 client it runs
|
|
50
|
+
* `legacy` instead, the pre-0.9 listener and read. Returns the cleanup.
|
|
51
|
+
*/
|
|
52
|
+
export function followAgents(
|
|
53
|
+
paseo: Paseo,
|
|
54
|
+
handlers: { snapshot(list: AgentList): void; update(update: AgentUpdate): void },
|
|
55
|
+
legacy: () => () => void,
|
|
56
|
+
): () => void {
|
|
57
|
+
if (!canObserveAgents(paseo)) return legacy();
|
|
58
|
+
|
|
59
|
+
const lifetime = new AbortController();
|
|
60
|
+
let observation: AgentObservation | null = null;
|
|
61
|
+
let retry: ReturnType<typeof setTimeout> | null = null;
|
|
62
|
+
let retryDelay = RETRY_MIN_MS;
|
|
63
|
+
|
|
64
|
+
function reopen(error: unknown): void {
|
|
65
|
+
observation = null;
|
|
66
|
+
if (lifetime.signal.aborted || retry !== null) return;
|
|
67
|
+
console.warn("skills: agent observation failed; reopening", error);
|
|
68
|
+
retry = setTimeout(() => {
|
|
69
|
+
retry = null;
|
|
70
|
+
open();
|
|
71
|
+
}, retryDelay);
|
|
72
|
+
retryDelay = Math.min(retryDelay * 2, RETRY_MAX_MS);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function open(): void {
|
|
76
|
+
(paseo.agents as unknown as ObservingAgents)
|
|
77
|
+
.list({ subscribe: {}, signal: lifetime.signal })
|
|
78
|
+
.then(({ subscription }) => {
|
|
79
|
+
if (lifetime.signal.aborted) return;
|
|
80
|
+
if (subscription === undefined) throw new Error("the host returned no agent observation");
|
|
81
|
+
observation = subscription;
|
|
82
|
+
subscription.subscribe({
|
|
83
|
+
snapshot(list) {
|
|
84
|
+
retryDelay = RETRY_MIN_MS;
|
|
85
|
+
handlers.snapshot(list);
|
|
86
|
+
},
|
|
87
|
+
update(message) {
|
|
88
|
+
if (message.type === "agent_update") handlers.update(message.payload as AgentUpdate);
|
|
89
|
+
},
|
|
90
|
+
error: reopen,
|
|
91
|
+
});
|
|
92
|
+
})
|
|
93
|
+
.catch(reopen);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
open();
|
|
97
|
+
return () => {
|
|
98
|
+
lifetime.abort();
|
|
99
|
+
if (retry !== null) clearTimeout(retry);
|
|
100
|
+
retry = null;
|
|
101
|
+
void observation?.release().catch(() => undefined);
|
|
102
|
+
observation = null;
|
|
103
|
+
};
|
|
104
|
+
}
|