@moltbankhq/openclaw 0.1.10 → 0.1.12
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/index.ts +562 -92
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
1
|
+
import { spawn, spawnSync } from 'child_process';
|
|
2
|
+
import { chmodSync, chownSync, existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, type Dirent } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
4
|
+
import { homedir, userInfo } from 'os';
|
|
5
5
|
const IS_WIN = process.platform === 'win32';
|
|
6
6
|
|
|
7
7
|
type OpenclawConfig = Record<string, unknown>;
|
|
@@ -32,6 +32,21 @@ interface LoggerApi {
|
|
|
32
32
|
logger: LoggerLike;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
type RunOptions = {
|
|
36
|
+
cwd?: string;
|
|
37
|
+
silent?: boolean;
|
|
38
|
+
env?: Record<string, string | undefined>;
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type RunResult = {
|
|
43
|
+
ok: boolean;
|
|
44
|
+
stdout: string;
|
|
45
|
+
stderr: string;
|
|
46
|
+
exitCode: number | null;
|
|
47
|
+
timedOut: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
35
50
|
export interface SetupCommandLoggerOptions {
|
|
36
51
|
verbose?: boolean;
|
|
37
52
|
statusCommand?: string;
|
|
@@ -350,31 +365,191 @@ function getExecErrorMessage(error: unknown): string {
|
|
|
350
365
|
return String(error);
|
|
351
366
|
}
|
|
352
367
|
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
368
|
+
function getPathEnvKey(env: NodeJS.ProcessEnv): string {
|
|
369
|
+
const existing = Object.keys(env).find((key) => key.toLowerCase() === 'path');
|
|
370
|
+
return existing ?? 'Path';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function splitPathEntries(value: string): string[] {
|
|
374
|
+
return value
|
|
375
|
+
.split(IS_WIN ? ';' : ':')
|
|
376
|
+
.map((entry) => entry.trim())
|
|
377
|
+
.filter(Boolean);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function prependPathEntries(env: NodeJS.ProcessEnv, entries: string[]): void {
|
|
381
|
+
if (entries.length === 0) return;
|
|
382
|
+
|
|
383
|
+
const pathKey = getPathEnvKey(env);
|
|
384
|
+
const existing = splitPathEntries(asString(env[pathKey]));
|
|
385
|
+
const seen = new Set<string>();
|
|
386
|
+
const ordered: string[] = [];
|
|
387
|
+
|
|
388
|
+
for (const entry of [...entries, ...existing]) {
|
|
389
|
+
const normalized = IS_WIN ? entry.toLowerCase() : entry;
|
|
390
|
+
if (seen.has(normalized)) continue;
|
|
391
|
+
seen.add(normalized);
|
|
392
|
+
ordered.push(entry);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
env[pathKey] = ordered.join(IS_WIN ? ';' : ':');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isExplicitCommandPath(command: string): boolean {
|
|
399
|
+
return command.includes('/') || command.includes('\\') || /^[A-Za-z]:/.test(command);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function getWindowsCommandExtensions(env: NodeJS.ProcessEnv): string[] {
|
|
403
|
+
const fromEnv = asString(env.PATHEXT || process.env.PATHEXT)
|
|
404
|
+
.split(';')
|
|
405
|
+
.map((value) => value.trim().toLowerCase())
|
|
406
|
+
.filter(Boolean);
|
|
407
|
+
|
|
408
|
+
const preferred = ['.exe', '.com', '.ps1', '.cmd', '.bat'];
|
|
409
|
+
const out: string[] = [];
|
|
410
|
+
for (const ext of [...preferred, ...fromEnv]) {
|
|
411
|
+
const normalized = ext.startsWith('.') ? ext : `.${ext}`;
|
|
412
|
+
if (!out.includes(normalized)) {
|
|
413
|
+
out.push(normalized);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function resolveWindowsCommandPath(command: string, env: NodeJS.ProcessEnv): string {
|
|
420
|
+
const extensions = getWindowsCommandExtensions(env);
|
|
421
|
+
|
|
422
|
+
const tryCandidates = (basePath: string): string => {
|
|
423
|
+
if (existsSync(basePath)) return basePath;
|
|
424
|
+
|
|
425
|
+
const lower = basePath.toLowerCase();
|
|
426
|
+
for (const ext of extensions) {
|
|
427
|
+
if (lower.endsWith(ext)) continue;
|
|
428
|
+
const candidate = `${basePath}${ext}`;
|
|
429
|
+
if (existsSync(candidate)) {
|
|
430
|
+
return candidate;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return '';
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
if (isExplicitCommandPath(command)) {
|
|
438
|
+
return tryCandidates(command);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const pathValue = asString(env[getPathEnvKey(env)]);
|
|
442
|
+
for (const entry of splitPathEntries(pathValue)) {
|
|
443
|
+
const resolved = tryCandidates(join(entry, command));
|
|
444
|
+
if (resolved) {
|
|
445
|
+
return resolved;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return '';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function quoteCmdArgument(value: string): string {
|
|
453
|
+
if (!value) return '""';
|
|
454
|
+
|
|
455
|
+
const escaped = value
|
|
456
|
+
.replace(/([()%!^"<>&|])/g, '^$1')
|
|
457
|
+
.replace(/\r/g, ' ')
|
|
458
|
+
.replace(/\n/g, ' ');
|
|
459
|
+
|
|
460
|
+
return /[\s()%!^"<>&|]/.test(value) ? `"${escaped}"` : escaped;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function prepareCommandForSpawn(command: string, args: string[], env: NodeJS.ProcessEnv): { command: string; args: string[] } {
|
|
464
|
+
if (!IS_WIN) {
|
|
465
|
+
return { command, args };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const resolved = resolveWindowsCommandPath(command, env);
|
|
469
|
+
if (!resolved) {
|
|
470
|
+
return { command, args };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const lower = resolved.toLowerCase();
|
|
474
|
+
if (lower.endsWith('.ps1')) {
|
|
475
|
+
return {
|
|
476
|
+
command: 'powershell.exe',
|
|
477
|
+
args: ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', resolved, ...args]
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (lower.endsWith('.cmd') || lower.endsWith('.bat')) {
|
|
482
|
+
const cmdExe = asString(env.ComSpec || env.COMSPEC, 'cmd.exe');
|
|
483
|
+
const commandLine = [resolved, ...args].map((value) => quoteCmdArgument(value)).join(' ');
|
|
484
|
+
return {
|
|
485
|
+
command: cmdExe,
|
|
486
|
+
args: ['/d', '/s', '/c', commandLine]
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return { command: resolved, args };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function runCommand(command: string, args: string[], opts: RunOptions = {}): RunResult {
|
|
357
494
|
try {
|
|
358
495
|
const mergedEnv: NodeJS.ProcessEnv = { ...process.env, ...(opts.env ?? {}) };
|
|
359
|
-
const
|
|
496
|
+
const prepared = prepareCommandForSpawn(command, args, mergedEnv);
|
|
497
|
+
const result = spawnSync(prepared.command, prepared.args, {
|
|
360
498
|
cwd: opts.cwd,
|
|
361
|
-
stdio: opts.silent ? 'pipe' : 'inherit',
|
|
362
499
|
env: mergedEnv,
|
|
500
|
+
encoding: 'utf8',
|
|
363
501
|
timeout: opts.timeoutMs,
|
|
364
|
-
|
|
502
|
+
stdio: 'pipe',
|
|
503
|
+
windowsHide: true
|
|
365
504
|
});
|
|
366
|
-
|
|
505
|
+
|
|
506
|
+
const stdout = (result.stdout ?? '').toString();
|
|
507
|
+
const stderr = (result.stderr ?? '').toString();
|
|
508
|
+
|
|
509
|
+
if (!opts.silent) {
|
|
510
|
+
if (stdout) process.stdout.write(stdout);
|
|
511
|
+
if (stderr) process.stderr.write(stderr);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const errorMessage = result.error ? getExecErrorMessage(result.error) : '';
|
|
515
|
+
const trimmedStdout = stdout.trim();
|
|
516
|
+
const trimmedStderr = stderr.trim();
|
|
517
|
+
const timedOut = Boolean(
|
|
518
|
+
result.error &&
|
|
519
|
+
((isRecord(result.error) && asString((result.error as { code?: unknown }).code) === 'ETIMEDOUT') ||
|
|
520
|
+
errorMessage.toLowerCase().includes('timed out'))
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (result.status === 0) {
|
|
524
|
+
return {
|
|
525
|
+
ok: true,
|
|
526
|
+
stdout: trimmedStdout,
|
|
527
|
+
stderr: trimmedStderr,
|
|
528
|
+
exitCode: result.status,
|
|
529
|
+
timedOut: false
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
ok: false,
|
|
535
|
+
stdout: trimmedStdout,
|
|
536
|
+
stderr: trimmedStderr || errorMessage || trimmedStdout,
|
|
537
|
+
exitCode: typeof result.status === 'number' ? result.status : null,
|
|
538
|
+
timedOut
|
|
539
|
+
};
|
|
367
540
|
} catch (e: unknown) {
|
|
368
541
|
return {
|
|
369
542
|
ok: false,
|
|
370
543
|
stdout: getExecOutputText(e, 'stdout'),
|
|
371
|
-
stderr: getExecErrorMessage(e)
|
|
544
|
+
stderr: getExecErrorMessage(e),
|
|
545
|
+
exitCode: null,
|
|
546
|
+
timedOut: getExecErrorMessage(e).toLowerCase().includes('timed out')
|
|
372
547
|
};
|
|
373
548
|
}
|
|
374
549
|
}
|
|
375
550
|
|
|
376
551
|
function hasBin(bin: string) {
|
|
377
|
-
return
|
|
552
|
+
return runCommand(IS_WIN ? 'where' : 'which', [bin], { silent: true }).ok;
|
|
378
553
|
}
|
|
379
554
|
|
|
380
555
|
function getJqInstallHint(): string {
|
|
@@ -396,7 +571,7 @@ function splitNonEmptyLines(text: string): string[] {
|
|
|
396
571
|
}
|
|
397
572
|
|
|
398
573
|
function getNpmGlobalBinDir(): string {
|
|
399
|
-
const prefix =
|
|
574
|
+
const prefix = runCommand('npm', ['config', 'get', 'prefix'], { silent: true });
|
|
400
575
|
if (!prefix.ok) return '';
|
|
401
576
|
|
|
402
577
|
const value = prefix.stdout.trim();
|
|
@@ -412,7 +587,7 @@ function findFirstMatchingFile(rootDir: string, predicate: (name: string) => boo
|
|
|
412
587
|
const current = queue.shift();
|
|
413
588
|
if (!current) break;
|
|
414
589
|
|
|
415
|
-
let entries:
|
|
590
|
+
let entries: Dirent[] = [];
|
|
416
591
|
try {
|
|
417
592
|
entries = readdirSync(current.dir, { withFileTypes: true });
|
|
418
593
|
} catch {
|
|
@@ -433,13 +608,210 @@ function findFirstMatchingFile(rootDir: string, predicate: (name: string) => boo
|
|
|
433
608
|
return '';
|
|
434
609
|
}
|
|
435
610
|
|
|
611
|
+
function visitFiles(rootDir: string, visitor: (fullPath: string, entry: Dirent) => void, maxDepth = Number.POSITIVE_INFINITY): void {
|
|
612
|
+
if (!rootDir || !existsSync(rootDir)) return;
|
|
613
|
+
|
|
614
|
+
const queue: Array<{ dir: string; depth: number }> = [{ dir: rootDir, depth: 0 }];
|
|
615
|
+
while (queue.length) {
|
|
616
|
+
const current = queue.shift();
|
|
617
|
+
if (!current) break;
|
|
618
|
+
|
|
619
|
+
let entries: Dirent[] = [];
|
|
620
|
+
try {
|
|
621
|
+
entries = readdirSync(current.dir, { withFileTypes: true });
|
|
622
|
+
} catch {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
for (const entry of entries) {
|
|
627
|
+
const fullPath = join(current.dir, entry.name);
|
|
628
|
+
visitor(fullPath, entry);
|
|
629
|
+
if (entry.isDirectory() && current.depth < maxDepth) {
|
|
630
|
+
queue.push({ dir: fullPath, depth: current.depth + 1 });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function normalizeFileLineEndings(filePath: string): void {
|
|
637
|
+
try {
|
|
638
|
+
const original = readFileSync(filePath, 'utf8');
|
|
639
|
+
const normalized = original.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
640
|
+
if (normalized !== original) {
|
|
641
|
+
writeFileSync(filePath, normalized, 'utf8');
|
|
642
|
+
}
|
|
643
|
+
} catch {
|
|
644
|
+
// ignore
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function normalizeFilesMatching(rootDir: string, matcher: (fullPath: string, entry: Dirent) => boolean, maxDepth = Number.POSITIVE_INFINITY): void {
|
|
649
|
+
visitFiles(
|
|
650
|
+
rootDir,
|
|
651
|
+
(fullPath, entry) => {
|
|
652
|
+
if (entry.isFile() && matcher(fullPath, entry)) {
|
|
653
|
+
normalizeFileLineEndings(fullPath);
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
maxDepth
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function applyRecursiveOwnership(rootDir: string, uid: number, gid: number): void {
|
|
661
|
+
try {
|
|
662
|
+
chownSync(rootDir, uid, gid);
|
|
663
|
+
} catch {
|
|
664
|
+
// ignore
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
visitFiles(rootDir, (fullPath) => {
|
|
668
|
+
try {
|
|
669
|
+
chownSync(fullPath, uid, gid);
|
|
670
|
+
} catch {
|
|
671
|
+
// ignore
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function applyRecursiveMode(rootDir: string, mode: number): void {
|
|
677
|
+
try {
|
|
678
|
+
chmodSync(rootDir, mode);
|
|
679
|
+
} catch {
|
|
680
|
+
// ignore
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
visitFiles(rootDir, (fullPath) => {
|
|
684
|
+
try {
|
|
685
|
+
chmodSync(fullPath, mode);
|
|
686
|
+
} catch {
|
|
687
|
+
// ignore
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function applyRecursiveModes(rootDir: string, dirMode: number, fileMode: number): void {
|
|
693
|
+
try {
|
|
694
|
+
chmodSync(rootDir, dirMode);
|
|
695
|
+
} catch {
|
|
696
|
+
// ignore
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
visitFiles(rootDir, (fullPath, entry) => {
|
|
700
|
+
try {
|
|
701
|
+
chmodSync(fullPath, entry.isDirectory() ? dirMode : fileMode);
|
|
702
|
+
} catch {
|
|
703
|
+
// ignore
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
type CommandSpec = {
|
|
709
|
+
command: string;
|
|
710
|
+
args: string[];
|
|
711
|
+
env?: Record<string, string | undefined>;
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
function withOptionalSudo(command: string, args: string[], env?: Record<string, string | undefined>): CommandSpec {
|
|
715
|
+
const isRoot = typeof process.getuid === 'function' ? process.getuid() === 0 : false;
|
|
716
|
+
if (isRoot || !hasBin('sudo')) {
|
|
717
|
+
return { command, args, env };
|
|
718
|
+
}
|
|
719
|
+
return { command: 'sudo', args: [command, ...args], env };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function getUnixJqInstallPlan(): { name: string; steps: CommandSpec[] } | null {
|
|
723
|
+
if (hasBin('apt-get')) {
|
|
724
|
+
return {
|
|
725
|
+
name: 'apt-get',
|
|
726
|
+
steps: [
|
|
727
|
+
withOptionalSudo('apt-get', ['update', '-qq'], { DEBIAN_FRONTEND: 'noninteractive' }),
|
|
728
|
+
withOptionalSudo('apt-get', ['install', '-y', 'jq'], { DEBIAN_FRONTEND: 'noninteractive' })
|
|
729
|
+
]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (hasBin('dnf')) {
|
|
734
|
+
return {
|
|
735
|
+
name: 'dnf',
|
|
736
|
+
steps: [withOptionalSudo('dnf', ['install', '-y', 'jq'])]
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (hasBin('yum')) {
|
|
741
|
+
return {
|
|
742
|
+
name: 'yum',
|
|
743
|
+
steps: [withOptionalSudo('yum', ['install', '-y', 'jq'])]
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (hasBin('pacman')) {
|
|
748
|
+
return {
|
|
749
|
+
name: 'pacman',
|
|
750
|
+
steps: [withOptionalSudo('pacman', ['-Sy', '--noconfirm', 'jq'])]
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (hasBin('apk')) {
|
|
755
|
+
return {
|
|
756
|
+
name: 'apk',
|
|
757
|
+
steps: [withOptionalSudo('apk', ['add', 'jq'])]
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (hasBin('zypper')) {
|
|
762
|
+
return {
|
|
763
|
+
name: 'zypper',
|
|
764
|
+
steps: [withOptionalSudo('zypper', ['install', '-y', 'jq'])]
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (hasBin('brew')) {
|
|
769
|
+
return {
|
|
770
|
+
name: 'brew',
|
|
771
|
+
steps: [{ command: 'brew', args: ['install', 'jq'] }]
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function ensureUnixJq(api: LoggerApi): boolean {
|
|
779
|
+
const plan = getUnixJqInstallPlan();
|
|
780
|
+
if (!plan) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
api.logger.info(`[moltbank] jq not found — attempting install via ${plan.name}...`);
|
|
785
|
+
|
|
786
|
+
for (const step of plan.steps) {
|
|
787
|
+
const result = runCommand(step.command, step.args, {
|
|
788
|
+
env: step.env,
|
|
789
|
+
timeoutMs: 180000
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
if (!result.ok) {
|
|
793
|
+
api.logger.warn(`[moltbank] jq install via ${plan.name} failed: ${result.stderr}`);
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (!hasBin('jq')) {
|
|
799
|
+
api.logger.warn(`[moltbank] jq install via ${plan.name} finished, but jq is still not visible in PATH`);
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const version = runCommand('jq', ['--version'], { silent: true });
|
|
804
|
+
api.logger.info(`[moltbank] ✓ jq installed via ${plan.name} (${version.stdout || 'unknown version'})`);
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
|
|
436
808
|
function findWindowsJqBinaryPath(): string {
|
|
437
809
|
if (!IS_WIN) return '';
|
|
438
810
|
|
|
439
811
|
const fromEnv = asString(process.env.JQ_PATH).trim();
|
|
440
812
|
if (fromEnv && existsSync(fromEnv)) return fromEnv;
|
|
441
813
|
|
|
442
|
-
const whereJq =
|
|
814
|
+
const whereJq = runCommand('where', ['jq'], { silent: true });
|
|
443
815
|
if (whereJq.ok) {
|
|
444
816
|
for (const line of splitNonEmptyLines(whereJq.stdout)) {
|
|
445
817
|
if (existsSync(line)) {
|
|
@@ -482,7 +854,7 @@ function findWindowsJqBinaryPath(): string {
|
|
|
482
854
|
|
|
483
855
|
const wingetPackagesRoot = localAppData ? join(localAppData, 'Microsoft', 'WinGet', 'Packages') : '';
|
|
484
856
|
if (wingetPackagesRoot && existsSync(wingetPackagesRoot)) {
|
|
485
|
-
let packageEntries:
|
|
857
|
+
let packageEntries: Dirent<string>[] = [];
|
|
486
858
|
try {
|
|
487
859
|
packageEntries = readdirSync(wingetPackagesRoot, { withFileTypes: true });
|
|
488
860
|
} catch {
|
|
@@ -522,6 +894,7 @@ function ensureWindowsJqShim(api: LoggerApi): boolean {
|
|
|
522
894
|
const shimBody = `@echo off\r\n"${escapedSource}" %*\r\n`;
|
|
523
895
|
writeFileSync(shimPath, shimBody, 'utf8');
|
|
524
896
|
}
|
|
897
|
+
prependPathEntries(process.env, [npmBinDir]);
|
|
525
898
|
} catch (e) {
|
|
526
899
|
api.logger.warn('[moltbank] could not create jq shim in npm global bin: ' + String(e));
|
|
527
900
|
return false;
|
|
@@ -529,7 +902,7 @@ function ensureWindowsJqShim(api: LoggerApi): boolean {
|
|
|
529
902
|
|
|
530
903
|
if (!hasBin('jq')) return false;
|
|
531
904
|
|
|
532
|
-
const v =
|
|
905
|
+
const v = runCommand('jq', ['--version'], { silent: true });
|
|
533
906
|
api.logger.info(`[moltbank] ✓ jq available via npm global bin shim (${v.stdout || 'unknown version'})`);
|
|
534
907
|
return true;
|
|
535
908
|
}
|
|
@@ -665,12 +1038,12 @@ function cleanupStaleMoltbankPluginLoadPaths(api: LoggerApi): boolean {
|
|
|
665
1038
|
|
|
666
1039
|
function ensureMcporter(api: LoggerApi) {
|
|
667
1040
|
if (hasBin('mcporter')) {
|
|
668
|
-
const v =
|
|
1041
|
+
const v = runCommand('mcporter', ['--version'], { silent: true });
|
|
669
1042
|
api.logger.info(`[moltbank] ✓ mcporter already installed (${v.stdout || 'unknown version'})`);
|
|
670
1043
|
return;
|
|
671
1044
|
}
|
|
672
1045
|
api.logger.info('[moltbank] installing mcporter globally...');
|
|
673
|
-
const result =
|
|
1046
|
+
const result = runCommand('npm', ['install', '-g', 'mcporter']);
|
|
674
1047
|
if (result.ok) {
|
|
675
1048
|
api.logger.info('[moltbank] ✓ mcporter installed');
|
|
676
1049
|
} else {
|
|
@@ -680,12 +1053,16 @@ function ensureMcporter(api: LoggerApi) {
|
|
|
680
1053
|
|
|
681
1054
|
function ensureJq(api: LoggerApi) {
|
|
682
1055
|
if (hasBin('jq')) {
|
|
683
|
-
const v =
|
|
1056
|
+
const v = runCommand('jq', ['--version'], { silent: true });
|
|
684
1057
|
api.logger.info(`[moltbank] ✓ jq already installed (${v.stdout || 'unknown version'})`);
|
|
685
1058
|
return;
|
|
686
1059
|
}
|
|
687
1060
|
|
|
688
1061
|
if (!IS_WIN) {
|
|
1062
|
+
if (ensureUnixJq(api)) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
689
1066
|
api.logger.warn('[moltbank] ✗ jq not found in PATH (required by MoltBank wrapper on host mode)');
|
|
690
1067
|
api.logger.warn(`[moltbank] install jq (${getJqInstallHint()}) and rerun setup`);
|
|
691
1068
|
return;
|
|
@@ -698,29 +1075,41 @@ function ensureJq(api: LoggerApi) {
|
|
|
698
1075
|
|
|
699
1076
|
api.logger.info('[moltbank] jq not found — attempting Windows install...');
|
|
700
1077
|
|
|
701
|
-
const installers: Array<{
|
|
1078
|
+
const installers: Array<{
|
|
1079
|
+
name: string;
|
|
1080
|
+
checkCommand: string;
|
|
1081
|
+
checkArgs: string[];
|
|
1082
|
+
installCommand: string;
|
|
1083
|
+
installArgs: string[];
|
|
1084
|
+
}> = [
|
|
702
1085
|
{
|
|
703
1086
|
name: 'winget',
|
|
704
|
-
|
|
705
|
-
|
|
1087
|
+
checkCommand: 'where',
|
|
1088
|
+
checkArgs: ['winget'],
|
|
1089
|
+
installCommand: 'winget',
|
|
1090
|
+
installArgs: ['install', '-e', '--id', 'jqlang.jq', '--accept-package-agreements', '--accept-source-agreements', '--silent']
|
|
706
1091
|
},
|
|
707
1092
|
{
|
|
708
1093
|
name: 'scoop',
|
|
709
|
-
|
|
710
|
-
|
|
1094
|
+
checkCommand: 'where',
|
|
1095
|
+
checkArgs: ['scoop'],
|
|
1096
|
+
installCommand: 'scoop',
|
|
1097
|
+
installArgs: ['install', 'jq']
|
|
711
1098
|
},
|
|
712
1099
|
{
|
|
713
1100
|
name: 'choco',
|
|
714
|
-
|
|
715
|
-
|
|
1101
|
+
checkCommand: 'where',
|
|
1102
|
+
checkArgs: ['choco'],
|
|
1103
|
+
installCommand: 'choco',
|
|
1104
|
+
installArgs: ['install', 'jq', '-y']
|
|
716
1105
|
}
|
|
717
1106
|
];
|
|
718
1107
|
|
|
719
1108
|
for (const installer of installers) {
|
|
720
|
-
if (!
|
|
1109
|
+
if (!runCommand(installer.checkCommand, installer.checkArgs, { silent: true }).ok) continue;
|
|
721
1110
|
|
|
722
1111
|
api.logger.info(`[moltbank] trying jq install via ${installer.name}...`);
|
|
723
|
-
const install =
|
|
1112
|
+
const install = runCommand(installer.installCommand, installer.installArgs, { timeoutMs: 180000 });
|
|
724
1113
|
const installOutput = `${install.stdout}\n${install.stderr}`;
|
|
725
1114
|
const installOutputLower = installOutput.toLowerCase();
|
|
726
1115
|
const alreadyInstalledOrNoUpdate =
|
|
@@ -741,7 +1130,7 @@ function ensureJq(api: LoggerApi) {
|
|
|
741
1130
|
}
|
|
742
1131
|
|
|
743
1132
|
if (hasBin('jq')) {
|
|
744
|
-
const v =
|
|
1133
|
+
const v = runCommand('jq', ['--version'], { silent: true });
|
|
745
1134
|
api.logger.info(`[moltbank] ✓ jq installed via ${installer.name} (${v.stdout || 'unknown version'})`);
|
|
746
1135
|
return;
|
|
747
1136
|
}
|
|
@@ -821,13 +1210,67 @@ function hasRequiredSkillFiles(skillDir: string): boolean {
|
|
|
821
1210
|
return SKILL_FILES.every((file) => existsSync(join(skillDir, file)));
|
|
822
1211
|
}
|
|
823
1212
|
|
|
824
|
-
function
|
|
1213
|
+
async function installSkillBundleFiles(appBaseUrl: string, skillDir: string): Promise<RunResult> {
|
|
1214
|
+
const base = appBaseUrl.endsWith('/') ? appBaseUrl.slice(0, -1) : appBaseUrl;
|
|
1215
|
+
const aliases: Record<string, string[]> = {
|
|
1216
|
+
'SKILL.md': ['SKILL.md', 'skill.md'],
|
|
1217
|
+
'skill.md': ['skill.md', 'SKILL.md']
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
try {
|
|
1221
|
+
for (const file of SKILL_FILES) {
|
|
1222
|
+
const outPath = join(skillDir, file);
|
|
1223
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
1224
|
+
|
|
1225
|
+
let body: string | null = null;
|
|
1226
|
+
let lastUrl = '';
|
|
1227
|
+
let lastStatus = '';
|
|
1228
|
+
for (const candidate of aliases[file] ?? [file]) {
|
|
1229
|
+
const url = `${base}/${candidate}`;
|
|
1230
|
+
lastUrl = url;
|
|
1231
|
+
|
|
1232
|
+
const response = await fetch(url);
|
|
1233
|
+
if (response.ok) {
|
|
1234
|
+
body = await response.text();
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
lastStatus = String(response.status);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (body === null) {
|
|
1242
|
+
return {
|
|
1243
|
+
ok: false,
|
|
1244
|
+
stdout: '',
|
|
1245
|
+
stderr: `download failed ${lastUrl} ${lastStatus}`.trim(),
|
|
1246
|
+
exitCode: 2,
|
|
1247
|
+
timedOut: false
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
writeFileSync(outPath, body, 'utf8');
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
writeFileSync(join(skillDir, '.install_success'), 'ok\n', 'utf8');
|
|
1255
|
+
return { ok: true, stdout: '', stderr: '', exitCode: 0, timedOut: false };
|
|
1256
|
+
} catch (e) {
|
|
1257
|
+
return {
|
|
1258
|
+
ok: false,
|
|
1259
|
+
stdout: '',
|
|
1260
|
+
stderr: String(e),
|
|
1261
|
+
exitCode: null,
|
|
1262
|
+
timedOut: false
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async function ensureSkillInstalled(
|
|
825
1268
|
skillDir: string,
|
|
826
1269
|
appBaseUrl: string,
|
|
827
1270
|
skillName: string,
|
|
828
1271
|
api: LoggerApi,
|
|
829
1272
|
mode: 'sandbox' | 'host' = 'sandbox'
|
|
830
|
-
) {
|
|
1273
|
+
): Promise<boolean> {
|
|
831
1274
|
const successFlag = join(skillDir, '.install_success');
|
|
832
1275
|
if (existsSync(successFlag) && hasRequiredSkillFiles(skillDir)) {
|
|
833
1276
|
ensureWrapperExecutable(skillDir, api);
|
|
@@ -847,11 +1290,7 @@ function ensureSkillInstalled(
|
|
|
847
1290
|
api.logger.info(`[moltbank] installing skill '${skillName}' to ${skillDir} (mode: ${mode})`);
|
|
848
1291
|
mkdirSync(skillDir, { recursive: true });
|
|
849
1292
|
|
|
850
|
-
const
|
|
851
|
-
const installNode = run(
|
|
852
|
-
`node --input-type=module -e "import fs from 'fs'; import path from 'path'; const baseRaw=process.argv[1]; const dir=process.argv[2]; const files=JSON.parse(process.argv[3]); const base=baseRaw.endsWith('/') ? baseRaw.slice(0,-1) : baseRaw; const aliases={ 'SKILL.md':['SKILL.md','skill.md'], 'skill.md':['skill.md','SKILL.md'] }; fs.mkdirSync(dir,{recursive:true}); for (const f of files){ const out=path.join(dir,f); fs.mkdirSync(path.dirname(out),{recursive:true}); let body=null; let lastUrl=''; let lastStatus=''; for (const candidate of (aliases[f] ?? [f])){ const u=base+'/'+candidate; lastUrl=u; const r=await fetch(u); if(r.ok){ body=await r.text(); break; } lastStatus=String(r.status); } if(body===null){ console.error('download failed',lastUrl,lastStatus); process.exit(2);} fs.writeFileSync(out, body,'utf8'); } fs.writeFileSync(path.join(dir,'.install_success'),'ok\\n','utf8');" "${appBaseUrl}" "${skillDir}" "${filesJson}"`,
|
|
853
|
-
{ cwd: dirname(skillDir), silent: true }
|
|
854
|
-
);
|
|
1293
|
+
const installNode = await installSkillBundleFiles(appBaseUrl, skillDir);
|
|
855
1294
|
|
|
856
1295
|
if (installNode.ok) {
|
|
857
1296
|
ensureWrapperExecutable(skillDir, api);
|
|
@@ -906,34 +1345,42 @@ function ensureSkillPermissions(skillDir: string, api: LoggerApi) {
|
|
|
906
1345
|
const referencesDir = join(skillDir, 'references');
|
|
907
1346
|
const scriptsDir = join(skillDir, 'scripts');
|
|
908
1347
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1348
|
+
let currentUserName = '';
|
|
1349
|
+
try {
|
|
1350
|
+
const currentUser = userInfo();
|
|
1351
|
+
currentUserName = currentUser.username;
|
|
1352
|
+
if (existsSync(skillDir)) {
|
|
1353
|
+
applyRecursiveOwnership(skillDir, currentUser.uid, currentUser.gid);
|
|
1354
|
+
api.logger.info(`[moltbank] ✓ ownership corrected to ${currentUserName}`);
|
|
1355
|
+
}
|
|
1356
|
+
} catch {
|
|
1357
|
+
// ignore ownership correction when uid/gid are unavailable
|
|
913
1358
|
}
|
|
914
1359
|
|
|
915
1360
|
if (existsSync(assetsDir)) {
|
|
916
|
-
|
|
917
|
-
api.logger.info('[moltbank] ✓ assets/ permissions →
|
|
1361
|
+
applyRecursiveModes(assetsDir, 0o750, 0o640);
|
|
1362
|
+
api.logger.info('[moltbank] ✓ assets/ permissions → 750 dirs, 640 files');
|
|
918
1363
|
}
|
|
919
1364
|
if (existsSync(configFile)) {
|
|
920
|
-
|
|
921
|
-
|
|
1365
|
+
try {
|
|
1366
|
+
chmodSync(configFile, 0o600);
|
|
1367
|
+
} catch {
|
|
1368
|
+
// ignore
|
|
1369
|
+
}
|
|
1370
|
+
api.logger.info('[moltbank] ✓ mcporter.json permissions → 600');
|
|
922
1371
|
}
|
|
923
1372
|
if (existsSync(scriptsDir)) {
|
|
924
|
-
|
|
925
|
-
api.logger.info('[moltbank] ✓ scripts/ permissions →
|
|
926
|
-
|
|
927
|
-
silent: true
|
|
928
|
-
});
|
|
1373
|
+
applyRecursiveModes(scriptsDir, 0o750, 0o750);
|
|
1374
|
+
api.logger.info('[moltbank] ✓ scripts/ permissions → 750');
|
|
1375
|
+
normalizeFilesMatching(scriptsDir, () => true);
|
|
929
1376
|
api.logger.info('[moltbank] ✓ scripts/ line endings normalized (CRLF → LF)');
|
|
930
1377
|
}
|
|
931
1378
|
|
|
932
1379
|
if (existsSync(skillDir)) {
|
|
933
|
-
|
|
1380
|
+
normalizeFilesMatching(skillDir, (fullPath, entry) => entry.isFile() && fullPath.endsWith('.md'), 0);
|
|
934
1381
|
}
|
|
935
1382
|
if (existsSync(referencesDir)) {
|
|
936
|
-
|
|
1383
|
+
normalizeFilesMatching(referencesDir, (fullPath, entry) => entry.isFile() && fullPath.endsWith('.md'), 0);
|
|
937
1384
|
}
|
|
938
1385
|
if (existsSync(skillDir) || existsSync(referencesDir)) {
|
|
939
1386
|
api.logger.info('[moltbank] ✓ markdown line endings normalized (CRLF → LF)');
|
|
@@ -961,7 +1408,7 @@ function ensureNpmDeps(skillDir: string, api: LoggerApi, mode: 'sandbox' | 'host
|
|
|
961
1408
|
}
|
|
962
1409
|
api.logger.info('[moltbank] installing npm deps...');
|
|
963
1410
|
if (mode === 'sandbox') {
|
|
964
|
-
const result =
|
|
1411
|
+
const result = runCommand('npm', ['install', '--ignore-scripts'], { cwd: skillDir });
|
|
965
1412
|
if (result.ok) {
|
|
966
1413
|
api.logger.info('[moltbank] ✓ npm deps installed');
|
|
967
1414
|
} else {
|
|
@@ -972,20 +1419,20 @@ function ensureNpmDeps(skillDir: string, api: LoggerApi, mode: 'sandbox' | 'host
|
|
|
972
1419
|
|
|
973
1420
|
const hasLock = existsSync(join(skillDir, 'package-lock.json')) || existsSync(join(skillDir, 'npm-shrinkwrap.json'));
|
|
974
1421
|
if (hasLock) {
|
|
975
|
-
const ci =
|
|
1422
|
+
const ci = runCommand('npm', ['ci'], { cwd: skillDir });
|
|
976
1423
|
if (ci.ok) {
|
|
977
1424
|
api.logger.info('[moltbank] ✓ npm deps installed with npm ci');
|
|
978
1425
|
return;
|
|
979
1426
|
}
|
|
980
1427
|
api.logger.warn('[moltbank] npm ci failed; trying npm install: ' + ci.stderr);
|
|
981
1428
|
}
|
|
982
|
-
const install =
|
|
1429
|
+
const install = runCommand('npm', ['install'], { cwd: skillDir });
|
|
983
1430
|
if (install.ok) {
|
|
984
1431
|
api.logger.info('[moltbank] ✓ npm deps installed with npm install');
|
|
985
1432
|
return;
|
|
986
1433
|
}
|
|
987
1434
|
api.logger.warn('[moltbank] npm install failed; trying safe fallback --ignore-scripts');
|
|
988
|
-
const fallback =
|
|
1435
|
+
const fallback = runCommand('npm', ['install', '--ignore-scripts'], { cwd: skillDir });
|
|
989
1436
|
if (fallback.ok) {
|
|
990
1437
|
api.logger.warn('[moltbank] npm deps installed with fallback --ignore-scripts (some packages may require postinstall)');
|
|
991
1438
|
} else {
|
|
@@ -994,7 +1441,7 @@ function ensureNpmDeps(skillDir: string, api: LoggerApi, mode: 'sandbox' | 'host
|
|
|
994
1441
|
}
|
|
995
1442
|
|
|
996
1443
|
function isMoltBankRegistered(): boolean {
|
|
997
|
-
const result =
|
|
1444
|
+
const result = runCommand('mcporter', ['config', 'list'], { silent: true });
|
|
998
1445
|
return result.stdout.toLowerCase().includes('moltbank');
|
|
999
1446
|
}
|
|
1000
1447
|
|
|
@@ -1266,7 +1713,7 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1266
1713
|
if (!deviceCode || !userCode) {
|
|
1267
1714
|
api.logger.info('[moltbank] no valid credentials found — starting onboarding flow...');
|
|
1268
1715
|
|
|
1269
|
-
const requestCode =
|
|
1716
|
+
const requestCode = runCommand(process.execPath, ['./scripts/request-oauth-device-code.mjs'], {
|
|
1270
1717
|
cwd: skillDir,
|
|
1271
1718
|
silent: true,
|
|
1272
1719
|
env: {
|
|
@@ -1341,8 +1788,9 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1341
1788
|
const pollIntervalSeconds = Number(process.env.MOLTBANK_OAUTH_POLL_INTERVAL_SECONDS ?? 5);
|
|
1342
1789
|
const safePollIntervalSeconds = Number.isFinite(pollIntervalSeconds) && pollIntervalSeconds > 0 ? Math.floor(pollIntervalSeconds) : 5;
|
|
1343
1790
|
|
|
1344
|
-
const poll =
|
|
1345
|
-
|
|
1791
|
+
const poll = runCommand(
|
|
1792
|
+
process.execPath,
|
|
1793
|
+
['./scripts/poll-oauth-token.mjs', deviceCode, String(safePollTimeoutSeconds), String(safePollIntervalSeconds), '--save'],
|
|
1346
1794
|
{
|
|
1347
1795
|
cwd: skillDir,
|
|
1348
1796
|
silent: true,
|
|
@@ -1506,7 +1954,7 @@ export function injectSandboxEnv(skillDir: string, api: LoggerApi): boolean {
|
|
|
1506
1954
|
|
|
1507
1955
|
if (!privateKey) {
|
|
1508
1956
|
api.logger.info('[moltbank] x402_signer_private_key not found — generating EOA signer...');
|
|
1509
|
-
const initResult =
|
|
1957
|
+
const initResult = runCommand(process.execPath, ['./scripts/init-openclaw-signer.mjs'], {
|
|
1510
1958
|
cwd: skillDir,
|
|
1511
1959
|
silent: true,
|
|
1512
1960
|
env: {
|
|
@@ -1629,39 +2077,52 @@ export function configureSandbox(api: LoggerApi): boolean {
|
|
|
1629
2077
|
export function recreateSandboxAndRestart(api: LoggerApi) {
|
|
1630
2078
|
api.logger.info('[moltbank] recreating sandbox containers...');
|
|
1631
2079
|
api.logger.info('[moltbank] ⏳ waiting 8s before recreate (hot container protection)...');
|
|
2080
|
+
|
|
2081
|
+
const getGatewayPids = (): string[] => {
|
|
2082
|
+
const result = runCommand('pgrep', ['-f', 'openclaw-gateway'], { silent: true });
|
|
2083
|
+
if (!result.ok) return [];
|
|
2084
|
+
return splitNonEmptyLines(result.stdout).filter((pid) => /^\d+$/.test(pid));
|
|
2085
|
+
};
|
|
2086
|
+
|
|
1632
2087
|
setTimeout(() => {
|
|
1633
2088
|
api.logger.info('[moltbank] stopping gateway...');
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
const stillRunning =
|
|
1637
|
-
if (stillRunning.
|
|
1638
|
-
api.logger.info(`[moltbank] gateway still running (pids: ${stillRunning.
|
|
1639
|
-
|
|
2089
|
+
runCommand('openclaw', ['gateway', 'stop'], { silent: true });
|
|
2090
|
+
|
|
2091
|
+
const stillRunning = getGatewayPids();
|
|
2092
|
+
if (stillRunning.length) {
|
|
2093
|
+
api.logger.info(`[moltbank] gateway still running (pids: ${stillRunning.join(' ')}) — sending SIGKILL...`);
|
|
2094
|
+
for (const pid of stillRunning) {
|
|
2095
|
+
try {
|
|
2096
|
+
process.kill(Number(pid), 'SIGKILL');
|
|
2097
|
+
} catch {
|
|
2098
|
+
// ignore
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
1640
2101
|
api.logger.info('[moltbank] ✓ gateway process killed');
|
|
1641
2102
|
} else {
|
|
1642
2103
|
api.logger.info('[moltbank] ✓ gateway stopped cleanly');
|
|
1643
2104
|
}
|
|
1644
2105
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
}
|
|
2106
|
+
setTimeout(() => {
|
|
2107
|
+
const recreate = runCommand('openclaw', ['sandbox', 'recreate', '--all', '--force'], {
|
|
2108
|
+
silent: false
|
|
2109
|
+
});
|
|
2110
|
+
if (recreate.ok) {
|
|
2111
|
+
api.logger.info('[moltbank] ✓ sandbox containers recreated — new container will be created on next agent message');
|
|
2112
|
+
} else {
|
|
2113
|
+
api.logger.warn('[moltbank] ✗ sandbox recreate failed');
|
|
2114
|
+
api.logger.warn('[moltbank] run manually: openclaw sandbox recreate --all --force');
|
|
2115
|
+
}
|
|
1656
2116
|
|
|
1657
|
-
|
|
1658
|
-
|
|
2117
|
+
api.logger.info('[moltbank] restarting gateway...');
|
|
2118
|
+
runCommand('openclaw', ['gateway'], { silent: true });
|
|
2119
|
+
}, 2000);
|
|
1659
2120
|
}, 8000);
|
|
1660
2121
|
}
|
|
1661
2122
|
|
|
1662
2123
|
function restartGatewayAfterHostSetup(api: LoggerApi): boolean {
|
|
1663
2124
|
api.logger.info('[moltbank] restarting gateway to refresh host skill state...');
|
|
1664
|
-
const restart =
|
|
2125
|
+
const restart = runCommand('openclaw', ['gateway', 'restart'], { silent: true, timeoutMs: 30000 });
|
|
1665
2126
|
if (restart.ok) {
|
|
1666
2127
|
api.logger.info('[moltbank] ✓ gateway restarted');
|
|
1667
2128
|
return true;
|
|
@@ -1709,7 +2170,7 @@ export async function runSetup(
|
|
|
1709
2170
|
ensureJq(api);
|
|
1710
2171
|
|
|
1711
2172
|
api.logger.info('[moltbank] [sandbox 1/10] installing skill files...');
|
|
1712
|
-
const skillInstalled = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'sandbox');
|
|
2173
|
+
const skillInstalled = await ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'sandbox');
|
|
1713
2174
|
if (!skillInstalled) {
|
|
1714
2175
|
api.logger.warn('[moltbank] skill install failed — aborting sandbox setup');
|
|
1715
2176
|
return;
|
|
@@ -1767,7 +2228,7 @@ export async function runSetup(
|
|
|
1767
2228
|
ensureJq(api);
|
|
1768
2229
|
|
|
1769
2230
|
api.logger.info('[moltbank] [host 2/8] installing skill files...');
|
|
1770
|
-
const installed = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'host');
|
|
2231
|
+
const installed = await ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'host');
|
|
1771
2232
|
if (!installed) {
|
|
1772
2233
|
api.logger.warn('[moltbank] host setup aborted: skill install failed. Verify install.sh/base URL and retry.');
|
|
1773
2234
|
return;
|
|
@@ -1811,7 +2272,7 @@ export async function runSetup(
|
|
|
1811
2272
|
if (IS_WIN) {
|
|
1812
2273
|
const mcporterConfigPath = join(skillDir, 'assets', 'mcporter.json');
|
|
1813
2274
|
const active = parseActiveTokenFromCredentials();
|
|
1814
|
-
const mcpSmoke =
|
|
2275
|
+
const mcpSmoke = runCommand('mcporter', ['--config', mcporterConfigPath, 'list', 'MoltBank'], {
|
|
1815
2276
|
cwd: skillDir,
|
|
1816
2277
|
silent: true,
|
|
1817
2278
|
timeoutMs: smokeTimeoutMs,
|
|
@@ -1838,8 +2299,16 @@ export async function runSetup(
|
|
|
1838
2299
|
);
|
|
1839
2300
|
|
|
1840
2301
|
const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
|
|
1841
|
-
const fallback =
|
|
1842
|
-
'powershell
|
|
2302
|
+
const fallback = runCommand(
|
|
2303
|
+
'powershell',
|
|
2304
|
+
[
|
|
2305
|
+
'-NoProfile',
|
|
2306
|
+
'-NonInteractive',
|
|
2307
|
+
'-ExecutionPolicy',
|
|
2308
|
+
'Bypass',
|
|
2309
|
+
'-Command',
|
|
2310
|
+
"$ErrorActionPreference = 'Stop'; if (-not (Test-Path -LiteralPath $env:MOLTBANK_WRAPPER_PATH)) { throw 'moltbank.ps1 not found' }; if (-not (Get-Command bash -ErrorAction SilentlyContinue)) { throw 'bash is not installed or not on PATH' }; if (-not (Get-Command mcporter -ErrorAction SilentlyContinue)) { throw 'mcporter is not installed or not on PATH' }; Write-Output 'ok'"
|
|
2311
|
+
],
|
|
1843
2312
|
{
|
|
1844
2313
|
cwd: skillDir,
|
|
1845
2314
|
silent: true,
|
|
@@ -1869,7 +2338,7 @@ export async function runSetup(
|
|
|
1869
2338
|
}
|
|
1870
2339
|
}
|
|
1871
2340
|
} else {
|
|
1872
|
-
const smoke =
|
|
2341
|
+
const smoke = runCommand(join(skillDir, 'scripts', 'moltbank.sh'), ['list', 'MoltBank'], {
|
|
1873
2342
|
cwd: skillDir,
|
|
1874
2343
|
silent: true,
|
|
1875
2344
|
timeoutMs: smokeTimeoutMs
|
|
@@ -1925,10 +2394,11 @@ export async function runSetup(
|
|
|
1925
2394
|
`[moltbank] setupCommand: ${finalCmd.includes('node_22.x') && !finalCmd.includes('/home/') ? '✓ ok (no host paths)' : '✗ may be corrupted'}`
|
|
1926
2395
|
);
|
|
1927
2396
|
|
|
1928
|
-
const
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2397
|
+
const sandboxContainer = runCommand('docker', ['ps', '-q', '--filter', 'name=openclaw-sbx'], { silent: true });
|
|
2398
|
+
const sandboxContainerId = splitNonEmptyLines(sandboxContainer.stdout)[0] || '';
|
|
2399
|
+
const containerCheck = sandboxContainerId
|
|
2400
|
+
? runCommand('docker', ['inspect', sandboxContainerId, '--format', '{{.HostConfig.NetworkMode}}'], { silent: true })
|
|
2401
|
+
: { ok: false, stdout: '', stderr: '', exitCode: 1, timedOut: false };
|
|
1932
2402
|
if (!containerCheck.ok || !containerCheck.stdout) {
|
|
1933
2403
|
api.logger.info(`[moltbank] container: not running yet — will be created on first agent message`);
|
|
1934
2404
|
} else if (containerCheck.stdout.trim() === 'bridge') {
|