@aiwg/cli 2026.9.0 → 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 +41 -0
- 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/bin/aiwg.mjs +15 -3
- package/dist/src/api/index.d.ts +3 -0
- package/dist/src/api/index.js +3 -0
- package/dist/src/auth/credential-store.js +6 -0
- package/dist/src/channel/manager.mjs +2 -2
- package/dist/src/cli/handlers/dataset.js +186 -0
- package/dist/src/cli/handlers/help.js +2 -1
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/init.js +1 -0
- package/dist/src/cli/handlers/output-mode.js +18 -1
- package/dist/src/cli/handlers/run.js +11 -3
- package/dist/src/cli/handlers/schema.js +221 -0
- package/dist/src/cli/handlers/sessions.js +30 -12
- package/dist/src/cli/handlers/steward.js +11 -1
- package/dist/src/cli/handlers/use.js +6 -2
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/scope-resolver.js +22 -0
- package/dist/src/dataset/adapter-sdk.d.ts +41 -0
- package/dist/src/dataset/adapter-sdk.js +147 -0
- package/dist/src/dataset/adapter-types.d.ts +179 -0
- package/dist/src/dataset/adapter-types.js +2 -0
- package/dist/src/dataset/adapters.d.ts +104 -0
- package/dist/src/dataset/adapters.js +518 -0
- package/dist/src/dataset/conformance-types.d.ts +84 -0
- package/dist/src/dataset/conformance-types.js +3 -0
- package/dist/src/dataset/conformance.d.ts +13 -0
- package/dist/src/dataset/conformance.js +90 -0
- package/dist/src/dataset/contracts.d.ts +17 -0
- package/dist/src/dataset/contracts.js +236 -0
- package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
- package/dist/src/dataset/file-orchestration-repository.js +68 -0
- package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
- package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
- package/dist/src/dataset/index.d.ts +21 -0
- package/dist/src/dataset/index.js +21 -0
- package/dist/src/dataset/ledger-types.d.ts +141 -0
- package/dist/src/dataset/ledger-types.js +2 -0
- package/dist/src/dataset/ledger.d.ts +27 -0
- package/dist/src/dataset/ledger.js +100 -0
- package/dist/src/dataset/local-execution-backend.d.ts +10 -0
- package/dist/src/dataset/local-execution-backend.js +32 -0
- package/dist/src/dataset/orchestration-repository.d.ts +29 -0
- package/dist/src/dataset/orchestration-repository.js +34 -0
- package/dist/src/dataset/orchestration-service.d.ts +56 -0
- package/dist/src/dataset/orchestration-service.js +466 -0
- package/dist/src/dataset/orchestration-types.d.ts +83 -0
- package/dist/src/dataset/orchestration-types.js +2 -0
- package/dist/src/dataset/presentation.d.ts +3 -0
- package/dist/src/dataset/presentation.js +8 -0
- package/dist/src/dataset/projections.d.ts +42 -0
- package/dist/src/dataset/projections.js +192 -0
- package/dist/src/dataset/schema-governance.d.ts +71 -0
- package/dist/src/dataset/schema-governance.js +135 -0
- package/dist/src/dataset/standards-types.d.ts +66 -0
- package/dist/src/dataset/standards-types.js +10 -0
- package/dist/src/dataset/standards.d.ts +13 -0
- package/dist/src/dataset/standards.js +291 -0
- package/dist/src/dataset/types.d.ts +258 -0
- package/dist/src/dataset/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +27 -1
- package/dist/src/installation/manager.mjs +5 -1
- 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/output-modes/index.js +4 -0
- package/dist/src/output-modes/registry.js +68 -24
- package/dist/src/output-modes/runtime.js +10 -8
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +41 -0
- package/dist/src/providers/provider-definitions.js +72 -0
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/schema/catalog.js +234 -0
- package/dist/src/schema/compatibility.js +42 -0
- package/dist/src/schema/diagnostics.js +36 -0
- package/dist/src/schema/index.js +8 -0
- package/dist/src/schema/policy.js +58 -0
- package/dist/src/schema/resolver.js +76 -0
- package/dist/src/schema/types.js +2 -0
- package/dist/src/schema/validator.js +82 -0
- 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/dist/src/storage/backends/fortemi.js +6 -0
- package/dist/src/storage/config.js +18 -4
- package/dist/src/storage/fortemi-qualification.js +106 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/types.js +1 -1
- package/package.json +3 -1
- package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
- package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
- package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
- package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
- package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
- package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
- package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
- package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
- package/schemas/dataset/run-ledger.v1.schema.json +39 -0
- package/schemas/dataset/source-adapter.v1.schema.json +112 -0
- package/tools/agents/deploy-agents.mjs +9 -3
- package/tools/agents/providers/pi.mjs +176 -0
|
@@ -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 = [];
|
|
@@ -301,6 +301,12 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
|
|
|
301
301
|
async callTool(name, args) {
|
|
302
302
|
return unwrapMcpToolResult(await client.callTool({ name, arguments: args }));
|
|
303
303
|
},
|
|
304
|
+
async listTools() {
|
|
305
|
+
return client.listTools();
|
|
306
|
+
},
|
|
307
|
+
serverVersion() {
|
|
308
|
+
return client.getServerVersion();
|
|
309
|
+
},
|
|
304
310
|
async close() {
|
|
305
311
|
await client.close();
|
|
306
312
|
},
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
* v1 schema, and returns a typed `StorageConfig`. Absence of the file is
|
|
6
6
|
* a no-op: every subsystem defaults to `fs` rooted under `.aiwg/`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* canonical for editor tooling and external consumers.
|
|
8
|
+
* Runtime validation is kept in parity with the canonical JSON Schema at
|
|
9
|
+
* `schemas/storage/storage.config.v1.schema.json`; the lightweight path here
|
|
10
|
+
* avoids loading the full catalog during storage bootstrap.
|
|
12
11
|
*
|
|
13
12
|
* @design @.aiwg/architecture/storage-design.md
|
|
14
13
|
* @issue #934
|
|
@@ -98,6 +97,7 @@ export function validateStorageConfig(parsed, source = '<input>') {
|
|
|
98
97
|
`(expected "1"). Update the AIWG CLI to read newer config versions.`);
|
|
99
98
|
}
|
|
100
99
|
walkRejectingCredentials(obj, source, 'storage');
|
|
100
|
+
rejectUnknownKeys(obj, ['version', 'roots', 'backends', 'fallback'], source, 'storage');
|
|
101
101
|
const roots = validateRoots(obj['roots'], source);
|
|
102
102
|
const backends = validateBackends(obj['backends'], source);
|
|
103
103
|
let fallback;
|
|
@@ -164,19 +164,24 @@ function validateBackendConfig(raw, source) {
|
|
|
164
164
|
// — the adapter is responsible for any further validation it needs.
|
|
165
165
|
switch (type) {
|
|
166
166
|
case 'fs':
|
|
167
|
+
rejectUnknownKeys(obj, ['type'], source, 'backend');
|
|
167
168
|
return { type: 'fs' };
|
|
168
169
|
case 'obsidian':
|
|
170
|
+
rejectUnknownKeys(obj, ['type', 'vault', 'folder', 'useCli'], source, 'backend');
|
|
169
171
|
requireString(obj, 'vault', source);
|
|
170
172
|
return obj;
|
|
171
173
|
case 'logseq':
|
|
174
|
+
rejectUnknownKeys(obj, ['type', 'graph', 'apiUrl', 'useApi'], source, 'backend');
|
|
172
175
|
requireString(obj, 'graph', source);
|
|
173
176
|
return obj;
|
|
174
177
|
case 'notion': {
|
|
178
|
+
rejectUnknownKeys(obj, ['type', 'parent', 'externalIdProperty'], source, 'backend');
|
|
175
179
|
const parent = obj['parent'];
|
|
176
180
|
if (typeof parent !== 'object' || parent === null) {
|
|
177
181
|
throw new Error(`${source}.parent must be an object with pageId or databaseId`);
|
|
178
182
|
}
|
|
179
183
|
const p = parent;
|
|
184
|
+
rejectUnknownKeys(p, ['pageId', 'databaseId'], source, 'parent');
|
|
180
185
|
const hasPage = typeof p['pageId'] === 'string' && p['pageId'].length > 0;
|
|
181
186
|
const hasDb = typeof p['databaseId'] === 'string' && p['databaseId'].length > 0;
|
|
182
187
|
if (hasPage === hasDb) {
|
|
@@ -185,21 +190,30 @@ function validateBackendConfig(raw, source) {
|
|
|
185
190
|
return obj;
|
|
186
191
|
}
|
|
187
192
|
case 'anythingllm':
|
|
193
|
+
rejectUnknownKeys(obj, ['type', 'baseUrl', 'workspace', 'folder'], source, 'backend');
|
|
188
194
|
requireString(obj, 'baseUrl', source);
|
|
189
195
|
requireString(obj, 'workspace', source);
|
|
190
196
|
return obj;
|
|
191
197
|
case 'fortemi':
|
|
198
|
+
rejectUnknownKeys(obj, ['type', 'mcpServer', 'scheme'], source, 'backend');
|
|
192
199
|
return obj;
|
|
193
200
|
case 's3':
|
|
201
|
+
rejectUnknownKeys(obj, ['type', 'bucket', 'prefix', 'region', 'endpoint'], source, 'backend');
|
|
194
202
|
requireString(obj, 'bucket', source);
|
|
195
203
|
return obj;
|
|
196
204
|
case 'webdav':
|
|
205
|
+
rejectUnknownKeys(obj, ['type', 'url', 'basePath', 'authMode'], source, 'backend');
|
|
197
206
|
requireString(obj, 'url', source);
|
|
198
207
|
return obj;
|
|
199
208
|
default:
|
|
200
209
|
throw new Error(`${source}: unhandled backend type ${type}`);
|
|
201
210
|
}
|
|
202
211
|
}
|
|
212
|
+
function rejectUnknownKeys(obj, allowed, source, label) {
|
|
213
|
+
const unknown = Object.keys(obj).find(key => !allowed.includes(key));
|
|
214
|
+
if (unknown)
|
|
215
|
+
throw new Error(`${source}: ${label}.${unknown} is not a supported property`);
|
|
216
|
+
}
|
|
203
217
|
function requireString(obj, key, source) {
|
|
204
218
|
const v = obj[key];
|
|
205
219
|
if (typeof v !== 'string' || v.length === 0) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { FortemiAdapter } from "./backends/fortemi.js";
|
|
3
|
+
export const FORTEMI_QUALIFICATION_VERSION = "aiwg.fortemi-live-qualification/v1";
|
|
4
|
+
const EXPECTED = {
|
|
5
|
+
read: { tool: "get_note", required: ["note_id"], properties: ["note_id"] },
|
|
6
|
+
write: {
|
|
7
|
+
tool: "capture_knowledge",
|
|
8
|
+
required: ["note_id", "content"],
|
|
9
|
+
properties: ["note_id", "content"],
|
|
10
|
+
},
|
|
11
|
+
update: {
|
|
12
|
+
tool: "update_note",
|
|
13
|
+
required: ["note_id"],
|
|
14
|
+
properties: ["note_id", "content", "archived"],
|
|
15
|
+
},
|
|
16
|
+
list: { tool: "list_notes", required: [], properties: ["id_prefix"] },
|
|
17
|
+
query: {
|
|
18
|
+
tool: "search",
|
|
19
|
+
required: ["query"],
|
|
20
|
+
properties: ["query", "id_prefix"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
function bounded(work, timeoutMs, label) {
|
|
24
|
+
let timer;
|
|
25
|
+
return Promise.race([
|
|
26
|
+
work,
|
|
27
|
+
new Promise((_, reject) => {
|
|
28
|
+
timer = setTimeout(() => reject(new Error(`FORTEMI_LIVE_TIMEOUT: ${label} exceeded ${timeoutMs}ms`)), timeoutMs);
|
|
29
|
+
}),
|
|
30
|
+
]).finally(() => clearTimeout(timer));
|
|
31
|
+
}
|
|
32
|
+
/** Schema-first live qualification. No tool operation runs when preflight detects drift. */
|
|
33
|
+
export async function qualifyLiveFortemi(client, options = {}) {
|
|
34
|
+
const timeoutMs = Math.max(250, Math.min(options.timeoutMs ?? 5_000, 30_000));
|
|
35
|
+
const namespace = `aiwg-qualification-${randomUUID()}`;
|
|
36
|
+
const report = {
|
|
37
|
+
schema: FORTEMI_QUALIFICATION_VERSION,
|
|
38
|
+
compatible: false,
|
|
39
|
+
mutationAttempted: false,
|
|
40
|
+
server: {
|
|
41
|
+
...(client.serverVersion?.() ?? {}),
|
|
42
|
+
...(options.contractRevision
|
|
43
|
+
? { contractRevision: options.contractRevision }
|
|
44
|
+
: {}),
|
|
45
|
+
},
|
|
46
|
+
namespace,
|
|
47
|
+
operations: [],
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
if (!client.listTools)
|
|
51
|
+
throw new Error("FORTEMI_TOOL_DISCOVERY_UNAVAILABLE");
|
|
52
|
+
const discovered = await bounded(client.listTools(), timeoutMs, "tools/list");
|
|
53
|
+
const tools = new Map((discovered.tools ?? []).map((tool) => [tool.name, tool]));
|
|
54
|
+
for (const [operation, expected] of Object.entries(EXPECTED)) {
|
|
55
|
+
const tool = tools.get(expected.tool);
|
|
56
|
+
const schema = tool?.inputSchema;
|
|
57
|
+
const properties = schema?.properties && typeof schema.properties === "object"
|
|
58
|
+
? schema.properties
|
|
59
|
+
: {};
|
|
60
|
+
const required = Array.isArray(schema?.required)
|
|
61
|
+
? schema.required.filter((value) => typeof value === "string")
|
|
62
|
+
: [];
|
|
63
|
+
const missing = expected.properties.filter((name) => !(name in properties));
|
|
64
|
+
const missingRequired = expected.required.filter((name) => !required.includes(name));
|
|
65
|
+
const compatible = Boolean(tool && schema) &&
|
|
66
|
+
missing.length === 0 &&
|
|
67
|
+
missingRequired.length === 0;
|
|
68
|
+
report.operations.push({
|
|
69
|
+
operation,
|
|
70
|
+
tool: expected.tool,
|
|
71
|
+
compatible,
|
|
72
|
+
code: compatible
|
|
73
|
+
? "FORTEMI_TOOL_SCHEMA_COMPATIBLE"
|
|
74
|
+
: tool
|
|
75
|
+
? "FORTEMI_TOOL_SCHEMA_DRIFT"
|
|
76
|
+
: "FORTEMI_TOOL_MISSING",
|
|
77
|
+
detail: compatible
|
|
78
|
+
? "expected adapter arguments are accepted"
|
|
79
|
+
: `missing properties: ${missing.join(", ") || "none"}; not required: ${missingRequired.join(", ") || "none"}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
report.compatible = report.operations.every((item) => item.compatible);
|
|
83
|
+
if (!report.compatible)
|
|
84
|
+
return report;
|
|
85
|
+
const adapter = new FortemiAdapter({
|
|
86
|
+
subsystem: namespace,
|
|
87
|
+
config: { type: "fortemi", mcpServer: "live-qualification" },
|
|
88
|
+
clientFactory: async () => client,
|
|
89
|
+
});
|
|
90
|
+
await bounded(adapter.init(), timeoutMs, "adapter init");
|
|
91
|
+
await bounded(adapter.read(randomUUID()), timeoutMs, "adapter read");
|
|
92
|
+
await bounded(adapter.list(""), timeoutMs, "adapter list");
|
|
93
|
+
await bounded(adapter.query(`aiwg qualification ${namespace}`), timeoutMs, "adapter query");
|
|
94
|
+
if (options.allowMutation) {
|
|
95
|
+
report.mutationAttempted = true;
|
|
96
|
+
await bounded(adapter.write(randomUUID(), `AIWG live qualification ${namespace}`, {
|
|
97
|
+
contentType: "text/plain",
|
|
98
|
+
}), timeoutMs, "adapter write");
|
|
99
|
+
}
|
|
100
|
+
return report;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await client.close?.();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=fortemi-qualification.js.map
|
|
@@ -21,6 +21,7 @@ export { FilesystemAdapter } from './backends/fs.js';
|
|
|
21
21
|
export { ObsidianAdapter } from './backends/obsidian.js';
|
|
22
22
|
export { LogseqAdapter } from './backends/logseq.js';
|
|
23
23
|
export { FortemiAdapter } from './backends/fortemi.js';
|
|
24
|
+
export { qualifyLiveFortemi, FORTEMI_QUALIFICATION_VERSION, } from './fortemi-qualification.js';
|
|
24
25
|
export { STORAGE_BACKEND_CONTRACT, STORAGE_BACKEND_MATRIX, StorageCapabilityError, negotiateStorageCapabilities, } from './backend-contract.js';
|
|
25
26
|
export { STORAGE_MIGRATION_PROTOCOL, MigrationProtocolError, StorageMigrationCoordinator, approvalDigest, digestRecordChunks, digestRecords, validateManifest, } from './migration-protocol.js';
|
|
26
27
|
export { PostgresStorageBackend, PostgresBackendError, } from './backends/postgres.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwg/cli",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.2",
|
|
4
4
|
"description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
"bin/",
|
|
29
29
|
"agentic/code/providers/",
|
|
30
30
|
"schemas/security/",
|
|
31
|
+
"schemas/dataset/",
|
|
31
32
|
"dist/src/",
|
|
32
33
|
"!dist/**/*.d.ts",
|
|
33
34
|
"!dist/**/*.map",
|
|
34
35
|
"dist/src/api/index.d.ts",
|
|
35
36
|
"dist/src/resources/index.d.ts",
|
|
36
37
|
"dist/src/resources/web-release.d.ts",
|
|
38
|
+
"dist/src/dataset/**/*.d.ts",
|
|
37
39
|
"tools/_resolve-impl.mjs",
|
|
38
40
|
"tools/agents/deploy-agents.mjs",
|
|
39
41
|
"tools/agents/providers/",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://aiwg.io/schemas/dataset/conformance-manifest.v1.schema.json",
|
|
4
|
+
"title": "AIWG Dataset Intelligence Conformance Manifest v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["contract", "schemaVersion", "corpusVersion", "cells"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"contract": { "const": "aiwg.dataset.conformance/v1" },
|
|
10
|
+
"schemaVersion": { "$ref": "#/$defs/version" },
|
|
11
|
+
"corpusVersion": { "$ref": "#/$defs/version" },
|
|
12
|
+
"cells": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/cell" } }
|
|
13
|
+
},
|
|
14
|
+
"$defs": {
|
|
15
|
+
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" },
|
|
16
|
+
"digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
|
17
|
+
"cell": {
|
|
18
|
+
"type": "object", "additionalProperties": false,
|
|
19
|
+
"required": ["id", "area", "requiredCapabilities", "sourceClass", "runtimeClass", "fixture", "expected", "maturity", "evidence", "resourceEnvelope"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9.-]+$" },
|
|
22
|
+
"area": { "enum": ["adapter", "capability", "replay", "checkpoint", "provenance", "security", "offline", "parity", "standards", "migration"] },
|
|
23
|
+
"requiredCapabilities": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
|
|
24
|
+
"sourceClass": { "enum": ["synthetic", "file", "directory", "jsonl", "csv", "http", "cache", "fortemi"] },
|
|
25
|
+
"runtimeClass": { "enum": ["local", "fortemi-core", "fortemi-server"] },
|
|
26
|
+
"fixture": { "type": "object", "additionalProperties": false, "required": ["path", "digest", "revision"], "properties": { "path": { "type": "string", "pattern": "^test/fixtures/dataset-intelligence/" }, "digest": { "$ref": "#/$defs/digest" }, "revision": { "$ref": "#/$defs/version" } } },
|
|
27
|
+
"expected": { "type": "object", "additionalProperties": false, "required": ["result"], "properties": { "result": { "enum": ["pass", "reject", "pending"] }, "diagnostic": { "type": "string", "minLength": 1 } } },
|
|
28
|
+
"maturity": { "enum": ["experimental", "qualified", "stable"] },
|
|
29
|
+
"evidence": { "type": "array", "minItems": 1, "items": { "enum": ["fixture", "real-source", "cross-repo", "live-qualification"] }, "uniqueItems": true },
|
|
30
|
+
"resourceEnvelope": { "type": "object", "additionalProperties": false, "required": ["maxBytes", "maxRecords", "maxDurationMs"], "properties": { "maxBytes": { "type": "integer", "minimum": 1 }, "maxRecords": { "type": "integer", "minimum": 1 }, "maxDurationMs": { "type": "integer", "minimum": 1 } } },
|
|
31
|
+
"liveAuthorizationRequired": { "type": "boolean" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://aiwg.io/schemas/dataset/conformance-receipt.v1.schema.json",
|
|
4
|
+
"title": "AIWG Dataset Intelligence Conformance Receipt v1",
|
|
5
|
+
"type": "object", "additionalProperties": false,
|
|
6
|
+
"required": ["contract", "schemaVersion", "corpusVersion", "manifestDigest", "resultDigest", "bindings", "startedAt", "endedAt", "results", "summary"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"contract": { "const": "aiwg.dataset.conformance/v1" }, "schemaVersion": { "$ref": "#/$defs/version" }, "corpusVersion": { "$ref": "#/$defs/version" },
|
|
9
|
+
"manifestDigest": { "$ref": "#/$defs/digest" }, "resultDigest": { "$ref": "#/$defs/digest" },
|
|
10
|
+
"startedAt": { "$ref": "#/$defs/time" }, "endedAt": { "$ref": "#/$defs/time" },
|
|
11
|
+
"bindings": { "$ref": "#/$defs/bindings" },
|
|
12
|
+
"results": { "type": "array", "items": { "$ref": "#/$defs/result" } },
|
|
13
|
+
"summary": { "type": "object", "additionalProperties": false, "required": ["passed", "failed", "pending", "stableEligible"], "properties": { "passed": { "type": "integer", "minimum": 0 }, "failed": { "type": "integer", "minimum": 0 }, "pending": { "type": "integer", "minimum": 0 }, "stableEligible": { "type": "boolean" } } }
|
|
14
|
+
},
|
|
15
|
+
"$defs": {
|
|
16
|
+
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" }, "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, "time": { "type": "string", "format": "date-time", "pattern": "Z$" },
|
|
17
|
+
"digestMap": { "type": "object", "minProperties": 1, "additionalProperties": { "$ref": "#/$defs/digest" } },
|
|
18
|
+
"bindings": { "type": "object", "additionalProperties": false, "required": ["aiwgCommit", "packageDigests", "schemaDigests", "fixtureDigest", "configurationDigest"], "properties": { "aiwgCommit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})$" }, "fortemiCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "packageDigests": { "$ref": "#/$defs/digestMap" }, "schemaDigests": { "$ref": "#/$defs/digestMap" }, "fixtureDigest": { "$ref": "#/$defs/digest" }, "descriptorDigest": { "$ref": "#/$defs/digest" }, "configurationDigest": { "$ref": "#/$defs/digest" } } },
|
|
19
|
+
"evidence": { "type": "object", "additionalProperties": false, "required": ["kind", "reference", "digest"], "properties": { "kind": { "enum": ["fixture", "real-source", "cross-repo", "live-qualification"] }, "reference": { "type": "string", "minLength": 1 }, "digest": { "$ref": "#/$defs/digest" } } },
|
|
20
|
+
"result": { "type": "object", "additionalProperties": false, "required": ["cellId", "status", "evidence", "observed"], "properties": { "cellId": { "type": "string", "minLength": 1 }, "status": { "enum": ["passed", "failed", "pending"] }, "diagnostic": { "type": "string", "minLength": 1 }, "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } }, "observed": { "type": "object", "additionalProperties": false, "required": ["networkAttempts"], "properties": { "records": { "type": "integer", "minimum": 0 }, "bytes": { "type": "integer", "minimum": 0 }, "durationMs": { "type": "integer", "minimum": 0 }, "networkAttempts": { "type": "integer", "minimum": 0 } } } } }
|
|
21
|
+
}
|
|
22
|
+
}
|