Synced from monorepo
Changes: - Non-blocking coding-data sharing upsell banner - Consolidate remediation in Doctor - Auto mode defers fail-closed gate asks to the classifier - Coalesce marketplace list fetches - Allow removing a marketplace source by name - Contain hung git marketplace sources (timeouts, non-blocking refresh, unbrick modal) - Label failed workspace RPCs with error_kind - Drop redundant explicit tonic/prost deps from xai-grok-shell - Report real exit codes for completed background shells - Narrow the date-rollover reminder to date-bearing templates - Wire toolOverrides through the session and agent - Security: Bash(git:*) allowlist matches whole command chain by prefix - Split prompt-trigger telemetry and record classifier provenance - Raise connectors-manager timeout to 60s - Auto classifier honors recorded approvals for repeat actions - Apply doctor fixes in the TUI - Auto-mode classifier timeouts prompt instead of silently denying - Scope subagent completion drains to the owning session - Add the toolOverrides wire types - Set client_identifier=grok-agent-sdk - Accept both spellings of the workspace-teleport kill switch - Persist one-shot occurrence journal - Stop turns that poll the exact same tool call 16x in a row - Copy compaction checkpoint files when forking sessions - Auto-focus permission prompt from scrollback - Esc cancels the running turn in non-vim and minimal modes - List Ctrl+Z undo and redo in keyboard shortcuts - Out-of-process macOS mic capture - Show active auth mode on session-info - Install the npm binary under $GROK_HOME - Remove hover/click dead zones between dashboard items - Route startup warnings to doctor - Document [feedback.user] author identity config - Extend bang command timeout - Close combine-queued edit-hold race - Integrate relocation recovery - Expose privacy notice rollout flag - Break harness discovery ref cycle so connections can idle-evict - Shift/Alt+Enter inserts newline when editing a queued prompt - Gate project Claude permissions on folder trust - Echo response.create.event_id on response.created - Toast when session creation fails from disk full - Add shared test process lifecycle - Enable dynamic workflows by default - Add relocation transaction state machine - Add shared test sandbox - Surface auth failures on model-switch compact - Persist durable scheduler expiry - Confirm before removing extensions-modal items - Re-run compact and prompt after login when compact hit expired auth - Recap sends hosted tools under backend search
This commit is contained in:
parent
3af4d5d398
commit
a5727c5960
482 changed files with 37627 additions and 13402 deletions
|
|
@ -9,6 +9,7 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const zlib = require('zlib');
|
||||
const assert = require('assert');
|
||||
|
||||
let passed = 0;
|
||||
|
|
@ -36,14 +37,16 @@ function cleanup(dir) {
|
|||
|
||||
// ─── Extracted logic (mirrors postinstall.js and bin/grok exactly) ─────
|
||||
|
||||
/** Semver-aware descending sort for "grok-X.Y.Z" filenames. */
|
||||
function semverSortDescending(a, b) {
|
||||
const pa = a.slice(5).split('.').map(Number);
|
||||
const pb = b.slice(5).split('.').map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if ((pa[i] || 0) !== (pb[i] || 0)) return (pb[i] || 0) - (pa[i] || 0);
|
||||
}
|
||||
return 0;
|
||||
/** Comparator: sort "<prefix>X.Y.Z" filenames by version, newest first. */
|
||||
function byVersionDescending(prefix) {
|
||||
return (a, b) => {
|
||||
const pa = a.slice(prefix.length).split('.').map(Number);
|
||||
const pb = b.slice(prefix.length).split('.').map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if ((pa[i] || 0) !== (pb[i] || 0)) return (pb[i] || 0) - (pa[i] || 0);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
/** Install a versioned binary + atomic symlink (same as postinstall.js). */
|
||||
|
|
@ -78,7 +81,7 @@ function cleanupOldVersions(canonicalDir, currentVersionedName) {
|
|||
const entries = fs.readdirSync(canonicalDir);
|
||||
const versionedBinaries = entries
|
||||
.filter(e => e.startsWith('grok-') && !e.includes('.tmp.') && !e.includes('.link.') && e !== currentVersionedName)
|
||||
.sort(semverSortDescending);
|
||||
.sort(byVersionDescending('grok-'));
|
||||
// Keep the most recent old version, remove anything older.
|
||||
for (const old of versionedBinaries.slice(1)) {
|
||||
try { fs.unlinkSync(path.join(canonicalDir, old)); } catch {}
|
||||
|
|
@ -86,6 +89,60 @@ function cleanupOldVersions(canonicalDir, currentVersionedName) {
|
|||
return versionedBinaries;
|
||||
}
|
||||
|
||||
/** Grok bin dir resolution (mirrors postinstall.js and bin/grok). */
|
||||
function resolveGrokBinDir(env, homedir) {
|
||||
const grokHome = env.GROK_HOME ?? path.join(homedir, '.grok');
|
||||
return path.join(grokHome, 'bin');
|
||||
}
|
||||
|
||||
/** Materialize the vendored binary at destPath (mirrors writeVendorBinary). */
|
||||
function writeVendorBinary(brPath, rawPath, destPath) {
|
||||
const tmp = destPath + `.tmp.${process.pid}`;
|
||||
try {
|
||||
if (fs.existsSync(brPath)) {
|
||||
fs.writeFileSync(tmp, zlib.brotliDecompressSync(fs.readFileSync(brPath)));
|
||||
} else if (fs.existsSync(rawPath)) {
|
||||
fs.copyFileSync(rawPath, tmp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
fs.chmodSync(tmp, 0o755);
|
||||
fs.renameSync(tmp, destPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
try { fs.unlinkSync(tmp); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
/** Decompress a brotli payload into the canonical dir (mirrors installBinary). */
|
||||
function installBinaryFromBrotli(brPath, version, canonicalDir) {
|
||||
fs.mkdirSync(canonicalDir, { recursive: true });
|
||||
const versionedName = `grok-${version}`;
|
||||
const versionedPath = path.join(canonicalDir, versionedName);
|
||||
const canonicalPath = path.join(canonicalDir, 'grok');
|
||||
|
||||
if (!fs.existsSync(versionedPath)) {
|
||||
const tmpPath = versionedPath + `.tmp.${process.pid}`;
|
||||
try {
|
||||
const decompressed = zlib.brotliDecompressSync(fs.readFileSync(brPath));
|
||||
fs.writeFileSync(tmpPath, decompressed);
|
||||
fs.chmodSync(tmpPath, 0o755);
|
||||
fs.renameSync(tmpPath, versionedPath);
|
||||
} finally {
|
||||
try { fs.unlinkSync(tmpPath); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
const tmpLink = canonicalPath + `.link.${process.pid}`;
|
||||
try { fs.unlinkSync(tmpLink); } catch {}
|
||||
fs.symlinkSync(versionedName, tmpLink);
|
||||
fs.renameSync(tmpLink, canonicalPath);
|
||||
|
||||
return { canonicalPath, versionedPath, versionedName };
|
||||
}
|
||||
|
||||
/** Bootstrap canonical from vendored (same as bin/grok trampoline). */
|
||||
function bootstrapCanonical(vendoredBinPath, version, canonicalDir) {
|
||||
const canonicalPath = path.join(canonicalDir, 'grok');
|
||||
|
|
@ -458,9 +515,9 @@ test('semver sort: minor version boundary (0.1.x vs 0.2.x)', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('semverSortDescending: unit test comparator directly', () => {
|
||||
test('byVersionDescending: unit test comparator directly', () => {
|
||||
const input = ['grok-0.1.9', 'grok-0.1.10', 'grok-0.1.2', 'grok-1.0.0', 'grok-0.2.0'];
|
||||
const sorted = [...input].sort(semverSortDescending);
|
||||
const sorted = [...input].sort(byVersionDescending('grok-'));
|
||||
assert.deepStrictEqual(sorted, [
|
||||
'grok-1.0.0',
|
||||
'grok-0.2.0',
|
||||
|
|
@ -674,14 +731,7 @@ function cleanupOldVersionsNamed(canonicalDir, binName, version) {
|
|||
const suffix = e.slice(prefix.length);
|
||||
return /^\d/.test(suffix);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const pa = a.slice(prefix.length).split('.').map(Number);
|
||||
const pb = b.slice(prefix.length).split('.').map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if ((pa[i] || 0) !== (pb[i] || 0)) return (pb[i] || 0) - (pa[i] || 0);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
.sort(byVersionDescending(prefix));
|
||||
for (const old of versionedBinaries.slice(1)) {
|
||||
try { fs.unlinkSync(path.join(canonicalDir, old)); } catch {}
|
||||
}
|
||||
|
|
@ -977,6 +1027,58 @@ test('canonical pager from non-npm install is preserved on Linux', () => {
|
|||
}
|
||||
});
|
||||
|
||||
console.log('\ngrok home + brotli install tests\n');
|
||||
|
||||
test('resolveGrokBinDir honors $GROK_HOME, else falls back to <home>/.grok/bin', () => {
|
||||
assert.strictEqual(
|
||||
resolveGrokBinDir({ GROK_HOME: '/fast/local/.grok' }, '/home/alice'),
|
||||
path.join('/fast/local/.grok', 'bin'),
|
||||
);
|
||||
assert.strictEqual(
|
||||
resolveGrokBinDir({}, '/home/alice'),
|
||||
path.join('/home/alice', '.grok', 'bin'),
|
||||
);
|
||||
assert.strictEqual(resolveGrokBinDir({ GROK_HOME: '' }, '/home/alice'), path.join('', 'bin'));
|
||||
});
|
||||
|
||||
test('writeVendorBinary returns false (not true) when the destination cannot be written', () => {
|
||||
const dir = makeTmpDir();
|
||||
try {
|
||||
const brPath = path.join(dir, 'grok.br');
|
||||
fs.writeFileSync(brPath, zlib.brotliCompressSync(Buffer.from('binary')));
|
||||
|
||||
// A non-empty directory at destPath makes the final rename fail.
|
||||
const dest = path.join(dir, 'dest');
|
||||
fs.mkdirSync(dest);
|
||||
fs.writeFileSync(path.join(dest, 'child'), 'x');
|
||||
|
||||
assert.strictEqual(writeVendorBinary(brPath, path.join(dir, 'raw'), dest), false);
|
||||
assert.ok(!fs.existsSync(`${dest}.tmp.${process.pid}`), 'temp file is cleaned up on failure');
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test('decompresses brotli into the canonical dir without duplicating into node_modules', () => {
|
||||
const dir = makeTmpDir();
|
||||
try {
|
||||
const vendorBin = path.join(dir, 'node_modules', 'bin');
|
||||
fs.mkdirSync(vendorBin, { recursive: true });
|
||||
const brPath = path.join(vendorBin, 'grok.br');
|
||||
fs.writeFileSync(brPath, zlib.brotliCompressSync(Buffer.from('native-binary-bytes')));
|
||||
|
||||
const binDir = path.join(dir, '.grok', 'bin');
|
||||
const result = installBinaryFromBrotli(brPath, '0.1.220', binDir);
|
||||
|
||||
assert.ok(fs.lstatSync(result.canonicalPath).isSymbolicLink());
|
||||
assert.strictEqual(fs.readFileSync(result.canonicalPath, 'utf8'), 'native-binary-bytes');
|
||||
assert.ok(!fs.existsSync(path.join(vendorBin, 'grok')), 'no uncompressed binary in node_modules');
|
||||
assert.ok(fs.existsSync(brPath), 'compressed .br payload is preserved');
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Summary ───────────────────────────────────────────────────────────
|
||||
|
||||
console.log(`\n${passed} passed, ${failed} failed`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue