@aiwg/cli 2026.9.1 → 2026.9.2
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/agentic/code/providers/capability-matrix.yaml +1 -1
- package/agentic/code/providers/model-capabilities.v1.json +11 -0
- package/agentic/code/providers/model-catalog.v1.json +8 -0
- package/agentic/code/providers/pi/aiwg-bridge.ts +26 -0
- package/dist/src/auth/credential-store.js +6 -0
- package/dist/src/cli/handlers/sessions.js +30 -12
- package/dist/src/models/model-capabilities.v1.json +11 -0
- package/dist/src/models/model-catalog.v1.json +8 -0
- package/dist/src/models/model-discovery.js +32 -0
- package/dist/src/models/provider-policy.js +3 -2
- package/dist/src/providers/capability-matrix.yaml +1 -1
- package/dist/src/sessions/adapters/pi.js +141 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/index.js +1 -0
- package/dist/src/sessions/workspace-discovery.js +10 -0
- package/package.json +1 -1
- package/tools/agents/providers/pi.mjs +13 -1
|
@@ -76,6 +76,17 @@
|
|
|
76
76
|
"verification": "Inspect active profile and run metadata",
|
|
77
77
|
"sourceUrl": "https://docs.warp.dev/agent-platform/capabilities/agent-profiles-permissions", "verifiedAt": "2026-07-20"
|
|
78
78
|
},
|
|
79
|
+
"pi": {
|
|
80
|
+
"agent": "native", "skill": "inherited", "globalChild": "native",
|
|
81
|
+
"identifierSyntax": "provider/model identifier accepted by Pi --model",
|
|
82
|
+
"effortValues": ["minimal", "low", "medium", "high", "xhigh"],
|
|
83
|
+
"inheritance": "Omitted model and thinking level inherit the invoking Pi session",
|
|
84
|
+
"invalidPinFallback": "Pi resolves against its configured catalog and exits on invalid explicit selection",
|
|
85
|
+
"configTarget": "Pi headless launch arguments",
|
|
86
|
+
"artifactFormat": "Runtime --model and --thinking flags",
|
|
87
|
+
"verification": "Inspect strict JSONL state and the selected provider/model",
|
|
88
|
+
"sourceUrl": "https://github.com/earendil-works/pi/tree/main/packages/coding-agent#cli-reference", "verifiedAt": "2026-09-04"
|
|
89
|
+
},
|
|
79
90
|
"windsurf": {
|
|
80
91
|
"agent": "unsupported", "skill": "unsupported", "globalChild": "inherited",
|
|
81
92
|
"identifierSyntax": "UI-selected provider model",
|
|
@@ -52,6 +52,14 @@
|
|
|
52
52
|
},
|
|
53
53
|
"sourceUrl": "https://opencode.ai/docs/models", "verifiedAt": "2026-07-20"
|
|
54
54
|
},
|
|
55
|
+
"pi": {
|
|
56
|
+
"roles": {
|
|
57
|
+
"reasoning": { "id": "configured/reasoning", "status": "unverified", "observed": false },
|
|
58
|
+
"coding": { "id": "configured/coding", "status": "unverified", "observed": false },
|
|
59
|
+
"efficiency": { "id": "configured/efficiency", "status": "unverified", "observed": false }
|
|
60
|
+
},
|
|
61
|
+
"sourceUrl": "https://github.com/earendil-works/pi/tree/main/packages/coding-agent#providers--models", "verifiedAt": "2026-09-04"
|
|
62
|
+
},
|
|
55
63
|
"warp": {
|
|
56
64
|
"roles": {
|
|
57
65
|
"reasoning": { "id": "profile-selected", "status": "unverified", "observed": false },
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** AIWG's reviewed Pi bridge. Loaded only after Pi project trust is granted. */
|
|
2
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
|
|
4
|
+
const destructive = /(?:^|[;&|]\s*)(?:sudo\s+)?(?:rm\s+-\S*r\S*\s+|git\s+(?:reset\s+--hard|clean\s+-)|chmod\s+777|chown\s+-R)/i;
|
|
5
|
+
const packageMutation = /(?:^|[;&|]\s*)(?:npm|pnpm|yarn|bun|pipx?|uv|cargo)\s+(?:install|add|update|upgrade)\b/i;
|
|
6
|
+
|
|
7
|
+
export async function evaluateAiwgPiCommand(
|
|
8
|
+
command: string,
|
|
9
|
+
hasUI: boolean,
|
|
10
|
+
confirm?: () => Promise<boolean>,
|
|
11
|
+
): Promise<{ block: true; reason: string } | undefined> {
|
|
12
|
+
if (!destructive.test(command) && !packageMutation.test(command)) return undefined;
|
|
13
|
+
if (!hasUI) return { block: true, reason: 'AIWG policy blocked a destructive or package-mutating command in headless mode.' };
|
|
14
|
+
return await confirm?.() ? undefined : { block: true, reason: 'Denied by AIWG operator policy.' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function aiwgBridge(pi: ExtensionAPI): void {
|
|
18
|
+
pi.on('tool_call', async (event, context) => {
|
|
19
|
+
if (event.toolName !== 'bash') return undefined;
|
|
20
|
+
const command = typeof event.input?.command === 'string' ? event.input.command : '';
|
|
21
|
+
return evaluateAiwgPiCommand(command, context.hasUI, async () => {
|
|
22
|
+
const choice = await context.ui.select('AIWG policy requires explicit approval for this command.', ['Deny', 'Allow once']);
|
|
23
|
+
return choice === 'Allow once';
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -17,6 +17,12 @@ export const defaultCommandRunner = (command, args, stdin = "") => new Promise((
|
|
|
17
17
|
child.stdout.on("data", (chunk) => collect(stdout, chunk));
|
|
18
18
|
child.stderr.on("data", (chunk) => collect(stderr, chunk));
|
|
19
19
|
child.once("error", reject);
|
|
20
|
+
child.stdin.once("error", (error) => {
|
|
21
|
+
// A short-lived credential helper may close stdin before Node flushes the
|
|
22
|
+
// payload. Its process exit remains the authoritative command result.
|
|
23
|
+
if (error.code !== "EPIPE")
|
|
24
|
+
reject(error);
|
|
25
|
+
});
|
|
20
26
|
child.once("close", (code) => resolve({
|
|
21
27
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
22
28
|
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, resolve, } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
|
|
4
|
+
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
|
|
5
5
|
const JSON_CONTRACT_VERSION = '1.0.0';
|
|
6
6
|
async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
|
|
7
7
|
const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
|
|
@@ -622,7 +622,7 @@ async function importSource(ctx, args) {
|
|
|
622
622
|
if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
|
|
623
623
|
&& provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
|
|
624
624
|
&& provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
|
|
625
|
-
&& provider !== 'openhuman' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
625
|
+
&& provider !== 'openhuman' && provider !== 'pi' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
626
626
|
throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
|
|
627
627
|
}
|
|
628
628
|
const sourceId = requiredValue(args, '--source-id');
|
|
@@ -638,6 +638,7 @@ async function importSource(ctx, args) {
|
|
|
638
638
|
const isOpenCode = provider === 'opencode';
|
|
639
639
|
const isOpenClaw = provider === 'openclaw';
|
|
640
640
|
const isOpenHuman = provider === 'openhuman';
|
|
641
|
+
const isPi = provider === 'pi';
|
|
641
642
|
const isWarp = provider === 'warp';
|
|
642
643
|
const isDevinDesktop = provider === 'devin-desktop';
|
|
643
644
|
const adapter = isClaude
|
|
@@ -658,11 +659,13 @@ async function importSource(ctx, args) {
|
|
|
658
659
|
? new OpenClawSessionAdapter()
|
|
659
660
|
: isOpenHuman
|
|
660
661
|
? new OpenHumanSessionAdapter()
|
|
661
|
-
:
|
|
662
|
-
? new
|
|
663
|
-
:
|
|
664
|
-
? new
|
|
665
|
-
:
|
|
662
|
+
: isPi
|
|
663
|
+
? new PiSessionAdapter()
|
|
664
|
+
: isWarp
|
|
665
|
+
? new WarpSessionAdapter()
|
|
666
|
+
: isDevinDesktop
|
|
667
|
+
? new DevinDesktopSessionAdapter()
|
|
668
|
+
: new GenericSessionInterchangeAdapter();
|
|
666
669
|
const locatorClass = isClaude
|
|
667
670
|
? (input.endsWith('.hooks.jsonl') ? 'claude-hook-jsonl' : 'claude-transcript-jsonl')
|
|
668
671
|
: isCodex
|
|
@@ -681,11 +684,13 @@ async function importSource(ctx, args) {
|
|
|
681
684
|
? 'openclaw-consistent-snapshot-jsonl'
|
|
682
685
|
: isOpenHuman
|
|
683
686
|
? 'openhuman-enriched-jsonl'
|
|
684
|
-
:
|
|
685
|
-
? '
|
|
686
|
-
:
|
|
687
|
-
? '
|
|
688
|
-
:
|
|
687
|
+
: isPi
|
|
688
|
+
? 'pi-session-v3-jsonl'
|
|
689
|
+
: isWarp
|
|
690
|
+
? 'warp-markdown-export'
|
|
691
|
+
: isDevinDesktop
|
|
692
|
+
? 'devin-desktop-cascade-hook-jsonl'
|
|
693
|
+
: 'manual-export';
|
|
689
694
|
const selectedSource = {
|
|
690
695
|
provider, locator: input, locatorClass, sourceId,
|
|
691
696
|
authorizedScope: { workspaceId, allowedRoots: [dirname(input)] },
|
|
@@ -1007,6 +1012,19 @@ function providerDisposition(provider) {
|
|
|
1007
1012
|
},
|
|
1008
1013
|
};
|
|
1009
1014
|
}
|
|
1015
|
+
if (provider === 'pi') {
|
|
1016
|
+
return {
|
|
1017
|
+
provider, disposition: 'implemented', operationalState: 'available',
|
|
1018
|
+
supportedOperations: ['discover', 'inspect', 'stream'],
|
|
1019
|
+
acquisitionModes: ['jsonl'], reasonCode: null,
|
|
1020
|
+
remediation: 'Authorize PI_CODING_AGENT_SESSION_DIR, the default Pi sessions root, or an explicit v3 JSONL export.',
|
|
1021
|
+
evidence: {
|
|
1022
|
+
adapterVersion: PI_ADAPTER_VERSION,
|
|
1023
|
+
verifiedAt: '2026-09-04',
|
|
1024
|
+
documentation: 'https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/session-manager.ts',
|
|
1025
|
+
},
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1010
1028
|
if (provider === 'warp') {
|
|
1011
1029
|
return {
|
|
1012
1030
|
provider, disposition: 'manual-only', operationalState: 'available',
|
|
@@ -76,6 +76,17 @@
|
|
|
76
76
|
"verification": "Inspect active profile and run metadata",
|
|
77
77
|
"sourceUrl": "https://docs.warp.dev/agent-platform/capabilities/agent-profiles-permissions", "verifiedAt": "2026-07-20"
|
|
78
78
|
},
|
|
79
|
+
"pi": {
|
|
80
|
+
"agent": "native", "skill": "inherited", "globalChild": "native",
|
|
81
|
+
"identifierSyntax": "provider/model identifier accepted by Pi --model",
|
|
82
|
+
"effortValues": ["minimal", "low", "medium", "high", "xhigh"],
|
|
83
|
+
"inheritance": "Omitted model and thinking level inherit the invoking Pi session",
|
|
84
|
+
"invalidPinFallback": "Pi resolves against its configured catalog and exits on invalid explicit selection",
|
|
85
|
+
"configTarget": "Pi headless launch arguments",
|
|
86
|
+
"artifactFormat": "Runtime --model and --thinking flags",
|
|
87
|
+
"verification": "Inspect strict JSONL state and the selected provider/model",
|
|
88
|
+
"sourceUrl": "https://github.com/earendil-works/pi/tree/main/packages/coding-agent#cli-reference", "verifiedAt": "2026-09-04"
|
|
89
|
+
},
|
|
79
90
|
"windsurf": {
|
|
80
91
|
"agent": "unsupported", "skill": "unsupported", "globalChild": "inherited",
|
|
81
92
|
"identifierSyntax": "UI-selected provider model",
|
|
@@ -52,6 +52,14 @@
|
|
|
52
52
|
},
|
|
53
53
|
"sourceUrl": "https://opencode.ai/docs/models", "verifiedAt": "2026-07-20"
|
|
54
54
|
},
|
|
55
|
+
"pi": {
|
|
56
|
+
"roles": {
|
|
57
|
+
"reasoning": { "id": "configured/reasoning", "status": "unverified", "observed": false },
|
|
58
|
+
"coding": { "id": "configured/coding", "status": "unverified", "observed": false },
|
|
59
|
+
"efficiency": { "id": "configured/efficiency", "status": "unverified", "observed": false }
|
|
60
|
+
},
|
|
61
|
+
"sourceUrl": "https://github.com/earendil-works/pi/tree/main/packages/coding-agent#providers--models", "verifiedAt": "2026-09-04"
|
|
62
|
+
},
|
|
55
63
|
"warp": {
|
|
56
64
|
"roles": {
|
|
57
65
|
"reasoning": { "id": "profile-selected", "status": "unverified", "observed": false },
|
|
@@ -67,6 +67,13 @@ export const PROVIDER_DISCOVERY_DECISIONS = {
|
|
|
67
67
|
reason: 'OpenHuman profiles accept semantic model hints but expose no standardized local model-list command.',
|
|
68
68
|
documentation: 'https://github.com/roctinam/openhuman',
|
|
69
69
|
},
|
|
70
|
+
pi: {
|
|
71
|
+
provider: 'pi',
|
|
72
|
+
status: 'native',
|
|
73
|
+
interface: 'pi --list-models',
|
|
74
|
+
reason: 'Pi exposes the configured provider/model catalog through a read-only non-interactive table.',
|
|
75
|
+
documentation: 'https://github.com/earendil-works/pi/tree/main/packages/coding-agent#cli-reference',
|
|
76
|
+
},
|
|
70
77
|
warp: {
|
|
71
78
|
provider: 'warp',
|
|
72
79
|
status: 'unsupported',
|
|
@@ -161,6 +168,30 @@ export async function discoverOpenCodeModels(command = 'opencode', runner = runM
|
|
|
161
168
|
: {}),
|
|
162
169
|
};
|
|
163
170
|
}
|
|
171
|
+
export async function discoverPiModels(command = 'pi', runner = runModelDiscoveryCommand) {
|
|
172
|
+
const observedAt = new Date().toISOString();
|
|
173
|
+
const [version, result] = await Promise.all([
|
|
174
|
+
runtimeVersion(command, runner),
|
|
175
|
+
runner(command, ['--list-models'], { cwd: tmpdir(), timeoutMs: 15_000 }),
|
|
176
|
+
]);
|
|
177
|
+
if (result.exitCode !== 0) {
|
|
178
|
+
const error = result.stderr.trim() || `Pi --list-models exited ${result.exitCode}`;
|
|
179
|
+
return { provider: 'pi', source: 'native', observedAt,
|
|
180
|
+
...(version ? { runtimeVersion: version } : {}), accountScope: 'local-runtime',
|
|
181
|
+
models: [], errorKind: classifyDiscoveryError(error), error };
|
|
182
|
+
}
|
|
183
|
+
const lines = result.stdout.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
184
|
+
const models = lines.slice(1).flatMap(line => {
|
|
185
|
+
const columns = line.split(/\s{2,}/);
|
|
186
|
+
if (columns.length < 2 || !/^[a-z0-9][a-z0-9._-]*$/i.test(columns[0]))
|
|
187
|
+
return [];
|
|
188
|
+
return [{ id: `${columns[0]}/${columns[1]}` }];
|
|
189
|
+
});
|
|
190
|
+
return { provider: 'pi', source: 'native', observedAt,
|
|
191
|
+
...(version ? { runtimeVersion: version } : {}), accountScope: 'local-runtime', models,
|
|
192
|
+
...(models.length === 0 ? { errorKind: 'invalid-output',
|
|
193
|
+
error: 'Pi returned no parseable provider/model rows.' } : {}) };
|
|
194
|
+
}
|
|
164
195
|
export async function discoverOpenClawModels(command = 'openclaw', runner = runModelDiscoveryCommand) {
|
|
165
196
|
const observedAt = new Date().toISOString();
|
|
166
197
|
const [version, result] = await Promise.all([
|
|
@@ -455,6 +486,7 @@ export async function resolveDynamicModelCatalog(options) {
|
|
|
455
486
|
codex: () => discoverCodexModels(),
|
|
456
487
|
opencode: () => discoverOpenCodeModels(),
|
|
457
488
|
openclaw: () => discoverOpenClawModels(),
|
|
489
|
+
pi: () => discoverPiModels(),
|
|
458
490
|
};
|
|
459
491
|
const providerDiscovery = {};
|
|
460
492
|
for (const provider of available) {
|
|
@@ -27,7 +27,7 @@ const capabilityData = requireModelResource('model-capabilities.v1.json');
|
|
|
27
27
|
const catalogData = requireModelResource('model-catalog.v1.json');
|
|
28
28
|
const ProviderSchema = z.enum([
|
|
29
29
|
'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
|
|
30
|
-
'opencode', 'openclaw', 'openhuman', 'warp', 'windsurf',
|
|
30
|
+
'opencode', 'openclaw', 'openhuman', 'pi', 'warp', 'windsurf',
|
|
31
31
|
]);
|
|
32
32
|
const OutcomeSchema = z.enum([
|
|
33
33
|
'native', 'compiled', 'inherited', 'global-only', 'informational', 'unsupported',
|
|
@@ -107,7 +107,7 @@ export function loadProviderModelCapabilities() {
|
|
|
107
107
|
const expected = new Set(ProviderSchema.options);
|
|
108
108
|
const actual = new Set(Object.keys(registryCache.providers));
|
|
109
109
|
if (actual.size !== expected.size || [...expected].some(id => !actual.has(id))) {
|
|
110
|
-
throw new Error('Provider model capability registry must cover all
|
|
110
|
+
throw new Error('Provider model capability registry must cover all 12 providers');
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
return registryCache;
|
|
@@ -159,6 +159,7 @@ function fieldNames(provider) {
|
|
|
159
159
|
case 'copilot':
|
|
160
160
|
case 'cursor':
|
|
161
161
|
case 'opencode': return { model: 'model' };
|
|
162
|
+
case 'pi': return { model: 'model', effort: 'thinking' };
|
|
162
163
|
case 'openhuman': return { model: 'model_hint' };
|
|
163
164
|
case 'openclaw': return { model: 'subagents.model' };
|
|
164
165
|
default: return {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { opendir } from 'node:fs/promises';
|
|
2
|
+
import { basename, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { SessionContractError, } from '../contracts.js';
|
|
5
|
+
import { readBoundedJsonLines, streamBoundedJsonLines } from '../readers.js';
|
|
6
|
+
export const PI_ADAPTER_VERSION = '1.0.0';
|
|
7
|
+
export const PI_SOURCE_SCHEMA_VERSION = '3.0.0';
|
|
8
|
+
const Header = z.object({ type: z.literal('session'), version: z.number().int(), id: z.string().min(1),
|
|
9
|
+
timestamp: z.string().datetime({ offset: true }), cwd: z.string(), parentSession: z.string().optional() }).passthrough();
|
|
10
|
+
const Entry = z.object({ type: z.string().min(1), id: z.string().min(1),
|
|
11
|
+
parentId: z.string().nullable(), timestamp: z.string().datetime({ offset: true }) }).passthrough();
|
|
12
|
+
export class PiSessionAdapter {
|
|
13
|
+
limits;
|
|
14
|
+
maxFiles;
|
|
15
|
+
provider = 'pi';
|
|
16
|
+
adapterVersion = PI_ADAPTER_VERSION;
|
|
17
|
+
disposition = 'implemented';
|
|
18
|
+
supportedOperations = ['discover', 'inspect', 'stream'];
|
|
19
|
+
acquisitionModes = ['jsonl'];
|
|
20
|
+
constructor(limits, maxFiles = 10_000) {
|
|
21
|
+
this.limits = limits;
|
|
22
|
+
this.maxFiles = maxFiles;
|
|
23
|
+
}
|
|
24
|
+
async *discover(scope) {
|
|
25
|
+
if (!scope.allowedRoots.length)
|
|
26
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Pi discovery requires an authorized sessions root');
|
|
27
|
+
let count = 0;
|
|
28
|
+
for (const root of [...scope.allowedRoots].sort()) {
|
|
29
|
+
for await (const locator of jsonlFiles(resolve(root))) {
|
|
30
|
+
if (++count > this.maxFiles)
|
|
31
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Pi discovery file limit exceeded');
|
|
32
|
+
yield { provider: 'pi', locator, locatorClass: 'pi-session-v3-jsonl' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async inspect(source) {
|
|
37
|
+
let input;
|
|
38
|
+
try {
|
|
39
|
+
input = await readBoundedJsonLines({ selectedPath: source.locator,
|
|
40
|
+
allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw normalizeAuthorizationError(error);
|
|
44
|
+
}
|
|
45
|
+
const header = parseHeader(input.records[0]?.value);
|
|
46
|
+
if (input.incompleteTail)
|
|
47
|
+
throw new SessionContractError('TRUNCATED_SOURCE', 'Pi session has a truncated JSONL tail');
|
|
48
|
+
return { sourceSchemaVersion: `${header.version}.0.0`, consistency: 'complete', operationalState: 'available' };
|
|
49
|
+
}
|
|
50
|
+
async *stream(source, cursor) {
|
|
51
|
+
let input;
|
|
52
|
+
try {
|
|
53
|
+
input = await streamBoundedJsonLines({ selectedPath: source.locator,
|
|
54
|
+
allowedRoots: source.authorizedScope.allowedRoots }, { cursor: cursor?.value, consistency: 'provisional', limits: this.limits });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw normalizeAuthorizationError(error);
|
|
58
|
+
}
|
|
59
|
+
let sessionId = '';
|
|
60
|
+
let index = 0;
|
|
61
|
+
const ids = new Set();
|
|
62
|
+
for await (const line of input) {
|
|
63
|
+
if (index++ === 0 && !cursor) {
|
|
64
|
+
sessionId = parseHeader(line.value).id;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!sessionId)
|
|
68
|
+
sessionId = basename(source.locator, '.jsonl');
|
|
69
|
+
const parsed = Entry.safeParse(line.value);
|
|
70
|
+
if (!parsed.success)
|
|
71
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Pi session entry is malformed');
|
|
72
|
+
const entry = parsed.data;
|
|
73
|
+
if (ids.has(entry.id))
|
|
74
|
+
throw new SessionContractError('DUPLICATE_NATIVE_ID', 'Pi session contains a duplicate entry id');
|
|
75
|
+
ids.add(entry.id);
|
|
76
|
+
const message = object(entry.message);
|
|
77
|
+
const role = string(message.role) ?? roleFor(entry.type);
|
|
78
|
+
const sensitive = role === 'toolResult' || entry.type === 'custom';
|
|
79
|
+
const text = sensitive ? '[redacted provider content]' : entryText(entry, message);
|
|
80
|
+
yield { nativeSessionId: sessionId, nativeEventId: entry.id, sequence: line.sequence,
|
|
81
|
+
kind: kindFor(entry.type, role), role, toolName: sensitive ? string(message.toolName) : undefined,
|
|
82
|
+
toolCallId: sensitive ? string(message.toolCallId) : undefined,
|
|
83
|
+
occurredAt: entry.timestamp, text, sourceCursor: String(line.byteOffset + line.byteLength),
|
|
84
|
+
sourceBytes: line.byteLength, rawReference: { locatorClass: 'pi-session-v3-jsonl', offset: line.byteOffset },
|
|
85
|
+
activityBoundary: entry.type === 'compaction' ? 'continuation' : undefined,
|
|
86
|
+
activityBoundaryBasis: entry.type === 'compaction' ? 'pi-session:compaction' : undefined,
|
|
87
|
+
activityBoundaryConfidence: entry.type === 'compaction' ? 'high' : undefined,
|
|
88
|
+
extensions: { provenance: { acquisition: 'pi-session-v3', parentId: entry.parentId },
|
|
89
|
+
opaque: !KNOWN.has(entry.type), redacted: sensitive,
|
|
90
|
+
nativeType: entry.type, parentId: entry.parentId } };
|
|
91
|
+
}
|
|
92
|
+
if (input.incompleteTail)
|
|
93
|
+
throw new SessionContractError('TRUNCATED_SOURCE', 'Pi session has a truncated JSONL tail');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const KNOWN = new Set(['message', 'thinking_level_change', 'model_change', 'compaction',
|
|
97
|
+
'branch_summary', 'custom', 'label', 'session_info', 'custom_message']);
|
|
98
|
+
function parseHeader(value) {
|
|
99
|
+
const parsed = Header.safeParse(value);
|
|
100
|
+
if (!parsed.success)
|
|
101
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Pi session header is malformed');
|
|
102
|
+
if (parsed.data.version !== 3)
|
|
103
|
+
throw new SessionContractError('UNKNOWN_SCHEMA_MAJOR', `unsupported Pi session version: ${parsed.data.version}`);
|
|
104
|
+
return parsed.data;
|
|
105
|
+
}
|
|
106
|
+
function object(value) { return value && typeof value === 'object' ? value : {}; }
|
|
107
|
+
function string(value) { return typeof value === 'string' && value ? value : undefined; }
|
|
108
|
+
function roleFor(type) { return type === 'compaction' || type === 'branch_summary' ? 'system' : undefined; }
|
|
109
|
+
function kindFor(type, role) { return type === 'message' ? `message.${role ?? 'unknown'}` : `pi.${type}`; }
|
|
110
|
+
function entryText(entry, message) {
|
|
111
|
+
const value = entry.type === 'message' ? message.content : entry.summary ?? entry.name ?? entry.label ?? '';
|
|
112
|
+
if (typeof value === 'string')
|
|
113
|
+
return value;
|
|
114
|
+
if (Array.isArray(value))
|
|
115
|
+
return value.flatMap(part => typeof part === 'string' ? part : string(object(part).text) ?? []).join('\n');
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
async function* jsonlFiles(root) {
|
|
119
|
+
let directory;
|
|
120
|
+
try {
|
|
121
|
+
directory = await opendir(root);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for await (const entry of directory) {
|
|
127
|
+
const path = resolve(root, entry.name);
|
|
128
|
+
if (entry.isDirectory())
|
|
129
|
+
yield* jsonlFiles(path);
|
|
130
|
+
else if (entry.isFile() && entry.name.endsWith('.jsonl'))
|
|
131
|
+
yield path;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function normalizeAuthorizationError(error) {
|
|
135
|
+
if (error instanceof SessionContractError && (error.code === 'SOURCE_OUTSIDE_ALLOWED_ROOT'
|
|
136
|
+
|| error.code === 'SOURCE_SYMLINK' || error.code === 'SOURCE_NOT_REGULAR_FILE')) {
|
|
137
|
+
return new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Pi source is not an authorized regular file');
|
|
138
|
+
}
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=pi.js.map
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
export const SESSION_CONTRACT_VERSION = '1.0.0';
|
|
4
4
|
export const SESSION_PROVIDER_IDS = [
|
|
5
5
|
'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
|
|
6
|
-
'opencode', 'openclaw', 'openhuman', 'warp', 'devin-desktop', 'generic',
|
|
6
|
+
'opencode', 'openclaw', 'openhuman', 'pi', 'warp', 'devin-desktop', 'generic',
|
|
7
7
|
];
|
|
8
8
|
export const SessionProviderIdSchema = z.enum(SESSION_PROVIDER_IDS);
|
|
9
9
|
export const SESSION_PROVIDER_ALIASES = Object.freeze({
|
|
@@ -28,6 +28,7 @@ export * from './adapters/hermes.js';
|
|
|
28
28
|
export * from './adapters/opencode.js';
|
|
29
29
|
export * from './adapters/openclaw.js';
|
|
30
30
|
export * from './adapters/openhuman.js';
|
|
31
|
+
export * from './adapters/pi.js';
|
|
31
32
|
export * from './adapters/warp.js';
|
|
32
33
|
export * from './adapters/windsurf.js';
|
|
33
34
|
//# sourceMappingURL=index.js.map
|
|
@@ -7,6 +7,7 @@ import { ClaudeSessionAdapter } from './adapters/claude.js';
|
|
|
7
7
|
import { CodexSessionAdapter } from './adapters/codex.js';
|
|
8
8
|
import { CursorSessionAdapter } from './adapters/cursor.js';
|
|
9
9
|
import { FactorySessionAdapter } from './adapters/factory.js';
|
|
10
|
+
import { PiSessionAdapter } from './adapters/pi.js';
|
|
10
11
|
import { SESSION_PROVIDER_IDS, sha256, } from './contracts.js';
|
|
11
12
|
import { redactSourceLocator } from './discovery.js';
|
|
12
13
|
import { fingerprintSourceFile } from './readers.js';
|
|
@@ -49,6 +50,15 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
49
50
|
join(providerHome, '.factory', 'sessions', keyWithLeadingDash),
|
|
50
51
|
]),
|
|
51
52
|
},
|
|
53
|
+
{
|
|
54
|
+
provider: 'pi',
|
|
55
|
+
adapter: new PiSessionAdapter(),
|
|
56
|
+
roots: options.providerHome
|
|
57
|
+
? [process.env.PI_CODING_AGENT_SESSION_DIR
|
|
58
|
+
? resolve(process.env.PI_CODING_AGENT_SESSION_DIR)
|
|
59
|
+
: join(resolve(options.providerHome), '.pi', 'agent', 'sessions')]
|
|
60
|
+
: [],
|
|
61
|
+
},
|
|
52
62
|
];
|
|
53
63
|
const reports = new Map();
|
|
54
64
|
const candidates = [];
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import path from 'path';
|
|
11
|
+
import fs from 'fs';
|
|
11
12
|
import {
|
|
12
13
|
collectFrameworkArtifacts,
|
|
13
14
|
createAgentsMdFromTemplate,
|
|
@@ -31,6 +32,8 @@ export const paths = {
|
|
|
31
32
|
rules: '',
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
export const extensionBridge = 'agentic/code/providers/pi/aiwg-bridge.ts';
|
|
36
|
+
|
|
34
37
|
export const kernelSkillsPath = '.agents/skills';
|
|
35
38
|
|
|
36
39
|
export const support = {
|
|
@@ -109,6 +112,14 @@ export function deployRules() {
|
|
|
109
112
|
return 0;
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
export function deployExtensionBridge(targetDir, opts) {
|
|
116
|
+
const source = path.join(resolveAiwgRoot(opts.srcRoot) || opts.srcRoot, extensionBridge);
|
|
117
|
+
if (!fs.existsSync(source)) return 0;
|
|
118
|
+
const destination = path.join(targetDir, '.pi/extensions');
|
|
119
|
+
ensureDir(destination, opts.dryRun);
|
|
120
|
+
return deployFiles([source], destination, opts);
|
|
121
|
+
}
|
|
122
|
+
|
|
112
123
|
export function createAgentsMd(target, srcRoot, dryRun) {
|
|
113
124
|
const aiwgRoot = resolveAiwgRoot(srcRoot) || srcRoot;
|
|
114
125
|
createAgentsMdFromTemplate(target, aiwgRoot, 'pi/AGENTS.md.aiwg-template', dryRun);
|
|
@@ -153,6 +164,7 @@ export async function deploy(opts) {
|
|
|
153
164
|
if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly) count += deployAgents(agentFiles, opts.target, opts);
|
|
154
165
|
if ((opts.deployCommands || opts.commandsOnly) && !opts.skillsOnly && !opts.rulesOnly) count += deployCommands(commandFiles, opts.target, opts);
|
|
155
166
|
if ((opts.deploySkills || opts.skillsOnly) && !opts.commandsOnly && !opts.rulesOnly) count += deploySkills(skillDirs, opts.target, opts);
|
|
167
|
+
if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly) count += deployExtensionBridge(opts.target, opts);
|
|
156
168
|
await postDeploy(opts.target, opts);
|
|
157
169
|
return count;
|
|
158
170
|
}
|
|
@@ -160,5 +172,5 @@ export async function deploy(opts) {
|
|
|
160
172
|
export default {
|
|
161
173
|
name, aliases, paths, kernelSkillsPath, support, capabilities,
|
|
162
174
|
mapModel, transformAgent, transformCommand, deployAgents, deployCommands,
|
|
163
|
-
deploySkills, deployRules, createAgentsMd, postDeploy, getFileExtension, deploy,
|
|
175
|
+
deploySkills, deployRules, deployExtensionBridge, createAgentsMd, postDeploy, getFileExtension, deploy,
|
|
164
176
|
};
|