@aiwg/cli 2026.7.25 → 2026.8.1
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/README.md +33 -0
- package/agentic/code/providers/capability-matrix.yaml +511 -0
- package/agentic/code/providers/model-capabilities.v1.json +120 -0
- package/agentic/code/providers/model-catalog.v1.json +96 -0
- package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
- package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
- package/bin/aiwg.mjs +14 -10
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/cli.js +55 -10
- package/dist/src/artifacts/fortemi-shard-export.js +107 -18
- package/dist/src/artifacts/types.js +4 -0
- package/dist/src/auth/client.js +209 -0
- package/dist/src/auth/config.js +38 -0
- package/dist/src/auth/credential-store.js +141 -0
- package/dist/src/auth/resource-credentials.js +25 -0
- package/dist/src/auth/types.js +2 -0
- package/dist/src/channel/manager.mjs +5 -5
- package/dist/src/cli/handlers/auth.js +125 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +6 -2
- package/dist/src/cli/handlers/job.js +97 -0
- package/dist/src/cli/handlers/resource-versions.js +2 -0
- package/dist/src/cli/handlers/runtime-info.js +2 -2
- package/dist/src/cli/handlers/serve.js +2 -2
- package/dist/src/cli/handlers/sessions.js +211 -5
- package/dist/src/cli/handlers/steward.js +16 -3
- package/dist/src/cli/handlers/subcommands.js +10 -1
- package/dist/src/cli/handlers/use.js +342 -43
- package/dist/src/config/gitignore.js +1 -0
- package/dist/src/extensions/commands/definitions.js +49 -5
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/features/catalog.js +3 -3
- package/dist/src/jobs/executor.js +83 -0
- package/dist/src/jobs/flow.js +106 -0
- package/dist/src/jobs/gitea.js +91 -0
- package/dist/src/jobs/render.js +53 -0
- package/dist/src/jobs/runner.js +315 -0
- package/dist/src/jobs/types.js +3 -0
- package/dist/src/memory/canonical-context.js +342 -0
- package/dist/src/memory/context-pack.js +282 -0
- package/dist/src/memory/index.js +4 -0
- package/dist/src/memory/intake.js +118 -0
- package/dist/src/providers/capability-matrix.js +11 -4
- package/dist/src/providers/capability-matrix.yaml +39 -42
- package/dist/src/resources/resolver.js +1 -0
- package/dist/src/resources/web-release.d.ts +3 -1
- package/dist/src/resources/web-release.js +14 -6
- package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
- package/dist/src/serve/fleet-mission-conductor.js +293 -0
- package/dist/src/sessions/analytics.js +303 -0
- package/dist/src/sessions/importer.js +7 -1
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/output-registration.js +338 -0
- package/dist/src/sessions/policy.js +1 -1
- package/dist/src/sessions/promotion.js +73 -2
- package/dist/src/sessions/repository.js +215 -1
- package/dist/src/update/notifier.mjs +13 -2
- package/package.json +17 -10
- package/tools/_resolve-impl.mjs +74 -0
- package/tools/agents/deploy-agents.mjs +962 -0
- package/tools/agents/providers/base.mjs +2954 -0
- package/tools/agents/providers/claude.mjs +711 -0
- package/tools/agents/providers/codex.mjs +699 -0
- package/tools/agents/providers/copilot.mjs +659 -0
- package/tools/agents/providers/cursor.mjs +714 -0
- package/tools/agents/providers/factory.mjs +1130 -0
- package/tools/agents/providers/hermes.mjs +663 -0
- package/tools/agents/providers/hook-capabilities.mjs +85 -0
- package/tools/agents/providers/model-role.mjs +56 -0
- package/tools/agents/providers/openclaw-translator.mjs +348 -0
- package/tools/agents/providers/openclaw.mjs +680 -0
- package/tools/agents/providers/opencode.mjs +675 -0
- package/tools/agents/providers/openhuman.mjs +292 -0
- package/tools/agents/providers/warp.mjs +413 -0
- package/tools/agents/providers/windsurf.mjs +748 -0
- package/tools/commands/deploy-prompts-codex.mjs +336 -0
- package/tools/plugin/package-plugins.mjs +1013 -0
- package/tools/skills/deploy-skills-codex.mjs +571 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { constants, promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { CodexJobExecutor } from '../../jobs/executor.js';
|
|
4
|
+
import { loadJobFlow, resolveWorkspaceFile } from '../../jobs/flow.js';
|
|
5
|
+
import { GiteaWorkItemClient } from '../../jobs/gitea.js';
|
|
6
|
+
import { renderExternalTrigger } from '../../jobs/render.js';
|
|
7
|
+
import { runExternalJob } from '../../jobs/runner.js';
|
|
8
|
+
import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js';
|
|
9
|
+
function option(args, name) {
|
|
10
|
+
const index = args.indexOf(name);
|
|
11
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
12
|
+
}
|
|
13
|
+
function usage(message) {
|
|
14
|
+
throw new AiwgError({ code: 'ERR_USAGE_JOB', message, exitCode: EXIT_CODES.USAGE, hint: 'Run `aiwg job help` for usage.' });
|
|
15
|
+
}
|
|
16
|
+
function help() {
|
|
17
|
+
console.log(`AIWG external-trigger jobs
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
aiwg job validate <flow>
|
|
21
|
+
aiwg job render-cron <flow> [--format cron|systemd|gitea-actions]
|
|
22
|
+
aiwg job run <flow> --once [--state-dir <absolute-path>] [--json]
|
|
23
|
+
|
|
24
|
+
The operating system or CI owns time. AIWG validates and executes one reviewed job.`);
|
|
25
|
+
}
|
|
26
|
+
async function validateFiles(flow) {
|
|
27
|
+
const workspace = await fs.realpath(flow.spec.executor.workspace);
|
|
28
|
+
if (workspace !== flow.spec.executor.workspace) {
|
|
29
|
+
throw new Error('executor.workspace must be canonical (no symlink or relative segments)');
|
|
30
|
+
}
|
|
31
|
+
const prompt = resolveWorkspaceFile(flow, flow.spec.executor.prompt);
|
|
32
|
+
const schema = resolveWorkspaceFile(flow, flow.spec.executor.resultSchema);
|
|
33
|
+
const [promptStat, schemaSource] = await Promise.all([fs.stat(prompt), fs.readFile(schema, 'utf8'), fs.access(flow.spec.executor.binary, constants.X_OK)]);
|
|
34
|
+
if (!promptStat.isFile())
|
|
35
|
+
throw new Error('executor.prompt must resolve to a regular file');
|
|
36
|
+
JSON.parse(schemaSource);
|
|
37
|
+
for (const root of flow.spec.security.approvedAttachmentRoots) {
|
|
38
|
+
const [canonical, stat] = await Promise.all([fs.realpath(root), fs.stat(root)]);
|
|
39
|
+
if (canonical !== root || !stat.isDirectory())
|
|
40
|
+
throw new Error('approvedAttachmentRoots must contain canonical directories');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function execute(ctx) {
|
|
44
|
+
try {
|
|
45
|
+
const [subcommand = 'help', flowArg] = ctx.args;
|
|
46
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
47
|
+
help();
|
|
48
|
+
return { exitCode: 0 };
|
|
49
|
+
}
|
|
50
|
+
if (!['validate', 'render-cron', 'run'].includes(subcommand))
|
|
51
|
+
usage(`Unknown job subcommand: ${subcommand}`);
|
|
52
|
+
if (!flowArg || flowArg.startsWith('-'))
|
|
53
|
+
usage(`job ${subcommand} requires a flow file`);
|
|
54
|
+
const loaded = await loadJobFlow(flowArg, ctx.cwd);
|
|
55
|
+
await validateFiles(loaded.flow);
|
|
56
|
+
if (subcommand === 'validate') {
|
|
57
|
+
console.log(JSON.stringify({ valid: true, apiVersion: loaded.flow.apiVersion, kind: loaded.flow.kind, name: loaded.flow.metadata.name }));
|
|
58
|
+
return { exitCode: 0 };
|
|
59
|
+
}
|
|
60
|
+
if (subcommand === 'render-cron') {
|
|
61
|
+
const format = (option(ctx.args.slice(2), '--format') ?? 'cron');
|
|
62
|
+
if (!['cron', 'systemd', 'gitea-actions'].includes(format))
|
|
63
|
+
usage(`Unsupported scheduler format: ${format}`);
|
|
64
|
+
console.log(renderExternalTrigger(loaded.flow, loaded.file, format));
|
|
65
|
+
return { exitCode: 0 };
|
|
66
|
+
}
|
|
67
|
+
if (!ctx.args.includes('--once'))
|
|
68
|
+
usage('job run requires --once; AIWG does not own a resident scheduler');
|
|
69
|
+
const stateRoot = option(ctx.args.slice(2), '--state-dir');
|
|
70
|
+
if (stateRoot && !stateRoot.startsWith('/'))
|
|
71
|
+
usage('--state-dir must be absolute');
|
|
72
|
+
if (stateRoot && path.resolve(stateRoot) === path.parse(path.resolve(stateRoot)).root)
|
|
73
|
+
usage('--state-dir must not be a filesystem root');
|
|
74
|
+
const client = await GiteaWorkItemClient.create(loaded.flow);
|
|
75
|
+
const result = await runExternalJob({
|
|
76
|
+
flow: loaded.flow,
|
|
77
|
+
client,
|
|
78
|
+
executor: new CodexJobExecutor(),
|
|
79
|
+
...(stateRoot ? { stateRoot } : {}),
|
|
80
|
+
signal: ctx.signal,
|
|
81
|
+
});
|
|
82
|
+
console.log(JSON.stringify(result, null, ctx.args.includes('--json') ? 2 : 0));
|
|
83
|
+
return { exitCode: result.status === 'failed-verification' ? EXIT_CODES.GENERAL : EXIT_CODES.OK };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return handlerResultFromError(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export const jobHandler = {
|
|
90
|
+
id: 'job',
|
|
91
|
+
name: 'External Job',
|
|
92
|
+
description: 'Validate, render, or run one externally triggered provider job',
|
|
93
|
+
category: 'orchestration',
|
|
94
|
+
aliases: ['job'],
|
|
95
|
+
execute,
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=job.js.map
|
|
@@ -3,6 +3,7 @@ import { loadResourceTrustRootFile, readVerifiedRegularFile, resolveWebRelease,
|
|
|
3
3
|
import { getProjectDir } from "../../config/aiwg-config.js";
|
|
4
4
|
import { cleanWebResourceCache } from "../../resources/cache-cleanup.js";
|
|
5
5
|
import { writeWebResourceLock } from "../../resources/lockfile.js";
|
|
6
|
+
import { createResourceCredentialProvider } from "../../auth/resource-credentials.js";
|
|
6
7
|
const MAX_RESOURCE_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
7
8
|
const DEFAULT_CHANNELS = ["stable", "latest", "canary", "main"];
|
|
8
9
|
function usage() {
|
|
@@ -82,6 +83,7 @@ function webReleaseOptionsFromEnvironment() {
|
|
|
82
83
|
? undefined
|
|
83
84
|
: loadResourceTrustRootFile(path.resolve(trustRootFile));
|
|
84
85
|
return {
|
|
86
|
+
credentialProvider: createResourceCredentialProvider(process.env),
|
|
85
87
|
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
86
88
|
...(cacheRoot === undefined ? {} : { cacheRoot }),
|
|
87
89
|
...(publicKeyPem === undefined ? {} : { publicKeyPem }),
|
|
@@ -258,7 +258,7 @@ async function handleRuntimeInfo(args, cwd = process.cwd()) {
|
|
|
258
258
|
console.log(`Catalog: ${summary.catalogPath}`);
|
|
259
259
|
// Scheduler backend detection
|
|
260
260
|
const { execSync } = await import('child_process');
|
|
261
|
-
let schedulerBackend = '
|
|
261
|
+
let schedulerBackend = 'external trigger required (system cron/systemd/CI)';
|
|
262
262
|
let chronyInstalled = false;
|
|
263
263
|
try {
|
|
264
264
|
execSync('which chronyc 2>/dev/null || which chronyd 2>/dev/null', { stdio: 'pipe' });
|
|
@@ -273,7 +273,7 @@ async function handleRuntimeInfo(args, cwd = process.cwd()) {
|
|
|
273
273
|
const isClaudeCodeContext = process.env.CLAUDE_CODE_VERSION !== undefined ||
|
|
274
274
|
process.env.ANTHROPIC_API_KEY !== undefined;
|
|
275
275
|
if (isClaudeCodeContext) {
|
|
276
|
-
schedulerBackend = 'native-cron (CronCreate)
|
|
276
|
+
schedulerBackend = 'native-cron (CronCreate); external trigger outside agent sessions';
|
|
277
277
|
}
|
|
278
278
|
console.log(`\nScheduler:`);
|
|
279
279
|
console.log(` Backend: ${schedulerBackend}`);
|
|
@@ -64,7 +64,7 @@ function parseServeArgs(args) {
|
|
|
64
64
|
// ============================================================
|
|
65
65
|
// WebSocket routing (#851)
|
|
66
66
|
//
|
|
67
|
-
// @hono/node-server
|
|
67
|
+
// @hono/node-server does not export createNodeWebSocket.
|
|
68
68
|
// We wire WebSocket routes directly via the Node.js HTTP server's
|
|
69
69
|
// 'upgrade' event and the `ws` npm package instead.
|
|
70
70
|
// ============================================================
|
|
@@ -550,7 +550,7 @@ export async function startServer(opts) {
|
|
|
550
550
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
551
551
|
const app = new Hono();
|
|
552
552
|
// WebSocket routes are handled via Node.js upgrade event below (see setupWebSockets).
|
|
553
|
-
// @hono/node-server
|
|
553
|
+
// @hono/node-server does not export createNodeWebSocket.
|
|
554
554
|
// Health check
|
|
555
555
|
app.get('/api/health', (c) => c.json({ status: 'ok', readOnly: opts.readOnly }));
|
|
556
556
|
// Connection status — server health, PTY sessions, sandboxes, subsystem status (#887)
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, resolve, } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
3
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
5
|
const JSON_CONTRACT_VERSION = '1.0.0';
|
|
6
|
+
async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
|
|
7
|
+
const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
|
|
8
|
+
if (!existsSync(modulePath)) {
|
|
9
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'line-memory promotion adapter is missing from the installed addon');
|
|
10
|
+
}
|
|
11
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
12
|
+
if (!loaded.LineMemoryPromotionDestination) {
|
|
13
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'line-memory addon does not export its promotion adapter');
|
|
14
|
+
}
|
|
15
|
+
return new loaded.LineMemoryPromotionDestination({
|
|
16
|
+
projectRoot,
|
|
17
|
+
consumer: 'line-memory',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
5
20
|
const EXIT = {
|
|
6
21
|
ok: 0, usage: 2, unsupported: 3, unavailable: 4, contract: 5, storage: 6,
|
|
7
22
|
locked: 7, coverage: 8,
|
|
@@ -28,6 +43,8 @@ Commands:
|
|
|
28
43
|
restore <session-id> Restore a reversible catalog tombstone
|
|
29
44
|
purge <session-id> Preview terminal AIWG-copy purge
|
|
30
45
|
audit --workspace <id> Read content-free mutation events
|
|
46
|
+
analytics <view> --workspace <id> Summary, tool-calls, escalations, or HITL
|
|
47
|
+
forensics <view> --workspace <id> Authorized timeline, indicators, or evidence
|
|
31
48
|
doctor Check catalog availability and integrity
|
|
32
49
|
|
|
33
50
|
Options:
|
|
@@ -219,6 +236,95 @@ async function executeCommand(ctx, args) {
|
|
|
219
236
|
nextCursor: result.nextCursor },
|
|
220
237
|
});
|
|
221
238
|
}
|
|
239
|
+
case 'analytics': {
|
|
240
|
+
const view = requiredPositional(args, 0, 'analytics view');
|
|
241
|
+
const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
|
|
242
|
+
const query = analyticsQuery(args, workspaceId);
|
|
243
|
+
if (view === 'summary') {
|
|
244
|
+
return ok(command, {
|
|
245
|
+
view,
|
|
246
|
+
summary: repository.analyticsSummary(query),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const categories = analyticsCategories(view);
|
|
250
|
+
const items = repository.listAnalyticsFacts({ ...query, categories });
|
|
251
|
+
return ok(command, {
|
|
252
|
+
analyticsVersion: '1.0.0',
|
|
253
|
+
view,
|
|
254
|
+
items,
|
|
255
|
+
count: items.length,
|
|
256
|
+
groupBy: analyticsGrouping(items, args.values.get('--group-by')),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
case 'forensics': {
|
|
260
|
+
if (!args.flags.has('--authorize-forensics')) {
|
|
261
|
+
throw new CliError('OPERATION_NOT_AUTHORIZED', 'forensic extraction requires --authorize-forensics for this invocation', EXIT.usage);
|
|
262
|
+
}
|
|
263
|
+
const view = requiredPositional(args, 0, 'forensics view');
|
|
264
|
+
const { workspaceId } = authorizationContext(ctx, args, command);
|
|
265
|
+
const query = analyticsQuery(args, workspaceId);
|
|
266
|
+
if (view === 'indicators') {
|
|
267
|
+
const items = repository.listAnalyticsFacts({
|
|
268
|
+
...query,
|
|
269
|
+
categories: ['indicator'],
|
|
270
|
+
});
|
|
271
|
+
return ok(command, forensicOutput(view, items, args));
|
|
272
|
+
}
|
|
273
|
+
if (view === 'timeline') {
|
|
274
|
+
const target = requiredPositional(args, 1, 'session-id or query');
|
|
275
|
+
const direct = repository.getSession(target, workspaceId);
|
|
276
|
+
const sessionIds = direct
|
|
277
|
+
? [target]
|
|
278
|
+
: [...new Set(repository.search({
|
|
279
|
+
query: target,
|
|
280
|
+
workspaceId,
|
|
281
|
+
limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
|
|
282
|
+
}).items.map((item) => item.sessionId))];
|
|
283
|
+
const items = sessionIds.flatMap((sessionId) => repository.listAnalyticsFacts({ ...query, sessionId }));
|
|
284
|
+
return ok(command, forensicOutput(view, items, args));
|
|
285
|
+
}
|
|
286
|
+
if (view === 'evidence') {
|
|
287
|
+
const id = requiredPositional(args, 1, 'event-id, fact-id, or candidate-id');
|
|
288
|
+
const evidence = repository.getAnalyticsEvidence(id, workspaceId);
|
|
289
|
+
if (evidence.fact && evidence.event) {
|
|
290
|
+
return ok(command, {
|
|
291
|
+
analyticsVersion: '1.0.0',
|
|
292
|
+
view,
|
|
293
|
+
fact: evidence.fact,
|
|
294
|
+
event: {
|
|
295
|
+
eventId: evidence.event.eventId,
|
|
296
|
+
sessionId: evidence.event.sessionId,
|
|
297
|
+
sourceId: evidence.event.sourceId,
|
|
298
|
+
importRunId: evidence.event.importRunId,
|
|
299
|
+
sequence: evidence.event.sequence,
|
|
300
|
+
kind: evidence.event.kind,
|
|
301
|
+
occurredAt: evidence.event.occurredAt,
|
|
302
|
+
sensitivity: evidence.event.sensitivity,
|
|
303
|
+
rawReference: evidence.event.rawReference,
|
|
304
|
+
digest: evidence.event.digest,
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const candidate = repository.getCandidate(id, undefined, workspaceId);
|
|
309
|
+
if (!candidate) {
|
|
310
|
+
throw new CliError('EVIDENCE_NOT_FOUND', `authorized analytics evidence not found: ${id}`, EXIT.unavailable);
|
|
311
|
+
}
|
|
312
|
+
return ok(command, {
|
|
313
|
+
analyticsVersion: '1.0.0',
|
|
314
|
+
view,
|
|
315
|
+
candidate: {
|
|
316
|
+
candidateId: candidate.candidateId,
|
|
317
|
+
version: candidate.version,
|
|
318
|
+
type: candidate.type,
|
|
319
|
+
evidence: candidate.evidence.map(({ quote: _quote, ...citation }) => citation),
|
|
320
|
+
sensitivity: candidate.sensitivity,
|
|
321
|
+
security: candidate.security,
|
|
322
|
+
reviewState: candidate.reviewState,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
throw new CliError('INVALID_ARGUMENT', `unknown forensics view: ${view}`, EXIT.usage);
|
|
327
|
+
}
|
|
222
328
|
case 'extract': {
|
|
223
329
|
const { workspaceId } = authorizationContext(ctx, args, command);
|
|
224
330
|
const sessionId = args.positionals[0];
|
|
@@ -315,11 +421,14 @@ async function executeCommand(ctx, args) {
|
|
|
315
421
|
const candidateId = requiredPositional(args, 0, 'candidate-id');
|
|
316
422
|
const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
|
|
317
423
|
const consumer = requiredValue(args, '--consumer');
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
424
|
+
const manifestPath = resolveMemoryConsumerManifest(ctx.cwd, consumer);
|
|
425
|
+
const destination = consumer === 'line-memory'
|
|
426
|
+
? await createLineMemoryPromotionDestination(ctx.cwd, manifestPath)
|
|
427
|
+
: new FilesystemMemoryDestination({
|
|
428
|
+
projectRoot: ctx.cwd,
|
|
429
|
+
consumer,
|
|
430
|
+
manifestPath,
|
|
431
|
+
});
|
|
323
432
|
const scopedPromotionStore = {
|
|
324
433
|
getCandidate: (id, candidateVersion) => repository.getCandidate(id, candidateVersion, workspaceId),
|
|
325
434
|
getPromotionReceipt: (id, candidateVersion, namedConsumer) => repository.getCandidate(id, candidateVersion, workspaceId)
|
|
@@ -997,6 +1106,7 @@ function parseArgs(argv) {
|
|
|
997
1106
|
'--manifest', '--provider-home', '--codex-root', '--lock-wait-ms', '--min-coverage', '--gap',
|
|
998
1107
|
'--inactivity-threshold',
|
|
999
1108
|
'--control-events',
|
|
1109
|
+
'--session', '--status', '--actor', '--group-by',
|
|
1000
1110
|
]);
|
|
1001
1111
|
let command;
|
|
1002
1112
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -1184,6 +1294,96 @@ function dependentAction(value) {
|
|
|
1184
1294
|
}
|
|
1185
1295
|
return value;
|
|
1186
1296
|
}
|
|
1297
|
+
function analyticsQuery(args, workspaceId) {
|
|
1298
|
+
const providerInput = args.values.get('--provider');
|
|
1299
|
+
const statusInput = args.values.get('--status');
|
|
1300
|
+
return {
|
|
1301
|
+
workspaceId,
|
|
1302
|
+
provider: providerInput ? assertSessionProviderId(providerInput) : undefined,
|
|
1303
|
+
sessionId: args.values.get('--session'),
|
|
1304
|
+
dateFrom: args.values.get('--date-from'),
|
|
1305
|
+
dateTo: args.values.get('--date-to'),
|
|
1306
|
+
tool: args.values.get('--tool'),
|
|
1307
|
+
status: statusInput ? analyticsStatus(statusInput) : undefined,
|
|
1308
|
+
actor: args.values.get('--actor') ?? args.values.get('--participant'),
|
|
1309
|
+
tag: args.values.get('--tag'),
|
|
1310
|
+
sensitivity: args.values.get('--sensitivity'),
|
|
1311
|
+
extractionState: args.values.get('--extraction-state'),
|
|
1312
|
+
limit: boundedInteger(args.values.get('--limit'), 500, 1, 5_000, '--limit'),
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
function analyticsStatus(value) {
|
|
1316
|
+
const allowed = new Set([
|
|
1317
|
+
'requested', 'running', 'succeeded', 'failed', 'granted', 'denied',
|
|
1318
|
+
'timed-out', 'unsupported', 'provider-unknown', 'observed',
|
|
1319
|
+
]);
|
|
1320
|
+
if (!allowed.has(value)) {
|
|
1321
|
+
throw new CliError('INVALID_ARGUMENT', `invalid analytics status: ${value}`, EXIT.usage);
|
|
1322
|
+
}
|
|
1323
|
+
return value;
|
|
1324
|
+
}
|
|
1325
|
+
function analyticsCategories(view) {
|
|
1326
|
+
if (view === 'tool-calls')
|
|
1327
|
+
return ['tool-call', 'tool-result'];
|
|
1328
|
+
if (view === 'escalations')
|
|
1329
|
+
return ['escalation'];
|
|
1330
|
+
if (view === 'hitl')
|
|
1331
|
+
return ['hitl'];
|
|
1332
|
+
throw new CliError('INVALID_ARGUMENT', `unknown analytics view: ${view}`, EXIT.usage);
|
|
1333
|
+
}
|
|
1334
|
+
function analyticsGrouping(items, groupBy) {
|
|
1335
|
+
if (!groupBy)
|
|
1336
|
+
return null;
|
|
1337
|
+
if (!['tool', 'session', 'provider'].includes(groupBy)) {
|
|
1338
|
+
throw new CliError('INVALID_ARGUMENT', `invalid --group-by value: ${groupBy}`, EXIT.usage);
|
|
1339
|
+
}
|
|
1340
|
+
const counts = {};
|
|
1341
|
+
for (const item of items) {
|
|
1342
|
+
const value = groupBy === 'tool'
|
|
1343
|
+
? item.toolName
|
|
1344
|
+
: groupBy === 'session'
|
|
1345
|
+
? item.sessionId
|
|
1346
|
+
: item.provider;
|
|
1347
|
+
const key = typeof value === 'string' && value ? value : '<unknown>';
|
|
1348
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
1349
|
+
}
|
|
1350
|
+
return Object.fromEntries(Object.entries(counts).sort(([left], [right]) => left.localeCompare(right)));
|
|
1351
|
+
}
|
|
1352
|
+
function forensicOutput(view, items, args) {
|
|
1353
|
+
const output = {
|
|
1354
|
+
analyticsVersion: '1.0.0',
|
|
1355
|
+
view,
|
|
1356
|
+
items,
|
|
1357
|
+
count: items.length,
|
|
1358
|
+
authorization: {
|
|
1359
|
+
explicit: true,
|
|
1360
|
+
providerLogsModified: false,
|
|
1361
|
+
historicalContentExecuted: false,
|
|
1362
|
+
},
|
|
1363
|
+
};
|
|
1364
|
+
if (args.flags.has('--markdown')) {
|
|
1365
|
+
output.markdown = [
|
|
1366
|
+
`# Session Forensics ${view}`,
|
|
1367
|
+
'',
|
|
1368
|
+
`Facts: ${items.length}`,
|
|
1369
|
+
'',
|
|
1370
|
+
'| Time | Provider | Session | Category | Status | Evidence |',
|
|
1371
|
+
'|---|---|---|---|---|---|',
|
|
1372
|
+
...items.map((item) => {
|
|
1373
|
+
const citation = item.sourceCitation;
|
|
1374
|
+
return [
|
|
1375
|
+
item.occurredAt ?? '<unknown>',
|
|
1376
|
+
item.provider ?? '<unknown>',
|
|
1377
|
+
item.sessionId ?? '<unknown>',
|
|
1378
|
+
item.category ?? '<unknown>',
|
|
1379
|
+
item.status ?? '<unknown>',
|
|
1380
|
+
citation?.eventId ?? item.eventId ?? '<unknown>',
|
|
1381
|
+
].map((value) => String(value).replaceAll('|', '\\|')).join(' | ');
|
|
1382
|
+
}).map((row) => `| ${row} |`),
|
|
1383
|
+
].join('\n');
|
|
1384
|
+
}
|
|
1385
|
+
return output;
|
|
1386
|
+
}
|
|
1187
1387
|
function isDryRun(ctx, args) {
|
|
1188
1388
|
return Boolean(ctx.dryRun || args.flags.has('--dry-run'));
|
|
1189
1389
|
}
|
|
@@ -1202,6 +1402,12 @@ function emit(value) {
|
|
|
1202
1402
|
function printHuman(value) {
|
|
1203
1403
|
if (value.status === 'preview')
|
|
1204
1404
|
console.log('Preview (no changes applied)');
|
|
1405
|
+
if (value.command === 'sessions.forensics' && value.data
|
|
1406
|
+
&& typeof value.data === 'object' && 'markdown' in value.data
|
|
1407
|
+
&& typeof value.data.markdown === 'string') {
|
|
1408
|
+
console.log(value.data.markdown);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1205
1411
|
if (value.command === 'sessions.timeline' && value.data
|
|
1206
1412
|
&& typeof value.data === 'object' && 'items' in value.data) {
|
|
1207
1413
|
const data = value.data;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { access } from 'node:fs/promises';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js';
|
|
22
|
-
import { loadCapabilityMatrix, } from '../../providers/capability-matrix.js';
|
|
22
|
+
import { loadCapabilityMatrix, isExternalStrategy, } from '../../providers/capability-matrix.js';
|
|
23
23
|
import { getProjectDir, readAiwgConfig, writeAiwgConfig } from '../../config/aiwg-config.js';
|
|
24
24
|
import { auditLegacyPermissions, archiveLegacyPermissionManifests, backupConfig, normalizeProjectPermissions, } from '../../policy/authorization.js';
|
|
25
25
|
import { capabilityProviderId, normalizeProviderId, resolveActiveProvider } from '../provider-resolution.js';
|
|
@@ -48,6 +48,7 @@ async function detectProvider(ctx) {
|
|
|
48
48
|
// ── Formatters ────────────────────────────────────────────────────────────────
|
|
49
49
|
const NATIVE_MARK = '✓ native';
|
|
50
50
|
const EMULATED_MARK = '~ emulated';
|
|
51
|
+
const EXTERNAL_MARK = '↗ external';
|
|
51
52
|
const UNSUPPORTED_MARK = '- not supported';
|
|
52
53
|
function emulationLabel(strategy) {
|
|
53
54
|
if (!strategy)
|
|
@@ -70,7 +71,9 @@ function formatProvider(id, provider, matrix, meta) {
|
|
|
70
71
|
for (const featureId of featureKeys) {
|
|
71
72
|
const isNative = provider.native_features?.[featureId] === true;
|
|
72
73
|
const emulation = provider.emulation?.[featureId] ?? null;
|
|
73
|
-
const status = isNative
|
|
74
|
+
const status = isNative
|
|
75
|
+
? NATIVE_MARK
|
|
76
|
+
: isExternalStrategy(emulation) ? EXTERNAL_MARK : (emulation ? EMULATED_MARK : UNSUPPORTED_MARK);
|
|
74
77
|
const feat = matrix.features[featureId];
|
|
75
78
|
lines.push(`\n ${featureId} — ${status}`);
|
|
76
79
|
if (feat?.description)
|
|
@@ -79,6 +82,9 @@ function formatProvider(id, provider, matrix, meta) {
|
|
|
79
82
|
if (feat?.native_example)
|
|
80
83
|
lines.push(` example: ${feat.native_example}`);
|
|
81
84
|
}
|
|
85
|
+
else if (isExternalStrategy(emulation)) {
|
|
86
|
+
lines.push(` trigger: system cron, systemd timer, or CI; AIWG does not own the clock`);
|
|
87
|
+
}
|
|
82
88
|
else if (emulation) {
|
|
83
89
|
lines.push(` fallback: ${emulationLabel(emulation)}`);
|
|
84
90
|
}
|
|
@@ -319,7 +325,9 @@ async function handleSteward(args, ctx) {
|
|
|
319
325
|
const emulation = provider.emulation?.[featureId] ?? null;
|
|
320
326
|
const status = isNative
|
|
321
327
|
? `✓ native`
|
|
322
|
-
: (emulation
|
|
328
|
+
: isExternalStrategy(emulation)
|
|
329
|
+
? `↗ external trigger (system cron/systemd/CI)`
|
|
330
|
+
: (emulation ? `~ emulated (${emulationLabel(emulation)})` : `- not supported`);
|
|
323
331
|
console.log(` ${provider.display_name.padEnd(20)} (${providerId.padEnd(12)}) ${status}`);
|
|
324
332
|
}
|
|
325
333
|
console.log('');
|
|
@@ -386,6 +394,11 @@ async function handleSteward(args, ctx) {
|
|
|
386
394
|
if (feat.native_example)
|
|
387
395
|
console.log(` Example: ${feat.native_example}`);
|
|
388
396
|
}
|
|
397
|
+
else if (isExternalStrategy(emulation)) {
|
|
398
|
+
console.log(` ↗ Use an external trigger`);
|
|
399
|
+
console.log(` Strategy: system cron, systemd timer, or CI launches a reviewed provider command`);
|
|
400
|
+
console.log(` Boundary: AIWG does not provide or own the clock/scheduler`);
|
|
401
|
+
}
|
|
389
402
|
else if (emulation) {
|
|
390
403
|
console.log(` ~ Use AIWG emulation`);
|
|
391
404
|
console.log(` Strategy: ${emulationLabel(emulation)}`);
|
|
@@ -1147,9 +1147,18 @@ export const packagePluginHandler = {
|
|
|
1147
1147
|
message: "Error: plugin name is required.\n\nRun `aiwg package-plugin --help` for usage.",
|
|
1148
1148
|
};
|
|
1149
1149
|
}
|
|
1150
|
+
const positionalSource = positional && (positional.includes('/') || positional.includes('\\'))
|
|
1151
|
+
? positional
|
|
1152
|
+
: undefined;
|
|
1150
1153
|
const normalizedArgs = hasExplicitPlugin
|
|
1151
1154
|
? ctx.args
|
|
1152
|
-
:
|
|
1155
|
+
: positionalSource
|
|
1156
|
+
? [
|
|
1157
|
+
"--plugin", path.basename(path.resolve(ctx.cwd, positionalSource)),
|
|
1158
|
+
"--source", positionalSource,
|
|
1159
|
+
...ctx.args.slice(1),
|
|
1160
|
+
]
|
|
1161
|
+
: ["--plugin", positional, ...ctx.args.slice(1)];
|
|
1153
1162
|
const frameworkRoot = await getFrameworkRoot();
|
|
1154
1163
|
const runner = createScriptRunner(frameworkRoot);
|
|
1155
1164
|
return runner.run("tools/plugin/package-plugins.mjs", normalizedArgs, {
|