@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.
Files changed (100) hide show
  1. package/bin/aiwg.mjs +4 -2
  2. package/dist/src/a2a/agent-card.js +11 -0
  3. package/dist/src/api/index.d.ts +1 -0
  4. package/dist/src/api/index.js +1 -0
  5. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
  6. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
  7. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
  8. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
  9. package/dist/src/artifacts/fortemi-shard-export.js +284 -0
  10. package/dist/src/artifacts/index-preparation.js +2 -0
  11. package/dist/src/auth/client.js +5 -1
  12. package/dist/src/auth/credential-store.js +5 -1
  13. package/dist/src/auth/sensitive-input.js +151 -0
  14. package/dist/src/channel/manager.mjs +3 -0
  15. package/dist/src/cli/handlers/agentcard.js +47 -25
  16. package/dist/src/cli/handlers/auth.js +22 -2
  17. package/dist/src/cli/handlers/cost-report.js +7 -1
  18. package/dist/src/cli/handlers/decision.js +36 -10
  19. package/dist/src/cli/handlers/effect.js +4 -84
  20. package/dist/src/cli/handlers/guided-setup.js +1 -1
  21. package/dist/src/cli/handlers/init.js +1 -1
  22. package/dist/src/cli/handlers/install.js +6 -0
  23. package/dist/src/cli/handlers/local-executor.js +33 -6
  24. package/dist/src/cli/handlers/marketplace.js +15 -4
  25. package/dist/src/cli/handlers/setup.js +24 -20
  26. package/dist/src/cli/handlers/use.js +57 -15
  27. package/dist/src/cli/prompt-utils.js +73 -0
  28. package/dist/src/config/gitignore.js +4 -0
  29. package/dist/src/config/workspace.js +2 -1
  30. package/dist/src/cost/fleet-report.js +20 -22
  31. package/dist/src/decision/adapters/jev.js +20 -31
  32. package/dist/src/decision/ask.js +6 -1
  33. package/dist/src/decision/benchmark.js +19 -6
  34. package/dist/src/decision/driver.js +2 -2
  35. package/dist/src/decision/jev-credentials.js +18 -5
  36. package/dist/src/decision/ledger.js +230 -34
  37. package/dist/src/decision/qualification/gate-evidence.js +6 -4
  38. package/dist/src/decision/qualification/load-staged.js +47 -10
  39. package/dist/src/decision/qualification/runner.js +7 -3
  40. package/dist/src/discovery/interview.js +10 -5
  41. package/dist/src/discovery/plan-apply.js +26 -30
  42. package/dist/src/discovery/plan-validation.js +2 -2
  43. package/dist/src/effects/host.js +91 -0
  44. package/dist/src/extensions/commands/definitions.js +2 -2
  45. package/dist/src/extensions/project-local-gitignore.js +2 -0
  46. package/dist/src/issues/cli.js +16 -5
  47. package/dist/src/issues/live.js +44 -12
  48. package/dist/src/marketplace/exchange.js +18 -6
  49. package/dist/src/marketplace/provenance.js +3 -2
  50. package/dist/src/mcp/adapters/codex-runtime.js +46 -76
  51. package/dist/src/mcp/cli.mjs +122 -109
  52. package/dist/src/mcp/credentials.d.mts +4 -0
  53. package/dist/src/mcp/credentials.mjs +44 -3
  54. package/dist/src/mcp/grok-build-config.mjs +4 -1
  55. package/dist/src/mcp/muse-native-config.mjs +2 -1
  56. package/dist/src/mcp/omp-config.mjs +2 -0
  57. package/dist/src/mcp/registry.js +19 -15
  58. package/dist/src/mcp/registry.mjs +2 -18
  59. package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
  60. package/dist/src/mcp/tools/worker-invoke.mjs +13 -2
  61. package/dist/src/memory/project-registry.js +2 -1
  62. package/dist/src/mission-protocol/worker-status.js +91 -1
  63. package/dist/src/models/grok-build-models.js +20 -0
  64. package/dist/src/models/worker-installation.d.mts +1 -1
  65. package/dist/src/models/worker-installation.mjs +65 -4
  66. package/dist/src/packages/adapters/git.js +99 -23
  67. package/dist/src/packages/adapters/gitea.js +8 -27
  68. package/dist/src/packages/adapters/github.js +7 -25
  69. package/dist/src/packages/registry.js +4 -4
  70. package/dist/src/process/cli-command.d.mts +5 -0
  71. package/dist/src/process/cli-command.mjs +42 -0
  72. package/dist/src/providers/provider-definitions.js +2 -2
  73. package/dist/src/serve/shared-host-scheduler.js +4 -1
  74. package/dist/src/serve/worker-invocation.js +10 -0
  75. package/dist/src/smiths/context-pipeline/claude-hook.js +9 -9
  76. package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
  77. package/dist/src/smiths/context-pipeline/finalization.js +3 -2
  78. package/dist/src/smiths/context-pipeline/generator.js +7 -19
  79. package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
  80. package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
  81. package/dist/src/smiths/context-pipeline/workspace-context.js +4 -12
  82. package/dist/src/storage/backends/fortemi.js +107 -49
  83. package/dist/src/tracker/project-inputs.js +2 -0
  84. package/dist/src/update/notifier.mjs +2 -4
  85. package/package.json +1 -1
  86. package/tools/agents/providers/base.mjs +5 -1
  87. package/tools/agents/providers/claude.mjs +5 -2
  88. package/tools/agents/providers/codex.mjs +4 -1
  89. package/tools/agents/providers/context-write.mjs +28 -0
  90. package/tools/agents/providers/copilot.mjs +5 -2
  91. package/tools/agents/providers/hermes.mjs +5 -2
  92. package/tools/agents/providers/omp.mjs +3 -1
  93. package/tools/agents/providers/rule-delivery.mjs +29 -12
  94. package/tools/agents/providers/warp.mjs +4 -1
  95. package/tools/agents/providers/windsurf.mjs +6 -3
  96. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
  97. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
  98. package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
  99. package/tools/ralph-external/process-monitor.mjs +42 -4
  100. 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 = { ...process.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
- const { stdout } = await execFileAsync('git', args, {
42
- cwd,
43
- env,
44
- timeout: 120_000,
45
- });
46
- return stdout.trim();
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
- return {
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 readGiteaToken();
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 with token
77
- gitUrl = `https://${token}@${host}/${ref.owner}/${ref.name}.git`;
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 readGitHubToken();
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://${token}@${GITHUB_HOST}/${ref.owner}/${ref.name}.git`;
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: '4247f661689354b831191f11eeeac8424993fe3d',
690
+ revision: '2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8',
691
691
  runtime: 'Grok Build CLI',
692
- lastVerified: '2026-09-20',
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
- if (!stat.isFile() || stat.nlink !== 1 || (stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.() || stat.size > 16 * 1024 * 1024)
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
- await fs.writeFile(claudeMdPath, minimal, 'utf8');
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
- await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
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
- await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
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
- await fs.writeFile(backupPath, existing, 'utf8');
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
- await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
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
- await fs.writeFile(claudeMdPath, existing, 'utf8');
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
- await fs.writeFile(backupPath, existing, 'utf8');
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
- await fs.writeFile(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
181
+ writeContextFileSync(claudeMdPath, dedupeProviderBootstrap(updated), 'utf8');
182
182
  result.action = 'updated';
183
183
  return result;
184
184
  }