@aiwg/cli 2026.10.6 → 2026.10.8
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/bin/aiwg.mjs +4 -2
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/cost-report.js +7 -1
- package/dist/src/cli/handlers/decision.js +36 -10
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +1 -1
- package/dist/src/cli/handlers/init.js +1 -1
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +33 -6
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/setup.js +24 -20
- package/dist/src/cli/handlers/use.js +57 -15
- package/dist/src/cli/prompt-utils.js +73 -0
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/cost/fleet-report.js +20 -22
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +19 -6
- package/dist/src/decision/driver.js +2 -2
- package/dist/src/decision/jev-credentials.js +18 -5
- package/dist/src/decision/ledger.js +230 -34
- package/dist/src/decision/qualification/gate-evidence.js +6 -4
- package/dist/src/decision/qualification/load-staged.js +47 -10
- package/dist/src/decision/qualification/runner.js +7 -3
- package/dist/src/discovery/interview.js +10 -5
- package/dist/src/discovery/plan-apply.js +26 -30
- package/dist/src/discovery/plan-validation.js +2 -2
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +2 -2
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +122 -109
- package/dist/src/mcp/credentials.d.mts +4 -0
- package/dist/src/mcp/credentials.mjs +44 -3
- package/dist/src/mcp/grok-build-config.mjs +4 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/omp-config.mjs +2 -0
- package/dist/src/mcp/registry.js +19 -15
- package/dist/src/mcp/registry.mjs +2 -18
- package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
- package/dist/src/mcp/tools/worker-invoke.mjs +13 -2
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/grok-build-models.js +20 -0
- package/dist/src/models/worker-installation.d.mts +1 -1
- package/dist/src/models/worker-installation.mjs +65 -4
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/provider-definitions.js +2 -2
- package/dist/src/serve/shared-host-scheduler.js +4 -1
- package/dist/src/serve/worker-invocation.js +10 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +9 -9
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +4 -12
- package/dist/src/storage/backends/fortemi.js +107 -49
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/package.json +1 -1
- package/tools/agents/providers/base.mjs +5 -1
- package/tools/agents/providers/claude.mjs +5 -2
- package/tools/agents/providers/codex.mjs +4 -1
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +5 -2
- package/tools/agents/providers/hermes.mjs +5 -2
- package/tools/agents/providers/omp.mjs +3 -1
- package/tools/agents/providers/rule-delivery.mjs +29 -12
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +6 -3
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/process-monitor.mjs +42 -4
- package/tools/ralph-external/session-launcher.mjs +125 -3
|
@@ -11,11 +11,40 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { execFile } from 'child_process';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
|
-
import { mkdir, mkdtemp, readFile, rename, rm } from 'fs/promises';
|
|
14
|
+
import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'fs/promises';
|
|
15
15
|
import { join } from 'path';
|
|
16
|
-
import { homedir } from 'os';
|
|
16
|
+
import { homedir, tmpdir } from 'os';
|
|
17
17
|
import { existsSync } from 'fs';
|
|
18
|
+
import { resolveSensitiveInput } from '../../auth/sensitive-input.js';
|
|
19
|
+
import { helperEnvironment, HELPER_ENV_ALLOWLIST } from '../../process/cli-command.mjs';
|
|
18
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
+
// Deliberately separate from serializable sources, locks and provenance.
|
|
22
|
+
const credentials = new WeakMap();
|
|
23
|
+
function hasTokenSource(input) {
|
|
24
|
+
return Boolean(input.interactive || input.stdin || input.file !== undefined || input.store !== undefined || input.env !== undefined);
|
|
25
|
+
}
|
|
26
|
+
/** Explicit sources precede the established env/file lookup; no token means SSH. */
|
|
27
|
+
export async function resolvePackageToken(input = {}, envName, paths = []) {
|
|
28
|
+
if (hasTokenSource(input))
|
|
29
|
+
return resolveSensitiveInput({ ...input, prefix: 'token' });
|
|
30
|
+
if (envName && process.env[envName])
|
|
31
|
+
return resolveSensitiveInput({ prefix: 'token', env: envName });
|
|
32
|
+
for (const file of paths) {
|
|
33
|
+
if (existsSync(file))
|
|
34
|
+
return resolveSensitiveInput({ prefix: 'token', file });
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
// GitHub rejects Bearer on git smart HTTP; Basic with x-access-token works on GitHub and Gitea.
|
|
39
|
+
function basicCredential(token) {
|
|
40
|
+
return Buffer.from(`x-access-token:${token}`).toString('base64');
|
|
41
|
+
}
|
|
42
|
+
function redact(value, token) {
|
|
43
|
+
if (!token)
|
|
44
|
+
return value;
|
|
45
|
+
return [token, encodeURIComponent(token), basicCredential(token)]
|
|
46
|
+
.reduce((result, secret) => result.split(secret).join('[REDACTED]'), value);
|
|
47
|
+
}
|
|
19
48
|
/**
|
|
20
49
|
* Default cache root
|
|
21
50
|
*/
|
|
@@ -34,16 +63,24 @@ export function buildCachePath(owner, name, version) {
|
|
|
34
63
|
/**
|
|
35
64
|
* Run a git command, returning stdout
|
|
36
65
|
*/
|
|
37
|
-
async function git(args, cwd) {
|
|
38
|
-
const env =
|
|
66
|
+
async function git(args, cwd, authentication) {
|
|
67
|
+
const env = helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
|
|
68
|
+
'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_SSH_VARIANT', 'XDG_CONFIG_HOME']);
|
|
39
69
|
// Suppress interactive prompts
|
|
40
70
|
env.GIT_TERMINAL_PROMPT = '0';
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
env
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
71
|
+
if (authentication) {
|
|
72
|
+
env.GIT_CONFIG_COUNT = '1';
|
|
73
|
+
env.GIT_CONFIG_KEY_0 = 'include.path';
|
|
74
|
+
env.GIT_CONFIG_VALUE_0 = authentication.config;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const { stdout } = await execFileAsync('git', args, { cwd, env, timeout: 120_000 });
|
|
78
|
+
return redact(stdout.trim(), authentication?.token);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Never rethrow execFile's object: it carries raw stdout/stderr properties.
|
|
82
|
+
throw new Error(redact(error instanceof Error ? error.message : 'Git command failed.', authentication?.token));
|
|
83
|
+
}
|
|
47
84
|
}
|
|
48
85
|
/**
|
|
49
86
|
* Detect the manifest type from a cloned package directory
|
|
@@ -69,9 +106,9 @@ async function detectManifestType(cachePath) {
|
|
|
69
106
|
/**
|
|
70
107
|
* Resolve the latest tag from a remote git repo
|
|
71
108
|
*/
|
|
72
|
-
async function resolveLatestTag(gitUrl) {
|
|
109
|
+
async function resolveLatestTag(gitUrl, authentication) {
|
|
73
110
|
try {
|
|
74
|
-
const output = await git(['ls-remote', '--tags', '--sort=-v:refname', gitUrl]);
|
|
111
|
+
const output = await git(['ls-remote', '--tags', '--sort=-v:refname', gitUrl], undefined, authentication);
|
|
75
112
|
const firstLine = output.split('\n')[0] ?? '';
|
|
76
113
|
const match = firstLine.match(/refs\/tags\/(.+)/);
|
|
77
114
|
if (match && match[1])
|
|
@@ -82,14 +119,14 @@ async function resolveLatestTag(gitUrl) {
|
|
|
82
119
|
}
|
|
83
120
|
return 'latest';
|
|
84
121
|
}
|
|
85
|
-
async function resolveRemoteCommit(gitUrl, requestedRef) {
|
|
122
|
+
async function resolveRemoteCommit(gitUrl, requestedRef, authentication) {
|
|
86
123
|
if (/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(requestedRef))
|
|
87
124
|
return requestedRef;
|
|
88
125
|
const patterns = requestedRef === 'HEAD'
|
|
89
126
|
? ['HEAD']
|
|
90
127
|
: [`refs/tags/${requestedRef}^{}`, `refs/tags/${requestedRef}`, `refs/heads/${requestedRef}`, requestedRef];
|
|
91
128
|
try {
|
|
92
|
-
const output = await git(['ls-remote', gitUrl, ...patterns]);
|
|
129
|
+
const output = await git(['ls-remote', gitUrl, ...patterns], undefined, authentication);
|
|
93
130
|
const rows = output.split('\n').filter(Boolean).map((line) => {
|
|
94
131
|
const [oid = '', ref = ''] = line.split(/\s+/, 2);
|
|
95
132
|
return { oid, ref };
|
|
@@ -136,30 +173,69 @@ export class GitAdapter {
|
|
|
136
173
|
ref.startsWith('ssh://') ||
|
|
137
174
|
ref.startsWith('git+https://'));
|
|
138
175
|
}
|
|
139
|
-
async resolve(ref) {
|
|
176
|
+
async resolve(ref, options = {}) {
|
|
140
177
|
if (!ref.rawUrl)
|
|
141
178
|
return null;
|
|
142
|
-
|
|
179
|
+
const source = {
|
|
143
180
|
gitUrl: ref.rawUrl,
|
|
144
181
|
ref: ref.version,
|
|
145
182
|
label: ref.rawUrl,
|
|
146
183
|
};
|
|
184
|
+
const token = await resolvePackageToken(options.tokenInput);
|
|
185
|
+
return this.withToken(source, token);
|
|
186
|
+
}
|
|
187
|
+
withToken(source, token) {
|
|
188
|
+
if (/^https?:\/\//.test(source.gitUrl)) {
|
|
189
|
+
const url = new URL(source.gitUrl);
|
|
190
|
+
if (url.username || url.password)
|
|
191
|
+
throw new Error('Credentials in Git URLs are refused; use --token-stdin, --token-file, --token-store or --token-env.');
|
|
192
|
+
}
|
|
193
|
+
if (token)
|
|
194
|
+
credentials.set(source, token);
|
|
195
|
+
return source;
|
|
147
196
|
}
|
|
148
197
|
async fetch(source, options = {}) {
|
|
198
|
+
this.withToken(source);
|
|
199
|
+
const token = credentials.get(source) ?? (hasTokenSource(options.tokenInput ?? {})
|
|
200
|
+
? await resolvePackageToken(options.tokenInput) : undefined);
|
|
201
|
+
let privateDir;
|
|
202
|
+
try {
|
|
203
|
+
let authentication;
|
|
204
|
+
if (token) {
|
|
205
|
+
if (process.platform === 'win32')
|
|
206
|
+
throw new Error('Git token authentication requires private temporary-file ACL validation on Windows; use SSH-agent authentication.');
|
|
207
|
+
const url = new URL(source.gitUrl);
|
|
208
|
+
if (url.protocol !== 'https:')
|
|
209
|
+
throw new Error('Git token authentication requires HTTPS.');
|
|
210
|
+
privateDir = await mkdtemp(join(tmpdir(), 'aiwg-git-credentials-'));
|
|
211
|
+
const config = join(privateDir, 'config');
|
|
212
|
+
// A path-only include survives NODE_DEBUG=child_process safely. The
|
|
213
|
+
// origin-scoped header is transient and never saved in .git/config.
|
|
214
|
+
await writeFile(config, `[http ${JSON.stringify(`${url.origin}/`)}]\n\textraHeader = ${JSON.stringify(`Authorization: Basic ${basicCredential(token)}`)}\n`, { mode: 0o600 });
|
|
215
|
+
authentication = { config, token };
|
|
216
|
+
}
|
|
217
|
+
return await this.fetchAuthenticated(source, options, authentication);
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
if (privateDir)
|
|
221
|
+
await rm(privateDir, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async fetchAuthenticated(source, options, authentication) {
|
|
149
225
|
// Resolve discovery inputs before any deployment and cache by immutable
|
|
150
226
|
// commit rather than by a movable tag/branch name (#2009).
|
|
151
227
|
let requestedRef = source.ref;
|
|
152
228
|
if (!requestedRef) {
|
|
153
|
-
const latest = await resolveLatestTag(source.gitUrl);
|
|
229
|
+
const latest = await resolveLatestTag(source.gitUrl, authentication);
|
|
154
230
|
requestedRef = latest === 'latest' ? 'HEAD' : latest;
|
|
155
231
|
source.ref = requestedRef;
|
|
156
232
|
}
|
|
157
|
-
const advertisedCommit = await resolveRemoteCommit(source.gitUrl, requestedRef);
|
|
233
|
+
const advertisedCommit = await resolveRemoteCommit(source.gitUrl, requestedRef, authentication);
|
|
158
234
|
const { owner, name } = urlCacheIdentity(source.gitUrl);
|
|
159
235
|
if (advertisedCommit) {
|
|
160
236
|
const existing = buildCachePath(owner, name, advertisedCommit);
|
|
161
237
|
if (!options.refresh && existsSync(join(existing, '.git'))) {
|
|
162
|
-
const existingCommit = await git(['rev-parse', 'HEAD^{commit}'], existing);
|
|
238
|
+
const existingCommit = await git(['rev-parse', 'HEAD^{commit}'], existing, authentication);
|
|
163
239
|
if (existingCommit !== advertisedCommit)
|
|
164
240
|
throw new Error(`Immutable package cache mismatch at ${existing}`);
|
|
165
241
|
return existing;
|
|
@@ -169,9 +245,9 @@ export class GitAdapter {
|
|
|
169
245
|
await mkdir(ownerDir, { recursive: true });
|
|
170
246
|
const stage = await mkdtemp(join(ownerDir, `.${name}.resolve-`));
|
|
171
247
|
try {
|
|
172
|
-
await git(['clone', '--no-checkout', source.gitUrl, stage]);
|
|
173
|
-
await git(['checkout', '--detach', requestedRef], stage);
|
|
174
|
-
const resolvedCommit = await git(['rev-parse', 'HEAD^{commit}'], stage);
|
|
248
|
+
await git(['clone', '--no-checkout', source.gitUrl, stage], undefined, authentication);
|
|
249
|
+
await git(['checkout', '--detach', requestedRef], stage, authentication);
|
|
250
|
+
const resolvedCommit = await git(['rev-parse', 'HEAD^{commit}'], stage, authentication);
|
|
175
251
|
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(resolvedCommit)) {
|
|
176
252
|
throw new Error(`Git resolved '${requestedRef}' to invalid commit '${resolvedCommit}'`);
|
|
177
253
|
}
|
|
@@ -180,7 +256,7 @@ export class GitAdapter {
|
|
|
180
256
|
}
|
|
181
257
|
const cachePath = buildCachePath(owner, name, resolvedCommit);
|
|
182
258
|
if (existsSync(join(cachePath, '.git'))) {
|
|
183
|
-
const cachedCommit = await git(['rev-parse', 'HEAD^{commit}'], cachePath);
|
|
259
|
+
const cachedCommit = await git(['rev-parse', 'HEAD^{commit}'], cachePath, authentication);
|
|
184
260
|
if (cachedCommit !== resolvedCommit)
|
|
185
261
|
throw new Error(`Immutable package cache mismatch at ${cachePath}`);
|
|
186
262
|
await rm(stage, { recursive: true, force: true });
|
|
@@ -11,36 +11,17 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Authentication:
|
|
13
13
|
* - SSH clone: uses default SSH agent (recommended for teams)
|
|
14
|
-
* - HTTPS clone: reads token from GITEA_TOKEN env or ~/.config/gitea/token
|
|
14
|
+
* - HTTPS clone: resolves explicit sources, otherwise reads token from GITEA_TOKEN env or ~/.config/gitea/token
|
|
15
15
|
*
|
|
16
16
|
* @implements #557
|
|
17
17
|
*/
|
|
18
|
-
import { existsSync } from 'fs';
|
|
19
|
-
import { readFile } from 'fs/promises';
|
|
20
18
|
import { join } from 'path';
|
|
21
19
|
import { homedir } from 'os';
|
|
22
|
-
import { GitAdapter } from './git.js';
|
|
20
|
+
import { GitAdapter, resolvePackageToken } from './git.js';
|
|
23
21
|
/**
|
|
24
22
|
* Regex for "owner/name" or "owner/name@version" (no scheme prefix)
|
|
25
23
|
*/
|
|
26
24
|
const GITEA_SHORTHAND = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+(@[a-zA-Z0-9._/-]+)?$/;
|
|
27
|
-
/**
|
|
28
|
-
* Read Gitea token from standard file path (same as global CLAUDE.md convention)
|
|
29
|
-
*/
|
|
30
|
-
async function readGiteaToken() {
|
|
31
|
-
if (process.env.GITEA_TOKEN)
|
|
32
|
-
return process.env.GITEA_TOKEN;
|
|
33
|
-
const tokenPaths = [
|
|
34
|
-
join(homedir(), '.config', 'gitea', 'token'),
|
|
35
|
-
join(homedir(), '.aiwg', 'credentials', 'gitea'),
|
|
36
|
-
];
|
|
37
|
-
for (const p of tokenPaths) {
|
|
38
|
-
if (existsSync(p)) {
|
|
39
|
-
return (await readFile(p, 'utf-8')).trim();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
25
|
/**
|
|
45
26
|
* Resolve the Gitea host
|
|
46
27
|
*/
|
|
@@ -66,25 +47,25 @@ export class GiteaAdapter extends GitAdapter {
|
|
|
66
47
|
return false; // has scheme prefix → not gitea shorthand
|
|
67
48
|
return GITEA_SHORTHAND.test(ref);
|
|
68
49
|
}
|
|
69
|
-
async resolve(ref) {
|
|
50
|
+
async resolve(ref, options = {}) {
|
|
70
51
|
if (!ref.owner || !ref.name)
|
|
71
52
|
return null;
|
|
72
53
|
const host = resolveGiteaHost();
|
|
73
|
-
const token = await
|
|
54
|
+
const token = await resolvePackageToken(options.tokenInput, 'GITEA_TOKEN', [join(homedir(), '.config', 'gitea', 'token'), join(homedir(), '.aiwg', 'credentials', 'gitea')]);
|
|
74
55
|
let gitUrl;
|
|
75
56
|
if (token) {
|
|
76
|
-
// HTTPS
|
|
77
|
-
gitUrl = `https://${
|
|
57
|
+
// HTTPS; credential is held outside the serializable source
|
|
58
|
+
gitUrl = `https://${host}/${ref.owner}/${ref.name}.git`;
|
|
78
59
|
}
|
|
79
60
|
else {
|
|
80
61
|
// SSH (relies on SSH agent)
|
|
81
62
|
gitUrl = `git@${host}:${ref.owner}/${ref.name}.git`;
|
|
82
63
|
}
|
|
83
|
-
return {
|
|
64
|
+
return this.withToken({
|
|
84
65
|
gitUrl,
|
|
85
66
|
ref: ref.version,
|
|
86
67
|
label: `${host}/${ref.owner}/${ref.name}${ref.version ? `@${ref.version}` : ''}`,
|
|
87
|
-
};
|
|
68
|
+
}, token);
|
|
88
69
|
}
|
|
89
70
|
async fetch(source, options = {}) {
|
|
90
71
|
return super.fetch(source, options);
|
|
@@ -5,32 +5,14 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Authentication:
|
|
7
7
|
* - SSH clone: uses default SSH agent
|
|
8
|
-
* - HTTPS clone: reads token from GITHUB_TOKEN env or ~/.aiwg/credentials/github
|
|
8
|
+
* - HTTPS clone: resolves explicit sources, otherwise reads token from GITHUB_TOKEN env or ~/.aiwg/credentials/github
|
|
9
9
|
*
|
|
10
10
|
* @implements #557
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync } from 'fs';
|
|
13
|
-
import { readFile } from 'fs/promises';
|
|
14
12
|
import { join } from 'path';
|
|
15
13
|
import { homedir } from 'os';
|
|
16
|
-
import { GitAdapter } from './git.js';
|
|
14
|
+
import { GitAdapter, resolvePackageToken } from './git.js';
|
|
17
15
|
const GITHUB_HOST = 'github.com';
|
|
18
|
-
/**
|
|
19
|
-
* Read GitHub token from standard locations
|
|
20
|
-
*/
|
|
21
|
-
async function readGitHubToken() {
|
|
22
|
-
if (process.env.GITHUB_TOKEN)
|
|
23
|
-
return process.env.GITHUB_TOKEN;
|
|
24
|
-
const tokenPaths = [
|
|
25
|
-
join(homedir(), '.aiwg', 'credentials', 'github'),
|
|
26
|
-
];
|
|
27
|
-
for (const p of tokenPaths) {
|
|
28
|
-
if (existsSync(p)) {
|
|
29
|
-
return (await readFile(p, 'utf-8')).trim();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
16
|
/**
|
|
35
17
|
* GitHubAdapter
|
|
36
18
|
*
|
|
@@ -45,22 +27,22 @@ export class GitHubAdapter extends GitAdapter {
|
|
|
45
27
|
canResolve(ref) {
|
|
46
28
|
return ref.startsWith('github:');
|
|
47
29
|
}
|
|
48
|
-
async resolve(ref) {
|
|
30
|
+
async resolve(ref, options = {}) {
|
|
49
31
|
if (!ref.owner || !ref.name)
|
|
50
32
|
return null;
|
|
51
|
-
const token = await
|
|
33
|
+
const token = await resolvePackageToken(options.tokenInput, 'GITHUB_TOKEN', [join(homedir(), '.aiwg', 'credentials', 'github')]);
|
|
52
34
|
let gitUrl;
|
|
53
35
|
if (token) {
|
|
54
|
-
gitUrl = `https://${
|
|
36
|
+
gitUrl = `https://${GITHUB_HOST}/${ref.owner}/${ref.name}.git`;
|
|
55
37
|
}
|
|
56
38
|
else {
|
|
57
39
|
gitUrl = `git@${GITHUB_HOST}:${ref.owner}/${ref.name}.git`;
|
|
58
40
|
}
|
|
59
|
-
return {
|
|
41
|
+
return this.withToken({
|
|
60
42
|
gitUrl,
|
|
61
43
|
ref: ref.version,
|
|
62
44
|
label: `github.com/${ref.owner}/${ref.name}${ref.version ? `@${ref.version}` : ''}`,
|
|
63
|
-
};
|
|
45
|
+
}, token);
|
|
64
46
|
}
|
|
65
47
|
async fetch(source, options = {}) {
|
|
66
48
|
return super.fetch(source, options);
|
|
@@ -99,11 +99,11 @@ export function parseRef(raw) {
|
|
|
99
99
|
/**
|
|
100
100
|
* Resolve a ref to a PackageSource using the appropriate adapter
|
|
101
101
|
*/
|
|
102
|
-
export async function resolveRef(ref) {
|
|
102
|
+
export async function resolveRef(ref, options = {}) {
|
|
103
103
|
for (const adapter of ALL_ADAPTERS) {
|
|
104
104
|
if (!adapter.canResolve(ref.raw))
|
|
105
105
|
continue;
|
|
106
|
-
const source = await adapter.resolve(ref);
|
|
106
|
+
const source = await adapter.resolve(ref, options);
|
|
107
107
|
if (source)
|
|
108
108
|
return { source, adapter };
|
|
109
109
|
}
|
|
@@ -171,7 +171,7 @@ export async function readPackageNamespace(cachePath, registryKey) {
|
|
|
171
171
|
*/
|
|
172
172
|
export async function installPackage(rawRef, options = {}) {
|
|
173
173
|
const ref = parseRef(rawRef);
|
|
174
|
-
const resolved = await resolveRef(ref);
|
|
174
|
+
const resolved = await resolveRef(ref, options);
|
|
175
175
|
if (!resolved) {
|
|
176
176
|
throw new Error(`Cannot resolve package reference: '${rawRef}'\n` +
|
|
177
177
|
`Supported formats:\n` +
|
|
@@ -185,7 +185,7 @@ export async function installPackage(rawRef, options = {}) {
|
|
|
185
185
|
const { source, adapter } = resolved;
|
|
186
186
|
if (options.ref)
|
|
187
187
|
source.ref = options.ref;
|
|
188
|
-
const cachePath = await adapter.fetch(source, { refresh: options.refresh });
|
|
188
|
+
const cachePath = await adapter.fetch(source, { refresh: options.refresh, tokenInput: options.tokenInput });
|
|
189
189
|
const discovered = await discoverInstallablePackage(cachePath, options.packageSelector);
|
|
190
190
|
const standardEnvelopePaths = [
|
|
191
191
|
join(cachePath, '.aiwg', 'marketplace', 'envelope.json'),
|
|
@@ -14,3 +14,8 @@ export function cliCommand(
|
|
|
14
14
|
pathExt?: string,
|
|
15
15
|
cwd?: string,
|
|
16
16
|
): [string, string[]];
|
|
17
|
+
|
|
18
|
+
export const HELPER_ENV_ALLOWLIST: readonly string[];
|
|
19
|
+
export const DESKTOP_HELPER_ENV_ALLOWLIST: readonly string[];
|
|
20
|
+
export const DEPLOYMENT_HELPER_ENV_ALLOWLIST: readonly string[];
|
|
21
|
+
export function helperEnvironment(source?: NodeJS.ProcessEnv, names?: readonly string[]): Record<string, string>;
|
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
// Like worker dispatch, helper processes receive only explicitly named state.
|
|
5
|
+
// Keep provider credentials, NODE_OPTIONS and unrelated shell variables out of
|
|
6
|
+
// process launch diagnostics as well as the child's own environment.
|
|
7
|
+
export const HELPER_ENV_ALLOWLIST = Object.freeze([
|
|
8
|
+
'PATH', 'PATHEXT', 'SYSTEMROOT', 'SYSTEMDRIVE', 'WINDIR', 'COMSPEC',
|
|
9
|
+
'HOME', 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH',
|
|
10
|
+
'TMPDIR', 'TMP', 'TEMP', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM',
|
|
11
|
+
// Platform and network plumbing a child may need to reach anything at all
|
|
12
|
+
// (corporate proxies and CA bundles, Windows per-user data, git global config).
|
|
13
|
+
'APPDATA', 'LOCALAPPDATA', 'PROGRAMDATA',
|
|
14
|
+
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'no_proxy',
|
|
15
|
+
'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'SSL_CERT_DIR',
|
|
16
|
+
'GIT_CONFIG_GLOBAL', 'GIT_CONFIG_SYSTEM', 'GIT_CONFIG_NOSYSTEM',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const DESKTOP_HELPER_ENV_ALLOWLIST = Object.freeze([
|
|
20
|
+
'DISPLAY', 'WAYLAND_DISPLAY', 'XAUTHORITY', 'XDG_RUNTIME_DIR',
|
|
21
|
+
'DBUS_SESSION_BUS_ADDRESS', 'XDG_CURRENT_DESKTOP',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
// Deployment helpers read project/configuration locations and policy switches;
|
|
25
|
+
// none consumes provider authentication or the caller's complete shell state.
|
|
26
|
+
export const DEPLOYMENT_HELPER_ENV_ALLOWLIST = Object.freeze([
|
|
27
|
+
...HELPER_ENV_ALLOWLIST, 'AIWG_ROOT', 'AIWG_HOME', 'AIWG_CONFIG', 'AIWG_PROJECT_DIR',
|
|
28
|
+
'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_STATE_HOME', 'LOCALAPPDATA',
|
|
29
|
+
'AIWG_ARTIFACTS_PATH', 'AIWG_PROJECT_ARTIFACTS_PATH', 'AIWG_PROJECT_AIWG_DIR',
|
|
30
|
+
'AIWG_PROJECT_MEMORY_HOME', 'AIWG_USER_REGISTRY_PATH', 'AIWG_RULE_BINDING_INDEX_CACHE',
|
|
31
|
+
'AIWG_DEPLOY_SUBPROCESS', 'AIWG_REPORT_DEPLOY_COLLISIONS', 'CODEX_HOME', 'HERMES_HOME',
|
|
32
|
+
'OPENCLAW_PROFILE', 'OMP_PROFILE', 'PI_PROFILE', 'PI_CONFIG_DIR', 'PI_CODING_AGENT_DIR',
|
|
33
|
+
'AIWG_RESOLVE_IMPL_FROM', 'NODE_ENV', 'VITEST', 'NO_UPDATE_NOTIFIER', 'NO_COLOR', 'CI',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/** Copy only the helper's declared variable names; never merge the parent. */
|
|
37
|
+
export function helperEnvironment(source = process.env, names = HELPER_ENV_ALLOWLIST) {
|
|
38
|
+
const environment = {};
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
const value = source?.[name];
|
|
41
|
+
if (typeof value === 'string' && !value.includes('\0')) environment[name] = value;
|
|
42
|
+
}
|
|
43
|
+
return environment;
|
|
44
|
+
}
|
|
45
|
+
|
|
4
46
|
// Share PATH/PATHEXT lookup with callers that can launch arbitrary batch files.
|
|
5
47
|
function* executableCandidates(command, searchPath, pathExt, preferNative = false, cwd = process.cwd()) {
|
|
6
48
|
const candidates = path.isAbsolute(command)
|
|
@@ -687,9 +687,9 @@ const BUILT_IN_SEEDS = [
|
|
|
687
687
|
source: 'https://github.com/xai-org/grok-build',
|
|
688
688
|
version: '1.0.38',
|
|
689
689
|
// Reviewed public tree (docs + inspect schema), not an all-zero placeholder.
|
|
690
|
-
revision: '
|
|
690
|
+
revision: '2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8',
|
|
691
691
|
runtime: 'Grok Build CLI',
|
|
692
|
-
lastVerified: '2026-
|
|
692
|
+
lastVerified: '2026-10-26',
|
|
693
693
|
},
|
|
694
694
|
surfaces: {
|
|
695
695
|
primary: 'grok-build',
|
|
@@ -233,7 +233,10 @@ export class FileAdmissionStore {
|
|
|
233
233
|
const file = openSync(this.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
234
234
|
try {
|
|
235
235
|
const stat = fstatSync(file);
|
|
236
|
-
|
|
236
|
+
// nlink is 0 when a writer atomically renamed a new snapshot over the
|
|
237
|
+
// file after this reader opened it; the open descriptor is still a
|
|
238
|
+
// complete earlier snapshot. Only extra hard links are refused.
|
|
239
|
+
if (!stat.isFile() || stat.nlink > 1 || (stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.() || stat.size > 16 * 1024 * 1024)
|
|
237
240
|
throw new Error('host authority must be a private bounded regular file');
|
|
238
241
|
return JSON.parse(readFileSync(file, 'utf8'));
|
|
239
242
|
}
|
|
@@ -640,6 +640,16 @@ export function workerInvocationDiagnostic(error, pointer, fallback = 'OG_CONFIG
|
|
|
640
640
|
if (error instanceof WorkerInvocationCheckError)
|
|
641
641
|
return { code: error.code, reason: error.reason, ...invocationChecks[error.reason] };
|
|
642
642
|
const code = error instanceof WorkerPolicyError ? error.code : fallback;
|
|
643
|
+
const signatureReason = error instanceof WorkerPolicyError
|
|
644
|
+
? error.details?.rejections.find(row => row.reason.startsWith('signature-'))?.reason : undefined;
|
|
645
|
+
if (code === 'OG_ADAPTER_UNQUALIFIED' && signatureReason) {
|
|
646
|
+
const messages = {
|
|
647
|
+
'signature-missing': 'Unsigned worker installation approval is unqualified. This requires deliberate one-time requalification: re-run aiwg doctor --probe-workers --qualify.',
|
|
648
|
+
'signature-trust-unavailable': 'Worker qualification signing trust is unavailable. Check effects.keyProvider in aiwg.config and the existing delivery effect-ledger keyring (aiwg effect keys list); restore trust, then re-run aiwg doctor --probe-workers --qualify.',
|
|
649
|
+
};
|
|
650
|
+
return { code, pointer, message: messages[signatureReason]
|
|
651
|
+
?? 'Worker installation signature is invalid or its keyid is untrusted. Re-run aiwg doctor --probe-workers --qualify.' };
|
|
652
|
+
}
|
|
643
653
|
const messages = {
|
|
644
654
|
OG_SANDBOX_EXTERNAL: "The CLI's own sandbox is disabled by explicit external policy; isolation relies on the per-task worktree and launcher resource scope.",
|
|
645
655
|
OG_NATIVE_SANDBOX_UNAVAILABLE: 'Native sandbox unavailable inside systemd user units; select policy launchMode native-sandbox or allow unprivileged user namespaces through host AppArmor policy.',
|
|
@@ -23,7 +23,7 @@ import * as fs from 'fs/promises';
|
|
|
23
23
|
import * as path from 'path';
|
|
24
24
|
import { buildProviderBootstrapBlock, PROVIDER_BOOTSTRAP_START, PROVIDER_BOOTSTRAP_END, dedupeProviderBootstrap, } from './workspace-context.js';
|
|
25
25
|
import { dominantLineEnding, withLineEnding } from './line-endings.js';
|
|
26
|
-
import { assertSafeContextFile } from './context-file-safety.js';
|
|
26
|
+
import { assertSafeContextFile, writeContextFileSync } from './context-file-safety.js';
|
|
27
27
|
export const CLAUDE_HOOK_START = '<!-- AIWG:claude-md-hook:start -->';
|
|
28
28
|
export const CLAUDE_HOOK_END = '<!-- AIWG:claude-md-hook:end -->';
|
|
29
29
|
export function buildClaudeHookBlock(_policy = {}) {
|
|
@@ -92,7 +92,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
92
92
|
catch (err) {
|
|
93
93
|
if (err.code === 'ENOENT') {
|
|
94
94
|
const minimal = `${block}\n`;
|
|
95
|
-
|
|
95
|
+
writeContextFileSync(claudeMdPath, minimal, 'utf8');
|
|
96
96
|
result.action = 'created';
|
|
97
97
|
return result;
|
|
98
98
|
}
|
|
@@ -121,7 +121,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
121
121
|
const updated = existing.substring(0, bootstrapStartIdx)
|
|
122
122
|
+ block
|
|
123
123
|
+ existing.substring(bootstrapEndIdx);
|
|
124
|
-
|
|
124
|
+
writeContextFileSync(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
125
125
|
result.action = 'updated';
|
|
126
126
|
return result;
|
|
127
127
|
}
|
|
@@ -132,7 +132,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
132
132
|
// Ensure the file ends with a single newline before appending.
|
|
133
133
|
const trimmed = existing.replace(/(?:\r?\n)+$/, lineEnding);
|
|
134
134
|
const updated = `${trimmed}${lineEnding}${block}${lineEnding}`;
|
|
135
|
-
|
|
135
|
+
writeContextFileSync(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
136
136
|
result.action = 'inserted';
|
|
137
137
|
return result;
|
|
138
138
|
}
|
|
@@ -145,12 +145,12 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
145
145
|
// With --force, back up and append a fresh block.
|
|
146
146
|
if (opts.detectExistingFiles !== false) {
|
|
147
147
|
const backupPath = `${claudeMdPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
148
|
-
|
|
148
|
+
writeContextFileSync(backupPath, existing, 'utf8');
|
|
149
149
|
result.backupPath = backupPath;
|
|
150
150
|
}
|
|
151
151
|
const trimmed = existing.replace(/(?:\r?\n)+$/, lineEnding);
|
|
152
152
|
const updated = `${trimmed}${lineEnding}${block}${lineEnding}`;
|
|
153
|
-
|
|
153
|
+
writeContextFileSync(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
154
154
|
result.action = 'inserted';
|
|
155
155
|
return result;
|
|
156
156
|
}
|
|
@@ -159,7 +159,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
159
159
|
const currentBlock = existing.substring(startIdx, blockEndOffset);
|
|
160
160
|
if (currentBlock === block) {
|
|
161
161
|
if (repaired) {
|
|
162
|
-
|
|
162
|
+
writeContextFileSync(claudeMdPath, existing, 'utf8');
|
|
163
163
|
result.action = 'updated';
|
|
164
164
|
return result;
|
|
165
165
|
}
|
|
@@ -171,14 +171,14 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
171
171
|
// the case where an operator made deliberate edits inside the block).
|
|
172
172
|
if (opts.force) {
|
|
173
173
|
const backupPath = `${claudeMdPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
174
|
-
|
|
174
|
+
writeContextFileSync(backupPath, existing, 'utf8');
|
|
175
175
|
result.backupPath = backupPath;
|
|
176
176
|
}
|
|
177
177
|
else {
|
|
178
178
|
result.warnings.push(`CLAUDE.md AIWG hook block has been modified inside the markers; updating to canonical content. Operator content outside the block is preserved.`);
|
|
179
179
|
}
|
|
180
180
|
const updated = existing.substring(0, startIdx) + block + existing.substring(blockEndOffset);
|
|
181
|
-
|
|
181
|
+
writeContextFileSync(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
|
|
182
182
|
result.action = 'updated';
|
|
183
183
|
return result;
|
|
184
184
|
}
|