@bacnh85/pi-subagent 0.16.0 → 0.17.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 +28 -0
- package/README.md +5 -3
- package/extensions/index.ts +36 -4
- package/extensions/roles-panel.ts +149 -8
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 (2026-08-31)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Add/remove model roles from the `/subagent` panel** — new `+ Add role` /
|
|
8
|
+
`− Remove role` action rows (two-prompt flow: name → chain). Custom roles are
|
|
9
|
+
deletable; built-in roles (`fast`/`coder`/`smart`) reset to their bundled
|
|
10
|
+
default. New names validate against `[A-Za-z0-9._-]{1,64}` with
|
|
11
|
+
case-insensitive collision checks.
|
|
12
|
+
- **Per-agent default display** — each override row now shows the agent's
|
|
13
|
+
default (no-override) chain, e.g. `scout (default: @fast → zai-coding-cn/
|
|
14
|
+
glm-5-turbo, …)`, so blank = inherit is meaningful. Labels track live role
|
|
15
|
+
edits and freshly added roles appear in `@role` completions immediately.
|
|
16
|
+
- Panel save is now guarded by a working-copy content diff (action-only
|
|
17
|
+
sessions — add/remove without row edits — previously left `editedKeys` empty
|
|
18
|
+
and silently skipped persistence).
|
|
19
|
+
|
|
20
|
+
## 0.16.1 (2026-08-29)
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `/subagent` argument completion: keywords (`list|all|agents|roles|reload|
|
|
25
|
+
refresh|history`), discovered agent names, and `@role` refs.
|
|
26
|
+
- Roles editor rows now offer inline model suggestions (Tab to pick, Enter
|
|
27
|
+
keeps typed text) when run against @bacnh85/pi-config-panel >= 0.1.1; the
|
|
28
|
+
package stays compilable and fully functional on 0.1.0 (suggestions simply
|
|
29
|
+
absent), so no dependency floor bump is required.
|
|
30
|
+
|
|
3
31
|
## 0.16.0 (2026-08-23)
|
|
4
32
|
|
|
5
33
|
### Features
|
package/README.md
CHANGED
|
@@ -34,9 +34,11 @@ in `~/.pi/agent/settings.json` under `subagent.roles`:
|
|
|
34
34
|
|
|
35
35
|
`/subagent` opens the interactive role editor (TUI panel via the shared
|
|
36
36
|
`@bacnh85/pi-config-panel` kernel; prints the effective mapping headless).
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
The panel supports `+ Add role` / `− Remove role` (custom roles are deletable,
|
|
38
|
+
built-ins reset to their bundled default) and shows each agent's default
|
|
39
|
+
(no-override) chain on its row. `/subagent list` lists agents,
|
|
40
|
+
`/subagent <name>` shows an agent's resolved chain, and `/subagent @role`
|
|
41
|
+
(or `/subagent fast`) shows a role's chain and the agents using it.
|
|
40
42
|
|
|
41
43
|
## Live progress widget
|
|
42
44
|
|
package/extensions/index.ts
CHANGED
|
@@ -329,6 +329,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
329
329
|
});
|
|
330
330
|
pi.registerCommand("subagent", {
|
|
331
331
|
description: "Configure model roles (/subagent), list agents (/subagent list), agent details (/subagent <name>), role detail (/subagent @role), reload definitions (/subagent reload), history (/subagent history)",
|
|
332
|
+
getArgumentCompletions: (prefix) => {
|
|
333
|
+
const ctx = currentCtx;
|
|
334
|
+
const keywords = ["list", "all", "agents", "roles", "reload", "refresh", "history"];
|
|
335
|
+
const vocab = [...keywords];
|
|
336
|
+
if (ctx) {
|
|
337
|
+
const discovery = discoverAgents(ctx.cwd, "both", bundledAgentsDir);
|
|
338
|
+
vocab.push(...discovery.agents.map((a) => a.name));
|
|
339
|
+
try { vocab.push(...Object.keys(readSubagentRoles(ctx).roles).map((r) => `@${r}`)); } catch { /* roles optional */ }
|
|
340
|
+
}
|
|
341
|
+
const q = prefix.trim().toLowerCase();
|
|
342
|
+
const items = vocab.filter((v) => v.toLowerCase().startsWith(q))
|
|
343
|
+
.map((v) => ({ value: v, label: v, description: keywords.includes(v) ? "subagent command" : "agent / role" }));
|
|
344
|
+
return items.length > 0 ? items : null;
|
|
345
|
+
},
|
|
332
346
|
handler: async (args, ctx) => {
|
|
333
347
|
const cmd = args.trim().toLowerCase();
|
|
334
348
|
const discovery = discoverAgents(ctx.cwd, "both", bundledAgentsDir);
|
|
@@ -364,7 +378,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
364
378
|
|
|
365
379
|
const openRolesEditor = async (): Promise<void> => {
|
|
366
380
|
// Role mapping editor: panel in TUI, plain text otherwise.
|
|
367
|
-
const [{ openConfigPanel }, { buildRows, buildRolesPanelCfg, cfgToPatch, preserveUnknownAgentModels, writeSubagentSection }] = await Promise.all([
|
|
381
|
+
const [{ openConfigPanel }, { buildRows, buildRolesPanelCfg, cfgToPatch, makeAddRoleAction, makeRemoveRoleAction, preserveUnknownAgentModels, writeSubagentSection }] = await Promise.all([
|
|
368
382
|
import("@bacnh85/pi-config-panel"),
|
|
369
383
|
import("./roles-panel.ts"),
|
|
370
384
|
]);
|
|
@@ -387,13 +401,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
387
401
|
}
|
|
388
402
|
const current = readSubagentRolesGlobal();
|
|
389
403
|
const working = buildRolesPanelCfg(discovery.agents, current);
|
|
404
|
+
// Actions (add/remove role) set model.dirty but not editedKeys — guard
|
|
405
|
+
// the save on a working-copy diff instead (pi-a2a pattern).
|
|
406
|
+
const before = JSON.stringify([working.roles, working.agentModels]);
|
|
407
|
+
const notify = (message: string, kind?: "info" | "warning" | "error") => ctx.ui.notify(message, kind ?? "info");
|
|
408
|
+
const panelOptions = {
|
|
409
|
+
models: () => {
|
|
410
|
+
try { return ctx.modelRegistry.getAvailable().map((m) => `${m.provider}/${m.id}`); }
|
|
411
|
+
catch { return []; }
|
|
412
|
+
},
|
|
413
|
+
// Working copy: tracks live edits + freshly added (unsaved) roles.
|
|
414
|
+
roles: () => Object.keys(working.roles),
|
|
415
|
+
effectiveRoles: working.roles,
|
|
416
|
+
};
|
|
417
|
+
const actions = {
|
|
418
|
+
addRole: makeAddRoleAction(working, { notify }),
|
|
419
|
+
removeRole: makeRemoveRoleAction(working, { notify }),
|
|
420
|
+
};
|
|
390
421
|
await openConfigPanel({
|
|
391
422
|
ctx,
|
|
392
423
|
cfg: working,
|
|
393
|
-
|
|
424
|
+
actions,
|
|
425
|
+
build: (cfg, panelActions) => buildRows(cfg, discovery.agents, panelOptions, panelActions),
|
|
394
426
|
title: "Subagent model roles",
|
|
395
|
-
onSave: (saved
|
|
396
|
-
if (!
|
|
427
|
+
onSave: (saved) => {
|
|
428
|
+
if (!saved || JSON.stringify([working.roles, working.agentModels]) === before) return;
|
|
397
429
|
const patch = cfgToPatch(working);
|
|
398
430
|
patch.agentModels = preserveUnknownAgentModels(
|
|
399
431
|
patch.agentModels,
|
|
@@ -13,9 +13,19 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { row } from "@bacnh85/pi-config-panel";
|
|
16
|
-
import type { PanelGroup } from "@bacnh85/pi-config-panel";
|
|
16
|
+
import type { PanelAction, PanelGroup, PanelRow } from "@bacnh85/pi-config-panel";
|
|
17
|
+
|
|
18
|
+
// ponytail: local structural type — the kernel only reads value/label/
|
|
19
|
+
// description, so this stays compatible with the published 0.1.0 range while
|
|
20
|
+
// completion support ships in 0.1.1 (no static import of new kernel symbols).
|
|
21
|
+
interface CompletionItem {
|
|
22
|
+
value: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
}
|
|
17
26
|
import type { AgentConfig } from "./agents.ts";
|
|
18
|
-
import {
|
|
27
|
+
import { getModelCandidates } from "./agents.ts";
|
|
28
|
+
import { DEFAULT_ROLES, readSubagentRoles, resolveAgentModelChain, type RoleMap, type RolesConfig } from "./roles.ts";
|
|
19
29
|
|
|
20
30
|
// ---------------------------------------------------------------------------
|
|
21
31
|
// Settings persistence (global only — repo .pi/settings.json is read-only)
|
|
@@ -83,6 +93,17 @@ export interface RolesPanelCfg {
|
|
|
83
93
|
agentModels: Record<string, string>;
|
|
84
94
|
}
|
|
85
95
|
|
|
96
|
+
/** Completion sources for the panel's model rows (lazy — resolved per keypress). */
|
|
97
|
+
export interface RolesPanelOptions {
|
|
98
|
+
/** Available model refs (`provider/id`), sorted; may be empty before registry sync. */
|
|
99
|
+
models: () => string[];
|
|
100
|
+
/** Known role names (defaults + configured), offered as `@role` on agent rows. */
|
|
101
|
+
roles: () => string[];
|
|
102
|
+
/** Effective role chains used to render each agent's default (falls back to
|
|
103
|
+
* DEFAULT_ROLES). Pass the working copy's roles so labels track live edits. */
|
|
104
|
+
effectiveRoles?: RoleMap;
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
/** Seed a working config from current effective settings + bundled agents. */
|
|
87
108
|
export function buildRolesPanelCfg(agents: AgentConfig[], current: RolesConfig): RolesPanelCfg {
|
|
88
109
|
const roleNames = new Set([...Object.keys(DEFAULT_ROLES), ...Object.keys(current.roles)]);
|
|
@@ -98,24 +119,62 @@ export function buildRolesPanelCfg(agents: AgentConfig[], current: RolesConfig):
|
|
|
98
119
|
return cfg;
|
|
99
120
|
}
|
|
100
121
|
|
|
101
|
-
/**
|
|
102
|
-
export function
|
|
122
|
+
/** Human-readable default (no-override) chain for an agent's panel row label. */
|
|
123
|
+
export function defaultChainLabel(
|
|
124
|
+
agent: Pick<AgentConfig, "name" | "model" | "models">,
|
|
125
|
+
roles: RoleMap,
|
|
126
|
+
): string {
|
|
127
|
+
const raw = getModelCandidates(agent);
|
|
128
|
+
const alias = raw.filter((m) => m.startsWith("@")).join(", ");
|
|
129
|
+
const { candidates } = resolveAgentModelChain(agent, { roles, agentModels: {} });
|
|
130
|
+
const chain = candidates.length > 0 ? candidates.join(", ") : "parent fallback";
|
|
131
|
+
// alias is @-prefixed, candidates are expanded model ids — never equal.
|
|
132
|
+
return alias ? `default: ${alias} → ${chain}` : `default: ${chain}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Build panel groups. Role rows first, then one override row per agent.
|
|
136
|
+
* `options` adds inline model/@role completions when provided (optional so
|
|
137
|
+
* existing unit tests and non-TUI callers stay unchanged). `actions` appends
|
|
138
|
+
* the + Add role / − Remove role action rows when provided. */
|
|
139
|
+
export function buildRows(
|
|
140
|
+
cfg: RolesPanelCfg,
|
|
141
|
+
agents: AgentConfig[],
|
|
142
|
+
options?: RolesPanelOptions,
|
|
143
|
+
actions?: Record<string, PanelAction>,
|
|
144
|
+
): PanelGroup[] {
|
|
103
145
|
const defaultChain = (name: string) => Array.isArray(DEFAULT_ROLES[name]) ? (DEFAULT_ROLES[name] as string[]).join(", ") : String(DEFAULT_ROLES[name] ?? "");
|
|
146
|
+
const modelItems = (): CompletionItem[] =>
|
|
147
|
+
(options?.models() ?? []).sort().map((ref) => ({ value: ref }));
|
|
148
|
+
const roleItems = (): CompletionItem[] =>
|
|
149
|
+
(options?.roles() ?? []).map((name) => ({ value: `@${name}`, description: "role chain" }));
|
|
150
|
+
// ponytail: opts spread keeps this compilable against kernel 0.1.0 (whose
|
|
151
|
+
// row() opts type lacks `completions`); the runtime contract is additive and
|
|
152
|
+
// 0.1.1+ consumes the field. Drop the cast when the dep floor moves to 0.1.1.
|
|
153
|
+
const withCompletions = (completions: () => CompletionItem[]) =>
|
|
154
|
+
({ completions }) as unknown as { mask?: boolean };
|
|
104
155
|
const roleRows = Object.keys(cfg.roles).sort().map((name) => {
|
|
105
156
|
// Label shows the default chain so blank is meaningful.
|
|
106
157
|
return row(`role.${name}`, `@${name} (default: ${defaultChain(name) || "none"})`, "string", cfg.roles[name], (v) => {
|
|
107
158
|
cfg.roles[name] = String(v ?? "").trim();
|
|
108
|
-
});
|
|
159
|
+
}, withCompletions(modelItems));
|
|
109
160
|
});
|
|
161
|
+
const effective = options?.effectiveRoles ?? DEFAULT_ROLES;
|
|
110
162
|
const agentRows = agents.map((agent) =>
|
|
111
|
-
row(`agent.${agent.name}`, agent.name, "string", cfg.agentModels[agent.name] ?? "", (v) => {
|
|
163
|
+
row(`agent.${agent.name}`, `${agent.name} (${defaultChainLabel(agent, effective)})`, "string", cfg.agentModels[agent.name] ?? "", (v) => {
|
|
112
164
|
const value = String(v ?? "").trim();
|
|
113
165
|
if (value) cfg.agentModels[agent.name] = value;
|
|
114
166
|
else delete cfg.agentModels[agent.name];
|
|
115
|
-
}),
|
|
167
|
+
}, withCompletions(() => [...modelItems(), ...roleItems()])),
|
|
116
168
|
);
|
|
169
|
+
const actionRows: PanelRow[] = [];
|
|
170
|
+
if (actions?.addRole) {
|
|
171
|
+
actionRows.push({ key: "action.addRole", label: "+ Add role", kind: "action", value: undefined, set: (p) => actions.addRole!.run(p as never) });
|
|
172
|
+
}
|
|
173
|
+
if (actions?.removeRole) {
|
|
174
|
+
actionRows.push({ key: "action.removeRole", label: "− Remove role", kind: "action", value: undefined, set: (p) => actions.removeRole!.run(p as never) });
|
|
175
|
+
}
|
|
117
176
|
return [
|
|
118
|
-
{ key: "roles", label: "Model roles (chain, blank = default)", rows: roleRows },
|
|
177
|
+
{ key: "roles", label: "Model roles (chain, blank = default)", rows: [...roleRows, ...actionRows] },
|
|
119
178
|
{ key: "agents", label: "Per-agent overrides (blank = inherit)", rows: agentRows },
|
|
120
179
|
];
|
|
121
180
|
}
|
|
@@ -137,6 +196,88 @@ export function cfgToPatch(cfg: RolesPanelCfg): { roles: RolesConfig["roles"]; a
|
|
|
137
196
|
return { roles, agentModels };
|
|
138
197
|
}
|
|
139
198
|
|
|
199
|
+
/** Validate a new role name; returns an error message or null when OK. */
|
|
200
|
+
export function validateNewRoleName(name: string, known: readonly string[]): string | null {
|
|
201
|
+
const trimmed = name.trim();
|
|
202
|
+
if (!trimmed) return "Role name is empty.";
|
|
203
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(trimmed)) {
|
|
204
|
+
return `Invalid role name "${trimmed}" — use letters, digits, dot, dash, underscore (max 64).`;
|
|
205
|
+
}
|
|
206
|
+
const clash = known.find((k) => k.toLowerCase() === trimmed.toLowerCase());
|
|
207
|
+
if (clash) return `Role @${clash} already exists.`;
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Apply a role removal to the working config. Built-in roles (DEFAULT_ROLES)
|
|
212
|
+
* can only be reset to their bundled chain (blank); custom roles are deleted
|
|
213
|
+
* (row disappears on rebuild, key dropped by cfgToPatch). Unknown → null. */
|
|
214
|
+
export function removeRoleFromCfg(cfg: RolesPanelCfg, name: string): "reset" | "deleted" | null {
|
|
215
|
+
const key = Object.keys(cfg.roles).find((k) => k.toLowerCase() === name.trim().toLowerCase());
|
|
216
|
+
if (!key) return null;
|
|
217
|
+
if (DEFAULT_ROLES[key] !== undefined) {
|
|
218
|
+
cfg.roles[key] = "";
|
|
219
|
+
return "reset";
|
|
220
|
+
}
|
|
221
|
+
delete cfg.roles[key];
|
|
222
|
+
return "deleted";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface RoleActionOpts {
|
|
226
|
+
/** User feedback (defaults to no-op so tests stay quiet). */
|
|
227
|
+
notify?: (message: string, kind?: "info" | "warning" | "error") => void;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** "+ Add role" panel action: prompt name → validate → prompt chain → mutate
|
|
231
|
+
* the working config (kernel rebuilds rows + marks dirty after the action). */
|
|
232
|
+
export function makeAddRoleAction(cfg: RolesPanelCfg, opts: RoleActionOpts = {}): PanelAction {
|
|
233
|
+
return {
|
|
234
|
+
label: "Add role",
|
|
235
|
+
run: (prompt) => new Promise<void>((resolve) => {
|
|
236
|
+
prompt("Role name (e.g. writer)", (name) => {
|
|
237
|
+
const trimmed = (name ?? "").trim();
|
|
238
|
+
if (!trimmed) return resolve();
|
|
239
|
+
const err = validateNewRoleName(trimmed, Object.keys(cfg.roles));
|
|
240
|
+
if (err) {
|
|
241
|
+
opts.notify?.(err, "warning");
|
|
242
|
+
return resolve();
|
|
243
|
+
}
|
|
244
|
+
prompt("Model chain (comma-separated; @role or * allowed)", (chain) => {
|
|
245
|
+
const value = (chain ?? "").trim();
|
|
246
|
+
if (!value) {
|
|
247
|
+
opts.notify?.("Chain required — role not added.", "warning");
|
|
248
|
+
return resolve();
|
|
249
|
+
}
|
|
250
|
+
cfg.roles[trimmed] = value;
|
|
251
|
+
resolve();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** "− Remove role" panel action: prompt pick from known roles, then reset
|
|
259
|
+
* (built-in) or delete (custom) via removeRoleFromCfg. */
|
|
260
|
+
export function makeRemoveRoleAction(cfg: RolesPanelCfg, opts: RoleActionOpts = {}): PanelAction {
|
|
261
|
+
return {
|
|
262
|
+
label: "Remove role",
|
|
263
|
+
run: (prompt) => new Promise<void>((resolve) => {
|
|
264
|
+
const names = Object.keys(cfg.roles).sort();
|
|
265
|
+
if (names.length === 0) {
|
|
266
|
+
opts.notify?.("No roles to remove.", "warning");
|
|
267
|
+
return resolve();
|
|
268
|
+
}
|
|
269
|
+
prompt(`Remove role (${names.join(", ")})`, (pick) => {
|
|
270
|
+
if (!pick) return resolve();
|
|
271
|
+
const result = removeRoleFromCfg(cfg, pick);
|
|
272
|
+
if (result === "reset") opts.notify?.(`@${pick.trim()} reset to bundled default`, "info");
|
|
273
|
+
else if (result === "deleted") opts.notify?.(`@${pick.trim()} removed`, "info");
|
|
274
|
+
else opts.notify?.(`No role named "${pick.trim()}".`, "warning");
|
|
275
|
+
resolve();
|
|
276
|
+
});
|
|
277
|
+
}),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
140
281
|
/** Keep overrides for agents NOT shown in the panel (e.g. overrides for
|
|
141
282
|
* project-local agents saved globally from another project) so a panel save
|
|
142
283
|
* doesn't wipe them. Discovered-agent entries always follow the panel. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "In-process subagents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"check": "npm run typecheck && npm test"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@earendil-works/pi-coding-agent": ">=0.80.0 <0.85.0",
|
|
66
|
-
"@earendil-works/pi-ai": ">=0.80.0 <0.85.0",
|
|
67
65
|
"@earendil-works/pi-agent-core": ">=0.80.0 <0.85.0",
|
|
66
|
+
"@earendil-works/pi-ai": ">=0.80.0 <0.85.0",
|
|
67
|
+
"@earendil-works/pi-coding-agent": ">=0.80.0 <0.85.0",
|
|
68
68
|
"@earendil-works/pi-tui": ">=0.80.0 <0.85.0",
|
|
69
69
|
"typebox": ">=1.3.0 <2.0.0"
|
|
70
70
|
},
|
|
@@ -74,14 +74,14 @@
|
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@earendil-works/pi-agent-core": "^0.84.0",
|
|
76
76
|
"@earendil-works/pi-ai": "^0.84.0",
|
|
77
|
-
"@earendil-works/pi-coding-agent": "^0.84.
|
|
78
|
-
"@earendil-works/pi-tui": "^0.84.
|
|
77
|
+
"@earendil-works/pi-coding-agent": "^0.84.3",
|
|
78
|
+
"@earendil-works/pi-tui": "^0.84.3",
|
|
79
79
|
"@types/mocha": "^10.0.10",
|
|
80
80
|
"@types/node": "^20.19.43",
|
|
81
81
|
"mocha": "^11.8.0",
|
|
82
82
|
"tsx": "^4.22.4",
|
|
83
|
-
"
|
|
84
|
-
"
|
|
83
|
+
"typebox": "^1.3.1",
|
|
84
|
+
"typescript": "^5.9.3"
|
|
85
85
|
},
|
|
86
86
|
"overrides": {
|
|
87
87
|
"serialize-javascript@>=5.0.0 <7.0.5": "^7.0.5",
|