@xpufx/paseo-x-comms 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/LICENSE +21 -0
- package/README.md +149 -0
- package/client/attribution.ts +7 -0
- package/client/conversations.ts +229 -0
- package/client/main.tsx +885 -0
- package/client/peer-label.ts +98 -0
- package/client/peer-status.tsx +201 -0
- package/client/settings-prototype.tsx +395 -0
- package/client/tool-call.ts +115 -0
- package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
- package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
- package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
- package/client/vendor/paseo-plugin-helper/host.ts +277 -0
- package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/index.ts +28 -0
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
- package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
- package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
- package/client/vendor/paseo-plugin-helper/query.ts +66 -0
- package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
- package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
- package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
- package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
- package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
- package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
- package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
- package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
- package/client/via-x-comms.tsx +15 -0
- package/client/x-comms-conversation.tsx +449 -0
- package/client/x-comms-panel.tsx +6 -0
- package/client/x-comms-pill.tsx +193 -0
- package/client/x-comms-timeline.tsx +176 -0
- package/client/x-comms-tool-call.tsx +85 -0
- package/docs/mesh.md +115 -0
- package/mcp/LICENSE +202 -0
- package/mcp/README.md +287 -0
- package/mcp/mcp-config.example.json +8 -0
- package/mcp/package-lock.json +1186 -0
- package/mcp/package.json +43 -0
- package/mcp/paseo-cross-daemon-comms.example.json +5 -0
- package/mcp/paseo-x-comms.bundled.mjs +36964 -0
- package/mcp/paseo-x-comms.mjs +630 -0
- package/mcp/test/fixtures/extensions/block/10-block.mjs +4 -0
- package/mcp/test/fixtures/extensions/broken-hook/10-thrower.mjs +6 -0
- package/mcp/test/fixtures/extensions/broken-hook/20-good.mjs +4 -0
- package/mcp/test/fixtures/extensions/broken-load/10-thrower.mjs +4 -0
- package/mcp/test/fixtures/extensions/broken-load/20-good.mjs +4 -0
- package/mcp/test/fixtures/extensions/custom-tool/10-tool.mjs +12 -0
- package/mcp/test/fixtures/extensions/tool-block/10-block.mjs +8 -0
- package/mcp/test/fixtures/extensions/transform/10-transform.mjs +15 -0
- package/mcp/test/fixtures/fake-paseo.mjs +92 -0
- package/mcp/test/register-ts-hooks.mjs +6 -0
- package/mcp/test/resolve-ts-hooks.mjs +46 -0
- package/package.json +58 -0
- package/paseo-plugin.json +7 -0
- package/server/conversations-snapshot.ts +249 -0
- package/server/handlers.ts +1081 -0
- package/server/injection.ts +152 -0
- package/server/local-send.ts +153 -0
- package/server/mcp-client.ts +47 -0
- package/server/outbox.ts +233 -0
- package/server/peer-channel.ts +151 -0
- package/server/peer-status.ts +233 -0
- package/server/presence.ts +204 -0
- package/server/registry.ts +215 -0
- package/server/relay-status.ts +9 -0
- package/server/server-status.ts +138 -0
- package/server/settings.ts +81 -0
- package/server/snapshot.ts +176 -0
- package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
- package/server/vendor/paseo-plugin-helper/index.ts +18 -0
- package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
- package/server/vendor/paseo-plugin-helper/mcp/client.ts +349 -0
- package/server/vendor/paseo-plugin-helper/mcp/http-client.ts +399 -0
- package/server/vendor/paseo-plugin-helper/mcp/index.ts +5 -0
- package/server/vendor/paseo-plugin-helper/mcp/process-killer.ts +65 -0
- package/server/vendor/paseo-plugin-helper/mcp/ring-buffer.ts +29 -0
- package/server/vendor/paseo-plugin-helper/mcp/types.ts +41 -0
- package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
- package/server/vendor/paseo-plugin-helper/network.ts +91 -0
- package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
- package/server/vendor/paseo-plugin-helper/process.ts +186 -0
- package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
- package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
- package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
- package/server/vendor/paseo-plugin-helper/system.ts +128 -0
- package/server/vendor/paseo-plugin-helper/task.ts +116 -0
- package/server/vendor/paseo-plugin-helper/version.ts +153 -0
- package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
- package/shared/conversations-snapshot.ts +39 -0
- package/shared/envelope.ts +92 -0
- package/shared/outbox.ts +21 -0
- package/shared/registry.ts +366 -0
- package/shared/vendor/paseo-plugin-helper/README.md +11 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
- package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
- package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
- package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
- package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
- package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
- package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
- package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
- package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
- package/shared/version.ts +2 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { defineRpc } from "paseo-plugin-helper/shared";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const DaemonEntrySchema = z.object({
|
|
5
|
+
name: z.string(),
|
|
6
|
+
value: z.string(),
|
|
7
|
+
valid: z.boolean(),
|
|
8
|
+
error: z.string().nullable(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const registryReadRpc = defineRpc({
|
|
12
|
+
name: "registry.read",
|
|
13
|
+
input: z.object({}),
|
|
14
|
+
output: z.object({
|
|
15
|
+
registryPath: z.string(),
|
|
16
|
+
exists: z.boolean(),
|
|
17
|
+
validJson: z.boolean(),
|
|
18
|
+
parseError: z.string().nullable(),
|
|
19
|
+
daemons: z.array(DaemonEntrySchema.extend({ serverId: z.string().nullable(), hostname: z.string().nullable() })),
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const daemonAddRpc = defineRpc({
|
|
24
|
+
name: "daemon.add",
|
|
25
|
+
input: z.object({
|
|
26
|
+
name: z.string().min(1).max(64),
|
|
27
|
+
value: z.string().min(1).max(512),
|
|
28
|
+
}),
|
|
29
|
+
output: z.object({
|
|
30
|
+
saved: z.boolean(),
|
|
31
|
+
error: z.string().nullable(),
|
|
32
|
+
registryPath: z.string(),
|
|
33
|
+
daemons: z.array(DaemonEntrySchema),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const daemonUpdateRpc = defineRpc({
|
|
38
|
+
name: "daemon.update",
|
|
39
|
+
input: z.object({
|
|
40
|
+
// `name` is the current key; `rename`/`value` are the optional new values.
|
|
41
|
+
name: z.string().min(1).max(64),
|
|
42
|
+
rename: z.string().min(1).max(64).optional(),
|
|
43
|
+
value: z.string().min(1).max(512).optional(),
|
|
44
|
+
}),
|
|
45
|
+
output: z.object({
|
|
46
|
+
saved: z.boolean(),
|
|
47
|
+
error: z.string().nullable(),
|
|
48
|
+
registryPath: z.string(),
|
|
49
|
+
daemons: z.array(DaemonEntrySchema),
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const daemonRemoveRpc = defineRpc({
|
|
54
|
+
name: "daemon.remove",
|
|
55
|
+
input: z.object({
|
|
56
|
+
name: z.string().min(1).max(64),
|
|
57
|
+
}),
|
|
58
|
+
output: z.object({
|
|
59
|
+
saved: z.boolean(),
|
|
60
|
+
error: z.string().nullable(),
|
|
61
|
+
registryPath: z.string(),
|
|
62
|
+
daemons: z.array(DaemonEntrySchema),
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const daemonHealthRpc = defineRpc({
|
|
67
|
+
name: "daemon.health",
|
|
68
|
+
input: z.object({}),
|
|
69
|
+
output: z.object({
|
|
70
|
+
results: z.array(
|
|
71
|
+
z.object({
|
|
72
|
+
name: z.string(),
|
|
73
|
+
reachable: z.boolean(),
|
|
74
|
+
error: z.string().nullable(),
|
|
75
|
+
agentCount: z.number().nullable(),
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Live per-peer probe for the known-daemon surface (#7). One entry per
|
|
83
|
+
* registry daemon; each carries its own reachability so an unreachable host
|
|
84
|
+
* never fails the whole list. Identity/relay fields come from the peer's own
|
|
85
|
+
* `daemon.get_status`; the hub relationship is best-effort and stays null when
|
|
86
|
+
* the peer does not support it.
|
|
87
|
+
*/
|
|
88
|
+
export const peerStatusRpc = defineRpc({
|
|
89
|
+
name: "peer.status",
|
|
90
|
+
input: z.object({}),
|
|
91
|
+
output: z.object({
|
|
92
|
+
results: z.array(
|
|
93
|
+
z.object({
|
|
94
|
+
name: z.string(),
|
|
95
|
+
value: z.string(),
|
|
96
|
+
valid: z.boolean(),
|
|
97
|
+
reachable: z.boolean(),
|
|
98
|
+
error: z.string().nullable(),
|
|
99
|
+
transport: z.enum(["relay", "direct", "unknown"]),
|
|
100
|
+
// Identity from the peer's own daemon.get_status (handshake serverId
|
|
101
|
+
// is the fallback when the status payload omits it).
|
|
102
|
+
serverId: z.string().nullable(),
|
|
103
|
+
hostname: z.string().nullable(),
|
|
104
|
+
version: z.string().nullable(),
|
|
105
|
+
pid: z.number().nullable(),
|
|
106
|
+
listen: z.string().nullable(),
|
|
107
|
+
relayEnabled: z.boolean().nullable(),
|
|
108
|
+
relayEndpoint: z.string().nullable(),
|
|
109
|
+
providerCount: z.number().nullable(),
|
|
110
|
+
providersAvailable: z.number().nullable(),
|
|
111
|
+
hubState: z.string().nullable(),
|
|
112
|
+
hubDaemonId: z.string().nullable(),
|
|
113
|
+
hubOrigin: z.string().nullable(),
|
|
114
|
+
hubLastError: z.string().nullable(),
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export const serverStatusRpc = defineRpc({
|
|
121
|
+
name: "server.status",
|
|
122
|
+
input: z.object({}),
|
|
123
|
+
output: z.object({
|
|
124
|
+
installPath: z.string(),
|
|
125
|
+
installed: z.boolean(),
|
|
126
|
+
configured: z.boolean(),
|
|
127
|
+
version: z.string().nullable(),
|
|
128
|
+
syntaxOk: z.boolean(),
|
|
129
|
+
error: z.string().nullable(),
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export const introspectAgentsRpc = defineRpc({
|
|
134
|
+
name: "agents.introspect",
|
|
135
|
+
input: z.object({}),
|
|
136
|
+
output: z.object({
|
|
137
|
+
daemons: z.array(
|
|
138
|
+
z.object({
|
|
139
|
+
name: z.string(),
|
|
140
|
+
reachable: z.boolean(),
|
|
141
|
+
error: z.string().nullable(),
|
|
142
|
+
projects: z.array(
|
|
143
|
+
z.object({
|
|
144
|
+
project: z.string(),
|
|
145
|
+
workspaces: z.array(
|
|
146
|
+
z.object({
|
|
147
|
+
name: z.string(),
|
|
148
|
+
agents: z.array(
|
|
149
|
+
z.object({
|
|
150
|
+
agentId: z.string(),
|
|
151
|
+
shortId: z.string(),
|
|
152
|
+
name: z.string(),
|
|
153
|
+
status: z.string(),
|
|
154
|
+
}),
|
|
155
|
+
),
|
|
156
|
+
}),
|
|
157
|
+
),
|
|
158
|
+
}),
|
|
159
|
+
),
|
|
160
|
+
}),
|
|
161
|
+
),
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export const introduceAgentsRpc = defineRpc({
|
|
166
|
+
name: "agents.introduce",
|
|
167
|
+
input: z.object({
|
|
168
|
+
first: z.object({ daemon: z.string(), agentId: z.string(), shortId: z.string(), name: z.string() }),
|
|
169
|
+
second: z.object({ daemon: z.string(), agentId: z.string(), shortId: z.string(), name: z.string() }),
|
|
170
|
+
message: z.string().min(1).max(4000),
|
|
171
|
+
}),
|
|
172
|
+
output: z.object({
|
|
173
|
+
sends: z.array(
|
|
174
|
+
z.object({
|
|
175
|
+
daemon: z.string(),
|
|
176
|
+
agentId: z.string(),
|
|
177
|
+
ok: z.boolean(),
|
|
178
|
+
error: z.string().nullable(),
|
|
179
|
+
}),
|
|
180
|
+
),
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export const daemonProbeRpc = defineRpc({
|
|
185
|
+
name: "daemon.probe",
|
|
186
|
+
input: z.object({ value: z.string().min(1).max(512) }),
|
|
187
|
+
output: z.object({
|
|
188
|
+
valid: z.boolean(),
|
|
189
|
+
formatError: z.string().nullable(),
|
|
190
|
+
reachable: z.boolean(),
|
|
191
|
+
error: z.string().nullable(),
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export const uiPrefsGetRpc = defineRpc({
|
|
196
|
+
name: "ui.prefs.get",
|
|
197
|
+
input: z.object({}),
|
|
198
|
+
output: z.object({
|
|
199
|
+
prereqsCollapsed: z.boolean(),
|
|
200
|
+
presenceEnabled: z.boolean(),
|
|
201
|
+
injectionEnabled: z.boolean(),
|
|
202
|
+
// Optional so a client still validates against a daemon that has not yet
|
|
203
|
+
// been reloaded with the outbox expiry field.
|
|
204
|
+
outboxExpirySeconds: z.number().int().positive().optional(),
|
|
205
|
+
daemonEnabled: z.record(z.string(), z.boolean()).optional(),
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export const uiPrefsSetRpc = defineRpc({
|
|
210
|
+
name: "ui.prefs.set",
|
|
211
|
+
input: z.object({
|
|
212
|
+
prereqsCollapsed: z.boolean(),
|
|
213
|
+
presenceEnabled: z.boolean().optional(),
|
|
214
|
+
injectionEnabled: z.boolean().optional(),
|
|
215
|
+
outboxExpirySeconds: z.number().int().positive().optional(),
|
|
216
|
+
daemonEnabled: z.record(z.string(), z.boolean()).optional(),
|
|
217
|
+
}),
|
|
218
|
+
output: z.object({
|
|
219
|
+
prereqsCollapsed: z.boolean(),
|
|
220
|
+
presenceEnabled: z.boolean(),
|
|
221
|
+
injectionEnabled: z.boolean(),
|
|
222
|
+
outboxExpirySeconds: z.number().int().positive().optional(),
|
|
223
|
+
daemonEnabled: z.record(z.string(), z.boolean()).optional(),
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const snapshotRefreshRpc = defineRpc({
|
|
228
|
+
name: "snapshot.refresh",
|
|
229
|
+
input: z.object({}),
|
|
230
|
+
output: z.object({ updatedAt: z.string() }),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const PresenceBirthSchema = z.object({
|
|
234
|
+
serverId: z.string().min(1).max(128),
|
|
235
|
+
agentId: z.string().min(1).max(128),
|
|
236
|
+
name: z.string().max(256),
|
|
237
|
+
provider: z.string().max(128),
|
|
238
|
+
timestamp: z.string().min(1),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
export const presenceAnnounceRpc = defineRpc({
|
|
242
|
+
name: "presence.announce",
|
|
243
|
+
input: z.object({
|
|
244
|
+
messageId: z.string().min(1).max(128),
|
|
245
|
+
entries: z.array(PresenceBirthSchema).max(500),
|
|
246
|
+
}),
|
|
247
|
+
output: z.object({
|
|
248
|
+
accepted: z.number(),
|
|
249
|
+
rejected: z.number(),
|
|
250
|
+
}),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
export const presenceRetractRpc = defineRpc({
|
|
254
|
+
name: "presence.retract",
|
|
255
|
+
input: z.object({
|
|
256
|
+
messageId: z.string().min(1).max(128),
|
|
257
|
+
serverId: z.string().min(1).max(128),
|
|
258
|
+
agentId: z.string().min(1).max(128),
|
|
259
|
+
timestamp: z.string().min(1),
|
|
260
|
+
}),
|
|
261
|
+
output: z.object({ applied: z.boolean() }),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
export const presenceListRpc = defineRpc({
|
|
265
|
+
name: "presence.list",
|
|
266
|
+
input: z.object({}),
|
|
267
|
+
output: z.object({
|
|
268
|
+
live: z.array(PresenceBirthSchema.extend({
|
|
269
|
+
origin: z.enum(["local", "remote"]),
|
|
270
|
+
lastSeenAt: z.string(),
|
|
271
|
+
})),
|
|
272
|
+
tombstones: z.array(z.object({
|
|
273
|
+
serverId: z.string(),
|
|
274
|
+
agentId: z.string(),
|
|
275
|
+
retractedAt: z.string(),
|
|
276
|
+
})),
|
|
277
|
+
pendingRetracts: z.number(),
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
export const daemonDumpRpc = defineRpc({
|
|
282
|
+
name: "daemon.dump",
|
|
283
|
+
input: z.object({ daemon: z.string() }),
|
|
284
|
+
output: z.object({
|
|
285
|
+
name: z.string(),
|
|
286
|
+
reached: z.boolean(),
|
|
287
|
+
error: z.string().nullable(),
|
|
288
|
+
// Identity (handshake server_info)
|
|
289
|
+
serverId: z.string().nullable(),
|
|
290
|
+
hostname: z.string().nullable(),
|
|
291
|
+
version: z.string().nullable(),
|
|
292
|
+
desktopManaged: z.boolean().nullable(),
|
|
293
|
+
capabilities: z.record(z.string(), z.unknown()).nullable(),
|
|
294
|
+
features: z.record(z.string(), z.boolean()).nullable(),
|
|
295
|
+
// Connection / daemon status
|
|
296
|
+
listen: z.string().nullable(),
|
|
297
|
+
pid: z.number().nullable(),
|
|
298
|
+
nodePath: z.string().nullable(),
|
|
299
|
+
startedAt: z.string().nullable(),
|
|
300
|
+
relayEndpoints: z.array(z.string()).nullable(),
|
|
301
|
+
relayEnabled: z.boolean().nullable(),
|
|
302
|
+
transport: z.string().nullable(),
|
|
303
|
+
// Agents (full entries)
|
|
304
|
+
agents: z.array(
|
|
305
|
+
z.object({
|
|
306
|
+
agentId: z.string(), shortId: z.string(), name: z.string(), status: z.string(),
|
|
307
|
+
provider: z.string(), model: z.string().nullable(), providerOptions: z.record(z.string(), z.unknown()).nullable(),
|
|
308
|
+
cwd: z.string().nullable(), workspaceId: z.string().nullable(), projectName: z.string().nullable(),
|
|
309
|
+
createdAt: z.string().nullable(), archived: z.boolean().nullable(),
|
|
310
|
+
}),
|
|
311
|
+
),
|
|
312
|
+
workspaces: z.array(z.object({ id: z.string(), name: z.string(), project: z.string(), isolation: z.string(), cwd: z.string().nullable() })),
|
|
313
|
+
projects: z.array(z.object({ id: z.string(), name: z.string(), source: z.string().nullable() })),
|
|
314
|
+
// Provider snapshot + recent sessions
|
|
315
|
+
providers: z.array(z.object({ provider: z.string(), available: z.boolean(), error: z.string().nullable() })),
|
|
316
|
+
providerCount: z.number(),
|
|
317
|
+
permissions: z.array(z.object({ id: z.string(), agentId: z.string(), name: z.string() })),
|
|
318
|
+
schedules: z.array(z.object({ id: z.string(), name: z.string(), state: z.string() })),
|
|
319
|
+
terminals: z.array(z.object({ id: z.string(), name: z.string(), cwd: z.string().nullable(), status: z.string().nullable() })),
|
|
320
|
+
}),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
export const identitySyncRpc = defineRpc({
|
|
324
|
+
name: "identity.sync",
|
|
325
|
+
input: z.object({}),
|
|
326
|
+
output: z.object({
|
|
327
|
+
identities: z.record(z.string(), z.string()),
|
|
328
|
+
hostnames: z.record(z.string(), z.string()),
|
|
329
|
+
}),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
export const conversationSendRpc = defineRpc({
|
|
333
|
+
name: "conversation.send",
|
|
334
|
+
input: z.object({
|
|
335
|
+
daemon: z.string(),
|
|
336
|
+
agentId: z.string(),
|
|
337
|
+
prompt: z.string(),
|
|
338
|
+
fromAgentId: z.string().nullable().optional(),
|
|
339
|
+
fromAgentName: z.string().nullable().optional(),
|
|
340
|
+
}),
|
|
341
|
+
output: z.object({
|
|
342
|
+
daemon: z.string(),
|
|
343
|
+
agentId: z.string(),
|
|
344
|
+
ok: z.boolean(),
|
|
345
|
+
error: z.string().nullable(),
|
|
346
|
+
}),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Consistency hint for direct hosts: if the value is a direct host (host:port
|
|
351
|
+
* or tcp://host:port) and the entered name does not match the URL host part,
|
|
352
|
+
* surface that as a warning. The name may legitimately differ from an IP, so
|
|
353
|
+
* this is advisory, not a rejection.
|
|
354
|
+
*/
|
|
355
|
+
export function directHostMismatch(name: string, value: string): string | null {
|
|
356
|
+
if (value.includes("#offer=") || value.startsWith("unix://") || value.startsWith("/")) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
const urlHost = value.replace(/^tcp:\/\//, "").replace(/^ws:\/\//, "").replace(/\?.*$/, "").split(":")[0];
|
|
360
|
+
if (!urlHost) return null;
|
|
361
|
+
const nameHost = name.split(":")[0];
|
|
362
|
+
if (nameHost && urlHost !== nameHost && !nameHost.includes(urlHost) && !urlHost.includes(nameHost)) {
|
|
363
|
+
return `host '${nameHost}' does not match the address host '${urlHost}'`;
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Vendored paseo-plugin-helper (Track B of issue #71, plugin #101)
|
|
2
|
+
|
|
3
|
+
Pinned helper version: 0.4.0-beta.12
|
|
4
|
+
|
|
5
|
+
Docs: https://github.com/xpufx/paseo/tree/main/packages/paseo-plugin-helper — npm: https://www.npmjs.com/package/paseo-plugin-helper
|
|
6
|
+
|
|
7
|
+
Zero host requirements (no npm, no registry, no build key): the daemon bundles
|
|
8
|
+
this source directly. Layout: `client/`, `server/`, `shared/` (+ `mcp/` under
|
|
9
|
+
server) vendor splits mapping helper `src/` trees (daemon accepts modules only
|
|
10
|
+
under those dirs). Updating: run `make vendor-sync` and bump the pinned version
|
|
11
|
+
above.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class TimeoutError extends Error {
|
|
2
|
+
readonly timeoutMs: number;
|
|
3
|
+
|
|
4
|
+
constructor(message: string, timeoutMs: number) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "TimeoutError";
|
|
7
|
+
this.timeoutMs = timeoutMs;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Races a promise against a timeout duration in milliseconds.
|
|
13
|
+
* If the timeout expires before the promise resolves, rejects with a TimeoutError.
|
|
14
|
+
* Automatically cleans up the timer on resolution or rejection.
|
|
15
|
+
*/
|
|
16
|
+
export async function withTimeout<T>(
|
|
17
|
+
promise: Promise<T>,
|
|
18
|
+
timeoutMs: number,
|
|
19
|
+
label = "Operation"
|
|
20
|
+
): Promise<T> {
|
|
21
|
+
let timer: any;
|
|
22
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
23
|
+
timer = setTimeout(() => {
|
|
24
|
+
reject(new TimeoutError(`${label} timed out after ${timeoutMs}ms`, timeoutMs));
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
30
|
+
} finally {
|
|
31
|
+
if (timer !== undefined) {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { StatusVariant } from "./types";
|
|
3
|
+
|
|
4
|
+
export const CustomPillThresholdsSchema = z.object({
|
|
5
|
+
warning: z.number().optional(),
|
|
6
|
+
danger: z.number().optional(),
|
|
7
|
+
/**
|
|
8
|
+
* If true, lower values trigger warnings/dangers instead of higher values
|
|
9
|
+
* (e.g. disk space remaining, battery percentage).
|
|
10
|
+
*/
|
|
11
|
+
invert: z.boolean().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const CustomPillModalSchema = z.object({
|
|
15
|
+
title: z.string().optional(),
|
|
16
|
+
description: z.string().optional(),
|
|
17
|
+
command: z.string().optional(),
|
|
18
|
+
/**
|
|
19
|
+
* Whether to format command output as monospace preformatted text (default: true).
|
|
20
|
+
*/
|
|
21
|
+
preformatted: z.boolean().default(true),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const CustomPillDefinitionSchema = z.object({
|
|
25
|
+
/**
|
|
26
|
+
* Unique identifier for the custom pill (e.g. "gpu-util", "docker-count").
|
|
27
|
+
*/
|
|
28
|
+
id: z.string().min(1),
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Title shown in the composer trackbar (e.g. "GPU", "Docker").
|
|
32
|
+
*/
|
|
33
|
+
title: z.string().min(1),
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Compact title shown when layout is compact (e.g. "G"). Defaults to title.
|
|
37
|
+
*/
|
|
38
|
+
compactTitle: z.string().optional(),
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Lucide icon name (e.g. "Cpu", "Flame", "HardDrive", "Layers").
|
|
42
|
+
*/
|
|
43
|
+
icon: z.string().optional(),
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Optional compact icon name. Defaults to icon.
|
|
47
|
+
*/
|
|
48
|
+
compactIcon: z.string().optional(),
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Shell command executed periodically to produce the pill's value.
|
|
52
|
+
* Can use session environment variables like $PASEO_AGENT_ID, $PASEO_WORKSPACE_ID.
|
|
53
|
+
*/
|
|
54
|
+
command: z.string().min(1),
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Optional prefix prepended to the output value (e.g. "$", "#").
|
|
58
|
+
*/
|
|
59
|
+
prefix: z.string().optional(),
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Optional suffix appended to the output value (e.g. "%", "ms", "GB").
|
|
63
|
+
*/
|
|
64
|
+
suffix: z.string().optional(),
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Polling interval in milliseconds. Minimum 500ms, defaults to 5000ms.
|
|
68
|
+
*/
|
|
69
|
+
intervalMs: z.number().min(500).default(5000),
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execution timeout in milliseconds. Defaults to 10000ms.
|
|
73
|
+
*/
|
|
74
|
+
timeoutMs: z.number().min(500).default(10000),
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Optional threshold rules to automatically transition badge color to warning or danger.
|
|
78
|
+
*/
|
|
79
|
+
thresholds: CustomPillThresholdsSchema.optional(),
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional modal configuration shown when the pill is pressed.
|
|
83
|
+
*/
|
|
84
|
+
modal: CustomPillModalSchema.optional(),
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether this custom pill is enabled. Defaults to true.
|
|
88
|
+
*/
|
|
89
|
+
enabled: z.boolean().default(true),
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Absolute path to the config file that defined this pill (e.g.
|
|
93
|
+
* ~/.paseo/top/pills/disk-usage.jsonc). Injected at discovery time; not
|
|
94
|
+
* intended to be authored in the config file itself.
|
|
95
|
+
*/
|
|
96
|
+
sourceFile: z.string().optional(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export type CustomPillDefinition = z.infer<typeof CustomPillDefinitionSchema>;
|
|
100
|
+
export type CustomPillThresholds = z.infer<typeof CustomPillThresholdsSchema>;
|
|
101
|
+
export type CustomPillModal = z.infer<typeof CustomPillModalSchema>;
|
|
102
|
+
|
|
103
|
+
export interface CustomPillState {
|
|
104
|
+
id: string;
|
|
105
|
+
title: string;
|
|
106
|
+
compactTitle?: string;
|
|
107
|
+
icon?: string;
|
|
108
|
+
compactIcon?: string;
|
|
109
|
+
rawValue: string;
|
|
110
|
+
displayValue: string;
|
|
111
|
+
numericValue?: number;
|
|
112
|
+
status: StatusVariant;
|
|
113
|
+
lastUpdated: number;
|
|
114
|
+
error?: string;
|
|
115
|
+
sourceFile?: string;
|
|
116
|
+
modalTitle?: string;
|
|
117
|
+
modalDescription?: string;
|
|
118
|
+
modalOutput?: string;
|
|
119
|
+
modalError?: string;
|
|
120
|
+
modalLastUpdated?: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Extracts a numeric value from the raw command output string (e.g. "45.2%" -> 45.2).
|
|
125
|
+
*/
|
|
126
|
+
export function parseNumericPillValue(rawValue: string): number | undefined {
|
|
127
|
+
const match = rawValue.match(/-?\d+(\.\d+)?/);
|
|
128
|
+
if (!match) return undefined;
|
|
129
|
+
const num = parseFloat(match[0]);
|
|
130
|
+
return Number.isNaN(num) ? undefined : num;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolves the status variant based on numeric value and thresholds.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveCustomPillStatus(
|
|
137
|
+
numericValue: number | undefined,
|
|
138
|
+
thresholds?: CustomPillThresholds,
|
|
139
|
+
): StatusVariant {
|
|
140
|
+
if (numericValue === undefined || !thresholds) {
|
|
141
|
+
return "neutral";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { warning, danger, invert } = thresholds;
|
|
145
|
+
|
|
146
|
+
if (invert) {
|
|
147
|
+
if (danger !== undefined && numericValue <= danger) return "danger";
|
|
148
|
+
if (warning !== undefined && numericValue <= warning) return "warning";
|
|
149
|
+
return "success";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (danger !== undefined && numericValue >= danger) return "danger";
|
|
153
|
+
if (warning !== undefined && numericValue >= warning) return "warning";
|
|
154
|
+
return "neutral";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Formats the raw output string with optional prefix and suffix.
|
|
159
|
+
*/
|
|
160
|
+
export function formatPillDisplay(
|
|
161
|
+
rawValue: string,
|
|
162
|
+
prefix?: string,
|
|
163
|
+
suffix?: string,
|
|
164
|
+
): string {
|
|
165
|
+
const cleaned = rawValue.trim();
|
|
166
|
+
const pre = prefix ?? "";
|
|
167
|
+
const suf = suffix ?? "";
|
|
168
|
+
return `${pre}${cleaned}${suf}`;
|
|
169
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared forge brand-mark resolution.
|
|
3
|
+
*
|
|
4
|
+
* One host/kind -> mark table for every plugin that shows forge iconography,
|
|
5
|
+
* so plugins never carry their own per-forge icon maps. The client-side
|
|
6
|
+
* `<ForgeIcon>` renders this descriptor; server/shared code can consume the
|
|
7
|
+
* pure resolver without pulling in React.
|
|
8
|
+
*
|
|
9
|
+
* GitHub and GitLab resolve to their Lucide marks; Codeberg, Forgejo and Gitea
|
|
10
|
+
* have no Lucide equivalent and resolve to helper-drawn official mono marks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type ForgeKind =
|
|
14
|
+
| "github"
|
|
15
|
+
| "gitlab"
|
|
16
|
+
| "codeberg"
|
|
17
|
+
| "forgejo"
|
|
18
|
+
| "gitea"
|
|
19
|
+
| "generic";
|
|
20
|
+
|
|
21
|
+
export interface ResolvedForgeMark {
|
|
22
|
+
/** Stable forge identity. */
|
|
23
|
+
kind: ForgeKind;
|
|
24
|
+
/** Human-readable forge name for labels and accessibility. */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Host Lucide icon name; the exact mark on hosts with SVG support, the closest fallback otherwise. */
|
|
27
|
+
lucideName: string;
|
|
28
|
+
/** True when `<ForgeIcon>` draws the official mark instead of delegating to the host icon set. */
|
|
29
|
+
custom: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ForgeMarkInput {
|
|
33
|
+
/** Forge hostname, e.g. `codeberg.org` or `forge.example.com`. */
|
|
34
|
+
host?: string | null;
|
|
35
|
+
/** Explicit forge identity; wins over host detection when it names a known forge. */
|
|
36
|
+
kind?: ForgeKind | string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const FORGE_MARKS: Record<ForgeKind, ResolvedForgeMark> = {
|
|
40
|
+
github: { kind: "github", label: "GitHub", lucideName: "Github", custom: false },
|
|
41
|
+
gitlab: { kind: "gitlab", label: "GitLab", lucideName: "Gitlab", custom: false },
|
|
42
|
+
codeberg: { kind: "codeberg", label: "Codeberg", lucideName: "Mountain", custom: true },
|
|
43
|
+
forgejo: { kind: "forgejo", label: "Forgejo", lucideName: "Hammer", custom: true },
|
|
44
|
+
gitea: { kind: "gitea", label: "Gitea", lucideName: "Coffee", custom: true },
|
|
45
|
+
generic: { kind: "generic", label: "Git forge", lucideName: "Globe", custom: false },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const FORGE_KINDS: ForgeKind[] = [
|
|
49
|
+
"github",
|
|
50
|
+
"gitlab",
|
|
51
|
+
"codeberg",
|
|
52
|
+
"forgejo",
|
|
53
|
+
"gitea",
|
|
54
|
+
"generic",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reduce a remote URL, `owner/repo` slug or bare hostname to a lowercase
|
|
59
|
+
* hostname without scheme, userinfo, port, path or a leading `www.`.
|
|
60
|
+
*/
|
|
61
|
+
export function normalizeForgeHost(host: string | null | undefined): string | null {
|
|
62
|
+
if (!host || typeof host !== "string") return null;
|
|
63
|
+
let value = host.trim().toLowerCase();
|
|
64
|
+
if (!value) return null;
|
|
65
|
+
value = value.replace(/^[a-z][a-z0-9+.-]*:\/\//, "");
|
|
66
|
+
value = value.replace(/^[^@/]+@/, "");
|
|
67
|
+
value = value.split("/")[0]!.split("?")[0]!.split("#")[0]!;
|
|
68
|
+
// Cuts both `host:port` and SCP-style `host:owner/repo` tails.
|
|
69
|
+
value = value.split(":")[0]!;
|
|
70
|
+
value = value.replace(/^www\./, "").replace(/\.$/, "");
|
|
71
|
+
return value || null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Ordered: the first match wins. Self-hosted instances routinely carry the
|
|
75
|
+
// project name in their hostname (e.g. `forgejo.example.com`); everything else
|
|
76
|
+
// is an unknown forge and gets the generic fallback.
|
|
77
|
+
const HOST_KIND_RULES: Array<{ matches: (host: string) => boolean; kind: ForgeKind }> = [
|
|
78
|
+
{ matches: (h) => h === "github.com" || h.endsWith(".github.com"), kind: "github" },
|
|
79
|
+
{ matches: (h) => h === "gitlab.com" || h.endsWith(".gitlab.com"), kind: "gitlab" },
|
|
80
|
+
{ matches: (h) => h === "codeberg.org" || h.endsWith(".codeberg.org"), kind: "codeberg" },
|
|
81
|
+
{ matches: (h) => h === "forgejo.org" || h.endsWith(".forgejo.org") || h.includes("forgejo"), kind: "forgejo" },
|
|
82
|
+
{ matches: (h) => h === "gitea.com" || h === "gitea.io" || h.includes("gitea"), kind: "gitea" },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
export function isForgeKind(value: unknown): value is ForgeKind {
|
|
86
|
+
return typeof value === "string" && FORGE_KINDS.includes(value as ForgeKind);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function forgeKindFromHost(host: string | null | undefined): ForgeKind {
|
|
90
|
+
const normalized = normalizeForgeHost(host);
|
|
91
|
+
if (!normalized) return "generic";
|
|
92
|
+
for (const rule of HOST_KIND_RULES) {
|
|
93
|
+
if (rule.matches(normalized)) return rule.kind;
|
|
94
|
+
}
|
|
95
|
+
return "generic";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolve a forge descriptor from a hostname and/or explicit kind. Accepts a
|
|
100
|
+
* bare host string for the common host-only case.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveForgeMark(
|
|
103
|
+
input: ForgeMarkInput | string | null | undefined,
|
|
104
|
+
): ResolvedForgeMark {
|
|
105
|
+
const { host, kind } =
|
|
106
|
+
input !== null && typeof input === "object" ? input : { host: input, kind: null };
|
|
107
|
+
const explicit = typeof kind === "string" ? kind.trim().toLowerCase() : null;
|
|
108
|
+
const resolved = isForgeKind(explicit) ? explicit : forgeKindFromHost(host);
|
|
109
|
+
return FORGE_MARKS[resolved];
|
|
110
|
+
}
|