@av-pi-studio/protocol 0.0.110 → 0.0.112

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/dist/messages.js CHANGED
@@ -1054,6 +1054,252 @@ export const viewerSettingsUpdateSchema = z
1054
1054
  })
1055
1055
  .passthrough();
1056
1056
  // ===========================================================================
1057
+ // Skills (swe/architecture/pi-resource-management.md § Wire family `skills_*`) — sprint-075
1058
+ // slice 1: list + toggle only. Preview/install/remove/update_all land in sprints 076-077 and
1059
+ // reuse `skillInfoSchema`/`piPackageInfoSchema` below.
1060
+ // ===========================================================================
1061
+ /**
1062
+ * One resolved Agent Skill as Pi's `DefaultPackageManager.resolve()` reports it, projected to the
1063
+ * wire. `origin` documents the four values Pi emits today (`"agent_dir" | "agents_dir" |
1064
+ * "package" | "settings_path"`) but stays `z.string()` — a future Pi origin must still parse on
1065
+ * an older client/daemon pair. `removable` reflects Pi's own rule (e.g. a package-owned skill
1066
+ * isn't independently removable); the daemon never re-derives it.
1067
+ */
1068
+ export const skillInfoSchema = z
1069
+ .object({
1070
+ name: z.string(),
1071
+ description: z.string().optional(),
1072
+ path: z.string(),
1073
+ enabled: z.boolean(),
1074
+ /** `"agent_dir" | "agents_dir" | "package" | "settings_path"` today; open string on the wire. */
1075
+ origin: z.string(),
1076
+ originLabel: z.string(),
1077
+ packageSource: z.string().optional(),
1078
+ shadowedBy: z.string().optional(),
1079
+ symlinkTarget: z.string().optional(),
1080
+ provenance: z
1081
+ .object({
1082
+ source: z.string(),
1083
+ sourceUrl: z.string().optional(),
1084
+ installedAt: z.string().optional(),
1085
+ })
1086
+ .passthrough()
1087
+ .optional(),
1088
+ disableModelInvocation: z.boolean().optional(),
1089
+ warnings: z.array(z.string()).optional(),
1090
+ removable: z.boolean(),
1091
+ })
1092
+ .passthrough();
1093
+ /** One `settings.json` `packages[]` entry resolved against disk. */
1094
+ export const piPackageInfoSchema = z
1095
+ .object({
1096
+ source: z.string(),
1097
+ kind: z.enum(["npm", "git", "local"]),
1098
+ identity: z.string(),
1099
+ pinned: z.boolean(),
1100
+ curated: z.boolean(),
1101
+ filtered: z.boolean(),
1102
+ skillCount: z.number(),
1103
+ })
1104
+ .passthrough();
1105
+ export const skillsListRequestSchema = z
1106
+ .object({
1107
+ type: z.literal("skills_list_request"),
1108
+ requestId: z.string(),
1109
+ })
1110
+ .passthrough();
1111
+ /**
1112
+ * `ok`/`error` are domain fields, not `rpc_error` (mirrors `extension_packs_set_response`'s
1113
+ * idiom) — e.g. a corrupt `settings.json` answers `{ok:false, error:"settings_unreadable"}`
1114
+ * rather than failing transport-level. `message` is an optional human-readable elaboration.
1115
+ */
1116
+ export const skillsListResponseSchema = z
1117
+ .object({
1118
+ type: z.literal("skills_list_response"),
1119
+ requestId: z.string(),
1120
+ ok: z.boolean(),
1121
+ error: z.string().optional(),
1122
+ message: z.string().optional(),
1123
+ skills: z.array(skillInfoSchema),
1124
+ packages: z.array(piPackageInfoSchema),
1125
+ })
1126
+ .passthrough();
1127
+ export const skillsSetEnabledRequestSchema = z
1128
+ .object({
1129
+ type: z.literal("skills_set_enabled_request"),
1130
+ requestId: z.string(),
1131
+ path: z.string(),
1132
+ enabled: z.boolean(),
1133
+ })
1134
+ .passthrough();
1135
+ export const skillsSetEnabledResponseSchema = z
1136
+ .object({
1137
+ type: z.literal("skills_set_enabled_response"),
1138
+ requestId: z.string(),
1139
+ ok: z.boolean(),
1140
+ error: z.string().optional(),
1141
+ message: z.string().optional(),
1142
+ skills: z.array(skillInfoSchema),
1143
+ packages: z.array(piPackageInfoSchema),
1144
+ })
1145
+ .passthrough();
1146
+ /**
1147
+ * Broadcast, no `requestId` — every connected client re-lists. `reason` is a plain string
1148
+ * (`"mutation" | "external"` documented) so a future reason stays append-only, same idiom as
1149
+ * `agent_timeline_reset`'s `reason`.
1150
+ */
1151
+ export const skillsChangedSchema = z
1152
+ .object({
1153
+ type: z.literal("skills_changed"),
1154
+ reason: z.string(),
1155
+ })
1156
+ .passthrough();
1157
+ /**
1158
+ * Base wire shape — used as the `sessionMessageSchema` union member (a `discriminatedUnion` entry
1159
+ * must stay a `ZodObject`, so the xor-refine below wraps this one instead of replacing it).
1160
+ */
1161
+ const skillsRemoveRequestShapeSchema = z
1162
+ .object({
1163
+ type: z.literal("skills_remove_request"),
1164
+ requestId: z.string(),
1165
+ path: z.string().optional(),
1166
+ packageSource: z.string().optional(),
1167
+ })
1168
+ .passthrough();
1169
+ /**
1170
+ * Exactly one of `path` (a top-level skill: `settings_path`/symlink/plain directory) or
1171
+ * `packageSource` (a whole `settings.json` `packages[]` entry) must be set — never both, never
1172
+ * neither. A package-owned skill's own `path` is rejected daemon-side as `not_removable`; this
1173
+ * schema only enforces the wire shape/xor, not daemon-side removability.
1174
+ */
1175
+ export const skillsRemoveRequestSchema = skillsRemoveRequestShapeSchema.refine((v) => (v.path !== undefined) !== (v.packageSource !== undefined), { message: "exactly one of path or packageSource is required" });
1176
+ /**
1177
+ * `stderr` is the spawned CLI's truncated (≤2048 bytes) stderr tail when the underlying `skills
1178
+ * remove`/`pi remove` process failed; absent on success or on a pre-spawn domain error (e.g.
1179
+ * `not_found`/`not_removable`). `ok`/`error` are domain fields, same idiom as `skills_list_response`.
1180
+ */
1181
+ export const skillsRemoveResponseSchema = z
1182
+ .object({
1183
+ type: z.literal("skills_remove_response"),
1184
+ requestId: z.string(),
1185
+ ok: z.boolean(),
1186
+ error: z.string().optional(),
1187
+ message: z.string().optional(),
1188
+ stderr: z.string().optional(),
1189
+ skills: z.array(skillInfoSchema),
1190
+ packages: z.array(piPackageInfoSchema),
1191
+ })
1192
+ .passthrough();
1193
+ /** One engine's outcome inside an `ItemResult[]` list (used by `skills_update_all_response`). */
1194
+ export const itemResultSchema = z
1195
+ .object({
1196
+ target: z.string(),
1197
+ ok: z.boolean(),
1198
+ error: z.string().optional(),
1199
+ stderr: z.string().optional(),
1200
+ })
1201
+ .passthrough();
1202
+ export const skillsUpdateAllRequestSchema = z
1203
+ .object({
1204
+ type: z.literal("skills_update_all_request"),
1205
+ requestId: z.string(),
1206
+ })
1207
+ .passthrough();
1208
+ /** One `ItemResult` per engine (`agent-skills` via `skills update -g -y`, `pi-packages` via `pi update --extensions`); a mixed ok/failed `results` array is expected, not an overall failure. */
1209
+ export const skillsUpdateAllResponseSchema = z
1210
+ .object({
1211
+ type: z.literal("skills_update_all_response"),
1212
+ requestId: z.string(),
1213
+ ok: z.boolean(),
1214
+ error: z.string().optional(),
1215
+ message: z.string().optional(),
1216
+ results: z.array(itemResultSchema),
1217
+ skills: z.array(skillInfoSchema),
1218
+ packages: z.array(piPackageInfoSchema),
1219
+ })
1220
+ .passthrough();
1221
+ // ---------------------------------------------------------------------------
1222
+ // Skills preview/install (sprint-077/task-002). Client-side source classification lives in
1223
+ // `source-detect.ts`'s `detectSkillSource()` — the daemon always re-runs detection
1224
+ // authoritatively, so neither request below carries a client-sent `kind`. Reuses
1225
+ // `itemResultSchema`/`skillInfoSchema`/`piPackageInfoSchema` above.
1226
+ // ---------------------------------------------------------------------------
1227
+ /**
1228
+ * One installable skill found inside a `skills_repo`/`local_path` preview (never package kinds —
1229
+ * those answer `{ok:true, kind, skills: []}`, nothing to pick). `valid` means frontmatter had a
1230
+ * name + description; `exists` means `~/.agents/skills/<sanitized-name>` is already present
1231
+ * (drives the client's "Already installed — replace" checkbox).
1232
+ */
1233
+ export const previewSkillSchema = z
1234
+ .object({
1235
+ name: z.string(),
1236
+ description: z.string().optional(),
1237
+ relPath: z.string(),
1238
+ warnings: z.array(z.string()).optional(),
1239
+ valid: z.boolean(),
1240
+ exists: z.boolean(),
1241
+ })
1242
+ .passthrough();
1243
+ export const skillsPreviewRequestSchema = z
1244
+ .object({
1245
+ type: z.literal("skills_preview_request"),
1246
+ requestId: z.string(),
1247
+ source: z.string(),
1248
+ })
1249
+ .passthrough();
1250
+ /**
1251
+ * `ok`/`error` are domain fields, same idiom as every other `skills_*` response. `kind` is the
1252
+ * daemon's own `detect(source)` result (never the client's) and `source` echoes the request's
1253
+ * `source` verbatim — both present even on a domain failure (e.g. `invalid_source`), so a client
1254
+ * can still render what it tried against. `pi_npm`/`pi_git` always answer
1255
+ * `{ok:true, kind, source, skills: []}` — package kinds install as a unit, nothing to pick.
1256
+ */
1257
+ export const skillsPreviewResponseSchema = z
1258
+ .object({
1259
+ type: z.literal("skills_preview_response"),
1260
+ requestId: z.string(),
1261
+ ok: z.boolean(),
1262
+ error: z.string().optional(),
1263
+ message: z.string().optional(),
1264
+ kind: z.string(),
1265
+ source: z.string(),
1266
+ skills: z.array(previewSkillSchema),
1267
+ })
1268
+ .passthrough();
1269
+ /**
1270
+ * `skills` is the picked skill names from a prior preview — meaningful for `skills_repo` only
1271
+ * (ignored for `pi_npm`/`pi_git`, which install every skill the package ships; irrelevant for
1272
+ * `local_path`, which registers the whole folder). `replace` resends an already-installed picked
1273
+ * name instead of excluding it as `name_exists`.
1274
+ */
1275
+ export const skillsInstallRequestSchema = z
1276
+ .object({
1277
+ type: z.literal("skills_install_request"),
1278
+ requestId: z.string(),
1279
+ source: z.string(),
1280
+ skills: z.array(z.string()).optional(),
1281
+ replace: z.boolean().optional(),
1282
+ })
1283
+ .passthrough();
1284
+ /**
1285
+ * One `ItemResult` per engine outcome — one entry for `pi_npm`/`pi_git`/`local_path`, one per
1286
+ * picked name for `skills_repo` (e.g. a `name_exists` exclusion still gets its own `ItemResult`).
1287
+ * `skills`/`packages` are the post-mutation list, same idiom as every other mutating `skills_*`
1288
+ * response.
1289
+ */
1290
+ export const skillsInstallResponseSchema = z
1291
+ .object({
1292
+ type: z.literal("skills_install_response"),
1293
+ requestId: z.string(),
1294
+ ok: z.boolean(),
1295
+ error: z.string().optional(),
1296
+ message: z.string().optional(),
1297
+ results: z.array(itemResultSchema),
1298
+ skills: z.array(skillInfoSchema),
1299
+ packages: z.array(piPackageInfoSchema),
1300
+ })
1301
+ .passthrough();
1302
+ // ===========================================================================
1057
1303
  // Extension UI (features/extension-ui-rpc.md) — additive, new in sprint-066
1058
1304
  // ===========================================================================
1059
1305
  /**
@@ -1263,6 +1509,19 @@ export const sessionMessageSchema = z.discriminatedUnion("type", [
1263
1509
  viewerSettingsSetRequestSchema,
1264
1510
  viewerSettingsSetResponseSchema,
1265
1511
  viewerSettingsUpdateSchema,
1512
+ skillsListRequestSchema,
1513
+ skillsListResponseSchema,
1514
+ skillsSetEnabledRequestSchema,
1515
+ skillsSetEnabledResponseSchema,
1516
+ skillsChangedSchema,
1517
+ skillsRemoveRequestShapeSchema,
1518
+ skillsRemoveResponseSchema,
1519
+ skillsUpdateAllRequestSchema,
1520
+ skillsUpdateAllResponseSchema,
1521
+ skillsPreviewRequestSchema,
1522
+ skillsPreviewResponseSchema,
1523
+ skillsInstallRequestSchema,
1524
+ skillsInstallResponseSchema,
1266
1525
  agentUiRequestSchema,
1267
1526
  agentUiResolvedSchema,
1268
1527
  agentUiRespondRequestSchema,
@@ -0,0 +1,18 @@
1
+ /** One of the four installable source kinds the daemon recognizes; `null` from
2
+ * `detectSkillSource` means "anything else" (`invalid_source` once the server rejects it). */
3
+ export type SkillSourceKind = "pi_npm" | "pi_git" | "skills_repo" | "local_path";
4
+ export interface DetectedSkillSource {
5
+ kind: SkillSourceKind;
6
+ /** Whitespace-trimmed input, unchanged otherwise. Further parsing (e.g. splitting a
7
+ * `/tree/<ref>/<path>` subpath out of a `skills_repo` URL, or expanding a `~` in a
8
+ * `local_path`) is each consumer's own job — this classifier never reaches into the
9
+ * filesystem or re-derives host state. */
10
+ normalized: string;
11
+ }
12
+ /**
13
+ * Classify a skill install source string. Returns `null` for anything that isn't one of the four
14
+ * recognized kinds (relative paths, bare words, non-git `http(s)` file URLs, empty input) — the
15
+ * caller renders that as "Unrecognized" / the daemon answers `invalid_source`.
16
+ */
17
+ export declare function detectSkillSource(input: string): DetectedSkillSource | null;
18
+ //# sourceMappingURL=source-detect.d.ts.map
@@ -0,0 +1,61 @@
1
+ // Pure, zero-dependency classifier for a skill install source string
2
+ // (swe/architecture/pi-resource-management.md § Source kinds). Shared between the web client
3
+ // (live label while the user types a source) and the daemon (`source-detect.ts` re-export per
4
+ // the architecture doc's component table) — the **daemon's** classification is always
5
+ // authoritative; a client-sent `kind` on the wire is ignored (`skills_preview_request` /
6
+ // `skills_install_request` carry only `source`, never `kind`).
7
+ //
8
+ // No filesystem access here: `local_path` is detected purely syntactically (absolute or
9
+ // `~`-prefixed); whether the directory actually exists on the daemon host is checked server-side.
10
+ const NPM_SOURCE_RE = /^npm:\S+$/;
11
+ const GIT_PREFIX_SOURCE_RE = /^git:\S+$/;
12
+ const SSH_URL_RE = /^ssh:\/\/\S+$/;
13
+ /** `git@host:owner/repo[.git]` scp-like syntax. */
14
+ const SCP_LIKE_RE = /^git@[^\s:/]+:\S+$/;
15
+ /** `http(s)://[www.]<github|gitlab>.com/<anything>` — covers plain repo URLs and
16
+ * `/tree/<ref>/<path>` subpaths alike; the subpath is opaque here. */
17
+ const GIT_HOST_URL_RE = /^https?:\/\/(?:[^\s/]*\.)?(?:github|gitlab)\.com\/\S+$/i;
18
+ /** Any scheme'd URL ending in `.git` (self-hosted Git forges, bare `git://`, etc). */
19
+ const GIT_SUFFIX_URL_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/\S+\.git$/;
20
+ const URL_SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
21
+ /** GitHub/GitLab slug segment: alnum, `.`/`_`/`-` in the middle, never a leading/trailing one. */
22
+ const OWNER_REPO_SEGMENT = "[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?";
23
+ /** `owner/repo` or `owner/repo@skill` — exactly one slash, so a deeper or `./`/`../`-prefixed
24
+ * relative path never matches (those fall through to `null`). */
25
+ const OWNER_REPO_RE = new RegExp(`^${OWNER_REPO_SEGMENT}/${OWNER_REPO_SEGMENT}(?:@${OWNER_REPO_SEGMENT})?$`);
26
+ /**
27
+ * Classify a skill install source string. Returns `null` for anything that isn't one of the four
28
+ * recognized kinds (relative paths, bare words, non-git `http(s)` file URLs, empty input) — the
29
+ * caller renders that as "Unrecognized" / the daemon answers `invalid_source`.
30
+ */
31
+ export function detectSkillSource(input) {
32
+ const normalized = input.trim();
33
+ if (normalized.length === 0) {
34
+ return null;
35
+ }
36
+ if (NPM_SOURCE_RE.test(normalized)) {
37
+ return { kind: "pi_npm", normalized };
38
+ }
39
+ if (GIT_PREFIX_SOURCE_RE.test(normalized)) {
40
+ return { kind: "pi_git", normalized };
41
+ }
42
+ if (normalized === "~" || normalized.startsWith("~/") || normalized.startsWith("/")) {
43
+ return { kind: "local_path", normalized };
44
+ }
45
+ if (SCP_LIKE_RE.test(normalized) ||
46
+ SSH_URL_RE.test(normalized) ||
47
+ GIT_HOST_URL_RE.test(normalized) ||
48
+ GIT_SUFFIX_URL_RE.test(normalized)) {
49
+ return { kind: "skills_repo", normalized };
50
+ }
51
+ if (URL_SCHEME_RE.test(normalized)) {
52
+ // Has a URL scheme but isn't a recognized git host or `*.git` suffix — e.g. a plain file
53
+ // link. Explicitly not installable, never falls through to the owner/repo check below.
54
+ return null;
55
+ }
56
+ if (OWNER_REPO_RE.test(normalized)) {
57
+ return { kind: "skills_repo", normalized };
58
+ }
59
+ return null;
60
+ }
61
+ //# sourceMappingURL=source-detect.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@av-pi-studio/protocol",
3
- "version": "0.0.110",
3
+ "version": "0.0.112",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.map",