@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
|
@@ -12,7 +12,7 @@ export class MemoryPromotionGateway {
|
|
|
12
12
|
throw new SessionContractError('MALFORMED_SOURCE', 'candidate version does not exist');
|
|
13
13
|
}
|
|
14
14
|
const existing = this.store.getPromotionReceipt(input.candidateId, input.version, input.destination.consumer);
|
|
15
|
-
if (candidate.reviewState !== 'accepted' &&
|
|
15
|
+
if (candidate.reviewState !== 'accepted' && candidate.reviewState !== 'promoted') {
|
|
16
16
|
throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'promotion requires an accepted exact candidate version');
|
|
17
17
|
}
|
|
18
18
|
const security = candidateSecurity(candidate);
|
|
@@ -165,7 +165,7 @@ export class FilesystemPromotionDispositionCoordinator {
|
|
|
165
165
|
if (!decision) {
|
|
166
166
|
throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'every promoted artifact requires an explicit disposition');
|
|
167
167
|
}
|
|
168
|
-
this.
|
|
168
|
+
this.authorizedDispositionRef(dependent.destinationRef);
|
|
169
169
|
return {
|
|
170
170
|
dependentId: dependent.dependentId,
|
|
171
171
|
destinationRef: dependent.destinationRef,
|
|
@@ -194,6 +194,12 @@ export class FilesystemPromotionDispositionCoordinator {
|
|
|
194
194
|
for (const effect of journal.effects) {
|
|
195
195
|
if (effect.outcome !== 'pending')
|
|
196
196
|
continue;
|
|
197
|
+
const lineMemory = this.lineMemoryRef(effect.destinationRef);
|
|
198
|
+
if (lineMemory) {
|
|
199
|
+
effect.outcome = this.applyLineMemoryDisposition(lineMemory.metadataPath, lineMemory.handle, purge.operationId, effect);
|
|
200
|
+
this.writeJournal(journalPath, journal);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
197
203
|
const target = this.authorizedPath(effect.destinationRef);
|
|
198
204
|
const marker = dispositionMarker(purge.operationId, effect);
|
|
199
205
|
if (effect.action === 'delete') {
|
|
@@ -252,6 +258,65 @@ export class FilesystemPromotionDispositionCoordinator {
|
|
|
252
258
|
}
|
|
253
259
|
return target;
|
|
254
260
|
}
|
|
261
|
+
authorizedDispositionRef(destinationRef) {
|
|
262
|
+
if (this.lineMemoryRef(destinationRef))
|
|
263
|
+
return;
|
|
264
|
+
this.authorizedPath(destinationRef);
|
|
265
|
+
}
|
|
266
|
+
lineMemoryRef(destinationRef) {
|
|
267
|
+
const separator = destinationRef.lastIndexOf('#');
|
|
268
|
+
if (separator < 1)
|
|
269
|
+
return null;
|
|
270
|
+
const metadataRef = destinationRef.slice(0, separator);
|
|
271
|
+
const handle = destinationRef.slice(separator + 1);
|
|
272
|
+
if (!/^lm_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(handle)) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return { metadataPath: this.authorizedPath(metadataRef), handle };
|
|
276
|
+
}
|
|
277
|
+
applyLineMemoryDisposition(metadataPath, handle, operationId, effect) {
|
|
278
|
+
if (!existsSync(metadataPath))
|
|
279
|
+
return 'already-applied';
|
|
280
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
281
|
+
if (metadata.schemaVersion !== 'aiwg.line-memory.v1' || !metadata.entries) {
|
|
282
|
+
throw new SessionContractError('IMPORT_CONFLICT', 'line-memory metadata schema is invalid');
|
|
283
|
+
}
|
|
284
|
+
const entry = metadata.entries[handle];
|
|
285
|
+
if (!entry)
|
|
286
|
+
return 'already-applied';
|
|
287
|
+
const priorOperation = entry.disposition?.operationId;
|
|
288
|
+
if (priorOperation === operationId)
|
|
289
|
+
return 'already-applied';
|
|
290
|
+
const memoryRef = metadata.store?.memoryPath ?? '.aiwg/memory/line-memory.txt';
|
|
291
|
+
const memoryPath = this.authorizedPath(memoryRef);
|
|
292
|
+
const lines = existsSync(memoryPath)
|
|
293
|
+
? readFileSync(memoryPath, 'utf8').split(/\r?\n/).filter(Boolean)
|
|
294
|
+
: [];
|
|
295
|
+
const removesActiveFact = ['delete', 'revoke', 'supersede'].includes(effect.action);
|
|
296
|
+
const nextLines = removesActiveFact
|
|
297
|
+
? removeFirstExact(lines, entry.value)
|
|
298
|
+
: lines;
|
|
299
|
+
if (effect.action === 'delete')
|
|
300
|
+
delete metadata.entries[handle];
|
|
301
|
+
else {
|
|
302
|
+
entry.status = effect.action === 'revoke' ? 'revoked'
|
|
303
|
+
: effect.action === 'supersede' ? 'superseded'
|
|
304
|
+
: effect.action === 'origin_unavailable' ? 'origin-unavailable'
|
|
305
|
+
: 'active';
|
|
306
|
+
entry.updatedAt = new Date().toISOString();
|
|
307
|
+
entry.disposition = {
|
|
308
|
+
operationId,
|
|
309
|
+
action: effect.action,
|
|
310
|
+
effect: effect.effect,
|
|
311
|
+
originAvailable: effect.action !== 'origin_unavailable',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (removesActiveFact) {
|
|
315
|
+
this.atomicWrite(memoryPath, nextLines.length ? `${nextLines.join('\n')}\n` : '');
|
|
316
|
+
}
|
|
317
|
+
this.atomicWrite(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
|
|
318
|
+
return 'applied';
|
|
319
|
+
}
|
|
255
320
|
journalPath(operationId) {
|
|
256
321
|
return resolve(this.journalRoot, `${operationId.replace(':', '-')}.json`);
|
|
257
322
|
}
|
|
@@ -290,6 +355,12 @@ function requireJournalFiles(root) {
|
|
|
290
355
|
.sort()
|
|
291
356
|
.map((name) => resolve(root, name));
|
|
292
357
|
}
|
|
358
|
+
function removeFirstExact(lines, value) {
|
|
359
|
+
const index = lines.indexOf(value);
|
|
360
|
+
return index < 0
|
|
361
|
+
? [...lines]
|
|
362
|
+
: lines.filter((_, candidate) => candidate !== index);
|
|
363
|
+
}
|
|
293
364
|
export function resolveMemoryConsumerManifest(projectRoot, consumer) {
|
|
294
365
|
const safeConsumer = assertConsumerId(consumer);
|
|
295
366
|
const candidates = [
|
|
@@ -2,6 +2,7 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { CandidateReviewReceiptSchema, DeletionReceiptSchema, IntelligenceCandidateSchema, PromotionDependencyDecisionSchema, PromotionReceiptSchema, SessionContractError, sha256, } from './contracts.js';
|
|
3
3
|
import { coverageFromBatchRun, } from './batch-contracts.js';
|
|
4
4
|
import { redactSessionText, sanitizeNativeExtensions } from './policy.js';
|
|
5
|
+
import { deriveSessionAnalytics, } from './analytics.js';
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
const POLICY_PROVIDER_MIGRATION = 'policy-provider-identity:v2';
|
|
7
8
|
const EVENT_ORIGIN_INTENT_MIGRATION = 'event-origin-intent:v1';
|
|
@@ -103,10 +104,23 @@ export class SessionRepository {
|
|
|
103
104
|
CREATE TABLE IF NOT EXISTS session_catalog_meta (
|
|
104
105
|
key TEXT PRIMARY KEY, value TEXT NOT NULL
|
|
105
106
|
);
|
|
107
|
+
CREATE TABLE IF NOT EXISTS session_analytics_facts (
|
|
108
|
+
fact_id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL,
|
|
109
|
+
session_id TEXT NOT NULL, event_id TEXT NOT NULL,
|
|
110
|
+
category TEXT NOT NULL, status TEXT NOT NULL,
|
|
111
|
+
provider TEXT NOT NULL, tool_name TEXT, occurred_at TEXT,
|
|
112
|
+
data TEXT NOT NULL,
|
|
113
|
+
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
|
|
114
|
+
FOREIGN KEY(event_id) REFERENCES session_events(event_id) ON DELETE CASCADE
|
|
115
|
+
);
|
|
106
116
|
CREATE INDEX IF NOT EXISTS idx_session_workspace_provider
|
|
107
117
|
ON sessions(workspace_id, source_id, lifecycle);
|
|
108
118
|
CREATE INDEX IF NOT EXISTS idx_event_session_sequence
|
|
109
119
|
ON session_events(session_id, sequence_no);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_session_analytics_scope
|
|
121
|
+
ON session_analytics_facts(
|
|
122
|
+
workspace_id, category, provider, status, occurred_at, fact_id
|
|
123
|
+
);
|
|
110
124
|
CREATE VIRTUAL TABLE IF NOT EXISTS session_event_fts USING fts5(
|
|
111
125
|
event_id UNINDEXED, searchable_text, tokenize='unicode61'
|
|
112
126
|
);
|
|
@@ -139,6 +153,23 @@ export class SessionRepository {
|
|
|
139
153
|
this.alignSessionFtsRowids();
|
|
140
154
|
this.migrateSessionPolicyAndProviderIdentity();
|
|
141
155
|
this.migrateEventOriginAndIntent();
|
|
156
|
+
this.alignSessionAnalytics();
|
|
157
|
+
}
|
|
158
|
+
alignSessionAnalytics() {
|
|
159
|
+
const eventCount = Number(this.db.prepare(`SELECT COUNT(*) AS count
|
|
160
|
+
FROM session_events e
|
|
161
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
162
|
+
JOIN sessions s ON s.session_id=e.session_id
|
|
163
|
+
WHERE r.status='committed' AND s.lifecycle!='tombstoned'`).get()?.count ?? 0);
|
|
164
|
+
const factSessions = Number(this.db.prepare('SELECT COUNT(DISTINCT session_id) AS count FROM session_analytics_facts').get()?.count ?? 0);
|
|
165
|
+
const sourceSessions = Number(this.db.prepare(`SELECT COUNT(DISTINCT e.session_id) AS count
|
|
166
|
+
FROM session_events e
|
|
167
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
168
|
+
JOIN sessions s ON s.session_id=e.session_id
|
|
169
|
+
WHERE r.status='committed' AND s.lifecycle!='tombstoned'`).get()?.count ?? 0);
|
|
170
|
+
if (eventCount === 0 || factSessions === sourceSessions)
|
|
171
|
+
return;
|
|
172
|
+
this.rebuildAnalytics();
|
|
142
173
|
}
|
|
143
174
|
alignSessionFtsRowids() {
|
|
144
175
|
const eventCount = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_events').get()?.count ?? 0);
|
|
@@ -470,6 +501,11 @@ export class SessionRepository {
|
|
|
470
501
|
}
|
|
471
502
|
const outcome = publish ? 'committed' : 'staged';
|
|
472
503
|
this.db.prepare(`UPDATE import_runs SET status=?, checkpoint=? WHERE import_run_id=?`).run(outcome, JSON.stringify(checkpoint), batch.run.importRunId);
|
|
504
|
+
if (publish) {
|
|
505
|
+
for (const session of batch.sessions) {
|
|
506
|
+
this.rebuildAnalytics(session.workspaceId, session.sessionId);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
473
509
|
const counts = { sessions: sessionsInserted, events: eventsInserted };
|
|
474
510
|
this.db.prepare(`INSERT INTO import_receipts(operation_id, import_run_id, outcome, counts, checkpoint)
|
|
475
511
|
VALUES (?, ?, ?, ?, ?)`).run(batch.run.importRunId, batch.run.importRunId, outcome, JSON.stringify(counts), JSON.stringify(checkpoint));
|
|
@@ -525,6 +561,8 @@ export class SessionRepository {
|
|
|
525
561
|
this.db.prepare(`UPDATE mutation_audit SET outcome='committed', occurred_at=?, data=?
|
|
526
562
|
WHERE operation_id=?`).run(updated.observedAt, JSON.stringify(updated), operationId);
|
|
527
563
|
}
|
|
564
|
+
if (result.changes > 0)
|
|
565
|
+
this.rebuildAnalytics();
|
|
528
566
|
return result.changes;
|
|
529
567
|
});
|
|
530
568
|
return commit();
|
|
@@ -561,6 +599,8 @@ export class SessionRepository {
|
|
|
561
599
|
this.db.prepare(`UPDATE mutation_audit SET outcome='committed', occurred_at=?, data=?
|
|
562
600
|
WHERE operation_id=?`).run(updated.observedAt, JSON.stringify(updated), operationId);
|
|
563
601
|
}
|
|
602
|
+
if (result.changes > 0)
|
|
603
|
+
this.rebuildAnalytics();
|
|
564
604
|
return result.changes;
|
|
565
605
|
});
|
|
566
606
|
return commit();
|
|
@@ -876,6 +916,163 @@ export class SessionRepository {
|
|
|
876
916
|
snapshotRowid,
|
|
877
917
|
};
|
|
878
918
|
}
|
|
919
|
+
rebuildAnalytics(workspaceId, sessionId) {
|
|
920
|
+
const where = [
|
|
921
|
+
`r.status='committed'`,
|
|
922
|
+
`s.lifecycle!='tombstoned'`,
|
|
923
|
+
];
|
|
924
|
+
const params = [];
|
|
925
|
+
if (workspaceId) {
|
|
926
|
+
where.push('s.workspace_id=?');
|
|
927
|
+
params.push(workspaceId);
|
|
928
|
+
}
|
|
929
|
+
if (sessionId) {
|
|
930
|
+
where.push('s.session_id=?');
|
|
931
|
+
params.push(sessionId);
|
|
932
|
+
}
|
|
933
|
+
const sessions = this.db.prepare(`SELECT s.session_id, s.data
|
|
934
|
+
FROM sessions s
|
|
935
|
+
WHERE s.lifecycle!='tombstoned'
|
|
936
|
+
${workspaceId ? 'AND s.workspace_id=?' : ''}
|
|
937
|
+
${sessionId ? 'AND s.session_id=?' : ''}
|
|
938
|
+
AND EXISTS (
|
|
939
|
+
SELECT 1 FROM session_events e
|
|
940
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
941
|
+
WHERE e.session_id=s.session_id AND r.status='committed'
|
|
942
|
+
)
|
|
943
|
+
ORDER BY s.session_id`).all(...[
|
|
944
|
+
...(workspaceId ? [workspaceId] : []),
|
|
945
|
+
...(sessionId ? [sessionId] : []),
|
|
946
|
+
]);
|
|
947
|
+
if (!workspaceId && !sessionId) {
|
|
948
|
+
this.db.exec('DELETE FROM session_analytics_facts');
|
|
949
|
+
}
|
|
950
|
+
else if (sessionId) {
|
|
951
|
+
this.db.prepare('DELETE FROM session_analytics_facts WHERE session_id=?').run(sessionId);
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
this.db.prepare('DELETE FROM session_analytics_facts WHERE workspace_id=?').run(workspaceId);
|
|
955
|
+
}
|
|
956
|
+
const eventQuery = this.db.prepare(`SELECT e.data
|
|
957
|
+
FROM session_events e
|
|
958
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
959
|
+
JOIN sessions s ON s.session_id=e.session_id
|
|
960
|
+
WHERE e.session_id=? AND ${where.join(' AND ')}
|
|
961
|
+
ORDER BY e.sequence_no, e.event_id`);
|
|
962
|
+
const insert = this.db.prepare(`INSERT OR REPLACE INTO session_analytics_facts(
|
|
963
|
+
fact_id, workspace_id, session_id, event_id, category, status,
|
|
964
|
+
provider, tool_name, occurred_at, data
|
|
965
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
966
|
+
let inserted = 0;
|
|
967
|
+
for (const row of sessions) {
|
|
968
|
+
const session = JSON.parse(String(row.data));
|
|
969
|
+
const events = eventQuery.all(session.sessionId, ...params)
|
|
970
|
+
.map((eventRow) => JSON.parse(String(eventRow.data)));
|
|
971
|
+
for (const fact of deriveSessionAnalytics(session, events)) {
|
|
972
|
+
inserted += insert.run(fact.factId, fact.workspaceId, fact.sessionId, fact.eventId, fact.category, fact.status, fact.provider, fact.toolName, fact.occurredAt, JSON.stringify(fact)).changes;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return inserted;
|
|
976
|
+
}
|
|
977
|
+
listAnalyticsFacts(options) {
|
|
978
|
+
const where = [
|
|
979
|
+
'f.workspace_id=?',
|
|
980
|
+
`s.lifecycle!='tombstoned'`,
|
|
981
|
+
`r.status='committed'`,
|
|
982
|
+
];
|
|
983
|
+
const params = [options.workspaceId];
|
|
984
|
+
if (options.categories?.length) {
|
|
985
|
+
where.push(`f.category IN (${options.categories.map(() => '?').join(', ')})`);
|
|
986
|
+
params.push(...options.categories);
|
|
987
|
+
}
|
|
988
|
+
const scalar = [
|
|
989
|
+
['f.provider', options.provider],
|
|
990
|
+
['f.session_id', options.sessionId],
|
|
991
|
+
['f.tool_name', options.tool],
|
|
992
|
+
['f.status', options.status],
|
|
993
|
+
[`json_extract(f.data, '$.actor')`, options.actor],
|
|
994
|
+
[`json_extract(f.data, '$.sensitivity')`, options.sensitivity],
|
|
995
|
+
[`json_extract(f.data, '$.extractionState')`, options.extractionState],
|
|
996
|
+
];
|
|
997
|
+
for (const [column, value] of scalar) {
|
|
998
|
+
if (value === undefined)
|
|
999
|
+
continue;
|
|
1000
|
+
where.push(`${column}=?`);
|
|
1001
|
+
params.push(value);
|
|
1002
|
+
}
|
|
1003
|
+
if (options.dateFrom) {
|
|
1004
|
+
where.push('f.occurred_at>=?');
|
|
1005
|
+
params.push(options.dateFrom);
|
|
1006
|
+
}
|
|
1007
|
+
if (options.dateTo) {
|
|
1008
|
+
where.push('f.occurred_at<=?');
|
|
1009
|
+
params.push(options.dateTo);
|
|
1010
|
+
}
|
|
1011
|
+
if (options.tag) {
|
|
1012
|
+
where.push(`EXISTS (
|
|
1013
|
+
SELECT 1 FROM session_tags t
|
|
1014
|
+
WHERE t.session_id=f.session_id AND t.tag=?
|
|
1015
|
+
)`);
|
|
1016
|
+
params.push(options.tag);
|
|
1017
|
+
}
|
|
1018
|
+
const limit = options.limit ?? 500;
|
|
1019
|
+
if (limit < 1 || limit > 5_000) {
|
|
1020
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'analytics limit must be between 1 and 5000');
|
|
1021
|
+
}
|
|
1022
|
+
return this.db.prepare(`SELECT f.data
|
|
1023
|
+
FROM session_analytics_facts f
|
|
1024
|
+
JOIN sessions s ON s.session_id=f.session_id
|
|
1025
|
+
JOIN session_events e ON e.event_id=f.event_id
|
|
1026
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
1027
|
+
WHERE ${where.join(' AND ')}
|
|
1028
|
+
ORDER BY
|
|
1029
|
+
CASE WHEN f.occurred_at IS NULL THEN 1 ELSE 0 END,
|
|
1030
|
+
f.occurred_at, f.session_id,
|
|
1031
|
+
json_extract(f.data, '$.sequence'), f.fact_id
|
|
1032
|
+
LIMIT ?`).all(...params, limit)
|
|
1033
|
+
.map((row) => JSON.parse(String(row.data)));
|
|
1034
|
+
}
|
|
1035
|
+
analyticsSummary(options) {
|
|
1036
|
+
const facts = this.listAnalyticsFacts({ ...options, limit: options.limit ?? 5_000 });
|
|
1037
|
+
const byCategory = countBy(facts, (fact) => fact.category);
|
|
1038
|
+
const byStatus = countBy(facts, (fact) => fact.status);
|
|
1039
|
+
const byProvider = countBy(facts, (fact) => fact.provider);
|
|
1040
|
+
const byTool = countBy(facts.filter((fact) => fact.toolName), (fact) => fact.toolName);
|
|
1041
|
+
return {
|
|
1042
|
+
analyticsVersion: '1.0.0',
|
|
1043
|
+
workspaceId: options.workspaceId,
|
|
1044
|
+
totals: {
|
|
1045
|
+
facts: facts.length,
|
|
1046
|
+
sessions: new Set(facts.map((fact) => fact.sessionId)).size,
|
|
1047
|
+
toolCalls: facts.filter((fact) => fact.category === 'tool-call').length,
|
|
1048
|
+
toolFailures: facts.filter((fact) => fact.category === 'tool-result' && fact.status === 'failed').length,
|
|
1049
|
+
escalations: facts.filter((fact) => fact.category === 'escalation').length,
|
|
1050
|
+
hitlDecisions: facts.filter((fact) => fact.category === 'hitl').length,
|
|
1051
|
+
indicators: facts.filter((fact) => fact.category === 'indicator').length,
|
|
1052
|
+
retryGroups: new Set(facts.map((fact) => fact.retryGroupId).filter(Boolean)).size,
|
|
1053
|
+
},
|
|
1054
|
+
byCategory,
|
|
1055
|
+
byStatus,
|
|
1056
|
+
byProvider,
|
|
1057
|
+
byTool,
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
getAnalyticsEvidence(id, workspaceId) {
|
|
1061
|
+
const row = this.db.prepare(`SELECT f.data AS fact_data, e.data AS event_data
|
|
1062
|
+
FROM session_analytics_facts f
|
|
1063
|
+
JOIN sessions s ON s.session_id=f.session_id
|
|
1064
|
+
JOIN session_events e ON e.event_id=f.event_id
|
|
1065
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
1066
|
+
WHERE (f.fact_id=? OR f.event_id=?)
|
|
1067
|
+
AND f.workspace_id=? AND s.lifecycle!='tombstoned' AND r.status='committed'
|
|
1068
|
+
ORDER BY f.fact_id LIMIT 1`).get(id, id, workspaceId);
|
|
1069
|
+
return row
|
|
1070
|
+
? {
|
|
1071
|
+
fact: JSON.parse(String(row.fact_data)),
|
|
1072
|
+
event: JSON.parse(String(row.event_data)),
|
|
1073
|
+
}
|
|
1074
|
+
: { fact: null, event: null };
|
|
1075
|
+
}
|
|
879
1076
|
saveCandidates(candidates) {
|
|
880
1077
|
const save = this.db.transaction(() => {
|
|
881
1078
|
let inserted = 0;
|
|
@@ -1022,7 +1219,8 @@ export class SessionRepository {
|
|
|
1022
1219
|
if (existing)
|
|
1023
1220
|
return { ...existing, duplicate: true };
|
|
1024
1221
|
const candidate = this.getCandidate(receipt.candidateId, receipt.candidateVersion);
|
|
1025
|
-
if (!candidate
|
|
1222
|
+
if (!candidate
|
|
1223
|
+
|| (candidate.reviewState !== 'accepted' && candidate.reviewState !== 'promoted')) {
|
|
1026
1224
|
throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'promotion requires an accepted exact candidate version');
|
|
1027
1225
|
}
|
|
1028
1226
|
const evidenceIds = [...new Set(candidate.evidence.map((item) => item.eventId))].sort();
|
|
@@ -1161,6 +1359,9 @@ export class SessionRepository {
|
|
|
1161
1359
|
counts: { sessions: 1 },
|
|
1162
1360
|
});
|
|
1163
1361
|
}
|
|
1362
|
+
if (changed) {
|
|
1363
|
+
this.db.prepare('DELETE FROM session_analytics_facts WHERE session_id=?').run(sessionId);
|
|
1364
|
+
}
|
|
1164
1365
|
return changed;
|
|
1165
1366
|
});
|
|
1166
1367
|
return tombstone();
|
|
@@ -1187,6 +1388,8 @@ export class SessionRepository {
|
|
|
1187
1388
|
counts: { sessions: 1 },
|
|
1188
1389
|
});
|
|
1189
1390
|
}
|
|
1391
|
+
if (changed)
|
|
1392
|
+
this.rebuildAnalytics(workspaceId, sessionId);
|
|
1190
1393
|
return changed;
|
|
1191
1394
|
});
|
|
1192
1395
|
return restore();
|
|
@@ -1385,6 +1588,7 @@ export class SessionRepository {
|
|
|
1385
1588
|
dependencyDispositions: validatedDecisions.length,
|
|
1386
1589
|
},
|
|
1387
1590
|
});
|
|
1591
|
+
this.db.prepare('DELETE FROM session_analytics_facts WHERE session_id=?').run(input.preview.sessionId);
|
|
1388
1592
|
return receipt;
|
|
1389
1593
|
});
|
|
1390
1594
|
return apply();
|
|
@@ -1400,6 +1604,7 @@ export class SessionRepository {
|
|
|
1400
1604
|
SELECT rowid, event_id, json_extract(data, '$.searchableText') FROM session_events
|
|
1401
1605
|
`);
|
|
1402
1606
|
indexed = Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_events').get()?.count ?? 0);
|
|
1607
|
+
this.rebuildAnalytics();
|
|
1403
1608
|
}
|
|
1404
1609
|
else {
|
|
1405
1610
|
this.db.prepare(`DELETE FROM session_event_fts WHERE rowid IN (
|
|
@@ -1412,6 +1617,7 @@ export class SessionRepository {
|
|
|
1412
1617
|
FROM session_events e
|
|
1413
1618
|
JOIN sessions s ON s.session_id=e.session_id
|
|
1414
1619
|
WHERE s.workspace_id=?`).run(workspaceId).changes;
|
|
1620
|
+
this.rebuildAnalytics(workspaceId);
|
|
1415
1621
|
}
|
|
1416
1622
|
this.emitRepositoryMutation({
|
|
1417
1623
|
operationId: sha256([
|
|
@@ -1506,6 +1712,14 @@ function canonicalDevinLocator(value) {
|
|
|
1506
1712
|
? `devin-desktop-${value.slice('windsurf-'.length)}`
|
|
1507
1713
|
: value;
|
|
1508
1714
|
}
|
|
1715
|
+
function countBy(items, key) {
|
|
1716
|
+
const counts = {};
|
|
1717
|
+
for (const item of items) {
|
|
1718
|
+
const value = key(item);
|
|
1719
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
1720
|
+
}
|
|
1721
|
+
return Object.fromEntries(Object.entries(counts).sort(([left], [right]) => left.localeCompare(right)));
|
|
1722
|
+
}
|
|
1509
1723
|
function encodeMutationCursor(rowid, workspaceId) {
|
|
1510
1724
|
const unsigned = { rowid, workspaceId };
|
|
1511
1725
|
return Buffer.from(JSON.stringify({
|
|
@@ -157,6 +157,16 @@ function readCurrentVersion(packageRoot) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Return true when a cached check belongs to the package currently handling
|
|
162
|
+
* the command. A global launcher can dispatch into a newer development
|
|
163
|
+
* checkout, so cache age alone is not sufficient.
|
|
164
|
+
*/
|
|
165
|
+
export function cacheMatchesPackage(cache, packageRoot) {
|
|
166
|
+
const currentVersion = readCurrentVersion(packageRoot);
|
|
167
|
+
return !!(currentVersion && cache?.current === currentVersion);
|
|
168
|
+
}
|
|
169
|
+
|
|
160
170
|
/**
|
|
161
171
|
* Run the actual update check and write the cache file. Invoked by the
|
|
162
172
|
* spawned background child via `node src/update/notifier.mjs --check`.
|
|
@@ -183,7 +193,7 @@ export function scheduleBackgroundCheck(packageRoot) {
|
|
|
183
193
|
if (isDisabled()) return;
|
|
184
194
|
|
|
185
195
|
const cache = readCache();
|
|
186
|
-
if (cache?.lastCheckAt) {
|
|
196
|
+
if (cacheMatchesPackage(cache, packageRoot) && cache?.lastCheckAt) {
|
|
187
197
|
const age = Date.now() - new Date(cache.lastCheckAt).getTime();
|
|
188
198
|
if (age < CHECK_INTERVAL_MS) return; // recent enough — skip
|
|
189
199
|
}
|
|
@@ -213,9 +223,10 @@ export function scheduleBackgroundCheck(packageRoot) {
|
|
|
213
223
|
* is interactive, print a single-line notice to stderr. Never prompts.
|
|
214
224
|
* Safe to call unconditionally from `bin/aiwg.mjs` — gated by isDisabled().
|
|
215
225
|
*/
|
|
216
|
-
export function maybePrintNotice() {
|
|
226
|
+
export function maybePrintNotice(packageRoot) {
|
|
217
227
|
if (isDisabled()) return;
|
|
218
228
|
const cache = readCache();
|
|
229
|
+
if (!cacheMatchesPackage(cache, packageRoot)) return;
|
|
219
230
|
if (!cache?.hasUpdate || !cache.current || !cache.latest) return;
|
|
220
231
|
// One-line, non-intrusive. Users who want to update run `aiwg update`.
|
|
221
232
|
const msg = `aiwg: update available ${cache.current} → ${cache.latest} (run: aiwg update)`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwg/cli",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.8.1",
|
|
4
4
|
"description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,12 +25,19 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"LICENSE",
|
|
27
27
|
"bin/",
|
|
28
|
+
"agentic/code/providers/",
|
|
28
29
|
"dist/src/",
|
|
29
30
|
"!dist/**/*.d.ts",
|
|
30
31
|
"!dist/**/*.map",
|
|
31
32
|
"dist/src/api/index.d.ts",
|
|
32
33
|
"dist/src/resources/index.d.ts",
|
|
33
34
|
"dist/src/resources/web-release.d.ts",
|
|
35
|
+
"tools/_resolve-impl.mjs",
|
|
36
|
+
"tools/agents/deploy-agents.mjs",
|
|
37
|
+
"tools/agents/providers/",
|
|
38
|
+
"tools/commands/deploy-prompts-codex.mjs",
|
|
39
|
+
"tools/plugin/package-plugins.mjs",
|
|
40
|
+
"tools/skills/deploy-skills-codex.mjs",
|
|
34
41
|
"README.md"
|
|
35
42
|
],
|
|
36
43
|
"repository": {
|
|
@@ -56,23 +63,23 @@
|
|
|
56
63
|
"node": ">=20.0.0"
|
|
57
64
|
},
|
|
58
65
|
"dependencies": {
|
|
59
|
-
"@fortemi/core": "2026.7.
|
|
60
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
66
|
+
"@fortemi/core": "2026.7.15",
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
61
68
|
"chalk": "^4.1.2",
|
|
62
|
-
"chokidar": "^
|
|
69
|
+
"chokidar": "^4.0.3",
|
|
63
70
|
"commander": "^12.1.0",
|
|
64
|
-
"glob": "^13.0.
|
|
71
|
+
"glob": "^13.0.6",
|
|
65
72
|
"graceful-fs": "^4.2.11",
|
|
66
|
-
"js-yaml": "^4.
|
|
73
|
+
"js-yaml": "^4.3.0",
|
|
67
74
|
"listr2": "^8.2.5",
|
|
68
75
|
"ora": "^5.4.1",
|
|
69
76
|
"semver": "^7.8.5",
|
|
70
|
-
"yaml": "^2.
|
|
77
|
+
"yaml": "^2.9.0",
|
|
71
78
|
"zod": "^3.25.0"
|
|
72
79
|
},
|
|
73
80
|
"optionalDependencies": {
|
|
74
|
-
"@hono/node-server": "
|
|
75
|
-
"hono": "^4.12.
|
|
76
|
-
"ws": "^8.
|
|
81
|
+
"@hono/node-server": "2.0.11",
|
|
82
|
+
"hono": "^4.12.31",
|
|
83
|
+
"ws": "^8.21.1"
|
|
77
84
|
}
|
|
78
85
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, extname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
function findPackageRoot(callerUrl) {
|
|
6
|
+
let dir = dirname(fileURLToPath(callerUrl));
|
|
7
|
+
for (let i = 0; i < 12; i += 1) {
|
|
8
|
+
const pkgPath = join(dir, 'package.json');
|
|
9
|
+
if (existsSync(pkgPath)) {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
12
|
+
if (pkg.name === 'aiwg' || pkg.name === '@aiwg/cli') return dir;
|
|
13
|
+
} catch {
|
|
14
|
+
// Keep walking.
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parent = dirname(dir);
|
|
19
|
+
if (parent === dir) break;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
throw new Error(`Could not locate AIWG package root from ${callerUrl}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function candidatesFor(root, relativeFromSrc, base) {
|
|
27
|
+
const primary = resolve(root, base, relativeFromSrc);
|
|
28
|
+
const candidates = [primary];
|
|
29
|
+
|
|
30
|
+
if (base === 'src' && extname(primary) === '.js') {
|
|
31
|
+
candidates.push(primary.slice(0, -3) + '.ts');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return candidates;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveCandidate(root, relativeFromSrc, base) {
|
|
38
|
+
for (const candidate of candidatesFor(root, relativeFromSrc, base)) {
|
|
39
|
+
if (existsSync(candidate)) return candidate;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a module that may live under compiled dist/src/ or source src/.
|
|
46
|
+
*
|
|
47
|
+
* Prefers dist/src/ when present. Set AIWG_RESOLVE_IMPL_FROM=dist or src to
|
|
48
|
+
* force a side during CI/package-layout checks.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveImpl(callerUrl, relativeFromSrc) {
|
|
51
|
+
const root = findPackageRoot(callerUrl);
|
|
52
|
+
const forced = process.env.AIWG_RESOLVE_IMPL_FROM;
|
|
53
|
+
|
|
54
|
+
const order = forced === 'src'
|
|
55
|
+
? ['src']
|
|
56
|
+
: forced === 'dist'
|
|
57
|
+
? ['dist/src']
|
|
58
|
+
: ['dist/src', 'src'];
|
|
59
|
+
|
|
60
|
+
for (const base of order) {
|
|
61
|
+
const candidate = resolveCandidate(root, relativeFromSrc, base);
|
|
62
|
+
if (candidate) return candidate;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const attempted = order
|
|
66
|
+
.flatMap((base) => candidatesFor(root, relativeFromSrc, base))
|
|
67
|
+
.join(', ');
|
|
68
|
+
throw new Error(`Could not resolve ${relativeFromSrc}; attempted: ${attempted}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function importImpl(callerUrl, relativeFromSrc) {
|
|
72
|
+
const modulePath = resolveImpl(callerUrl, relativeFromSrc);
|
|
73
|
+
return import(pathToFileURL(modulePath).href);
|
|
74
|
+
}
|