@aiwg/cli 2026.8.0 → 2026.8.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/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 +2 -0
- 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 +3 -1
- package/dist/src/cli/handlers/install.js +42 -4
- package/dist/src/cli/handlers/marketplace.js +375 -122
- package/dist/src/cli/handlers/resource-versions.js +2 -0
- package/dist/src/cli/handlers/sessions.js +23 -5
- 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 +19 -0
- package/dist/src/marketplace/exchange.js +602 -0
- package/dist/src/marketplace/provenance-types.js +19 -0
- package/dist/src/marketplace/provenance.js +834 -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/packages/adapters/git.js +79 -29
- package/dist/src/packages/package-discovery.js +81 -0
- package/dist/src/packages/package-registry.js +2 -0
- package/dist/src/packages/registry.js +119 -20
- 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/index.js +1 -0
- package/dist/src/sessions/output-registration.js +338 -0
- package/dist/src/sessions/promotion.js +73 -2
- package/dist/src/sessions/repository.js +2 -1
- package/dist/src/update/notifier.mjs +13 -2
- package/package.json +8 -1
- 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,338 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { dirname, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { SessionContractError, sha256 } from './contracts.js';
|
|
6
|
+
const DigestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/);
|
|
7
|
+
const ReferenceSchema = z.string().min(1).max(512).refine(value => !/[\r\n\0]/.test(value), 'references must be single-line inert locators');
|
|
8
|
+
export const OutputSourceReferenceSchema = z.object({
|
|
9
|
+
kind: z.enum(['file', 'url', 'note', 'session', 'artifact', 'context-pack']),
|
|
10
|
+
ref: ReferenceSchema,
|
|
11
|
+
digest: DigestSchema.nullable().default(null),
|
|
12
|
+
span: z.object({
|
|
13
|
+
start: z.number().int().nonnegative(),
|
|
14
|
+
end: z.number().int().positive(),
|
|
15
|
+
quoteDigest: DigestSchema,
|
|
16
|
+
}).strict().nullable().default(null),
|
|
17
|
+
}).strict().superRefine((value, context) => {
|
|
18
|
+
if (value.span && value.span.end <= value.span.start) {
|
|
19
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: 'source span end must exceed start' });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
export const OutputRegistrationRequestSchema = z.object({
|
|
23
|
+
outputPath: z.string().min(1),
|
|
24
|
+
mediaType: z.string().min(1).max(128),
|
|
25
|
+
contextPack: z.object({
|
|
26
|
+
id: ReferenceSchema,
|
|
27
|
+
digest: DigestSchema,
|
|
28
|
+
sources: z.array(OutputSourceReferenceSchema).min(1).max(256),
|
|
29
|
+
}).strict(),
|
|
30
|
+
supersedes: z.array(ReferenceSchema).max(128).default([]),
|
|
31
|
+
conflictsWith: z.array(ReferenceSchema).max(128).default([]),
|
|
32
|
+
}).strict();
|
|
33
|
+
/**
|
|
34
|
+
* Minimal incremental index sink. Each registration is independently atomic,
|
|
35
|
+
* discoverable, and replay-safe; corpus-wide index builders can consume these
|
|
36
|
+
* bounded records without rescanning output bodies.
|
|
37
|
+
*/
|
|
38
|
+
export class FilesystemDerivedOutputIndex {
|
|
39
|
+
root;
|
|
40
|
+
constructor(projectRoot) {
|
|
41
|
+
this.root = resolve(projectRoot, '.aiwg/memory/output-registration/index');
|
|
42
|
+
assertStorageRootInsideProject(projectRoot, this.root);
|
|
43
|
+
}
|
|
44
|
+
register(registration) {
|
|
45
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(registration.registrationId)) {
|
|
46
|
+
throw new Error('invalid output registration identity');
|
|
47
|
+
}
|
|
48
|
+
const filePath = resolve(this.root, `${registration.registrationId.replace(':', '_')}.json`);
|
|
49
|
+
const existing = readJsonIfPresent(filePath);
|
|
50
|
+
if (existing) {
|
|
51
|
+
if (JSON.stringify(existing) !== JSON.stringify(registration)) {
|
|
52
|
+
throw new SessionContractError('IMPORT_CONFLICT', 'derived output index identity already has different content');
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
writeJsonAtomic(filePath, registration);
|
|
57
|
+
}
|
|
58
|
+
registrations() {
|
|
59
|
+
if (!existsSync(this.root))
|
|
60
|
+
return [];
|
|
61
|
+
return readdirSync(this.root)
|
|
62
|
+
.filter(name => /^sha256_[0-9a-f]{64}\.json$/.test(name))
|
|
63
|
+
.sort()
|
|
64
|
+
.map(name => JSON.parse(readFileSync(resolve(this.root, name), 'utf8')));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function canonicalReference(value) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = new URL(value);
|
|
70
|
+
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
|
71
|
+
parsed.username = '';
|
|
72
|
+
parsed.password = '';
|
|
73
|
+
parsed.search = '';
|
|
74
|
+
parsed.hash = '';
|
|
75
|
+
return parsed.toString();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Non-URL references are opaque inert identifiers.
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
function assertStorageRootInsideProject(projectRoot, storageRoot) {
|
|
84
|
+
const root = realpathSync(projectRoot);
|
|
85
|
+
const candidate = resolve(projectRoot, storageRoot);
|
|
86
|
+
if (candidate !== resolve(projectRoot) && !candidate.startsWith(`${resolve(projectRoot)}${sep}`)) {
|
|
87
|
+
throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'memory storage must be inside the project');
|
|
88
|
+
}
|
|
89
|
+
let ancestor = candidate;
|
|
90
|
+
while (!existsSync(ancestor)) {
|
|
91
|
+
const parent = dirname(ancestor);
|
|
92
|
+
if (parent === ancestor)
|
|
93
|
+
break;
|
|
94
|
+
ancestor = parent;
|
|
95
|
+
}
|
|
96
|
+
const actualAncestor = realpathSync(ancestor);
|
|
97
|
+
if (actualAncestor !== root && !actualAncestor.startsWith(`${root}${sep}`)) {
|
|
98
|
+
throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'memory storage cannot traverse a link outside the project');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function assertNoSecretMaterial(value, field) {
|
|
102
|
+
const secretAssignment = /(?:^|[?&;:\s])(?:api[_-]?key|access[_-]?token|token|secret|password|passwd|authorization)\s*[:=]\s*[^\s&;]+/i;
|
|
103
|
+
const privateKey = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/;
|
|
104
|
+
const providerToken = /(?:^|[^a-z0-9])(?:ghp|github_pat|sk|xox[baprs])_[a-z0-9_-]{16,}/i;
|
|
105
|
+
if (secretAssignment.test(value) || privateKey.test(value) || providerToken.test(value)) {
|
|
106
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', `${field} appears to contain secret material; store only a non-secret locator`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function canonicalRequest(request) {
|
|
110
|
+
const canonical = {
|
|
111
|
+
...request,
|
|
112
|
+
contextPack: {
|
|
113
|
+
...request.contextPack,
|
|
114
|
+
id: canonicalReference(request.contextPack.id),
|
|
115
|
+
sources: request.contextPack.sources.map(source => ({
|
|
116
|
+
...source,
|
|
117
|
+
ref: canonicalReference(source.ref),
|
|
118
|
+
})),
|
|
119
|
+
},
|
|
120
|
+
supersedes: [...new Set(request.supersedes.map(canonicalReference))].sort(),
|
|
121
|
+
conflictsWith: [...new Set(request.conflictsWith.map(canonicalReference))].sort(),
|
|
122
|
+
};
|
|
123
|
+
assertNoSecretMaterial(canonical.contextPack.id, 'context-pack identity');
|
|
124
|
+
for (const source of canonical.contextPack.sources) {
|
|
125
|
+
assertNoSecretMaterial(source.ref, 'source reference');
|
|
126
|
+
}
|
|
127
|
+
for (const value of [...canonical.supersedes, ...canonical.conflictsWith]) {
|
|
128
|
+
assertNoSecretMaterial(value, 'lifecycle reference');
|
|
129
|
+
}
|
|
130
|
+
return canonical;
|
|
131
|
+
}
|
|
132
|
+
function resolveImmutableOutput(projectRoot, requestedPath) {
|
|
133
|
+
if (requestedPath.includes('\0')) {
|
|
134
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'output path contains a null byte');
|
|
135
|
+
}
|
|
136
|
+
const pathSegments = requestedPath.toLocaleLowerCase().split(/[\\/]+/);
|
|
137
|
+
if (pathSegments.some(segment => segment === '.env'
|
|
138
|
+
|| segment === '.ssh'
|
|
139
|
+
|| /^(?:credentials?|secrets?|tokens?)(?:\.|$)/.test(segment))) {
|
|
140
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'sensitive credential/secret paths cannot be registered as ordinary outputs');
|
|
141
|
+
}
|
|
142
|
+
const root = realpathSync(projectRoot);
|
|
143
|
+
const candidate = realpathSync(resolve(root, requestedPath));
|
|
144
|
+
if (candidate !== root && !candidate.startsWith(`${root}${sep}`)) {
|
|
145
|
+
throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'registered output must resolve inside the project');
|
|
146
|
+
}
|
|
147
|
+
return { absolute: candidate, locator: relative(root, candidate).split(sep).join('/') };
|
|
148
|
+
}
|
|
149
|
+
function registrationFor(projectRoot, raw) {
|
|
150
|
+
const request = canonicalRequest(OutputRegistrationRequestSchema.parse(raw));
|
|
151
|
+
const output = resolveImmutableOutput(projectRoot, request.outputPath);
|
|
152
|
+
const content = readFileSync(output.absolute);
|
|
153
|
+
const digest = `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
154
|
+
const identity = {
|
|
155
|
+
output: { locator: output.locator, mediaType: request.mediaType, digest, byteLength: content.length },
|
|
156
|
+
contextPack: request.contextPack,
|
|
157
|
+
supersedes: request.supersedes,
|
|
158
|
+
conflictsWith: request.conflictsWith,
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
schemaVersion: 'aiwg.output-registration.v1',
|
|
162
|
+
registrationId: sha256(JSON.stringify(identity)),
|
|
163
|
+
...identity,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export class FilesystemOutputRegistrationStore {
|
|
167
|
+
root;
|
|
168
|
+
outboxRoot;
|
|
169
|
+
receiptRoot;
|
|
170
|
+
constructor(projectRoot) {
|
|
171
|
+
this.root = resolve(projectRoot, '.aiwg/memory/output-registration');
|
|
172
|
+
assertStorageRootInsideProject(projectRoot, this.root);
|
|
173
|
+
this.outboxRoot = resolve(this.root, 'outbox');
|
|
174
|
+
this.receiptRoot = resolve(this.root, 'receipts');
|
|
175
|
+
}
|
|
176
|
+
getReceipt(registrationId) {
|
|
177
|
+
return readJsonIfPresent(this.receiptPath(registrationId));
|
|
178
|
+
}
|
|
179
|
+
begin(operationId, registration) {
|
|
180
|
+
const existing = readJsonIfPresent(this.outboxPath(registration.registrationId));
|
|
181
|
+
if (existing)
|
|
182
|
+
return existing;
|
|
183
|
+
const record = {
|
|
184
|
+
schemaVersion: 'aiwg.output-registration-outbox.v1',
|
|
185
|
+
operationId,
|
|
186
|
+
registration,
|
|
187
|
+
state: 'pending',
|
|
188
|
+
attempts: 0,
|
|
189
|
+
lastError: null,
|
|
190
|
+
updatedAt: new Date().toISOString(),
|
|
191
|
+
};
|
|
192
|
+
writeJsonAtomic(this.outboxPath(registration.registrationId), record);
|
|
193
|
+
return record;
|
|
194
|
+
}
|
|
195
|
+
fail(registrationId, message) {
|
|
196
|
+
const path = this.outboxPath(registrationId);
|
|
197
|
+
const record = readJsonIfPresent(path);
|
|
198
|
+
if (!record)
|
|
199
|
+
throw new Error(`missing output-registration outbox record: ${registrationId}`);
|
|
200
|
+
const failed = {
|
|
201
|
+
...record,
|
|
202
|
+
attempts: record.attempts + 1,
|
|
203
|
+
lastError: message.slice(0, 512),
|
|
204
|
+
updatedAt: new Date().toISOString(),
|
|
205
|
+
};
|
|
206
|
+
writeJsonAtomic(path, failed);
|
|
207
|
+
return failed;
|
|
208
|
+
}
|
|
209
|
+
complete(operationId, registration) {
|
|
210
|
+
const existing = this.getReceipt(registration.registrationId);
|
|
211
|
+
if (existing) {
|
|
212
|
+
unlinkIfPresent(this.outboxPath(registration.registrationId));
|
|
213
|
+
return { ...existing, duplicate: true };
|
|
214
|
+
}
|
|
215
|
+
const receipt = {
|
|
216
|
+
schemaVersion: 'aiwg.output-registration-receipt.v1',
|
|
217
|
+
receiptId: sha256(`${operationId}\0${registration.registrationId}`),
|
|
218
|
+
registrationId: registration.registrationId,
|
|
219
|
+
operationId,
|
|
220
|
+
outputLocator: registration.output.locator,
|
|
221
|
+
outputDigest: registration.output.digest,
|
|
222
|
+
contextPackId: registration.contextPack.id,
|
|
223
|
+
contextPackDigest: registration.contextPack.digest,
|
|
224
|
+
sourceRefs: registration.contextPack.sources.map(source => source.ref),
|
|
225
|
+
registeredAt: new Date().toISOString(),
|
|
226
|
+
duplicate: false,
|
|
227
|
+
};
|
|
228
|
+
writeJsonAtomic(this.receiptPath(registration.registrationId), receipt);
|
|
229
|
+
unlinkIfPresent(this.outboxPath(registration.registrationId));
|
|
230
|
+
return receipt;
|
|
231
|
+
}
|
|
232
|
+
pending() {
|
|
233
|
+
if (!existsSync(this.outboxRoot))
|
|
234
|
+
return [];
|
|
235
|
+
return readdirSync(this.outboxRoot)
|
|
236
|
+
.filter(name => /^sha256_[0-9a-f]{64}\.json$/.test(name))
|
|
237
|
+
.sort()
|
|
238
|
+
.map(name => JSON.parse(readFileSync(resolve(this.outboxRoot, name), 'utf8')));
|
|
239
|
+
}
|
|
240
|
+
safeName(registrationId) {
|
|
241
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(registrationId)) {
|
|
242
|
+
throw new Error('invalid output registration identity');
|
|
243
|
+
}
|
|
244
|
+
return `${registrationId.replace(':', '_')}.json`;
|
|
245
|
+
}
|
|
246
|
+
outboxPath(registrationId) {
|
|
247
|
+
return resolve(this.outboxRoot, this.safeName(registrationId));
|
|
248
|
+
}
|
|
249
|
+
receiptPath(registrationId) {
|
|
250
|
+
return resolve(this.receiptRoot, this.safeName(registrationId));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
export class OutputRegistrationCoordinator {
|
|
254
|
+
store;
|
|
255
|
+
index;
|
|
256
|
+
projectRoot;
|
|
257
|
+
constructor(projectRoot, store, index) {
|
|
258
|
+
this.store = store;
|
|
259
|
+
this.index = index;
|
|
260
|
+
this.projectRoot = resolve(projectRoot);
|
|
261
|
+
}
|
|
262
|
+
preview(request) {
|
|
263
|
+
const registration = registrationFor(this.projectRoot, request);
|
|
264
|
+
const operationId = sha256(JSON.stringify({
|
|
265
|
+
registrationId: registration.registrationId,
|
|
266
|
+
outputDigest: registration.output.digest,
|
|
267
|
+
contextPackDigest: registration.contextPack.digest,
|
|
268
|
+
}));
|
|
269
|
+
return {
|
|
270
|
+
...registration,
|
|
271
|
+
operationId,
|
|
272
|
+
duplicate: Boolean(this.store.getReceipt(registration.registrationId)),
|
|
273
|
+
confirmationRequired: true,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async register(input) {
|
|
277
|
+
const preview = this.preview(input.request);
|
|
278
|
+
if (preview.operationId !== input.operationId) {
|
|
279
|
+
throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'output registration requires confirmation of the exact current preview');
|
|
280
|
+
}
|
|
281
|
+
const existing = this.store.getReceipt(preview.registrationId);
|
|
282
|
+
if (existing)
|
|
283
|
+
return { ...existing, duplicate: true };
|
|
284
|
+
const registration = registrationFor(this.projectRoot, input.request);
|
|
285
|
+
if (registration.registrationId !== preview.registrationId
|
|
286
|
+
|| registration.output.digest !== preview.output.digest) {
|
|
287
|
+
throw new SessionContractError('IMPORT_CONFLICT', 'output changed after registration preview');
|
|
288
|
+
}
|
|
289
|
+
this.store.begin(input.operationId, registration);
|
|
290
|
+
try {
|
|
291
|
+
await this.index.register(registration);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
this.store.fail(registration.registrationId, error instanceof Error ? error.message : String(error));
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
return this.store.complete(input.operationId, registration);
|
|
298
|
+
}
|
|
299
|
+
async replayPending() {
|
|
300
|
+
const receipts = [];
|
|
301
|
+
for (const record of this.store.pending()) {
|
|
302
|
+
try {
|
|
303
|
+
await this.index.register(record.registration);
|
|
304
|
+
receipts.push(this.store.complete(record.operationId, record.registration));
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
this.store.fail(record.registration.registrationId, error instanceof Error ? error.message : String(error));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return receipts;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function readJsonIfPresent(filePath) {
|
|
314
|
+
try {
|
|
315
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (error.code === 'ENOENT')
|
|
319
|
+
return null;
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function writeJsonAtomic(filePath, value) {
|
|
324
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
325
|
+
const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
326
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
327
|
+
renameSync(temporary, filePath);
|
|
328
|
+
}
|
|
329
|
+
function unlinkIfPresent(filePath) {
|
|
330
|
+
try {
|
|
331
|
+
unlinkSync(filePath);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
if (error.code !== 'ENOENT')
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=output-registration.js.map
|
|
@@ -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 = [
|
|
@@ -1219,7 +1219,8 @@ export class SessionRepository {
|
|
|
1219
1219
|
if (existing)
|
|
1220
1220
|
return { ...existing, duplicate: true };
|
|
1221
1221
|
const candidate = this.getCandidate(receipt.candidateId, receipt.candidateVersion);
|
|
1222
|
-
if (!candidate
|
|
1222
|
+
if (!candidate
|
|
1223
|
+
|| (candidate.reviewState !== 'accepted' && candidate.reviewState !== 'promoted')) {
|
|
1223
1224
|
throw new SessionContractError('OPERATION_NOT_AUTHORIZED', 'promotion requires an accepted exact candidate version');
|
|
1224
1225
|
}
|
|
1225
1226
|
const evidenceIds = [...new Set(candidate.evidence.map((item) => item.eventId))].sort();
|
|
@@ -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.8.
|
|
3
|
+
"version": "2026.8.2",
|
|
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": {
|
|
@@ -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
|
+
}
|