pi-pstack-adapter 0.3.0 → 0.5.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 +15 -0
- package/README.md +38 -6
- package/VERSION +1 -1
- package/adapter-skills/setup.md +48 -6
- package/docs/compatibility.md +64 -17
- package/index.ts +61 -24
- package/package.json +1 -1
- package/src/activation.ts +41 -24
- package/src/agents.ts +36 -8
- package/src/capabilities.ts +56 -31
- package/src/certified.ts +3 -3
- package/src/config.ts +189 -8
- package/src/delegation.ts +143 -0
- package/src/echo.ts +9 -0
- package/src/generate.ts +212 -20
- package/src/namespace.ts +3 -1
- package/src/registry.ts +5 -2
- package/src/status.ts +51 -8
- package/src/types.ts +9 -0
- package/src/upstream.ts +19 -5
package/src/status.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { CERTIFIED } from "./certified.ts";
|
|
2
2
|
import { CAPABILITY_LABELS, MODEL_ROLES, TIER_ORDER } from "./registry.ts";
|
|
3
3
|
import type { AdapterConfig } from "./config.ts";
|
|
4
|
-
import { trustWarning } from "./config.ts";
|
|
4
|
+
import { splitModelSpec, trustWarning } from "./config.ts";
|
|
5
|
+
import { DELEGATION_BACKENDS, delegationOrder } from "./delegation.ts";
|
|
5
6
|
import type { PrerequisiteReport } from "./capabilities.ts";
|
|
6
7
|
import type {
|
|
7
8
|
CapabilityStatus,
|
|
9
|
+
DelegationProvider,
|
|
8
10
|
Diagnostic,
|
|
9
11
|
GeneratedSkill,
|
|
10
12
|
GenerationResult,
|
|
@@ -46,13 +48,18 @@ export function renderStatus(input: StatusInput): string {
|
|
|
46
48
|
lines.push(`| certified commit | ${CERTIFIED.commit} |`);
|
|
47
49
|
lines.push(`| trust | ${source.trust} |`);
|
|
48
50
|
lines.push(`| checkout | ${source.checkoutDir} |`);
|
|
51
|
+
lines.push(`| plugin directory | ${source.pluginDir} |`);
|
|
49
52
|
lines.push(`| generated resources | ${input.generation.outDir} |`);
|
|
53
|
+
lines.push(`| generated skills | ${input.generation.skillsDir} |`);
|
|
54
|
+
lines.push(`| agent prompts | ${input.generation.agentPromptsDir} |`);
|
|
50
55
|
lines.push(`| cache root | ${input.cacheRoot} |`);
|
|
51
56
|
lines.push(`| cache state | ${input.bootstrapped ? "downloaded this session" : "reused, no network needed"} |`);
|
|
52
57
|
lines.push(`| namespace | ${config.namespace} |`);
|
|
53
58
|
lines.push(`| config layers | ${config.sources.length > 0 ? config.sources.join(", ") : "built-in defaults"} |`);
|
|
54
59
|
lines.push(`| platform | ${input.platformName} (${input.platform}) |`);
|
|
55
60
|
lines.push(`| Poteto Mode | ${input.modeActive ? "active" : "off"} |`);
|
|
61
|
+
lines.push(`| delegation order | ${delegationOrder(config.delegation).map((backend) => backend.id).join(", then ")} |`);
|
|
62
|
+
lines.push(`| delegation | ${describeDelegation(input.capabilities)} |`);
|
|
56
63
|
lines.push("");
|
|
57
64
|
|
|
58
65
|
const warning = trustWarning(source.trust, config);
|
|
@@ -81,14 +88,17 @@ export function renderStatus(input: StatusInput): string {
|
|
|
81
88
|
|
|
82
89
|
lines.push(`## Model roles`);
|
|
83
90
|
lines.push("");
|
|
84
|
-
lines.push(`| role | model |`);
|
|
85
|
-
lines.push(`| --- | --- |`);
|
|
86
|
-
for (const role of MODEL_ROLES) {
|
|
87
|
-
lines.push(`| ${role} | ${config.models[role] ?? "inherit (parent session model)"} |`);
|
|
88
|
-
}
|
|
91
|
+
lines.push(`| role | model | thinking |`);
|
|
92
|
+
lines.push(`| --- | --- | --- |`);
|
|
89
93
|
const extra = Object.keys(config.models).filter((role) => !MODEL_ROLES.includes(role)).sort();
|
|
90
|
-
for (const role of extra) {
|
|
91
|
-
|
|
94
|
+
for (const role of [...MODEL_ROLES, ...extra]) {
|
|
95
|
+
const configured = config.models[role];
|
|
96
|
+
if (configured === undefined) {
|
|
97
|
+
lines.push(`| ${role} | inherit (parent session model) | inherit (parent session thinking) |`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const spec = splitModelSpec(configured);
|
|
101
|
+
lines.push(`| ${role} | ${spec.model} | ${spec.thinking ?? "backend default"} |`);
|
|
92
102
|
}
|
|
93
103
|
lines.push("");
|
|
94
104
|
|
|
@@ -124,6 +134,13 @@ export function renderStatus(input: StatusInput): string {
|
|
|
124
134
|
return lines.join("\n");
|
|
125
135
|
}
|
|
126
136
|
|
|
137
|
+
function describeDelegation(capabilities: readonly CapabilityStatus[]): string {
|
|
138
|
+
const backend = capabilities.find((capability) => capability.id === "delegation")?.backend;
|
|
139
|
+
if (!backend) return "none available";
|
|
140
|
+
const { label, tool } = DELEGATION_BACKENDS[backend];
|
|
141
|
+
return `${backend} (${label}, \`${tool}\`)`;
|
|
142
|
+
}
|
|
143
|
+
|
|
127
144
|
function describeSelection(input: StatusInput): string {
|
|
128
145
|
const { config } = input;
|
|
129
146
|
if (config.localPath) return `local checkout ${config.localPath}`;
|
|
@@ -153,6 +170,8 @@ export function renderCheck(
|
|
|
153
170
|
skill: GeneratedSkill,
|
|
154
171
|
entry: RegistryEntry,
|
|
155
172
|
report: PrerequisiteReport,
|
|
173
|
+
/** The resolved `delegation` capability, named for a workflow that delegates. */
|
|
174
|
+
delegation?: CapabilityStatus,
|
|
156
175
|
): string {
|
|
157
176
|
const parts = [`\`/skill:${skill.generatedName}\` is tier **${skill.tier}**.`];
|
|
158
177
|
if (entry.note) parts.push(entry.note);
|
|
@@ -166,12 +185,36 @@ export function renderCheck(
|
|
|
166
185
|
} else if (skill.tier !== "unsupported") {
|
|
167
186
|
parts.push("Prerequisites satisfied. Start the workflow.");
|
|
168
187
|
}
|
|
188
|
+
if (skill.capabilities.includes("delegation")) {
|
|
189
|
+
parts.push(
|
|
190
|
+
delegation?.backend ? delegationInstruction(delegation.backend, delegation.tools) : "No delegation tool is usable in this session, so do not delegate.",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
169
193
|
if (skill.tier === "experimental" && report.ok) {
|
|
170
194
|
parts.push("Tier `experimental` means parts of this workflow have no Pi equivalent. Expect gaps.");
|
|
171
195
|
}
|
|
172
196
|
return parts.join("\n\n");
|
|
173
197
|
}
|
|
174
198
|
|
|
199
|
+
function delegationInstruction(backend: DelegationProvider, tools: readonly string[] | undefined): string {
|
|
200
|
+
const { tool, companions } = DELEGATION_BACKENDS[backend];
|
|
201
|
+
const vetted = tools ?? [tool];
|
|
202
|
+
const lines = [
|
|
203
|
+
`Delegate only with \`${tool}\` (${backend}). The adapter resolved this backend for the session, so do not use any other delegation tool.`,
|
|
204
|
+
];
|
|
205
|
+
if (companions.length > 0) {
|
|
206
|
+
const usable = companions.filter((name) => vetted.includes(name));
|
|
207
|
+
const unusable = companions.filter((name) => !vetted.includes(name));
|
|
208
|
+
if (usable.length > 0) lines.push(`You may also call ${usable.map((name) => `\`${name}\``).join(", ")}.`);
|
|
209
|
+
if (unusable.length > 0) {
|
|
210
|
+
lines.push(
|
|
211
|
+
`Do not call ${unusable.map((name) => `\`${name}\``).join(", ")}, even if it is in your tools: the adapter could not confirm it belongs to this backend. Wait for children to report back instead.`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return lines.join(" ");
|
|
216
|
+
}
|
|
217
|
+
|
|
175
218
|
/** Platforms a release certifies. Windows means native Windows with Pi's Git Bash `bash` tool. */
|
|
176
219
|
export function platformSupport(platform: string): PlatformSupport {
|
|
177
220
|
return platform === "linux" || platform === "darwin" || platform === "win32" ? "supported" : "experimental";
|
package/src/types.ts
CHANGED
|
@@ -22,6 +22,9 @@ export type SupportTier =
|
|
|
22
22
|
|
|
23
23
|
export type CapabilityId = "delegation" | "structured-question";
|
|
24
24
|
|
|
25
|
+
/** Where a delegating workflow starts its children. */
|
|
26
|
+
export type DelegationProvider = "t3" | "pi-subagents";
|
|
27
|
+
|
|
25
28
|
export interface RegistryEntry {
|
|
26
29
|
readonly tier: SupportTier;
|
|
27
30
|
readonly capabilities: readonly CapabilityId[];
|
|
@@ -85,6 +88,8 @@ export interface GenerationResult {
|
|
|
85
88
|
readonly skillsDir: string;
|
|
86
89
|
readonly skills: readonly GeneratedSkill[];
|
|
87
90
|
readonly agents: readonly GeneratedAgent[];
|
|
91
|
+
/** One `<generatedName>.md` system prompt per agent, for providers without named agents. */
|
|
92
|
+
readonly agentPromptsDir: string;
|
|
88
93
|
readonly diagnostics: readonly Diagnostic[];
|
|
89
94
|
}
|
|
90
95
|
|
|
@@ -92,6 +97,10 @@ export interface CapabilityStatus {
|
|
|
92
97
|
readonly id: CapabilityId;
|
|
93
98
|
readonly available: boolean;
|
|
94
99
|
readonly provider?: string;
|
|
100
|
+
/** The delegation backend that satisfies `delegation`, when one does. */
|
|
101
|
+
readonly backend?: DelegationProvider;
|
|
102
|
+
/** Every tool of that backend the adapter vetted: the delegation tool, then its companions. */
|
|
103
|
+
readonly tools?: readonly string[];
|
|
95
104
|
readonly recommendation: string;
|
|
96
105
|
}
|
|
97
106
|
|
package/src/upstream.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { MANIFEST_RELATIVE_PATH } from "./manifest.ts";
|
|
6
6
|
import { cacheRoot, checkoutDir, stateFile, validateContainedPath } from "./paths.ts";
|
|
@@ -119,17 +119,19 @@ export async function resolveSource(options: ResolveOptions): Promise<ResolvedSo
|
|
|
119
119
|
const git = options.git ?? systemGit;
|
|
120
120
|
const selection = selectionOf(config);
|
|
121
121
|
const trust = trustOf(config);
|
|
122
|
+
// Validate before either branch touches the filesystem, so a local checkout cannot be
|
|
123
|
+
// escaped through the plugin path any more than a Git one can.
|
|
124
|
+
const pluginPath = validateContainedPath(config.pluginPath, "the upstream plugin path");
|
|
122
125
|
|
|
123
126
|
if (selection.kind === "local") {
|
|
124
127
|
const checkout = resolve(selection.path);
|
|
125
|
-
const pluginDir = locatePluginDir(checkout,
|
|
128
|
+
const pluginDir = locatePluginDir(checkout, pluginPath);
|
|
126
129
|
const head = await git(["rev-parse", "HEAD"], checkout);
|
|
127
130
|
return { selection, trust, commit: head.code === 0 ? head.stdout.trim() : "", checkoutDir: checkout, pluginDir };
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
const repo = validateRepo(config.repo);
|
|
131
134
|
const wanted = validateRef(selection.ref);
|
|
132
|
-
const pluginPath = validateContainedPath(config.pluginPath, "the upstream plugin path");
|
|
133
135
|
const root = cacheRoot(config.cacheDir);
|
|
134
136
|
const dir = checkoutDir(root, repo);
|
|
135
137
|
|
|
@@ -337,7 +339,19 @@ async function expect(result: Promise<GitResult>, what: string): Promise<GitResu
|
|
|
337
339
|
|
|
338
340
|
function locatePluginDir(checkout: string, pluginPath: string): string {
|
|
339
341
|
const nested = join(checkout, pluginPath);
|
|
340
|
-
if (existsSync(join(nested, MANIFEST_RELATIVE_PATH)))
|
|
342
|
+
if (existsSync(join(nested, MANIFEST_RELATIVE_PATH))) {
|
|
343
|
+
// The plugin path is textually contained, but a symbolic link inside the checkout could
|
|
344
|
+
// still lead outside it. Compare resolved paths so the link target decides.
|
|
345
|
+
const root = realpathSync(checkout);
|
|
346
|
+
const target = realpathSync(nested);
|
|
347
|
+
const inside = relative(root, target);
|
|
348
|
+
if (inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`Refusing the upstream plugin path ${JSON.stringify(pluginPath)}. It resolves to ${target}, outside the checkout ${root}. Point it at a real directory inside the checkout.`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return nested;
|
|
354
|
+
}
|
|
341
355
|
if (existsSync(join(checkout, MANIFEST_RELATIVE_PATH))) return checkout;
|
|
342
356
|
throw new Error(
|
|
343
357
|
`No pstack manifest under ${checkout}. Expected ${join(pluginPath, MANIFEST_RELATIVE_PATH)} or ${MANIFEST_RELATIVE_PATH}.`,
|