@factiii/runner 0.2.0 → 0.6.0
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/dist/cli.js +744 -271
- package/package.json +6 -4
package/dist/cli.js
CHANGED
|
@@ -30,9 +30,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// node_modules/commander/lib/error.js
|
|
34
34
|
var require_error = __commonJS({
|
|
35
|
-
"
|
|
35
|
+
"node_modules/commander/lib/error.js"(exports2) {
|
|
36
36
|
var CommanderError2 = class extends Error {
|
|
37
37
|
/**
|
|
38
38
|
* Constructs the CommanderError class
|
|
@@ -65,9 +65,9 @@ var require_error = __commonJS({
|
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// node_modules/commander/lib/argument.js
|
|
69
69
|
var require_argument = __commonJS({
|
|
70
|
-
"
|
|
70
|
+
"node_modules/commander/lib/argument.js"(exports2) {
|
|
71
71
|
var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
72
72
|
var Argument2 = class {
|
|
73
73
|
/**
|
|
@@ -192,9 +192,9 @@ var require_argument = __commonJS({
|
|
|
192
192
|
}
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
//
|
|
195
|
+
// node_modules/commander/lib/help.js
|
|
196
196
|
var require_help = __commonJS({
|
|
197
|
-
"
|
|
197
|
+
"node_modules/commander/lib/help.js"(exports2) {
|
|
198
198
|
var { humanReadableArgName } = require_argument();
|
|
199
199
|
var Help2 = class {
|
|
200
200
|
constructor() {
|
|
@@ -606,9 +606,9 @@ var require_help = __commonJS({
|
|
|
606
606
|
}
|
|
607
607
|
});
|
|
608
608
|
|
|
609
|
-
//
|
|
609
|
+
// node_modules/commander/lib/option.js
|
|
610
610
|
var require_option = __commonJS({
|
|
611
|
-
"
|
|
611
|
+
"node_modules/commander/lib/option.js"(exports2) {
|
|
612
612
|
var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
613
613
|
var Option2 = class {
|
|
614
614
|
/**
|
|
@@ -878,9 +878,9 @@ var require_option = __commonJS({
|
|
|
878
878
|
}
|
|
879
879
|
});
|
|
880
880
|
|
|
881
|
-
//
|
|
881
|
+
// node_modules/commander/lib/suggestSimilar.js
|
|
882
882
|
var require_suggestSimilar = __commonJS({
|
|
883
|
-
"
|
|
883
|
+
"node_modules/commander/lib/suggestSimilar.js"(exports2) {
|
|
884
884
|
var maxDistance = 3;
|
|
885
885
|
function editDistance(a, b) {
|
|
886
886
|
if (Math.abs(a.length - b.length) > maxDistance)
|
|
@@ -958,13 +958,13 @@ var require_suggestSimilar = __commonJS({
|
|
|
958
958
|
}
|
|
959
959
|
});
|
|
960
960
|
|
|
961
|
-
//
|
|
961
|
+
// node_modules/commander/lib/command.js
|
|
962
962
|
var require_command = __commonJS({
|
|
963
|
-
"
|
|
963
|
+
"node_modules/commander/lib/command.js"(exports2) {
|
|
964
964
|
var EventEmitter = require("node:events").EventEmitter;
|
|
965
965
|
var childProcess = require("node:child_process");
|
|
966
|
-
var
|
|
967
|
-
var
|
|
966
|
+
var path8 = require("node:path");
|
|
967
|
+
var fs7 = require("node:fs");
|
|
968
968
|
var process2 = require("node:process");
|
|
969
969
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
970
970
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1896,11 +1896,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1896
1896
|
let launchWithNode = false;
|
|
1897
1897
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1898
1898
|
function findFile(baseDir, baseName) {
|
|
1899
|
-
const localBin =
|
|
1900
|
-
if (
|
|
1901
|
-
if (sourceExt.includes(
|
|
1899
|
+
const localBin = path8.resolve(baseDir, baseName);
|
|
1900
|
+
if (fs7.existsSync(localBin)) return localBin;
|
|
1901
|
+
if (sourceExt.includes(path8.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
|
-
(ext) =>
|
|
1903
|
+
(ext) => fs7.existsSync(`${localBin}${ext}`)
|
|
1904
1904
|
);
|
|
1905
1905
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1906
1906
|
return void 0;
|
|
@@ -1912,21 +1912,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1912
1912
|
if (this._scriptPath) {
|
|
1913
1913
|
let resolvedScriptPath;
|
|
1914
1914
|
try {
|
|
1915
|
-
resolvedScriptPath =
|
|
1915
|
+
resolvedScriptPath = fs7.realpathSync(this._scriptPath);
|
|
1916
1916
|
} catch (err) {
|
|
1917
1917
|
resolvedScriptPath = this._scriptPath;
|
|
1918
1918
|
}
|
|
1919
|
-
executableDir =
|
|
1920
|
-
|
|
1919
|
+
executableDir = path8.resolve(
|
|
1920
|
+
path8.dirname(resolvedScriptPath),
|
|
1921
1921
|
executableDir
|
|
1922
1922
|
);
|
|
1923
1923
|
}
|
|
1924
1924
|
if (executableDir) {
|
|
1925
1925
|
let localFile = findFile(executableDir, executableFile);
|
|
1926
1926
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1927
|
-
const legacyName =
|
|
1927
|
+
const legacyName = path8.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path8.extname(this._scriptPath)
|
|
1930
1930
|
);
|
|
1931
1931
|
if (legacyName !== this._name) {
|
|
1932
1932
|
localFile = findFile(
|
|
@@ -1937,7 +1937,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1937
1937
|
}
|
|
1938
1938
|
executableFile = localFile || executableFile;
|
|
1939
1939
|
}
|
|
1940
|
-
launchWithNode = sourceExt.includes(
|
|
1940
|
+
launchWithNode = sourceExt.includes(path8.extname(executableFile));
|
|
1941
1941
|
let proc;
|
|
1942
1942
|
if (process2.platform !== "win32") {
|
|
1943
1943
|
if (launchWithNode) {
|
|
@@ -2777,7 +2777,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2777
2777
|
* @return {Command}
|
|
2778
2778
|
*/
|
|
2779
2779
|
nameFromFilename(filename) {
|
|
2780
|
-
this._name =
|
|
2780
|
+
this._name = path8.basename(filename, path8.extname(filename));
|
|
2781
2781
|
return this;
|
|
2782
2782
|
}
|
|
2783
2783
|
/**
|
|
@@ -2791,9 +2791,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2791
2791
|
* @param {string} [path]
|
|
2792
2792
|
* @return {(string|null|Command)}
|
|
2793
2793
|
*/
|
|
2794
|
-
executableDir(
|
|
2795
|
-
if (
|
|
2796
|
-
this._executableDir =
|
|
2794
|
+
executableDir(path9) {
|
|
2795
|
+
if (path9 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path9;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -3001,9 +3001,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3001
3001
|
}
|
|
3002
3002
|
});
|
|
3003
3003
|
|
|
3004
|
-
//
|
|
3004
|
+
// node_modules/commander/index.js
|
|
3005
3005
|
var require_commander = __commonJS({
|
|
3006
|
-
"
|
|
3006
|
+
"node_modules/commander/index.js"(exports2) {
|
|
3007
3007
|
var { Argument: Argument2 } = require_argument();
|
|
3008
3008
|
var { Command: Command2 } = require_command();
|
|
3009
3009
|
var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
@@ -3026,7 +3026,7 @@ var require_commander = __commonJS({
|
|
|
3026
3026
|
// ../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
|
|
3027
3027
|
var require_XMLHttpRequest = __commonJS({
|
|
3028
3028
|
"../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js"(exports2, module2) {
|
|
3029
|
-
var
|
|
3029
|
+
var fs7 = require("fs");
|
|
3030
3030
|
var Url = require("url");
|
|
3031
3031
|
var spawn3 = require("child_process").spawn;
|
|
3032
3032
|
module2.exports = XMLHttpRequest3;
|
|
@@ -3184,7 +3184,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3184
3184
|
throw new Error("XMLHttpRequest: Only GET method is supported");
|
|
3185
3185
|
}
|
|
3186
3186
|
if (settings.async) {
|
|
3187
|
-
|
|
3187
|
+
fs7.readFile(unescape(url2.pathname), function(error, data2) {
|
|
3188
3188
|
if (error) {
|
|
3189
3189
|
self.handleError(error, error.errno || -1);
|
|
3190
3190
|
} else {
|
|
@@ -3196,7 +3196,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3196
3196
|
});
|
|
3197
3197
|
} else {
|
|
3198
3198
|
try {
|
|
3199
|
-
this.response =
|
|
3199
|
+
this.response = fs7.readFileSync(unescape(url2.pathname));
|
|
3200
3200
|
this.responseText = this.response.toString("utf8");
|
|
3201
3201
|
this.status = 200;
|
|
3202
3202
|
setState(self.DONE);
|
|
@@ -3322,15 +3322,15 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3322
3322
|
} else {
|
|
3323
3323
|
var contentFile = ".node-xmlhttprequest-content-" + process.pid;
|
|
3324
3324
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
3325
|
-
|
|
3325
|
+
fs7.writeFileSync(syncFile, "", "utf8");
|
|
3326
3326
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
3327
3327
|
var syncProc = spawn3(process.argv[0], ["-e", execString]);
|
|
3328
3328
|
var statusText;
|
|
3329
|
-
while (
|
|
3329
|
+
while (fs7.existsSync(syncFile)) {
|
|
3330
3330
|
}
|
|
3331
|
-
self.responseText =
|
|
3331
|
+
self.responseText = fs7.readFileSync(contentFile, "utf8");
|
|
3332
3332
|
syncProc.stdin.end();
|
|
3333
|
-
|
|
3333
|
+
fs7.unlinkSync(contentFile);
|
|
3334
3334
|
if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
|
|
3335
3335
|
var errorObj = JSON.parse(self.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, ""));
|
|
3336
3336
|
self.handleError(errorObj, 503);
|
|
@@ -4260,9 +4260,9 @@ var require_src = __commonJS({
|
|
|
4260
4260
|
}
|
|
4261
4261
|
});
|
|
4262
4262
|
|
|
4263
|
-
// ../../node_modules/
|
|
4263
|
+
// ../../node_modules/ws/lib/constants.js
|
|
4264
4264
|
var require_constants = __commonJS({
|
|
4265
|
-
"../../node_modules/
|
|
4265
|
+
"../../node_modules/ws/lib/constants.js"(exports2, module2) {
|
|
4266
4266
|
"use strict";
|
|
4267
4267
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
4268
4268
|
var hasBlob = typeof Blob !== "undefined";
|
|
@@ -4283,9 +4283,9 @@ var require_constants = __commonJS({
|
|
|
4283
4283
|
}
|
|
4284
4284
|
});
|
|
4285
4285
|
|
|
4286
|
-
// ../../node_modules/
|
|
4286
|
+
// ../../node_modules/ws/lib/buffer-util.js
|
|
4287
4287
|
var require_buffer_util = __commonJS({
|
|
4288
|
-
"../../node_modules/
|
|
4288
|
+
"../../node_modules/ws/lib/buffer-util.js"(exports2, module2) {
|
|
4289
4289
|
"use strict";
|
|
4290
4290
|
var { EMPTY_BUFFER } = require_constants();
|
|
4291
4291
|
var FastBuffer = Buffer[Symbol.species];
|
|
@@ -4358,9 +4358,9 @@ var require_buffer_util = __commonJS({
|
|
|
4358
4358
|
}
|
|
4359
4359
|
});
|
|
4360
4360
|
|
|
4361
|
-
// ../../node_modules/
|
|
4361
|
+
// ../../node_modules/ws/lib/limiter.js
|
|
4362
4362
|
var require_limiter = __commonJS({
|
|
4363
|
-
"../../node_modules/
|
|
4363
|
+
"../../node_modules/ws/lib/limiter.js"(exports2, module2) {
|
|
4364
4364
|
"use strict";
|
|
4365
4365
|
var kDone = Symbol("kDone");
|
|
4366
4366
|
var kRun = Symbol("kRun");
|
|
@@ -4408,9 +4408,9 @@ var require_limiter = __commonJS({
|
|
|
4408
4408
|
}
|
|
4409
4409
|
});
|
|
4410
4410
|
|
|
4411
|
-
// ../../node_modules/
|
|
4411
|
+
// ../../node_modules/ws/lib/permessage-deflate.js
|
|
4412
4412
|
var require_permessage_deflate = __commonJS({
|
|
4413
|
-
"../../node_modules/
|
|
4413
|
+
"../../node_modules/ws/lib/permessage-deflate.js"(exports2, module2) {
|
|
4414
4414
|
"use strict";
|
|
4415
4415
|
var zlib = require("zlib");
|
|
4416
4416
|
var bufferUtil = require_buffer_util();
|
|
@@ -4791,9 +4791,9 @@ var require_permessage_deflate = __commonJS({
|
|
|
4791
4791
|
}
|
|
4792
4792
|
});
|
|
4793
4793
|
|
|
4794
|
-
// ../../node_modules/
|
|
4794
|
+
// ../../node_modules/ws/lib/validation.js
|
|
4795
4795
|
var require_validation = __commonJS({
|
|
4796
|
-
"../../node_modules/
|
|
4796
|
+
"../../node_modules/ws/lib/validation.js"(exports2, module2) {
|
|
4797
4797
|
"use strict";
|
|
4798
4798
|
var { isUtf8 } = require("buffer");
|
|
4799
4799
|
var { hasBlob } = require_constants();
|
|
@@ -4992,9 +4992,9 @@ var require_validation = __commonJS({
|
|
|
4992
4992
|
}
|
|
4993
4993
|
});
|
|
4994
4994
|
|
|
4995
|
-
// ../../node_modules/
|
|
4995
|
+
// ../../node_modules/ws/lib/receiver.js
|
|
4996
4996
|
var require_receiver = __commonJS({
|
|
4997
|
-
"../../node_modules/
|
|
4997
|
+
"../../node_modules/ws/lib/receiver.js"(exports2, module2) {
|
|
4998
4998
|
"use strict";
|
|
4999
4999
|
var { Writable } = require("stream");
|
|
5000
5000
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -5027,6 +5027,10 @@ var require_receiver = __commonJS({
|
|
|
5027
5027
|
* extensions
|
|
5028
5028
|
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
|
|
5029
5029
|
* client or server mode
|
|
5030
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
5031
|
+
* buffered data chunks
|
|
5032
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
5033
|
+
* fragments
|
|
5030
5034
|
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
5031
5035
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
5032
5036
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -5037,6 +5041,8 @@ var require_receiver = __commonJS({
|
|
|
5037
5041
|
this._binaryType = options.binaryType || BINARY_TYPES[0];
|
|
5038
5042
|
this._extensions = options.extensions || {};
|
|
5039
5043
|
this._isServer = !!options.isServer;
|
|
5044
|
+
this._maxBufferedChunks = options.maxBufferedChunks | 0;
|
|
5045
|
+
this._maxFragments = options.maxFragments | 0;
|
|
5040
5046
|
this._maxPayload = options.maxPayload | 0;
|
|
5041
5047
|
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
|
5042
5048
|
this[kWebSocket] = void 0;
|
|
@@ -5066,6 +5072,18 @@ var require_receiver = __commonJS({
|
|
|
5066
5072
|
*/
|
|
5067
5073
|
_write(chunk, encoding, cb) {
|
|
5068
5074
|
if (this._opcode === 8 && this._state == GET_INFO) return cb();
|
|
5075
|
+
if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) {
|
|
5076
|
+
cb(
|
|
5077
|
+
this.createError(
|
|
5078
|
+
RangeError,
|
|
5079
|
+
"Too many buffered chunks",
|
|
5080
|
+
false,
|
|
5081
|
+
1008,
|
|
5082
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
5083
|
+
)
|
|
5084
|
+
);
|
|
5085
|
+
return;
|
|
5086
|
+
}
|
|
5069
5087
|
this._bufferedBytes += chunk.length;
|
|
5070
5088
|
this._buffers.push(chunk);
|
|
5071
5089
|
this.startLoop(cb);
|
|
@@ -5395,6 +5413,17 @@ var require_receiver = __commonJS({
|
|
|
5395
5413
|
return;
|
|
5396
5414
|
}
|
|
5397
5415
|
if (data.length) {
|
|
5416
|
+
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
5417
|
+
const error = this.createError(
|
|
5418
|
+
RangeError,
|
|
5419
|
+
"Too many message fragments",
|
|
5420
|
+
false,
|
|
5421
|
+
1008,
|
|
5422
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
5423
|
+
);
|
|
5424
|
+
cb(error);
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5398
5427
|
this._messageLength = this._totalPayloadLength;
|
|
5399
5428
|
this._fragments.push(data);
|
|
5400
5429
|
}
|
|
@@ -5424,6 +5453,17 @@ var require_receiver = __commonJS({
|
|
|
5424
5453
|
cb(error);
|
|
5425
5454
|
return;
|
|
5426
5455
|
}
|
|
5456
|
+
if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
|
|
5457
|
+
const error = this.createError(
|
|
5458
|
+
RangeError,
|
|
5459
|
+
"Too many message fragments",
|
|
5460
|
+
false,
|
|
5461
|
+
1008,
|
|
5462
|
+
"WS_ERR_TOO_MANY_BUFFERED_PARTS"
|
|
5463
|
+
);
|
|
5464
|
+
cb(error);
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5427
5467
|
this._fragments.push(buf);
|
|
5428
5468
|
}
|
|
5429
5469
|
this.dataMessage(cb);
|
|
@@ -5584,9 +5624,9 @@ var require_receiver = __commonJS({
|
|
|
5584
5624
|
}
|
|
5585
5625
|
});
|
|
5586
5626
|
|
|
5587
|
-
// ../../node_modules/
|
|
5627
|
+
// ../../node_modules/ws/lib/sender.js
|
|
5588
5628
|
var require_sender = __commonJS({
|
|
5589
|
-
"../../node_modules/
|
|
5629
|
+
"../../node_modules/ws/lib/sender.js"(exports2, module2) {
|
|
5590
5630
|
"use strict";
|
|
5591
5631
|
var { Duplex } = require("stream");
|
|
5592
5632
|
var { randomFillSync } = require("crypto");
|
|
@@ -6077,9 +6117,9 @@ var require_sender = __commonJS({
|
|
|
6077
6117
|
}
|
|
6078
6118
|
});
|
|
6079
6119
|
|
|
6080
|
-
// ../../node_modules/
|
|
6120
|
+
// ../../node_modules/ws/lib/event-target.js
|
|
6081
6121
|
var require_event_target = __commonJS({
|
|
6082
|
-
"../../node_modules/
|
|
6122
|
+
"../../node_modules/ws/lib/event-target.js"(exports2, module2) {
|
|
6083
6123
|
"use strict";
|
|
6084
6124
|
var { kForOnEventAttribute, kListener } = require_constants();
|
|
6085
6125
|
var kCode = Symbol("kCode");
|
|
@@ -6306,9 +6346,9 @@ var require_event_target = __commonJS({
|
|
|
6306
6346
|
}
|
|
6307
6347
|
});
|
|
6308
6348
|
|
|
6309
|
-
// ../../node_modules/
|
|
6349
|
+
// ../../node_modules/ws/lib/extension.js
|
|
6310
6350
|
var require_extension = __commonJS({
|
|
6311
|
-
"../../node_modules/
|
|
6351
|
+
"../../node_modules/ws/lib/extension.js"(exports2, module2) {
|
|
6312
6352
|
"use strict";
|
|
6313
6353
|
var { tokenChars } = require_validation();
|
|
6314
6354
|
function push(dest, name, elem) {
|
|
@@ -6459,9 +6499,9 @@ var require_extension = __commonJS({
|
|
|
6459
6499
|
}
|
|
6460
6500
|
});
|
|
6461
6501
|
|
|
6462
|
-
// ../../node_modules/
|
|
6502
|
+
// ../../node_modules/ws/lib/websocket.js
|
|
6463
6503
|
var require_websocket = __commonJS({
|
|
6464
|
-
"../../node_modules/
|
|
6504
|
+
"../../node_modules/ws/lib/websocket.js"(exports2, module2) {
|
|
6465
6505
|
"use strict";
|
|
6466
6506
|
var EventEmitter = require("events");
|
|
6467
6507
|
var https = require("https");
|
|
@@ -6630,6 +6670,10 @@ var require_websocket = __commonJS({
|
|
|
6630
6670
|
* multiple times in the same tick
|
|
6631
6671
|
* @param {Function} [options.generateMask] The function used to generate the
|
|
6632
6672
|
* masking key
|
|
6673
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
6674
|
+
* buffered data chunks
|
|
6675
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
6676
|
+
* fragments
|
|
6633
6677
|
* @param {Number} [options.maxPayload=0] The maximum allowed message size
|
|
6634
6678
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
6635
6679
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -6641,6 +6685,8 @@ var require_websocket = __commonJS({
|
|
|
6641
6685
|
binaryType: this.binaryType,
|
|
6642
6686
|
extensions: this._extensions,
|
|
6643
6687
|
isServer: this._isServer,
|
|
6688
|
+
maxBufferedChunks: options.maxBufferedChunks,
|
|
6689
|
+
maxFragments: options.maxFragments,
|
|
6644
6690
|
maxPayload: options.maxPayload,
|
|
6645
6691
|
skipUTF8Validation: options.skipUTF8Validation
|
|
6646
6692
|
});
|
|
@@ -6940,6 +6986,8 @@ var require_websocket = __commonJS({
|
|
|
6940
6986
|
autoPong: true,
|
|
6941
6987
|
closeTimeout: CLOSE_TIMEOUT,
|
|
6942
6988
|
protocolVersion: protocolVersions[1],
|
|
6989
|
+
maxBufferedChunks: 1024 * 1024,
|
|
6990
|
+
maxFragments: 128 * 1024,
|
|
6943
6991
|
maxPayload: 100 * 1024 * 1024,
|
|
6944
6992
|
skipUTF8Validation: false,
|
|
6945
6993
|
perMessageDeflate: true,
|
|
@@ -7182,6 +7230,8 @@ var require_websocket = __commonJS({
|
|
|
7182
7230
|
websocket.setSocket(socket, head, {
|
|
7183
7231
|
allowSynchronousEvents: opts.allowSynchronousEvents,
|
|
7184
7232
|
generateMask: opts.generateMask,
|
|
7233
|
+
maxBufferedChunks: opts.maxBufferedChunks,
|
|
7234
|
+
maxFragments: opts.maxFragments,
|
|
7185
7235
|
maxPayload: opts.maxPayload,
|
|
7186
7236
|
skipUTF8Validation: opts.skipUTF8Validation
|
|
7187
7237
|
});
|
|
@@ -7345,9 +7395,9 @@ var require_websocket = __commonJS({
|
|
|
7345
7395
|
}
|
|
7346
7396
|
});
|
|
7347
7397
|
|
|
7348
|
-
// ../../node_modules/
|
|
7398
|
+
// ../../node_modules/ws/lib/stream.js
|
|
7349
7399
|
var require_stream = __commonJS({
|
|
7350
|
-
"../../node_modules/
|
|
7400
|
+
"../../node_modules/ws/lib/stream.js"(exports2, module2) {
|
|
7351
7401
|
"use strict";
|
|
7352
7402
|
var WebSocket2 = require_websocket();
|
|
7353
7403
|
var { Duplex } = require("stream");
|
|
@@ -7443,9 +7493,9 @@ var require_stream = __commonJS({
|
|
|
7443
7493
|
}
|
|
7444
7494
|
});
|
|
7445
7495
|
|
|
7446
|
-
// ../../node_modules/
|
|
7496
|
+
// ../../node_modules/ws/lib/subprotocol.js
|
|
7447
7497
|
var require_subprotocol = __commonJS({
|
|
7448
|
-
"../../node_modules/
|
|
7498
|
+
"../../node_modules/ws/lib/subprotocol.js"(exports2, module2) {
|
|
7449
7499
|
"use strict";
|
|
7450
7500
|
var { tokenChars } = require_validation();
|
|
7451
7501
|
function parse3(header) {
|
|
@@ -7488,9 +7538,9 @@ var require_subprotocol = __commonJS({
|
|
|
7488
7538
|
}
|
|
7489
7539
|
});
|
|
7490
7540
|
|
|
7491
|
-
// ../../node_modules/
|
|
7541
|
+
// ../../node_modules/ws/lib/websocket-server.js
|
|
7492
7542
|
var require_websocket_server = __commonJS({
|
|
7493
|
-
"../../node_modules/
|
|
7543
|
+
"../../node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
7494
7544
|
"use strict";
|
|
7495
7545
|
var EventEmitter = require("events");
|
|
7496
7546
|
var http = require("http");
|
|
@@ -7524,6 +7574,10 @@ var require_websocket_server = __commonJS({
|
|
|
7524
7574
|
* called
|
|
7525
7575
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
7526
7576
|
* @param {String} [options.host] The hostname where to bind the server
|
|
7577
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
7578
|
+
* buffered data chunks
|
|
7579
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
7580
|
+
* fragments
|
|
7527
7581
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
7528
7582
|
* size
|
|
7529
7583
|
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
@@ -7545,6 +7599,8 @@ var require_websocket_server = __commonJS({
|
|
|
7545
7599
|
options = {
|
|
7546
7600
|
allowSynchronousEvents: true,
|
|
7547
7601
|
autoPong: true,
|
|
7602
|
+
maxBufferedChunks: 1024 * 1024,
|
|
7603
|
+
maxFragments: 128 * 1024,
|
|
7548
7604
|
maxPayload: 100 * 1024 * 1024,
|
|
7549
7605
|
skipUTF8Validation: false,
|
|
7550
7606
|
perMessageDeflate: false,
|
|
@@ -7824,6 +7880,8 @@ var require_websocket_server = __commonJS({
|
|
|
7824
7880
|
socket.removeListener("error", socketOnError);
|
|
7825
7881
|
ws.setSocket(socket, head, {
|
|
7826
7882
|
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
|
7883
|
+
maxBufferedChunks: this.options.maxBufferedChunks,
|
|
7884
|
+
maxFragments: this.options.maxFragments,
|
|
7827
7885
|
maxPayload: this.options.maxPayload,
|
|
7828
7886
|
skipUTF8Validation: this.options.skipUTF8Validation
|
|
7829
7887
|
});
|
|
@@ -7884,7 +7942,7 @@ var require_websocket_server = __commonJS({
|
|
|
7884
7942
|
// src/cli.ts
|
|
7885
7943
|
var import_node_dns = __toESM(require("node:dns"));
|
|
7886
7944
|
|
|
7887
|
-
//
|
|
7945
|
+
// node_modules/commander/esm.mjs
|
|
7888
7946
|
var import_index = __toESM(require_commander(), 1);
|
|
7889
7947
|
var {
|
|
7890
7948
|
program,
|
|
@@ -7979,8 +8037,144 @@ function configExists() {
|
|
|
7979
8037
|
return import_fs.default.existsSync(CONFIG_FILE);
|
|
7980
8038
|
}
|
|
7981
8039
|
|
|
7982
|
-
//
|
|
8040
|
+
// src/setup.ts
|
|
7983
8041
|
var import_child_process2 = require("child_process");
|
|
8042
|
+
var import_crypto = __toESM(require("crypto"));
|
|
8043
|
+
var import_fs2 = __toESM(require("fs"));
|
|
8044
|
+
var import_path2 = __toESM(require("path"));
|
|
8045
|
+
var DOCKERFILE_LABEL = "factiii.dockerfile-sha";
|
|
8046
|
+
function run(bin, args, opts) {
|
|
8047
|
+
try {
|
|
8048
|
+
return (0, import_child_process2.execFileSync)(bin, args, {
|
|
8049
|
+
encoding: "utf-8",
|
|
8050
|
+
stdio: opts?.silent ? "pipe" : "inherit",
|
|
8051
|
+
// Avoids a cmd.exe window flash on Windows during silent checks.
|
|
8052
|
+
windowsHide: true
|
|
8053
|
+
});
|
|
8054
|
+
} catch {
|
|
8055
|
+
return "";
|
|
8056
|
+
}
|
|
8057
|
+
}
|
|
8058
|
+
function check(cmd) {
|
|
8059
|
+
const lookup2 = process.platform === "win32" ? "where" : "which";
|
|
8060
|
+
try {
|
|
8061
|
+
(0, import_child_process2.execFileSync)(lookup2, [cmd], { stdio: "pipe", windowsHide: true });
|
|
8062
|
+
return true;
|
|
8063
|
+
} catch {
|
|
8064
|
+
return false;
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
async function setup(serverUrl) {
|
|
8068
|
+
console.log("\n=== Factiii Runner \u2014 Setup ===\n");
|
|
8069
|
+
console.log("[1/3] Checking Docker...");
|
|
8070
|
+
if (!check("docker")) {
|
|
8071
|
+
console.error(
|
|
8072
|
+
" Docker is not installed. Install it from https://docs.docker.com/get-docker/ and re-run setup."
|
|
8073
|
+
);
|
|
8074
|
+
process.exit(1);
|
|
8075
|
+
}
|
|
8076
|
+
if (!run("docker", ["info"], { silent: true })) {
|
|
8077
|
+
console.error(
|
|
8078
|
+
" Docker is installed but the daemon is not running. Start Docker and re-run setup."
|
|
8079
|
+
);
|
|
8080
|
+
process.exit(1);
|
|
8081
|
+
}
|
|
8082
|
+
console.log(" Docker is ready.");
|
|
8083
|
+
console.log("\n[2/3] Building factiii-claude Docker image...");
|
|
8084
|
+
const dockerfilePath = findDockerfile();
|
|
8085
|
+
if (!dockerfilePath) {
|
|
8086
|
+
console.error(
|
|
8087
|
+
" Dockerfile.claude not found. Reinstall the package and re-run setup."
|
|
8088
|
+
);
|
|
8089
|
+
process.exit(1);
|
|
8090
|
+
}
|
|
8091
|
+
const wantedHash = import_crypto.default.createHash("sha256").update(import_fs2.default.readFileSync(dockerfilePath)).digest("hex");
|
|
8092
|
+
const existingHash = run(
|
|
8093
|
+
"docker",
|
|
8094
|
+
[
|
|
8095
|
+
"image",
|
|
8096
|
+
"inspect",
|
|
8097
|
+
"factiii-claude",
|
|
8098
|
+
"--format",
|
|
8099
|
+
`{{ index .Config.Labels "${DOCKERFILE_LABEL}" }}`
|
|
8100
|
+
],
|
|
8101
|
+
{ silent: true }
|
|
8102
|
+
).trim();
|
|
8103
|
+
if (existingHash === wantedHash) {
|
|
8104
|
+
console.log(" Image is up to date.");
|
|
8105
|
+
} else {
|
|
8106
|
+
console.log(
|
|
8107
|
+
existingHash ? ` Dockerfile changed since last build, rebuilding from ${dockerfilePath}...` : ` Building from ${dockerfilePath}...`
|
|
8108
|
+
);
|
|
8109
|
+
run("docker", [
|
|
8110
|
+
"build",
|
|
8111
|
+
"-t",
|
|
8112
|
+
"factiii-claude",
|
|
8113
|
+
"-f",
|
|
8114
|
+
dockerfilePath,
|
|
8115
|
+
"--label",
|
|
8116
|
+
`${DOCKERFILE_LABEL}=${wantedHash}`,
|
|
8117
|
+
"."
|
|
8118
|
+
]);
|
|
8119
|
+
console.log(" Image built.");
|
|
8120
|
+
}
|
|
8121
|
+
run("docker", ["volume", "create", "factiii-card-cache"], { silent: true });
|
|
8122
|
+
console.log("\n[3/3] Pairing this runner with a Factiii account...");
|
|
8123
|
+
const { token, runnerName } = await pairWithBrowser(serverUrl);
|
|
8124
|
+
writeRunnerConfig({ serverUrl, authToken: token, runnerName });
|
|
8125
|
+
console.log(" Paired and config saved.");
|
|
8126
|
+
console.log("\n=== Setup complete ===\n");
|
|
8127
|
+
console.log("Start the runner with: factiii-runner start\n");
|
|
8128
|
+
}
|
|
8129
|
+
function findDockerfile() {
|
|
8130
|
+
const p = import_path2.default.join(__dirname, "..", "Dockerfile.claude");
|
|
8131
|
+
return import_fs2.default.existsSync(p) ? p : null;
|
|
8132
|
+
}
|
|
8133
|
+
|
|
8134
|
+
// src/selfUpdate.ts
|
|
8135
|
+
var import_node_child_process = require("node:child_process");
|
|
8136
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
8137
|
+
var import_node_path = __toESM(require("node:path"));
|
|
8138
|
+
var PACKAGE_NAME = "@factiii/runner";
|
|
8139
|
+
var RESTART_FOR_UPDATE = 75;
|
|
8140
|
+
var WORKER_ENV = "FACTIII_RUNNER_WORKER";
|
|
8141
|
+
function markWorkerEnv(env) {
|
|
8142
|
+
return { ...env, [WORKER_ENV]: "1" };
|
|
8143
|
+
}
|
|
8144
|
+
function isWorker() {
|
|
8145
|
+
return process.env[WORKER_ENV] === "1";
|
|
8146
|
+
}
|
|
8147
|
+
function currentVersion() {
|
|
8148
|
+
try {
|
|
8149
|
+
const pkg = JSON.parse(
|
|
8150
|
+
import_node_fs.default.readFileSync(import_node_path.default.join(__dirname, "..", "package.json"), "utf-8")
|
|
8151
|
+
);
|
|
8152
|
+
return pkg.version ?? "0.0.0";
|
|
8153
|
+
} catch {
|
|
8154
|
+
return "0.0.0";
|
|
8155
|
+
}
|
|
8156
|
+
}
|
|
8157
|
+
function installLatest() {
|
|
8158
|
+
console.log(`[update] installing ${PACKAGE_NAME}@latest...`);
|
|
8159
|
+
const res = (0, import_node_child_process.spawnSync)("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], {
|
|
8160
|
+
stdio: "inherit",
|
|
8161
|
+
// On Windows npm is npm.cmd, which spawnSync can't exec without a shell.
|
|
8162
|
+
shell: process.platform === "win32"
|
|
8163
|
+
});
|
|
8164
|
+
if (res.status !== 0) {
|
|
8165
|
+
console.error(
|
|
8166
|
+
`[update] npm install failed (exit ${res.status ?? "signal"}). Staying on ${currentVersion()}.`
|
|
8167
|
+
);
|
|
8168
|
+
return false;
|
|
8169
|
+
}
|
|
8170
|
+
return true;
|
|
8171
|
+
}
|
|
8172
|
+
|
|
8173
|
+
// src/supervisor.ts
|
|
8174
|
+
var import_node_child_process2 = require("node:child_process");
|
|
8175
|
+
|
|
8176
|
+
// ../../shared/all/helpers/board-agent-core/claude.ts
|
|
8177
|
+
var import_child_process3 = require("child_process");
|
|
7984
8178
|
|
|
7985
8179
|
// ../../shared/all/helpers/board-agent-core/claude-stream.ts
|
|
7986
8180
|
function truncate(s, max) {
|
|
@@ -8210,6 +8404,12 @@ function pipeClaudeStream(proc, callbacks) {
|
|
|
8210
8404
|
for (const line of lines) {
|
|
8211
8405
|
const trimmed = line.trim();
|
|
8212
8406
|
if (!trimmed) continue;
|
|
8407
|
+
if (trimmed[0] !== "{" && trimmed[0] !== "[") {
|
|
8408
|
+
state.isError = true;
|
|
8409
|
+
state.fullText = state.fullText ? `${state.fullText}
|
|
8410
|
+
${trimmed}` : trimmed;
|
|
8411
|
+
continue;
|
|
8412
|
+
}
|
|
8213
8413
|
try {
|
|
8214
8414
|
const raw = JSON.parse(trimmed);
|
|
8215
8415
|
if (processStreamEvent(raw, state, callbacks)) continue;
|
|
@@ -8450,7 +8650,7 @@ function spawnClaude(opts) {
|
|
|
8450
8650
|
if (opts.resumeSessionId) {
|
|
8451
8651
|
args.push("--resume", opts.resumeSessionId);
|
|
8452
8652
|
}
|
|
8453
|
-
const proc = (0,
|
|
8653
|
+
const proc = (0, import_child_process3.spawn)("docker", args);
|
|
8454
8654
|
const result = new Promise(
|
|
8455
8655
|
(resolve, reject) => {
|
|
8456
8656
|
let errorOutput = "";
|
|
@@ -8482,7 +8682,7 @@ function spawnClaude(opts) {
|
|
|
8482
8682
|
}
|
|
8483
8683
|
function generateCommitMessage(containerName, diff) {
|
|
8484
8684
|
return new Promise((resolve) => {
|
|
8485
|
-
const proc = (0,
|
|
8685
|
+
const proc = (0, import_child_process3.spawn)("docker", [
|
|
8486
8686
|
"exec",
|
|
8487
8687
|
"-i",
|
|
8488
8688
|
"--user",
|
|
@@ -8511,16 +8711,16 @@ ${diff.slice(0, 8e3)}`);
|
|
|
8511
8711
|
}
|
|
8512
8712
|
|
|
8513
8713
|
// ../../shared/all/helpers/board-agent-core/docker.ts
|
|
8514
|
-
var
|
|
8515
|
-
var
|
|
8714
|
+
var import_child_process4 = require("child_process");
|
|
8715
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
8516
8716
|
var import_promises = __toESM(require("fs/promises"));
|
|
8517
8717
|
var import_net = __toESM(require("net"));
|
|
8518
8718
|
var import_util = require("util");
|
|
8519
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
8520
|
-
var
|
|
8719
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
8720
|
+
var DOCKERFILE_LABEL2 = "factiii.dockerfile-sha";
|
|
8521
8721
|
async function hashDockerfile(dockerfilePath) {
|
|
8522
8722
|
const buf = await import_promises.default.readFile(dockerfilePath);
|
|
8523
|
-
return
|
|
8723
|
+
return import_crypto2.default.createHash("sha256").update(buf).digest("hex");
|
|
8524
8724
|
}
|
|
8525
8725
|
async function getFreePort() {
|
|
8526
8726
|
return new Promise((resolve, reject) => {
|
|
@@ -8559,7 +8759,7 @@ async function ensureDockerImage(dockerfilePath, onLog) {
|
|
|
8559
8759
|
"inspect",
|
|
8560
8760
|
"factiii-claude",
|
|
8561
8761
|
"--format",
|
|
8562
|
-
`{{ index .Config.Labels "${
|
|
8762
|
+
`{{ index .Config.Labels "${DOCKERFILE_LABEL2}" }}`
|
|
8563
8763
|
],
|
|
8564
8764
|
{}
|
|
8565
8765
|
);
|
|
@@ -8585,7 +8785,7 @@ async function resetNixVolume() {
|
|
|
8585
8785
|
}
|
|
8586
8786
|
function buildImageStreaming(dockerfilePath, wantedHash, onLog) {
|
|
8587
8787
|
return new Promise((resolve, reject) => {
|
|
8588
|
-
const child = (0,
|
|
8788
|
+
const child = (0, import_child_process4.spawn)(
|
|
8589
8789
|
"docker",
|
|
8590
8790
|
[
|
|
8591
8791
|
"build",
|
|
@@ -8595,7 +8795,7 @@ function buildImageStreaming(dockerfilePath, wantedHash, onLog) {
|
|
|
8595
8795
|
"-f",
|
|
8596
8796
|
dockerfilePath,
|
|
8597
8797
|
"--label",
|
|
8598
|
-
`${
|
|
8798
|
+
`${DOCKERFILE_LABEL2}=${wantedHash}`,
|
|
8599
8799
|
"."
|
|
8600
8800
|
],
|
|
8601
8801
|
{ env: { ...process.env, DOCKER_BUILDKIT: "1" } }
|
|
@@ -8654,6 +8854,44 @@ async function killContainer(containerName) {
|
|
|
8654
8854
|
} catch {
|
|
8655
8855
|
}
|
|
8656
8856
|
}
|
|
8857
|
+
async function listContainersByPrefix(namePrefix) {
|
|
8858
|
+
try {
|
|
8859
|
+
const { stdout } = await execFileAsync(
|
|
8860
|
+
"docker",
|
|
8861
|
+
[
|
|
8862
|
+
"ps",
|
|
8863
|
+
"-a",
|
|
8864
|
+
"--no-trunc",
|
|
8865
|
+
"--filter",
|
|
8866
|
+
`name=${namePrefix}`,
|
|
8867
|
+
"--format",
|
|
8868
|
+
"{{.Names}} {{.State}} {{.Status}}"
|
|
8869
|
+
],
|
|
8870
|
+
{ encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
|
|
8871
|
+
);
|
|
8872
|
+
const rows = [];
|
|
8873
|
+
for (const line of stdout.split("\n")) {
|
|
8874
|
+
if (!line.trim()) continue;
|
|
8875
|
+
const [name, state, ...statusParts] = line.split(" ");
|
|
8876
|
+
if (!name || !name.startsWith(namePrefix)) continue;
|
|
8877
|
+
rows.push({
|
|
8878
|
+
name,
|
|
8879
|
+
running: state === "running",
|
|
8880
|
+
status: statusParts.join(" ").trim() || state || ""
|
|
8881
|
+
});
|
|
8882
|
+
}
|
|
8883
|
+
return rows;
|
|
8884
|
+
} catch {
|
|
8885
|
+
return [];
|
|
8886
|
+
}
|
|
8887
|
+
}
|
|
8888
|
+
async function renameContainer(oldName, newName) {
|
|
8889
|
+
if (oldName === newName) return;
|
|
8890
|
+
try {
|
|
8891
|
+
await execFileAsync("docker", ["rename", oldName, newName], {});
|
|
8892
|
+
} catch {
|
|
8893
|
+
}
|
|
8894
|
+
}
|
|
8657
8895
|
async function spawnContainer(containerName, claudeToken, opts) {
|
|
8658
8896
|
const NUM_PORTS = 6;
|
|
8659
8897
|
const ports = [];
|
|
@@ -8728,7 +8966,7 @@ async function execInContainer(containerName, cmd) {
|
|
|
8728
8966
|
return stdout;
|
|
8729
8967
|
}
|
|
8730
8968
|
function streamExecInContainer(containerName, cmd, onLine) {
|
|
8731
|
-
const child = (0,
|
|
8969
|
+
const child = (0, import_child_process4.spawn)("docker", [
|
|
8732
8970
|
"exec",
|
|
8733
8971
|
"--user",
|
|
8734
8972
|
"claude",
|
|
@@ -8811,9 +9049,9 @@ async function getContainerPort(containerName, containerPort) {
|
|
|
8811
9049
|
}
|
|
8812
9050
|
|
|
8813
9051
|
// ../../shared/all/helpers/board-agent-core/git.ts
|
|
8814
|
-
var
|
|
9052
|
+
var import_child_process5 = require("child_process");
|
|
8815
9053
|
var import_util2 = require("util");
|
|
8816
|
-
var execFileAsync2 = (0, import_util2.promisify)(
|
|
9054
|
+
var execFileAsync2 = (0, import_util2.promisify)(import_child_process5.execFile);
|
|
8817
9055
|
function buildCloneUrl(repoUrl, githubToken) {
|
|
8818
9056
|
const url2 = new URL(repoUrl);
|
|
8819
9057
|
url2.username = githubToken;
|
|
@@ -9254,6 +9492,27 @@ function withTimeout(work, ms, label) {
|
|
|
9254
9492
|
});
|
|
9255
9493
|
return Promise.race([work.finally(() => clearTimeout(timer)), timeout]);
|
|
9256
9494
|
}
|
|
9495
|
+
function computeInstancePrefix(instanceId) {
|
|
9496
|
+
const raw = (instanceId || "shared").toLowerCase();
|
|
9497
|
+
return raw.replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "shared";
|
|
9498
|
+
}
|
|
9499
|
+
function parseFactiiiContainerName(name, instancePrefix) {
|
|
9500
|
+
const head = `factiii-${instancePrefix}-`;
|
|
9501
|
+
if (!name.startsWith(head)) return null;
|
|
9502
|
+
const rest = name.slice(head.length);
|
|
9503
|
+
const withId = rest.match(/^(card|util)-(\d+)-(.+)$/);
|
|
9504
|
+
if (withId) {
|
|
9505
|
+
const kind = withId[1];
|
|
9506
|
+
const spaceId = Number(withId[2]);
|
|
9507
|
+
return kind === "card" ? { kind, spaceId, taskId: withId[3] } : { kind, spaceId, cacheKey: withId[3] };
|
|
9508
|
+
}
|
|
9509
|
+
const legacy = rest.match(/^(card|util)-(.+)$/);
|
|
9510
|
+
if (legacy) {
|
|
9511
|
+
const kind = legacy[1];
|
|
9512
|
+
return kind === "card" ? { kind, spaceId: null, taskId: legacy[2] } : { kind, spaceId: null, cacheKey: legacy[2] };
|
|
9513
|
+
}
|
|
9514
|
+
return null;
|
|
9515
|
+
}
|
|
9257
9516
|
var ContainerCore = class {
|
|
9258
9517
|
constructor(options) {
|
|
9259
9518
|
// Utility container state.
|
|
@@ -9261,12 +9520,17 @@ var ContainerCore = class {
|
|
|
9261
9520
|
this.lastConfigKey = "";
|
|
9262
9521
|
this.setupPromise = null;
|
|
9263
9522
|
this.spaceSlug = options.spaceSlug;
|
|
9523
|
+
this.spaceId = options.spaceId ?? null;
|
|
9264
9524
|
this.emitter = options.emitter;
|
|
9265
9525
|
this.configProvider = options.configProvider;
|
|
9266
9526
|
this.dockerfilePath = options.dockerfilePath;
|
|
9267
9527
|
this.runnerToken = options.runnerToken;
|
|
9268
|
-
|
|
9269
|
-
|
|
9528
|
+
this.instancePrefix = computeInstancePrefix(options.instanceId);
|
|
9529
|
+
}
|
|
9530
|
+
/** The space segment used in container names: the stable id when we have it,
|
|
9531
|
+
* else the slug (legacy / id-less callers). */
|
|
9532
|
+
get spaceSegment() {
|
|
9533
|
+
return this.spaceId != null ? String(this.spaceId) : this.spaceSlug;
|
|
9270
9534
|
}
|
|
9271
9535
|
readConfig() {
|
|
9272
9536
|
return this.configProvider.readConfig(this.spaceSlug);
|
|
@@ -9366,7 +9630,11 @@ var ContainerCore = class {
|
|
|
9366
9630
|
}
|
|
9367
9631
|
const configKey = onedrive ? `onedrive|${this.spaceSlug}|${config.claudeToken}` : `${config.repoUrl}|${config.claudeToken}|${config.githubToken}`;
|
|
9368
9632
|
const cacheKey = onedrive ? `onedrive-${this.spaceSlug}`.replace(/[^a-zA-Z0-9._-]/g, "_") : repoCacheKey(config.repoUrl);
|
|
9369
|
-
const name = `factiii-${this.instancePrefix}-util-${this.
|
|
9633
|
+
const name = `factiii-${this.instancePrefix}-util-${this.spaceSegment}-${cacheKey}`;
|
|
9634
|
+
if (this.spaceId != null) {
|
|
9635
|
+
const legacyName = `factiii-${this.instancePrefix}-util-${this.spaceSlug}-${cacheKey}`;
|
|
9636
|
+
await renameContainer(legacyName, name);
|
|
9637
|
+
}
|
|
9370
9638
|
if (this.containerName && this.lastConfigKey !== configKey) {
|
|
9371
9639
|
await killContainer(this.containerName);
|
|
9372
9640
|
this.containerName = null;
|
|
@@ -9597,9 +9865,9 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
9597
9865
|
}
|
|
9598
9866
|
|
|
9599
9867
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
9600
|
-
var
|
|
9601
|
-
var
|
|
9602
|
-
var
|
|
9868
|
+
var import_child_process6 = require("child_process");
|
|
9869
|
+
var import_fs3 = require("fs");
|
|
9870
|
+
var import_path3 = __toESM(require("path"));
|
|
9603
9871
|
var import_util3 = require("util");
|
|
9604
9872
|
|
|
9605
9873
|
// ../../shared/all/helpers/card-ai/scripts.ts
|
|
@@ -9653,15 +9921,15 @@ fi
|
|
|
9653
9921
|
`;
|
|
9654
9922
|
|
|
9655
9923
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
9656
|
-
var execFileAsync3 = (0, import_util3.promisify)(
|
|
9924
|
+
var execFileAsync3 = (0, import_util3.promisify)(import_child_process6.execFile);
|
|
9657
9925
|
var FRP_SERVER_ADDR = "dev.factiii.com";
|
|
9658
9926
|
var FRP_SERVER_PORT = 7443;
|
|
9659
9927
|
var FRP_DOMAIN_SUFFIX = "dev.factiii.com";
|
|
9660
|
-
async function writeFileAsRoot(containerName,
|
|
9928
|
+
async function writeFileAsRoot(containerName, path8, contents) {
|
|
9661
9929
|
const b64 = Buffer.from(contents, "utf-8").toString("base64");
|
|
9662
9930
|
await execInContainerAsRoot(
|
|
9663
9931
|
containerName,
|
|
9664
|
-
`echo '${b64}' | base64 -d > '${
|
|
9932
|
+
`echo '${b64}' | base64 -d > '${path8}'`
|
|
9665
9933
|
);
|
|
9666
9934
|
}
|
|
9667
9935
|
async function installFrpcBinary(containerName) {
|
|
@@ -9674,8 +9942,8 @@ async function installFrpcBinary(containerName) {
|
|
|
9674
9942
|
if (!binaryName) {
|
|
9675
9943
|
throw new Error(`Unsupported container arch for frpc: ${arch}`);
|
|
9676
9944
|
}
|
|
9677
|
-
const src =
|
|
9678
|
-
if (!(0,
|
|
9945
|
+
const src = import_path3.default.join(__dirname, "..", "vendor", binaryName);
|
|
9946
|
+
if (!(0, import_fs3.existsSync)(src)) {
|
|
9679
9947
|
throw new Error(
|
|
9680
9948
|
`Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
|
|
9681
9949
|
);
|
|
@@ -9766,6 +10034,7 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
9766
10034
|
);
|
|
9767
10035
|
}
|
|
9768
10036
|
const containerName = this.core.containerName;
|
|
10037
|
+
await execInContainer(containerName, "mkdir -p /home/claude/workspace");
|
|
9769
10038
|
this.board.messages.push({ role: "user", content: params.question });
|
|
9770
10039
|
let prompt2;
|
|
9771
10040
|
if (this.board.sessionId) {
|
|
@@ -10418,10 +10687,10 @@ var TerminalModeEngine = class {
|
|
|
10418
10687
|
}
|
|
10419
10688
|
async bareFsList({
|
|
10420
10689
|
postId,
|
|
10421
|
-
path:
|
|
10690
|
+
path: path8
|
|
10422
10691
|
}) {
|
|
10423
10692
|
const container = this.getBareContainer(postId);
|
|
10424
|
-
const abs = this.resolveBarePath(
|
|
10693
|
+
const abs = this.resolveBarePath(path8);
|
|
10425
10694
|
const relRoot = abs.slice(BARE_REPO_ROOT.length) || "/";
|
|
10426
10695
|
const out = await execInContainer(
|
|
10427
10696
|
container,
|
|
@@ -10471,21 +10740,21 @@ var TerminalModeEngine = class {
|
|
|
10471
10740
|
}
|
|
10472
10741
|
async bareFsRead({
|
|
10473
10742
|
postId,
|
|
10474
|
-
path:
|
|
10743
|
+
path: path8
|
|
10475
10744
|
}) {
|
|
10476
10745
|
const container = this.getBareContainer(postId);
|
|
10477
|
-
const abs = this.resolveBarePath(
|
|
10746
|
+
const abs = this.resolveBarePath(path8);
|
|
10478
10747
|
const b64 = (await execInContainer(container, `base64 -w 0 ${shEscape(abs)}`)).trim();
|
|
10479
10748
|
return { content: b64, encoding: "base64" };
|
|
10480
10749
|
}
|
|
10481
10750
|
async bareFsWrite({
|
|
10482
10751
|
postId,
|
|
10483
|
-
path:
|
|
10752
|
+
path: path8,
|
|
10484
10753
|
content,
|
|
10485
10754
|
encoding
|
|
10486
10755
|
}) {
|
|
10487
10756
|
const container = this.getBareContainer(postId);
|
|
10488
|
-
const abs = this.resolveBarePath(
|
|
10757
|
+
const abs = this.resolveBarePath(path8);
|
|
10489
10758
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
10490
10759
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
10491
10760
|
throw new Error("Invalid base64 payload.");
|
|
@@ -10495,7 +10764,7 @@ var TerminalModeEngine = class {
|
|
|
10495
10764
|
`mkdir -p ${shEscape(posixDirname(abs))} && printf %s ${shEscape(b64)} | base64 -d > ${shEscape(abs)}`
|
|
10496
10765
|
);
|
|
10497
10766
|
const session = this.sessions.get(String(postId));
|
|
10498
|
-
if (session) delete session.buffers[
|
|
10767
|
+
if (session) delete session.buffers[path8];
|
|
10499
10768
|
}
|
|
10500
10769
|
bareEditorState({
|
|
10501
10770
|
postId: rawPostId
|
|
@@ -10512,38 +10781,38 @@ var TerminalModeEngine = class {
|
|
|
10512
10781
|
}
|
|
10513
10782
|
bareOpenFile({
|
|
10514
10783
|
postId: rawPostId,
|
|
10515
|
-
path:
|
|
10784
|
+
path: path8
|
|
10516
10785
|
}) {
|
|
10517
10786
|
const session = this.sessions.get(String(rawPostId));
|
|
10518
10787
|
if (!session || session.mode !== "bare") return;
|
|
10519
|
-
if (!session.openedFiles.includes(
|
|
10520
|
-
session.openedFiles.push(
|
|
10788
|
+
if (!session.openedFiles.includes(path8)) {
|
|
10789
|
+
session.openedFiles.push(path8);
|
|
10521
10790
|
}
|
|
10522
10791
|
}
|
|
10523
10792
|
bareCloseFile({
|
|
10524
10793
|
postId: rawPostId,
|
|
10525
|
-
path:
|
|
10794
|
+
path: path8
|
|
10526
10795
|
}) {
|
|
10527
10796
|
const session = this.sessions.get(String(rawPostId));
|
|
10528
10797
|
if (!session || session.mode !== "bare") return;
|
|
10529
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
10530
|
-
delete session.buffers[
|
|
10798
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path8);
|
|
10799
|
+
delete session.buffers[path8];
|
|
10531
10800
|
}
|
|
10532
10801
|
bareBufferSet({
|
|
10533
10802
|
postId: rawPostId,
|
|
10534
|
-
path:
|
|
10803
|
+
path: path8,
|
|
10535
10804
|
content
|
|
10536
10805
|
}) {
|
|
10537
10806
|
const session = this.sessions.get(String(rawPostId));
|
|
10538
10807
|
if (!session || session.mode !== "bare") return;
|
|
10539
|
-
session.buffers[
|
|
10808
|
+
session.buffers[path8] = content;
|
|
10540
10809
|
}
|
|
10541
10810
|
async bareFsDelete({
|
|
10542
10811
|
postId,
|
|
10543
|
-
path:
|
|
10812
|
+
path: path8
|
|
10544
10813
|
}) {
|
|
10545
10814
|
const container = this.getBareContainer(postId);
|
|
10546
|
-
const abs = this.resolveBarePath(
|
|
10815
|
+
const abs = this.resolveBarePath(path8);
|
|
10547
10816
|
if (abs === BARE_REPO_ROOT) {
|
|
10548
10817
|
throw new Error("Refusing to delete the repo root.");
|
|
10549
10818
|
}
|
|
@@ -10551,10 +10820,10 @@ var TerminalModeEngine = class {
|
|
|
10551
10820
|
}
|
|
10552
10821
|
async bareFsMkdir({
|
|
10553
10822
|
postId,
|
|
10554
|
-
path:
|
|
10823
|
+
path: path8
|
|
10555
10824
|
}) {
|
|
10556
10825
|
const container = this.getBareContainer(postId);
|
|
10557
|
-
const abs = this.resolveBarePath(
|
|
10826
|
+
const abs = this.resolveBarePath(path8);
|
|
10558
10827
|
await execInContainer(container, `mkdir -p ${shEscape(abs)}`);
|
|
10559
10828
|
}
|
|
10560
10829
|
async bareReview({
|
|
@@ -10967,7 +11236,9 @@ var BoardAgentEngine = class {
|
|
|
10967
11236
|
targetBranch: targetBranch || "",
|
|
10968
11237
|
effort: effort || "high",
|
|
10969
11238
|
todos: [],
|
|
10970
|
-
|
|
11239
|
+
// Name by stable spaceId when we have it; fall back to the id-less legacy
|
|
11240
|
+
// form for old callers that only send the slug.
|
|
11241
|
+
containerName: this.core.spaceId != null ? `factiii-${this.core.instancePrefix}-card-${this.core.spaceId}-${taskId}` : `factiii-${this.core.instancePrefix}-card-${taskId}`
|
|
10971
11242
|
};
|
|
10972
11243
|
if (mode === "bare") {
|
|
10973
11244
|
this.terminal.start(base, {
|
|
@@ -10996,6 +11267,57 @@ var BoardAgentEngine = class {
|
|
|
10996
11267
|
cardSessions() {
|
|
10997
11268
|
return { ...this.terminal.listSummaries(), ...this.claude.listSummaries() };
|
|
10998
11269
|
}
|
|
11270
|
+
/** The stable board id this engine runs, if the caller provided one. */
|
|
11271
|
+
get spaceId() {
|
|
11272
|
+
return this.core.spaceId;
|
|
11273
|
+
}
|
|
11274
|
+
/**
|
|
11275
|
+
* Every factiii container/VM on this host (running + stopped) for this
|
|
11276
|
+
* runner's instance prefix - card and util, across ALL boards, since the
|
|
11277
|
+
* prefix is shared by every engine on the host. Returns them decoded; the
|
|
11278
|
+
* daemon scope-filters before handing them to a remote member. Electron
|
|
11279
|
+
* (single owner) gets the raw list straight through.
|
|
11280
|
+
*/
|
|
11281
|
+
async listContainers() {
|
|
11282
|
+
const rows = await listContainersByPrefix(
|
|
11283
|
+
`factiii-${this.core.instancePrefix}-`
|
|
11284
|
+
);
|
|
11285
|
+
const out = [];
|
|
11286
|
+
for (const row of rows) {
|
|
11287
|
+
const parsed = parseFactiiiContainerName(
|
|
11288
|
+
row.name,
|
|
11289
|
+
this.core.instancePrefix
|
|
11290
|
+
);
|
|
11291
|
+
if (!parsed) continue;
|
|
11292
|
+
out.push({
|
|
11293
|
+
containerName: row.name,
|
|
11294
|
+
kind: parsed.kind,
|
|
11295
|
+
spaceId: parsed.spaceId,
|
|
11296
|
+
// This engine only knows its own board's slug; the daemon fills in
|
|
11297
|
+
// slugs for other boards (cross-board) when scope-filtering.
|
|
11298
|
+
spaceSlug: parsed.spaceId != null && parsed.spaceId === this.core.spaceId ? this.core.spaceSlug : void 0,
|
|
11299
|
+
taskId: parsed.taskId,
|
|
11300
|
+
cacheKey: parsed.cacheKey,
|
|
11301
|
+
running: row.running,
|
|
11302
|
+
status: row.status
|
|
11303
|
+
});
|
|
11304
|
+
}
|
|
11305
|
+
return out;
|
|
11306
|
+
}
|
|
11307
|
+
/**
|
|
11308
|
+
* Force-remove a container/VM by name. Guards the name against this runner's
|
|
11309
|
+
* prefix so a peer can't `docker rm` an arbitrary container on the host. The
|
|
11310
|
+
* daemon adds the per-board scope check on top for remote members.
|
|
11311
|
+
*/
|
|
11312
|
+
async removeContainer({
|
|
11313
|
+
containerName
|
|
11314
|
+
}) {
|
|
11315
|
+
if (!containerName.startsWith(`factiii-${this.core.instancePrefix}-`)) {
|
|
11316
|
+
throw new Error("Refusing to remove a container outside this runner.");
|
|
11317
|
+
}
|
|
11318
|
+
await killContainer(containerName);
|
|
11319
|
+
return { removed: true };
|
|
11320
|
+
}
|
|
10999
11321
|
cardSession({ postId }) {
|
|
11000
11322
|
return this.claude.sessionWithLogs(String(postId)) ?? this.terminal.sessionWithLogs(String(postId));
|
|
11001
11323
|
}
|
|
@@ -11240,6 +11562,12 @@ var BoardAgentEngine = class {
|
|
|
11240
11562
|
return this.bareListExposed(
|
|
11241
11563
|
payload
|
|
11242
11564
|
);
|
|
11565
|
+
case "listContainers":
|
|
11566
|
+
return await this.listContainers();
|
|
11567
|
+
case "removeContainer":
|
|
11568
|
+
return await this.removeContainer(
|
|
11569
|
+
payload
|
|
11570
|
+
);
|
|
11243
11571
|
default:
|
|
11244
11572
|
throw new Error(`Unknown board-agent action: ${String(action)}`);
|
|
11245
11573
|
}
|
|
@@ -11296,7 +11624,7 @@ function makeChunkReassembler() {
|
|
|
11296
11624
|
|
|
11297
11625
|
// src/daemon.ts
|
|
11298
11626
|
var import_node_datachannel = require("node-datachannel");
|
|
11299
|
-
var
|
|
11627
|
+
var import_path6 = __toESM(require("path"));
|
|
11300
11628
|
|
|
11301
11629
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
11302
11630
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -12229,7 +12557,7 @@ var XHR = class extends BaseXHR {
|
|
|
12229
12557
|
}
|
|
12230
12558
|
};
|
|
12231
12559
|
|
|
12232
|
-
// ../../node_modules/
|
|
12560
|
+
// ../../node_modules/ws/wrapper.mjs
|
|
12233
12561
|
var import_stream = __toESM(require_stream(), 1);
|
|
12234
12562
|
var import_extension = __toESM(require_extension(), 1);
|
|
12235
12563
|
var import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
|
|
@@ -12476,12 +12804,12 @@ function parse2(str) {
|
|
|
12476
12804
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
12477
12805
|
return uri;
|
|
12478
12806
|
}
|
|
12479
|
-
function pathNames(obj,
|
|
12480
|
-
const regx = /\/{2,9}/g, names =
|
|
12481
|
-
if (
|
|
12807
|
+
function pathNames(obj, path8) {
|
|
12808
|
+
const regx = /\/{2,9}/g, names = path8.replace(regx, "/").split("/");
|
|
12809
|
+
if (path8.slice(0, 1) == "/" || path8.length === 0) {
|
|
12482
12810
|
names.splice(0, 1);
|
|
12483
12811
|
}
|
|
12484
|
-
if (
|
|
12812
|
+
if (path8.slice(-1) == "/") {
|
|
12485
12813
|
names.splice(names.length - 1, 1);
|
|
12486
12814
|
}
|
|
12487
12815
|
return names;
|
|
@@ -13085,11 +13413,12 @@ var SocketWithUpgrade = class extends SocketWithoutUpgrade {
|
|
|
13085
13413
|
};
|
|
13086
13414
|
var Socket = class extends SocketWithUpgrade {
|
|
13087
13415
|
constructor(uri, opts = {}) {
|
|
13088
|
-
const
|
|
13416
|
+
const isOptionsOnly = typeof uri === "object";
|
|
13417
|
+
const o = isOptionsOnly ? { ...uri } : { ...opts };
|
|
13089
13418
|
if (!o.transports || o.transports && typeof o.transports[0] === "string") {
|
|
13090
13419
|
o.transports = (o.transports || ["polling", "websocket", "webtransport"]).map((transportName) => transports[transportName]).filter((t) => !!t);
|
|
13091
13420
|
}
|
|
13092
|
-
super(uri, o);
|
|
13421
|
+
super(isOptionsOnly ? o : uri, o);
|
|
13093
13422
|
}
|
|
13094
13423
|
};
|
|
13095
13424
|
|
|
@@ -13099,7 +13428,7 @@ var protocol2 = Socket.protocol;
|
|
|
13099
13428
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
13100
13429
|
var import_debug7 = __toESM(require_src(), 1);
|
|
13101
13430
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
13102
|
-
function url(uri,
|
|
13431
|
+
function url(uri, path8 = "", loc) {
|
|
13103
13432
|
let obj = uri;
|
|
13104
13433
|
loc = loc || typeof location !== "undefined" && location;
|
|
13105
13434
|
if (null == uri)
|
|
@@ -13133,7 +13462,7 @@ function url(uri, path7 = "", loc) {
|
|
|
13133
13462
|
obj.path = obj.path || "/";
|
|
13134
13463
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
13135
13464
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
13136
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
13465
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path8;
|
|
13137
13466
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
13138
13467
|
return obj;
|
|
13139
13468
|
}
|
|
@@ -14767,8 +15096,8 @@ function lookup(uri, opts) {
|
|
|
14767
15096
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
14768
15097
|
const source = parsed.source;
|
|
14769
15098
|
const id = parsed.id;
|
|
14770
|
-
const
|
|
14771
|
-
const sameNamespace = cache[id] &&
|
|
15099
|
+
const path8 = parsed.path;
|
|
15100
|
+
const sameNamespace = cache[id] && path8 in cache[id]["nsps"];
|
|
14772
15101
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
14773
15102
|
let io;
|
|
14774
15103
|
if (newConnection) {
|
|
@@ -14794,22 +15123,22 @@ Object.assign(lookup, {
|
|
|
14794
15123
|
});
|
|
14795
15124
|
|
|
14796
15125
|
// ../../shared/all/helpers/terminal-ai/terminal.ts
|
|
14797
|
-
var
|
|
15126
|
+
var import_child_process7 = require("child_process");
|
|
14798
15127
|
var import_dockerode = __toESM(require("dockerode"));
|
|
14799
|
-
var
|
|
15128
|
+
var import_fs4 = __toESM(require("fs"));
|
|
14800
15129
|
var import_os2 = __toESM(require("os"));
|
|
14801
|
-
var
|
|
15130
|
+
var import_path4 = __toESM(require("path"));
|
|
14802
15131
|
function resolveDocker() {
|
|
14803
15132
|
if (process.env.DOCKER_HOST) return new import_dockerode.default();
|
|
14804
15133
|
try {
|
|
14805
|
-
const out = (0,
|
|
15134
|
+
const out = (0, import_child_process7.execFileSync)(
|
|
14806
15135
|
"docker",
|
|
14807
15136
|
["context", "inspect", "--format", "{{.Endpoints.docker.Host}}"],
|
|
14808
15137
|
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
14809
15138
|
).trim();
|
|
14810
15139
|
if (out.startsWith("unix://")) {
|
|
14811
15140
|
const p = out.slice("unix://".length);
|
|
14812
|
-
if (
|
|
15141
|
+
if (import_fs4.default.existsSync(p)) return new import_dockerode.default({ socketPath: p });
|
|
14813
15142
|
} else if (out) {
|
|
14814
15143
|
process.env.DOCKER_HOST = out;
|
|
14815
15144
|
return new import_dockerode.default();
|
|
@@ -14818,15 +15147,15 @@ function resolveDocker() {
|
|
|
14818
15147
|
}
|
|
14819
15148
|
const candidates = [
|
|
14820
15149
|
"/var/run/docker.sock",
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
15150
|
+
import_path4.default.join(import_os2.default.homedir(), ".docker/run/docker.sock"),
|
|
15151
|
+
import_path4.default.join(import_os2.default.homedir(), ".docker/desktop/docker.sock"),
|
|
15152
|
+
import_path4.default.join(import_os2.default.homedir(), ".orbstack/run/docker.sock"),
|
|
15153
|
+
import_path4.default.join(import_os2.default.homedir(), ".colima/default/docker.sock"),
|
|
15154
|
+
import_path4.default.join(import_os2.default.homedir(), ".rd/docker.sock")
|
|
14826
15155
|
];
|
|
14827
15156
|
for (const p of candidates) {
|
|
14828
15157
|
try {
|
|
14829
|
-
|
|
15158
|
+
import_fs4.default.statSync(p);
|
|
14830
15159
|
return new import_dockerode.default({ socketPath: p });
|
|
14831
15160
|
} catch {
|
|
14832
15161
|
}
|
|
@@ -15027,50 +15356,119 @@ var TerminalManager = class {
|
|
|
15027
15356
|
};
|
|
15028
15357
|
|
|
15029
15358
|
// src/agent-adapter.ts
|
|
15030
|
-
var
|
|
15359
|
+
var import_fs5 = __toESM(require("fs"));
|
|
15031
15360
|
var import_os3 = __toESM(require("os"));
|
|
15032
|
-
var
|
|
15033
|
-
var CONFIG_DIR2 =
|
|
15361
|
+
var import_path5 = __toESM(require("path"));
|
|
15362
|
+
var CONFIG_DIR2 = import_path5.default.join(import_os3.default.homedir(), ".factiii-runner");
|
|
15363
|
+
var RUNNER_CONFIG_PATH = import_path5.default.join(CONFIG_DIR2, "runner-config.json");
|
|
15034
15364
|
function safeSlug(spaceSlug) {
|
|
15035
15365
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
15036
15366
|
}
|
|
15037
|
-
function configPath(spaceSlug) {
|
|
15038
|
-
return import_path4.default.join(CONFIG_DIR2, `space-${safeSlug(spaceSlug)}.json`);
|
|
15039
|
-
}
|
|
15040
15367
|
function connectionPath(spaceSlug) {
|
|
15041
|
-
return
|
|
15368
|
+
return import_path5.default.join(CONFIG_DIR2, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
15042
15369
|
}
|
|
15043
15370
|
function writeJson(filePath, value2) {
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15371
|
+
import_fs5.default.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
15372
|
+
import_fs5.default.writeFileSync(filePath, JSON.stringify(value2, null, 2));
|
|
15373
|
+
import_fs5.default.chmodSync(filePath, 384);
|
|
15047
15374
|
}
|
|
15048
|
-
var
|
|
15049
|
-
|
|
15050
|
-
|
|
15375
|
+
var CONFIG_FIELDS = [
|
|
15376
|
+
"repoUrl",
|
|
15377
|
+
"mainBranch",
|
|
15378
|
+
"gitName",
|
|
15379
|
+
"gitEmail",
|
|
15380
|
+
"claudeToken",
|
|
15381
|
+
"githubToken",
|
|
15382
|
+
"storageBackend"
|
|
15383
|
+
];
|
|
15384
|
+
function fillDefaults(config) {
|
|
15385
|
+
return {
|
|
15386
|
+
repoUrl: config.repoUrl || "",
|
|
15387
|
+
mainBranch: config.mainBranch || "main",
|
|
15388
|
+
gitName: config.gitName || "",
|
|
15389
|
+
gitEmail: config.gitEmail || "",
|
|
15390
|
+
claudeToken: config.claudeToken || "",
|
|
15391
|
+
githubToken: config.githubToken || "",
|
|
15392
|
+
storageBackend: config.storageBackend || "github"
|
|
15393
|
+
};
|
|
15394
|
+
}
|
|
15395
|
+
function migrateLegacyConfigs() {
|
|
15396
|
+
const file = { boards: {} };
|
|
15397
|
+
let legacy = [];
|
|
15398
|
+
try {
|
|
15399
|
+
legacy = import_fs5.default.readdirSync(CONFIG_DIR2).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
|
|
15400
|
+
} catch {
|
|
15401
|
+
return file;
|
|
15402
|
+
}
|
|
15403
|
+
for (const name of legacy) {
|
|
15404
|
+
const slug = name.slice("space-".length, -".json".length);
|
|
15405
|
+
let parsed = {};
|
|
15051
15406
|
try {
|
|
15052
|
-
|
|
15053
|
-
|
|
15407
|
+
parsed = JSON.parse(
|
|
15408
|
+
import_fs5.default.readFileSync(import_path5.default.join(CONFIG_DIR2, name), "utf-8")
|
|
15054
15409
|
);
|
|
15055
15410
|
} catch {
|
|
15411
|
+
continue;
|
|
15056
15412
|
}
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15413
|
+
if (!file.defaultSlug) {
|
|
15414
|
+
file.defaultSlug = slug;
|
|
15415
|
+
file.default = parsed;
|
|
15416
|
+
} else {
|
|
15417
|
+
file.boards[slug] = parsed;
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
if (file.defaultSlug) writeJson(RUNNER_CONFIG_PATH, file);
|
|
15421
|
+
return file;
|
|
15422
|
+
}
|
|
15423
|
+
function readRunnerConfig2() {
|
|
15424
|
+
try {
|
|
15425
|
+
return JSON.parse(
|
|
15426
|
+
import_fs5.default.readFileSync(RUNNER_CONFIG_PATH, "utf-8")
|
|
15427
|
+
);
|
|
15428
|
+
} catch {
|
|
15429
|
+
return migrateLegacyConfigs();
|
|
15430
|
+
}
|
|
15431
|
+
}
|
|
15432
|
+
var localConfigProvider = {
|
|
15433
|
+
readConfig(spaceSlug) {
|
|
15434
|
+
const file = readRunnerConfig2();
|
|
15435
|
+
const base = file.default ?? {};
|
|
15436
|
+
const over = file.boards?.[spaceSlug] ?? {};
|
|
15437
|
+
return fillDefaults({
|
|
15438
|
+
repoUrl: over.repoUrl || base.repoUrl,
|
|
15439
|
+
mainBranch: over.mainBranch || base.mainBranch,
|
|
15440
|
+
gitName: over.gitName || base.gitName,
|
|
15441
|
+
gitEmail: over.gitEmail || base.gitEmail,
|
|
15442
|
+
claudeToken: over.claudeToken || base.claudeToken,
|
|
15443
|
+
githubToken: over.githubToken || base.githubToken,
|
|
15444
|
+
storageBackend: over.storageBackend || base.storageBackend
|
|
15445
|
+
});
|
|
15066
15446
|
},
|
|
15067
15447
|
writeConfig(spaceSlug, config) {
|
|
15068
|
-
|
|
15448
|
+
const file = readRunnerConfig2();
|
|
15449
|
+
file.boards ??= {};
|
|
15450
|
+
if (!file.defaultSlug) {
|
|
15451
|
+
file.defaultSlug = spaceSlug;
|
|
15452
|
+
file.default = config;
|
|
15453
|
+
} else if (spaceSlug === file.defaultSlug) {
|
|
15454
|
+
file.default = config;
|
|
15455
|
+
} else {
|
|
15456
|
+
const base = file.default ?? {};
|
|
15457
|
+
const diff = {};
|
|
15458
|
+
for (const f of CONFIG_FIELDS) {
|
|
15459
|
+
if (config[f] && config[f] !== base[f]) {
|
|
15460
|
+
diff[f] = config[f];
|
|
15461
|
+
}
|
|
15462
|
+
}
|
|
15463
|
+
if (Object.keys(diff).length) file.boards[spaceSlug] = diff;
|
|
15464
|
+
else delete file.boards[spaceSlug];
|
|
15465
|
+
}
|
|
15466
|
+
writeJson(RUNNER_CONFIG_PATH, file);
|
|
15069
15467
|
},
|
|
15070
15468
|
readOneDriveConnection(spaceSlug) {
|
|
15071
15469
|
try {
|
|
15072
15470
|
return JSON.parse(
|
|
15073
|
-
|
|
15471
|
+
import_fs5.default.readFileSync(connectionPath(spaceSlug), "utf-8")
|
|
15074
15472
|
);
|
|
15075
15473
|
} catch {
|
|
15076
15474
|
return null;
|
|
@@ -15079,7 +15477,7 @@ var localConfigProvider = {
|
|
|
15079
15477
|
writeOneDriveConnection(spaceSlug, connection) {
|
|
15080
15478
|
const filePath = connectionPath(spaceSlug);
|
|
15081
15479
|
if (!connection) {
|
|
15082
|
-
|
|
15480
|
+
import_fs5.default.rmSync(filePath, { force: true });
|
|
15083
15481
|
return;
|
|
15084
15482
|
}
|
|
15085
15483
|
writeJson(filePath, connection);
|
|
@@ -15087,6 +15485,13 @@ var localConfigProvider = {
|
|
|
15087
15485
|
};
|
|
15088
15486
|
|
|
15089
15487
|
// src/daemon.ts
|
|
15488
|
+
function toChannelScope(raw) {
|
|
15489
|
+
if (!raw || raw.role === "owner") return { role: "owner" };
|
|
15490
|
+
return { role: "member", spaceSlugs: new Set(raw.spaceSlugs) };
|
|
15491
|
+
}
|
|
15492
|
+
function scopeAllows(scope, spaceSlug) {
|
|
15493
|
+
return scope.role === "owner" || scope.spaceSlugs.has(spaceSlug);
|
|
15494
|
+
}
|
|
15090
15495
|
async function startDaemon(config) {
|
|
15091
15496
|
console.log(`Connecting to ${config.serverUrl} as "${config.runnerName}"...`);
|
|
15092
15497
|
const socket = lookup(config.serverUrl, {
|
|
@@ -15096,10 +15501,11 @@ async function startDaemon(config) {
|
|
|
15096
15501
|
autoConnect: false
|
|
15097
15502
|
});
|
|
15098
15503
|
const engines = /* @__PURE__ */ new Map();
|
|
15099
|
-
const
|
|
15504
|
+
const channelScopes = /* @__PURE__ */ new Map();
|
|
15100
15505
|
const terminalByChannel = /* @__PURE__ */ new WeakMap();
|
|
15101
|
-
const
|
|
15102
|
-
for (const engine of engines
|
|
15506
|
+
const findCardContainerInScope = (scope, postId) => {
|
|
15507
|
+
for (const [spaceSlug, engine] of engines) {
|
|
15508
|
+
if (!scopeAllows(scope, spaceSlug)) continue;
|
|
15103
15509
|
const name = engine.getCardContainerName(postId);
|
|
15104
15510
|
if (name) return name;
|
|
15105
15511
|
}
|
|
@@ -15107,7 +15513,19 @@ async function startDaemon(config) {
|
|
|
15107
15513
|
};
|
|
15108
15514
|
const broadcastToAll = (msg) => {
|
|
15109
15515
|
const frames = chunkWire(msg);
|
|
15110
|
-
for (const dc of
|
|
15516
|
+
for (const dc of channelScopes.keys()) {
|
|
15517
|
+
for (const frame of frames) {
|
|
15518
|
+
try {
|
|
15519
|
+
dc.sendMessage(frame);
|
|
15520
|
+
} catch {
|
|
15521
|
+
}
|
|
15522
|
+
}
|
|
15523
|
+
}
|
|
15524
|
+
};
|
|
15525
|
+
const broadcastToBoard = (spaceSlug, msg) => {
|
|
15526
|
+
const frames = chunkWire(msg);
|
|
15527
|
+
for (const [dc, scope] of channelScopes) {
|
|
15528
|
+
if (!scopeAllows(scope, spaceSlug)) continue;
|
|
15111
15529
|
for (const frame of frames) {
|
|
15112
15530
|
try {
|
|
15113
15531
|
dc.sendMessage(frame);
|
|
@@ -15116,7 +15534,7 @@ async function startDaemon(config) {
|
|
|
15116
15534
|
}
|
|
15117
15535
|
}
|
|
15118
15536
|
};
|
|
15119
|
-
function getEngine(spaceSlug) {
|
|
15537
|
+
function getEngine(spaceSlug, spaceId) {
|
|
15120
15538
|
let engine = engines.get(spaceSlug);
|
|
15121
15539
|
if (!engine) {
|
|
15122
15540
|
const send = (event, data) => {
|
|
@@ -15126,10 +15544,13 @@ async function startDaemon(config) {
|
|
|
15126
15544
|
event,
|
|
15127
15545
|
payload: data
|
|
15128
15546
|
};
|
|
15129
|
-
|
|
15547
|
+
broadcastToBoard(spaceSlug, JSON.stringify(msg));
|
|
15130
15548
|
};
|
|
15131
15549
|
engine = new BoardAgentEngine({
|
|
15132
15550
|
spaceSlug,
|
|
15551
|
+
// Stable id for durable container naming (slugs can change); the engine
|
|
15552
|
+
// falls back to the slug when absent.
|
|
15553
|
+
spaceId,
|
|
15133
15554
|
emitter: createEventSink(send, spaceSlug),
|
|
15134
15555
|
configProvider: localConfigProvider,
|
|
15135
15556
|
dockerfilePath: getDockerfilePath(),
|
|
@@ -15143,6 +15564,7 @@ async function startDaemon(config) {
|
|
|
15143
15564
|
}
|
|
15144
15565
|
return engine;
|
|
15145
15566
|
}
|
|
15567
|
+
const instancePrefix = computeInstancePrefix(`runner-${config.runnerName}`);
|
|
15146
15568
|
socket.on("connect", () => {
|
|
15147
15569
|
console.log(`Connected. Runner "${config.runnerName}" is online.`);
|
|
15148
15570
|
console.log("Waiting for connections from UI clients...\n");
|
|
@@ -15155,6 +15577,7 @@ async function startDaemon(config) {
|
|
|
15155
15577
|
});
|
|
15156
15578
|
socket.on("runnerOffer", (data) => {
|
|
15157
15579
|
console.log(`Received WebRTC offer from ${data.fromSocketId}`);
|
|
15580
|
+
const scope = toChannelScope(data.scope);
|
|
15158
15581
|
try {
|
|
15159
15582
|
const peer = new import_node_datachannel.PeerConnection("runner", {
|
|
15160
15583
|
iceServers: ["stun:stun.l.google.com:19302"]
|
|
@@ -15202,7 +15625,7 @@ async function startDaemon(config) {
|
|
|
15202
15625
|
console.log(
|
|
15203
15626
|
`Data channel "${dc.getLabel()}" opened with ${data.fromSocketId}`
|
|
15204
15627
|
);
|
|
15205
|
-
|
|
15628
|
+
channelScopes.set(dc, scope);
|
|
15206
15629
|
const reassembler = makeChunkReassembler();
|
|
15207
15630
|
const sendToChannel = (msg) => {
|
|
15208
15631
|
for (const frame of chunkWire(msg)) {
|
|
@@ -15220,10 +15643,17 @@ async function startDaemon(config) {
|
|
|
15220
15643
|
};
|
|
15221
15644
|
terminalByChannel.set(
|
|
15222
15645
|
dc,
|
|
15223
|
-
new TerminalManager(
|
|
15646
|
+
new TerminalManager(
|
|
15647
|
+
sendRawToChannel,
|
|
15648
|
+
(postId) => findCardContainerInScope(scope, postId)
|
|
15649
|
+
)
|
|
15224
15650
|
);
|
|
15225
15651
|
dc.onOpen(() => {
|
|
15652
|
+
sendToChannel(
|
|
15653
|
+
JSON.stringify({ type: "runner-info", version: currentVersion() })
|
|
15654
|
+
);
|
|
15226
15655
|
for (const [spaceSlug, engine] of engines.entries()) {
|
|
15656
|
+
if (!scopeAllows(scope, spaceSlug)) continue;
|
|
15227
15657
|
const sessions = engine.cardSessions();
|
|
15228
15658
|
for (const summary of Object.values(sessions)) {
|
|
15229
15659
|
const msg = JSON.stringify({
|
|
@@ -15251,15 +15681,21 @@ async function startDaemon(config) {
|
|
|
15251
15681
|
}
|
|
15252
15682
|
return;
|
|
15253
15683
|
}
|
|
15684
|
+
if (parsed.type === "runner-control" && parsed.action === "update") {
|
|
15685
|
+
applyUpdateAndRestart();
|
|
15686
|
+
return;
|
|
15687
|
+
}
|
|
15254
15688
|
if (parsed.type === "broadcast" && parsed.spaceSlug && parsed.event) {
|
|
15689
|
+
if (!scopeAllows(scope, parsed.spaceSlug)) return;
|
|
15255
15690
|
const eventMsg = JSON.stringify({
|
|
15256
15691
|
type: "event",
|
|
15257
15692
|
event: `board-agent:${parsed.spaceSlug}:${parsed.event}`,
|
|
15258
15693
|
payload: parsed.payload
|
|
15259
15694
|
});
|
|
15260
15695
|
const frames = chunkWire(eventMsg);
|
|
15261
|
-
for (const other of
|
|
15696
|
+
for (const [other, otherScope] of channelScopes) {
|
|
15262
15697
|
if (other === dc) continue;
|
|
15698
|
+
if (!scopeAllows(otherScope, parsed.spaceSlug)) continue;
|
|
15263
15699
|
for (const frame of frames) {
|
|
15264
15700
|
try {
|
|
15265
15701
|
other.sendMessage(frame);
|
|
@@ -15274,14 +15710,17 @@ async function startDaemon(config) {
|
|
|
15274
15710
|
}
|
|
15275
15711
|
handleDataChannelMessage(
|
|
15276
15712
|
msgStr,
|
|
15713
|
+
scope,
|
|
15277
15714
|
sendToChannel,
|
|
15278
|
-
|
|
15279
|
-
getEngine
|
|
15715
|
+
broadcastToBoard,
|
|
15716
|
+
getEngine,
|
|
15717
|
+
engines,
|
|
15718
|
+
instancePrefix
|
|
15280
15719
|
);
|
|
15281
15720
|
});
|
|
15282
15721
|
dc.onClosed(() => {
|
|
15283
15722
|
console.log(`Data channel closed with ${data.fromSocketId}`);
|
|
15284
|
-
|
|
15723
|
+
channelScopes.delete(dc);
|
|
15285
15724
|
const mgr = terminalByChannel.get(dc);
|
|
15286
15725
|
if (mgr) {
|
|
15287
15726
|
mgr.destroyAll();
|
|
@@ -15307,6 +15746,25 @@ async function startDaemon(config) {
|
|
|
15307
15746
|
}
|
|
15308
15747
|
socket.disconnect();
|
|
15309
15748
|
};
|
|
15749
|
+
const applyUpdateAndRestart = () => {
|
|
15750
|
+
console.log("[update] update approved by a client \u2014 installing latest...");
|
|
15751
|
+
const status = (state) => broadcastToAll(JSON.stringify({ type: "runner-update", state }));
|
|
15752
|
+
status("installing");
|
|
15753
|
+
if (!installLatest()) {
|
|
15754
|
+
status("failed");
|
|
15755
|
+
return;
|
|
15756
|
+
}
|
|
15757
|
+
status("restarting");
|
|
15758
|
+
cleanup();
|
|
15759
|
+
process.exit(RESTART_FOR_UPDATE);
|
|
15760
|
+
};
|
|
15761
|
+
socket.on("runnerRevoked", () => {
|
|
15762
|
+
console.log(
|
|
15763
|
+
"\nThis runner was disconnected from your Factiii account. Stopping all jobs and removing containers..."
|
|
15764
|
+
);
|
|
15765
|
+
cleanup();
|
|
15766
|
+
process.exit(0);
|
|
15767
|
+
});
|
|
15310
15768
|
process.on("SIGINT", () => {
|
|
15311
15769
|
console.log("\nShutting down...");
|
|
15312
15770
|
cleanup();
|
|
@@ -15316,8 +15774,27 @@ async function startDaemon(config) {
|
|
|
15316
15774
|
cleanup();
|
|
15317
15775
|
process.exit(0);
|
|
15318
15776
|
});
|
|
15777
|
+
await new Promise(() => {
|
|
15778
|
+
});
|
|
15779
|
+
}
|
|
15780
|
+
function spaceIdToSlug(engines) {
|
|
15781
|
+
const map = /* @__PURE__ */ new Map();
|
|
15782
|
+
for (const [slug, engine] of engines) {
|
|
15783
|
+
if (engine.spaceId != null) map.set(engine.spaceId, slug);
|
|
15784
|
+
}
|
|
15785
|
+
return map;
|
|
15319
15786
|
}
|
|
15320
|
-
|
|
15787
|
+
function scopeFilterContainers(all, scope, engines) {
|
|
15788
|
+
const idToSlug = spaceIdToSlug(engines);
|
|
15789
|
+
return all.flatMap((c) => {
|
|
15790
|
+
const slug = c.spaceId != null ? idToSlug.get(c.spaceId) : void 0;
|
|
15791
|
+
if (scope.role !== "owner") {
|
|
15792
|
+
if (!slug || !scopeAllows(scope, slug)) return [];
|
|
15793
|
+
}
|
|
15794
|
+
return [{ ...c, spaceId: slug ? c.spaceId : null, spaceSlug: slug }];
|
|
15795
|
+
});
|
|
15796
|
+
}
|
|
15797
|
+
async function handleDataChannelMessage(raw, scope, sendToChannel, broadcastToBoard, getEngine, engines, instancePrefix) {
|
|
15321
15798
|
let msg;
|
|
15322
15799
|
try {
|
|
15323
15800
|
msg = JSON.parse(raw);
|
|
@@ -15325,9 +15802,76 @@ async function handleDataChannelMessage(raw, sendToChannel, broadcastToAll, getE
|
|
|
15325
15802
|
return;
|
|
15326
15803
|
}
|
|
15327
15804
|
if (msg.type !== "dispatch") return;
|
|
15328
|
-
const engine = getEngine(msg.spaceSlug);
|
|
15329
15805
|
const { action, payload, requestId } = msg;
|
|
15330
15806
|
const ts = () => (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
15807
|
+
const sendResult = (res) => sendToChannel(JSON.stringify(res));
|
|
15808
|
+
if (action === "listContainers") {
|
|
15809
|
+
try {
|
|
15810
|
+
const rows = await listContainersByPrefix(`factiii-${instancePrefix}-`);
|
|
15811
|
+
const all = rows.flatMap((row) => {
|
|
15812
|
+
const parsed = parseFactiiiContainerName(row.name, instancePrefix);
|
|
15813
|
+
if (!parsed) return [];
|
|
15814
|
+
return [
|
|
15815
|
+
{
|
|
15816
|
+
containerName: row.name,
|
|
15817
|
+
kind: parsed.kind,
|
|
15818
|
+
spaceId: parsed.spaceId,
|
|
15819
|
+
taskId: parsed.taskId,
|
|
15820
|
+
cacheKey: parsed.cacheKey,
|
|
15821
|
+
running: row.running,
|
|
15822
|
+
status: row.status
|
|
15823
|
+
}
|
|
15824
|
+
];
|
|
15825
|
+
});
|
|
15826
|
+
sendResult({
|
|
15827
|
+
type: "result",
|
|
15828
|
+
requestId,
|
|
15829
|
+
result: scopeFilterContainers(all, scope, engines)
|
|
15830
|
+
});
|
|
15831
|
+
} catch (err) {
|
|
15832
|
+
sendResult({
|
|
15833
|
+
type: "result",
|
|
15834
|
+
requestId,
|
|
15835
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15836
|
+
});
|
|
15837
|
+
}
|
|
15838
|
+
return;
|
|
15839
|
+
}
|
|
15840
|
+
if (action === "removeContainer") {
|
|
15841
|
+
const { containerName } = payload;
|
|
15842
|
+
try {
|
|
15843
|
+
if (!containerName.startsWith(`factiii-${instancePrefix}-`)) {
|
|
15844
|
+
throw new Error("Refusing to remove a container outside this runner.");
|
|
15845
|
+
}
|
|
15846
|
+
if (scope.role !== "owner") {
|
|
15847
|
+
const parsed = parseFactiiiContainerName(containerName, instancePrefix);
|
|
15848
|
+
const slug = parsed?.spaceId != null ? spaceIdToSlug(engines).get(parsed.spaceId) : void 0;
|
|
15849
|
+
if (!slug || !scopeAllows(scope, slug)) {
|
|
15850
|
+
throw new Error("Not authorized to remove this container");
|
|
15851
|
+
}
|
|
15852
|
+
}
|
|
15853
|
+
await killContainer(containerName);
|
|
15854
|
+
sendResult({ type: "result", requestId, result: { removed: true } });
|
|
15855
|
+
} catch (err) {
|
|
15856
|
+
sendResult({
|
|
15857
|
+
type: "result",
|
|
15858
|
+
requestId,
|
|
15859
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15860
|
+
});
|
|
15861
|
+
}
|
|
15862
|
+
return;
|
|
15863
|
+
}
|
|
15864
|
+
if (!scopeAllows(scope, msg.spaceSlug)) {
|
|
15865
|
+
console.warn(`[${ts()}] \u20E0 ${action} denied for board "${msg.spaceSlug}"`);
|
|
15866
|
+
const denied = {
|
|
15867
|
+
type: "result",
|
|
15868
|
+
requestId,
|
|
15869
|
+
error: "Not authorized for this board on this runner"
|
|
15870
|
+
};
|
|
15871
|
+
sendToChannel(JSON.stringify(denied));
|
|
15872
|
+
return;
|
|
15873
|
+
}
|
|
15874
|
+
const engine = getEngine(msg.spaceSlug, msg.spaceId);
|
|
15331
15875
|
console.log(`[${ts()}] \u2190 ${action} (${requestId})`);
|
|
15332
15876
|
const askLogSender = (entry) => {
|
|
15333
15877
|
const event = {
|
|
@@ -15336,7 +15880,7 @@ async function handleDataChannelMessage(raw, sendToChannel, broadcastToAll, getE
|
|
|
15336
15880
|
event: `board-agent:${msg.spaceSlug}:log`,
|
|
15337
15881
|
payload: entry
|
|
15338
15882
|
};
|
|
15339
|
-
|
|
15883
|
+
broadcastToBoard(msg.spaceSlug, JSON.stringify(event));
|
|
15340
15884
|
};
|
|
15341
15885
|
try {
|
|
15342
15886
|
const result = await engine.dispatch(
|
|
@@ -15359,107 +15903,41 @@ async function handleDataChannelMessage(raw, sendToChannel, broadcastToAll, getE
|
|
|
15359
15903
|
}
|
|
15360
15904
|
}
|
|
15361
15905
|
function getDockerfilePath() {
|
|
15362
|
-
return
|
|
15906
|
+
return import_path6.default.join(__dirname, "..", "Dockerfile.claude");
|
|
15363
15907
|
}
|
|
15364
15908
|
|
|
15365
|
-
// src/
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
stdio: opts?.silent ? "pipe" : "inherit",
|
|
15376
|
-
// Avoids a cmd.exe window flash on Windows during silent checks.
|
|
15377
|
-
windowsHide: true
|
|
15378
|
-
});
|
|
15379
|
-
} catch {
|
|
15380
|
-
return "";
|
|
15381
|
-
}
|
|
15382
|
-
}
|
|
15383
|
-
function check(cmd) {
|
|
15384
|
-
const lookup2 = process.platform === "win32" ? "where" : "which";
|
|
15385
|
-
try {
|
|
15386
|
-
(0, import_child_process7.execFileSync)(lookup2, [cmd], { stdio: "pipe", windowsHide: true });
|
|
15387
|
-
return true;
|
|
15388
|
-
} catch {
|
|
15389
|
-
return false;
|
|
15390
|
-
}
|
|
15391
|
-
}
|
|
15392
|
-
async function setup(serverUrl) {
|
|
15393
|
-
console.log("\n=== Factiii Runner \u2014 Setup ===\n");
|
|
15394
|
-
console.log("[1/3] Checking Docker...");
|
|
15395
|
-
if (!check("docker")) {
|
|
15396
|
-
console.error(
|
|
15397
|
-
" Docker is not installed. Install it from https://docs.docker.com/get-docker/ and re-run setup."
|
|
15398
|
-
);
|
|
15399
|
-
process.exit(1);
|
|
15400
|
-
}
|
|
15401
|
-
if (!run("docker", ["info"], { silent: true })) {
|
|
15402
|
-
console.error(
|
|
15403
|
-
" Docker is installed but the daemon is not running. Start Docker and re-run setup."
|
|
15404
|
-
);
|
|
15405
|
-
process.exit(1);
|
|
15406
|
-
}
|
|
15407
|
-
console.log(" Docker is ready.");
|
|
15408
|
-
console.log("\n[2/3] Building factiii-claude Docker image...");
|
|
15409
|
-
const dockerfilePath = findDockerfile();
|
|
15410
|
-
if (!dockerfilePath) {
|
|
15411
|
-
console.error(
|
|
15412
|
-
" Dockerfile.claude not found. Reinstall the package and re-run setup."
|
|
15413
|
-
);
|
|
15414
|
-
process.exit(1);
|
|
15909
|
+
// src/supervisor.ts
|
|
15910
|
+
async function runSupervised(workerArgs) {
|
|
15911
|
+
if (isWorker()) {
|
|
15912
|
+
const config = readRunnerConfig();
|
|
15913
|
+
if (!config) {
|
|
15914
|
+
console.error('Failed to read config. Run "factiii-runner setup" again.');
|
|
15915
|
+
process.exit(1);
|
|
15916
|
+
}
|
|
15917
|
+
await startDaemon(config);
|
|
15918
|
+
process.exit(0);
|
|
15415
15919
|
}
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15419
|
-
|
|
15420
|
-
|
|
15421
|
-
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
`{{ index .Config.Labels "${DOCKERFILE_LABEL2}" }}`
|
|
15425
|
-
],
|
|
15426
|
-
{ silent: true }
|
|
15427
|
-
).trim();
|
|
15428
|
-
if (existingHash === wantedHash) {
|
|
15429
|
-
console.log(" Image is up to date.");
|
|
15430
|
-
} else {
|
|
15431
|
-
console.log(
|
|
15432
|
-
existingHash ? ` Dockerfile changed since last build, rebuilding from ${dockerfilePath}...` : ` Building from ${dockerfilePath}...`
|
|
15920
|
+
for (; ; ) {
|
|
15921
|
+
const child = (0, import_node_child_process2.spawnSync)(
|
|
15922
|
+
process.execPath,
|
|
15923
|
+
[...process.execArgv, ...workerArgs],
|
|
15924
|
+
{
|
|
15925
|
+
stdio: "inherit",
|
|
15926
|
+
env: markWorkerEnv(process.env)
|
|
15927
|
+
}
|
|
15433
15928
|
);
|
|
15434
|
-
|
|
15435
|
-
"
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
dockerfilePath,
|
|
15440
|
-
"--label",
|
|
15441
|
-
`${DOCKERFILE_LABEL2}=${wantedHash}`,
|
|
15442
|
-
"."
|
|
15443
|
-
]);
|
|
15444
|
-
console.log(" Image built.");
|
|
15929
|
+
if (child.status === RESTART_FOR_UPDATE) {
|
|
15930
|
+
console.log("[update] relaunching the runner on the new version...");
|
|
15931
|
+
continue;
|
|
15932
|
+
}
|
|
15933
|
+
process.exit(child.signal ? 0 : child.status ?? 0);
|
|
15445
15934
|
}
|
|
15446
|
-
run("docker", ["volume", "create", "factiii-card-cache"], { silent: true });
|
|
15447
|
-
console.log("\n[3/3] Pairing this runner with a Factiii account...");
|
|
15448
|
-
const { token, runnerName } = await pairWithBrowser(serverUrl);
|
|
15449
|
-
writeRunnerConfig({ serverUrl, authToken: token, runnerName });
|
|
15450
|
-
console.log(" Paired and config saved.");
|
|
15451
|
-
console.log("\n=== Setup complete ===\n");
|
|
15452
|
-
console.log("Start the runner with: factiii-runner start\n");
|
|
15453
|
-
}
|
|
15454
|
-
function findDockerfile() {
|
|
15455
|
-
const p = import_path6.default.join(__dirname, "..", "Dockerfile.claude");
|
|
15456
|
-
return import_fs5.default.existsSync(p) ? p : null;
|
|
15457
15935
|
}
|
|
15458
15936
|
|
|
15459
15937
|
// src/cli.ts
|
|
15460
15938
|
import_node_dns.default.setDefaultResultOrder("ipv4first");
|
|
15461
15939
|
var program2 = new Command();
|
|
15462
|
-
program2.name("factiii-runner").description("Factiii Runner - run Board AI agents on remote servers").version(
|
|
15940
|
+
program2.name("factiii-runner").description("Factiii Runner - run Board AI agents on remote servers").version(currentVersion());
|
|
15463
15941
|
program2.command("setup").description("Verify Docker, build the Claude image, and pair this runner").option("-s, --server <url>", "API server URL", "https://api.factiii.com").action(async (opts) => {
|
|
15464
15942
|
try {
|
|
15465
15943
|
await setup(opts.server);
|
|
@@ -15494,12 +15972,7 @@ program2.command("start").description("Start the runner daemon").action(async ()
|
|
|
15494
15972
|
console.error('No config found. Run "factiii-runner setup" first.');
|
|
15495
15973
|
process.exit(1);
|
|
15496
15974
|
}
|
|
15497
|
-
|
|
15498
|
-
if (!config) {
|
|
15499
|
-
console.error('Failed to read config. Run "factiii-runner setup" again.');
|
|
15500
|
-
process.exit(1);
|
|
15501
|
-
}
|
|
15502
|
-
await startDaemon(config);
|
|
15975
|
+
await runSupervised(process.argv.slice(1));
|
|
15503
15976
|
});
|
|
15504
15977
|
program2.command("status").description("Show current runner configuration").action(() => {
|
|
15505
15978
|
if (!configExists()) {
|