@cmmd-center/forge 0.13.20 → 0.13.22
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/bin.cjs +218 -25
- package/dist/bin.mjs +242 -49
- package/dist/client/assets/{CanvasPanel-uofZ5KUR.js → CanvasPanel-DeDjy-X2.js} +2 -2
- package/dist/client/assets/{CompositeItem-C5YSRi5B.js → CompositeItem-CQubmTuU.js} +2 -2
- package/dist/client/assets/{DiffPanel-vMgoOgmn.js → DiffPanel-4dMGnRQx.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-BL2ToTuV.js → DiffPanel.logic-SlNrN8IK.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-Dq2v4pBo.js → DiffPanelShell-BEZZSyhr.js} +3 -3
- package/dist/client/assets/{DiffWorkerPoolProvider-B2Da35Tj.js → DiffWorkerPoolProvider-DE_ppGS9.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-ByFVxByx.js → PullRequestCodePanel-CsYCbixY.js} +2 -2
- package/dist/client/assets/{Virtualizer-B3YnhtJt.js → Virtualizer-DzZ4roLU.js} +2 -2
- package/dist/client/assets/{canvasStateStore-IBP3QM_C.js → canvasStateStore-UwPGvihn.js} +2 -2
- package/dist/client/assets/{cmmdAuthRedirect-Ck1QfYaS.js → cmmdAuthRedirect-BUli2MC4.js} +2 -2
- package/dist/client/assets/{index-gEW4XdYw.js → index-DW9nsKTK.js} +6 -6
- package/dist/client/assets/{popover-BQaoSlk7.js → popover-BpdZi4Uc.js} +2 -2
- package/dist/client/assets/src-DVtrA7UW.js +3 -0
- package/dist/client/assets/{toggle-CE2NHkQL.js → toggle-BUkm7Wlt.js} +2 -2
- package/dist/client/index.html +10 -10
- package/dist/forge-build.json +1 -1
- package/dist/{open-D8mGyWmt.mjs → open-FIgTDosf.mjs} +3 -3
- package/package.json +1 -1
- package/dist/client/assets/src-Bj8vTcnP.js +0 -3
package/dist/bin.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
|
|
|
22
22
|
import * as FS$1 from "node:fs";
|
|
23
23
|
import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
24
24
|
import * as NodeOS from "node:os";
|
|
25
|
-
import
|
|
25
|
+
import os, { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
26
26
|
import * as Path$1 from "node:path";
|
|
27
27
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
28
28
|
import { pathToFileURL } from "node:url";
|
|
@@ -17203,7 +17203,8 @@ const ExecutionEnvironmentAuthCapabilities = Struct({
|
|
|
17203
17203
|
ownerBootstrapPromotion: Boolean$1,
|
|
17204
17204
|
bearerBootstrap: Boolean$1,
|
|
17205
17205
|
desktopBootstrap: Boolean$1,
|
|
17206
|
-
providerCredentialBroker: Boolean$1
|
|
17206
|
+
providerCredentialBroker: Boolean$1,
|
|
17207
|
+
cloudOwnerAssertion: optionalKey(Boolean$1)
|
|
17207
17208
|
});
|
|
17208
17209
|
const ExecutionEnvironmentToolCapabilities = Struct({
|
|
17209
17210
|
agents: Boolean$1,
|
|
@@ -50548,7 +50549,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
50548
50549
|
const min = forceColor || 0;
|
|
50549
50550
|
if (env.TERM === "dumb") return min;
|
|
50550
50551
|
if (process$1.platform === "win32") {
|
|
50551
|
-
const osRelease =
|
|
50552
|
+
const osRelease = os.release().split(".");
|
|
50552
50553
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
50553
50554
|
return 1;
|
|
50554
50555
|
}
|
|
@@ -52991,7 +52992,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
52991
52992
|
exports.processDetector = void 0;
|
|
52992
52993
|
const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
|
|
52993
52994
|
const semconv_1 = require_semconv$1();
|
|
52994
|
-
const os$
|
|
52995
|
+
const os$4 = __require("os");
|
|
52995
52996
|
/**
|
|
52996
52997
|
* ProcessDetector will be used to detect the resources related current process running
|
|
52997
52998
|
* and being instrumented from the NodeJS Process module.
|
|
@@ -53013,7 +53014,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
53013
53014
|
};
|
|
53014
53015
|
if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
|
|
53015
53016
|
try {
|
|
53016
|
-
const userInfo = os$
|
|
53017
|
+
const userInfo = os$4.userInfo();
|
|
53017
53018
|
attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
|
|
53018
53019
|
} catch (e) {
|
|
53019
53020
|
api_1.diag.debug(`error obtaining process owner: ${e}`);
|
|
@@ -65936,7 +65937,7 @@ function normalizeNumberish(value) {
|
|
|
65936
65937
|
}
|
|
65937
65938
|
//#endregion
|
|
65938
65939
|
//#region package.json
|
|
65939
|
-
var version$1 = "0.13.
|
|
65940
|
+
var version$1 = "0.13.22";
|
|
65940
65941
|
//#endregion
|
|
65941
65942
|
//#region src/sentry.ts
|
|
65942
65943
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -68060,6 +68061,7 @@ function extractCookieValue(setCookieHeaders, name) {
|
|
|
68060
68061
|
}
|
|
68061
68062
|
return null;
|
|
68062
68063
|
}
|
|
68064
|
+
/** @internal exported for the other owner lanes and their tests. */
|
|
68063
68065
|
function readCmmdJsonResponse(response, fallbackMessage) {
|
|
68064
68066
|
return gen(function* () {
|
|
68065
68067
|
const payload = yield* tryPromise({
|
|
@@ -68321,11 +68323,14 @@ function cmmdUserCredential(payload) {
|
|
|
68321
68323
|
/**
|
|
68322
68324
|
* Read one CMMD token-exchange response into the credential set Forge stores.
|
|
68323
68325
|
*
|
|
68324
|
-
*
|
|
68325
|
-
*
|
|
68326
|
-
*
|
|
68327
|
-
*
|
|
68328
|
-
*
|
|
68326
|
+
* Every owner lane that redeems a CMMD grant — the browser OAuth callback,
|
|
68327
|
+
* Cloud's password login, the cookie-shaped session renew, and the bearer
|
|
68328
|
+
* device-session redeem in cloudOwnerDeviceSession.ts — sees the same bundle
|
|
68329
|
+
* shape. Reading it in one place is what keeps them from drifting on which
|
|
68330
|
+
* field carries the PAT; callers still own their own error reporting, which
|
|
68331
|
+
* is the only part that legitimately differs between them.
|
|
68332
|
+
*
|
|
68333
|
+
* @internal exported for the other owner lanes and their tests.
|
|
68329
68334
|
*/
|
|
68330
68335
|
function resolveCmmdTokenBundleCredentials(tokenResult) {
|
|
68331
68336
|
return gen(function* () {
|
|
@@ -69277,22 +69282,52 @@ const deriveServerPaths = fn$1(function* (baseDir, _devUrl, _mode) {
|
|
|
69277
69282
|
secretsDir: join(stateDir, "secrets")
|
|
69278
69283
|
};
|
|
69279
69284
|
});
|
|
69280
|
-
|
|
69285
|
+
/**
|
|
69286
|
+
* Create `dir`, healing the one case a recursive mkdir cannot: a symlink whose
|
|
69287
|
+
* target no longer exists.
|
|
69288
|
+
*
|
|
69289
|
+
* A recursive mkdir is a no-op when the path is already a directory, but it
|
|
69290
|
+
* fails when the path is a DANGLING symlink, because nothing on the way down
|
|
69291
|
+
* resolves. bun reports that as EEXIST and node as ENOENT; either way the call
|
|
69292
|
+
* raises instead of recreating what the link points at.
|
|
69293
|
+
*
|
|
69294
|
+
* That shape is normal on a CMMD cloud runtime. The regenerable cache
|
|
69295
|
+
* directories there are symlinks onto the sprite's machine-local /tmp, which is
|
|
69296
|
+
* a separate device that is NOT part of the checkpointed overlay, so a wipe
|
|
69297
|
+
* leaves the links behind and takes their targets. Only provisioning recreated
|
|
69298
|
+
* them, and provisioning does not re-run on a restart, so a wiped store meant
|
|
69299
|
+
* the server raised at boot, exited 1, and did so again on every supervisor
|
|
69300
|
+
* restart for as long as the runtime existed.
|
|
69301
|
+
*
|
|
69302
|
+
* Observed live on cloud runtime 33dff4ee: /forge/caches pointed at
|
|
69303
|
+
* /tmp/forge-store/caches, the target was gone, and the server failed to boot
|
|
69304
|
+
* 974 times between 2026-09-15 and 2026-09-16. Recreating the target directory
|
|
69305
|
+
* by hand was the whole repair, which is what this does automatically.
|
|
69306
|
+
*
|
|
69307
|
+
* Healing is deliberately narrow. A path that is not a symlink, or a symlink
|
|
69308
|
+
* whose target cannot be created, re-raises the original error rather than
|
|
69309
|
+
* inventing a directory somewhere the caller did not ask for.
|
|
69310
|
+
*/
|
|
69311
|
+
const ensureDirectory = fn$1(function* (dir) {
|
|
69281
69312
|
const fs = yield* FileSystem;
|
|
69313
|
+
const path = yield* Path$2;
|
|
69314
|
+
yield* fs.makeDirectory(dir, { recursive: true }).pipe(catch_((error) => fs.readLink(dir).pipe(flatMap((target) => fs.makeDirectory(path.resolve(path.dirname(dir), target), { recursive: true })), mapError(() => error))));
|
|
69315
|
+
});
|
|
69316
|
+
const ensureServerDirectories = fn$1(function* (derivedPaths) {
|
|
69282
69317
|
const path = yield* Path$2;
|
|
69283
69318
|
yield* all([
|
|
69284
|
-
|
|
69285
|
-
|
|
69286
|
-
|
|
69287
|
-
|
|
69288
|
-
|
|
69289
|
-
|
|
69290
|
-
|
|
69291
|
-
|
|
69292
|
-
|
|
69293
|
-
|
|
69294
|
-
|
|
69295
|
-
|
|
69319
|
+
ensureDirectory(derivedPaths.stateDir),
|
|
69320
|
+
ensureDirectory(derivedPaths.logsDir),
|
|
69321
|
+
ensureDirectory(derivedPaths.providerLogsDir),
|
|
69322
|
+
ensureDirectory(derivedPaths.terminalLogsDir),
|
|
69323
|
+
ensureDirectory(derivedPaths.attachmentsDir),
|
|
69324
|
+
ensureDirectory(derivedPaths.worktreesDir),
|
|
69325
|
+
ensureDirectory(path.dirname(derivedPaths.keybindingsConfigPath)),
|
|
69326
|
+
ensureDirectory(path.dirname(derivedPaths.settingsPath)),
|
|
69327
|
+
ensureDirectory(derivedPaths.providerStatusCacheDir),
|
|
69328
|
+
ensureDirectory(path.dirname(derivedPaths.anonymousIdPath)),
|
|
69329
|
+
ensureDirectory(path.dirname(derivedPaths.environmentMetadataPath)),
|
|
69330
|
+
ensureDirectory(path.dirname(derivedPaths.serverRuntimeStatePath))
|
|
69296
69331
|
], { concurrency: "unbounded" });
|
|
69297
69332
|
});
|
|
69298
69333
|
/**
|
|
@@ -75784,7 +75819,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
75784
75819
|
var fs$6 = __require("fs");
|
|
75785
75820
|
var path$5 = __require("path");
|
|
75786
75821
|
var url$1 = __require("url");
|
|
75787
|
-
var os$
|
|
75822
|
+
var os$3 = __require("os");
|
|
75788
75823
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
75789
75824
|
var vars = process.config && process.config.variables || {};
|
|
75790
75825
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
@@ -75792,8 +75827,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
75792
75827
|
var abi = versions.modules;
|
|
75793
75828
|
if (versions.deno || process.isBun) abi = "unsupported";
|
|
75794
75829
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
75795
|
-
var arch = process.env.npm_config_arch || os$
|
|
75796
|
-
var platform = process.env.npm_config_platform || os$
|
|
75830
|
+
var arch = process.env.npm_config_arch || os$3.arch();
|
|
75831
|
+
var platform = process.env.npm_config_platform || os$3.platform();
|
|
75797
75832
|
var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
|
|
75798
75833
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
75799
75834
|
var uv = (versions.uv || "").split(".")[0];
|
|
@@ -93481,7 +93516,7 @@ const launchDetached = (launch) => gen(function* () {
|
|
|
93481
93516
|
});
|
|
93482
93517
|
const OpenLive = effect(Open, gen(function* () {
|
|
93483
93518
|
const open = yield* tryPromise({
|
|
93484
|
-
try: () => import("./open-
|
|
93519
|
+
try: () => import("./open-FIgTDosf.mjs"),
|
|
93485
93520
|
catch: (cause) => new OpenError$1({
|
|
93486
93521
|
message: "failed to load browser opener",
|
|
93487
93522
|
cause
|
|
@@ -190957,7 +190992,7 @@ var Vce = {
|
|
|
190957
190992
|
function Ve() {
|
|
190958
190993
|
return Vce;
|
|
190959
190994
|
}
|
|
190960
|
-
var Us =
|
|
190995
|
+
var Us = os.homedir(), lk = os.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
|
|
190961
190996
|
let t = path.join(Us, "Library");
|
|
190962
190997
|
return {
|
|
190963
190998
|
data: path.join(t, "Application Support", e),
|
|
@@ -201157,12 +201192,12 @@ function qZ(e, t) {
|
|
|
201157
201192
|
}
|
|
201158
201193
|
return n;
|
|
201159
201194
|
}
|
|
201160
|
-
var os$
|
|
201195
|
+
var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
201161
201196
|
{
|
|
201162
201197
|
matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
|
|
201163
201198
|
tip: {
|
|
201164
201199
|
suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
|
|
201165
|
-
docLink: `${os$
|
|
201200
|
+
docLink: `${os$2}/iam#permission-modes`
|
|
201166
201201
|
}
|
|
201167
201202
|
},
|
|
201168
201203
|
{
|
|
@@ -201177,7 +201212,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201177
201212
|
matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
|
|
201178
201213
|
tip: {
|
|
201179
201214
|
suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
|
|
201180
|
-
docLink: `${os$
|
|
201215
|
+
docLink: `${os$2}/settings#environment-variables`
|
|
201181
201216
|
}
|
|
201182
201217
|
},
|
|
201183
201218
|
{
|
|
@@ -201188,14 +201223,14 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201188
201223
|
matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
|
|
201189
201224
|
tip: {
|
|
201190
201225
|
suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
|
|
201191
|
-
docLink: `${os$
|
|
201226
|
+
docLink: `${os$2}/hooks`
|
|
201192
201227
|
}
|
|
201193
201228
|
},
|
|
201194
201229
|
{
|
|
201195
201230
|
matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
|
|
201196
201231
|
tip: {
|
|
201197
201232
|
suggestion: "Command hooks require `command`. For exec form (no shell), set `command` to the executable and `args` to its arguments: {\"type\": \"command\", \"command\": \"echo\", \"args\": [\"hi\"]}. For shell form, set `command` to the full shell string: {\"type\": \"command\", \"command\": \"echo hi\"}.",
|
|
201198
|
-
docLink: `${os$
|
|
201233
|
+
docLink: `${os$2}/hooks#exec-form-and-shell-form`
|
|
201199
201234
|
}
|
|
201200
201235
|
},
|
|
201201
201236
|
{
|
|
@@ -201210,7 +201245,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201210
201245
|
matches: (e) => e.code === "unrecognized_keys",
|
|
201211
201246
|
tip: {
|
|
201212
201247
|
suggestion: "Check for typos or refer to the documentation for valid fields",
|
|
201213
|
-
docLink: `${os$
|
|
201248
|
+
docLink: `${os$2}/settings`
|
|
201214
201249
|
}
|
|
201215
201250
|
},
|
|
201216
201251
|
{
|
|
@@ -201225,13 +201260,13 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201225
201260
|
matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
|
|
201226
201261
|
tip: {
|
|
201227
201262
|
suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
|
|
201228
|
-
docLink: `${os$
|
|
201263
|
+
docLink: `${os$2}/iam#working-directories`
|
|
201229
201264
|
}
|
|
201230
201265
|
}
|
|
201231
201266
|
], AKe = {
|
|
201232
|
-
permissions: `${os$
|
|
201233
|
-
env: `${os$
|
|
201234
|
-
hooks: `${os$
|
|
201267
|
+
permissions: `${os$2}/iam#configuring-permissions`,
|
|
201268
|
+
env: `${os$2}/settings#environment-variables`,
|
|
201269
|
+
hooks: `${os$2}/hooks`
|
|
201235
201270
|
};
|
|
201236
201271
|
function GZ(e) {
|
|
201237
201272
|
let t = kKe.find((r) => r.matches(e));
|
|
@@ -268541,7 +268576,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
268541
268576
|
var dgram = __require("dgram");
|
|
268542
268577
|
var thunky = require_thunky();
|
|
268543
268578
|
var events = __require("events");
|
|
268544
|
-
var os = __require("os");
|
|
268579
|
+
var os$1 = __require("os");
|
|
268545
268580
|
var noop = function() {};
|
|
268546
268581
|
module.exports = function(opts) {
|
|
268547
268582
|
if (!opts) opts = {};
|
|
@@ -268672,14 +268707,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
268672
268707
|
return that;
|
|
268673
268708
|
};
|
|
268674
268709
|
function defaultInterface() {
|
|
268675
|
-
var networks = os.networkInterfaces();
|
|
268710
|
+
var networks = os$1.networkInterfaces();
|
|
268676
268711
|
var names = Object.keys(networks);
|
|
268677
268712
|
for (var i = 0; i < names.length; i++) {
|
|
268678
268713
|
var net = networks[names[i]];
|
|
268679
268714
|
for (var j = 0; j < net.length; j++) {
|
|
268680
268715
|
var iface = net[j];
|
|
268681
268716
|
if (isIPv4(iface.family) && !iface.internal) {
|
|
268682
|
-
if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
|
|
268717
|
+
if (os$1.platform() === "darwin" && names[i] === "en0") return iface.address;
|
|
268683
268718
|
return "0.0.0.0";
|
|
268684
268719
|
}
|
|
268685
268720
|
}
|
|
@@ -268687,7 +268722,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
268687
268722
|
return "127.0.0.1";
|
|
268688
268723
|
}
|
|
268689
268724
|
function allInterfaces() {
|
|
268690
|
-
var networks = os.networkInterfaces();
|
|
268725
|
+
var networks = os$1.networkInterfaces();
|
|
268691
268726
|
var names = Object.keys(networks);
|
|
268692
268727
|
var res = [];
|
|
268693
268728
|
for (var i = 0; i < names.length; i++) {
|
|
@@ -297643,7 +297678,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
297643
297678
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
297644
297679
|
*/
|
|
297645
297680
|
function readBakedBuildCommit() {
|
|
297646
|
-
return "
|
|
297681
|
+
return "c9bc6514eb0d5f3c0eff3b12d396a11f7a96abef";
|
|
297647
297682
|
}
|
|
297648
297683
|
async function resolveServerBuildCommit(input) {
|
|
297649
297684
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -297771,9 +297806,10 @@ function executionEnvironmentCapabilities(serverConfig) {
|
|
|
297771
297806
|
auth: {
|
|
297772
297807
|
cmmd: true,
|
|
297773
297808
|
ownerBootstrapPromotion: !isRuntime && !isCloudShell,
|
|
297774
|
-
bearerBootstrap:
|
|
297809
|
+
bearerBootstrap: !isCloudShell,
|
|
297775
297810
|
desktopBootstrap: serverConfig.mode === "desktop" || serverConfig.desktopBootstrapToken !== void 0,
|
|
297776
|
-
providerCredentialBroker: false
|
|
297811
|
+
providerCredentialBroker: false,
|
|
297812
|
+
cloudOwnerAssertion: isCloudShell
|
|
297777
297813
|
},
|
|
297778
297814
|
tools: {
|
|
297779
297815
|
agents: isExecutionPlane,
|
|
@@ -297863,6 +297899,145 @@ const ServerEnvironmentLive = effect(ServerEnvironment, fn$1("makeServerEnvironm
|
|
|
297863
297899
|
};
|
|
297864
297900
|
})());
|
|
297865
297901
|
//#endregion
|
|
297902
|
+
//#region src/auth/http/cloudOwnerDeviceSession.ts
|
|
297903
|
+
/**
|
|
297904
|
+
* Native OAuth clients allowed to redeem a Forge Cloud session grant through
|
|
297905
|
+
* this bearer lane. Starts with only the mobile client from FOR-36841's
|
|
297906
|
+
* companion CMMD change; widen deliberately, not by accident.
|
|
297907
|
+
*/
|
|
297908
|
+
const CLOUD_NATIVE_OAUTH_CLIENT_IDS = new Set(["cmmd-forge-mobile"]);
|
|
297909
|
+
const CloudOwnerSessionRedeemInput = Struct({
|
|
297910
|
+
sessionGrant: String$1,
|
|
297911
|
+
clientId: String$1
|
|
297912
|
+
});
|
|
297913
|
+
function outsideCloudShell() {
|
|
297914
|
+
return fail(new AuthError({
|
|
297915
|
+
message: "Forge Cloud session redemption is unavailable.",
|
|
297916
|
+
status: 403
|
|
297917
|
+
}));
|
|
297918
|
+
}
|
|
297919
|
+
function redeemSessionGrantWithCmmd(input) {
|
|
297920
|
+
return tryPromise({
|
|
297921
|
+
try: () => fetch(resolveCommandAppEndpoint("/api/mcp-server/oauth/token"), {
|
|
297922
|
+
method: "POST",
|
|
297923
|
+
headers: { "content-type": "application/json" },
|
|
297924
|
+
body: JSON.stringify({
|
|
297925
|
+
grant_type: "forge_session_grant",
|
|
297926
|
+
grant_token: input.sessionGrant,
|
|
297927
|
+
client_id: input.clientId
|
|
297928
|
+
})
|
|
297929
|
+
}),
|
|
297930
|
+
catch: (cause) => new AuthError({
|
|
297931
|
+
message: "Failed to redeem the Forge Cloud session grant with CMMD.",
|
|
297932
|
+
status: 500,
|
|
297933
|
+
cause
|
|
297934
|
+
})
|
|
297935
|
+
}).pipe(flatMap((response) => readCmmdJsonResponse(response, "Failed to redeem the Forge Cloud session grant with CMMD.")));
|
|
297936
|
+
}
|
|
297937
|
+
const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session", gen(function* () {
|
|
297938
|
+
const serverAuth = yield* ServerAuth;
|
|
297939
|
+
if (serverAuth.isForgeCloudShell?.() !== true) return yield* outsideCloudShell();
|
|
297940
|
+
const request = yield* HttpServerRequest;
|
|
297941
|
+
const limited = rateLimitOrRespond({
|
|
297942
|
+
routeClass: "auth-bootstrap",
|
|
297943
|
+
clientKey: clientKeyFromRequest(request)
|
|
297944
|
+
});
|
|
297945
|
+
if (limited) return limited;
|
|
297946
|
+
yield* serverAuth.authenticateHttpRequest(request);
|
|
297947
|
+
const payload = yield* schemaBodyJson(CloudOwnerSessionRedeemInput).pipe(mapError((cause) => new AuthError({
|
|
297948
|
+
message: "Invalid Forge Cloud session redeem payload.",
|
|
297949
|
+
status: 400,
|
|
297950
|
+
cause
|
|
297951
|
+
})));
|
|
297952
|
+
if (!CLOUD_NATIVE_OAUTH_CLIENT_IDS.has(payload.clientId)) return yield* new AuthError({
|
|
297953
|
+
message: "Unknown Forge Cloud native client.",
|
|
297954
|
+
status: 403
|
|
297955
|
+
});
|
|
297956
|
+
const tokenResult = yield* redeemSessionGrantWithCmmd(payload);
|
|
297957
|
+
const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, refreshToken, forgeOwnerAssertion, sessionGrantExpiresAt, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
|
|
297958
|
+
if (!cmmdIdentity || !userId || !patToStore) {
|
|
297959
|
+
yield* logError("Forge Cloud session redeem: CMMD token response incomplete.", {
|
|
297960
|
+
clientId: payload.clientId,
|
|
297961
|
+
tokenResponse: describeCmmdTokenResponseForLog({
|
|
297962
|
+
tokenObj,
|
|
297963
|
+
tokenResult,
|
|
297964
|
+
accessToken,
|
|
297965
|
+
mcpToken,
|
|
297966
|
+
refreshToken,
|
|
297967
|
+
isJwt,
|
|
297968
|
+
userId,
|
|
297969
|
+
patToStore,
|
|
297970
|
+
jwtToStore
|
|
297971
|
+
})
|
|
297972
|
+
});
|
|
297973
|
+
return yield* new AuthError({
|
|
297974
|
+
message: "CMMD did not return a complete redeemed Forge Cloud session.",
|
|
297975
|
+
status: 500
|
|
297976
|
+
});
|
|
297977
|
+
}
|
|
297978
|
+
const assertion = yield* validateCloudOwnerCallbackAssertion({
|
|
297979
|
+
serverAuth,
|
|
297980
|
+
assertionToken: forgeOwnerAssertion,
|
|
297981
|
+
authenticatedUserId: userId
|
|
297982
|
+
});
|
|
297983
|
+
if (!assertion) return yield* die$1(/* @__PURE__ */ new Error("Expected a redeemed Forge Cloud owner assertion for a native client."));
|
|
297984
|
+
yield* persistCloudOwnerArtifacts({
|
|
297985
|
+
assertion,
|
|
297986
|
+
accessToken: patToStore,
|
|
297987
|
+
identity: cmmdIdentity,
|
|
297988
|
+
jwtToken: jwtToStore,
|
|
297989
|
+
refreshToken,
|
|
297990
|
+
sessionGrant: payload.sessionGrant,
|
|
297991
|
+
sessionGrantExpiresAt
|
|
297992
|
+
});
|
|
297993
|
+
yield* logInfo("Forge Cloud owner session redeemed.", {
|
|
297994
|
+
sessionId: assertion.sessionId,
|
|
297995
|
+
clientId: payload.clientId,
|
|
297996
|
+
userId
|
|
297997
|
+
});
|
|
297998
|
+
return jsonUnsafe({
|
|
297999
|
+
assertion: assertion.token,
|
|
298000
|
+
assertionExpiresAt: toUtc(assertion.expiresAt),
|
|
298001
|
+
sessionGrantExpiresAt,
|
|
298002
|
+
user: {
|
|
298003
|
+
id: assertion.userId,
|
|
298004
|
+
email: cmmdIdentity.email
|
|
298005
|
+
}
|
|
298006
|
+
}, { status: 200 });
|
|
298007
|
+
}).pipe(catchTag("AuthError", (error) => respondToAuthError(error))));
|
|
298008
|
+
/**
|
|
298009
|
+
* Seam for CMMD-side session-grant revocation.
|
|
298010
|
+
*
|
|
298011
|
+
* As of FOR-36841, CMMD does not expose an endpoint to revoke a
|
|
298012
|
+
* `forge_session_grant` token server-side (confirm with the CMMD change
|
|
298013
|
+
* before wiring this in). When it does, call it here, best-effort: a failure
|
|
298014
|
+
* revoking at CMMD must never block the local row deletion below, which is
|
|
298015
|
+
* this endpoint's actual guarantee. Left as a documented no-op rather than a
|
|
298016
|
+
* guess at CMMD's eventual route shape.
|
|
298017
|
+
*/
|
|
298018
|
+
function revokeCmmdSessionGrantBestEffort(_sessionId) {
|
|
298019
|
+
return void_$1;
|
|
298020
|
+
}
|
|
298021
|
+
const cloudOwnerSessionRevokeRouteLayer = add$1("POST", "/api/auth/cloud/session/revoke", gen(function* () {
|
|
298022
|
+
const serverAuth = yield* ServerAuth;
|
|
298023
|
+
if (serverAuth.isForgeCloudShell?.() !== true) return yield* outsideCloudShell();
|
|
298024
|
+
const request = yield* HttpServerRequest;
|
|
298025
|
+
const limited = rateLimitOrRespond({
|
|
298026
|
+
routeClass: "auth-bootstrap",
|
|
298027
|
+
clientKey: clientKeyFromRequest(request)
|
|
298028
|
+
});
|
|
298029
|
+
if (limited) return limited;
|
|
298030
|
+
const session = yield* serverAuth.authenticateHttpRequest(request);
|
|
298031
|
+
yield* clearCommandAccessTokenForSession(session.sessionId).pipe(mapError((cause) => new AuthError({
|
|
298032
|
+
message: "Failed to revoke the Forge Cloud owner session.",
|
|
298033
|
+
status: 500,
|
|
298034
|
+
cause
|
|
298035
|
+
})));
|
|
298036
|
+
yield* revokeCmmdSessionGrantBestEffort(session.sessionId);
|
|
298037
|
+
yield* logInfo("Forge Cloud owner session revoked.", { sessionId: session.sessionId });
|
|
298038
|
+
return empty$6({ status: 204 });
|
|
298039
|
+
}).pipe(catchTag("AuthError", (error) => respondToAuthError(error))));
|
|
298040
|
+
//#endregion
|
|
297866
298041
|
//#region src/docs/http.ts
|
|
297867
298042
|
const SCALAR_API_REFERENCE_URL = "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.64.1";
|
|
297868
298043
|
const SCALAR_API_REFERENCE_INTEGRITY = "sha384-SmFRDuBBmEoCbbBCTcn8/+cIQONyH0xgOKpsp6OttBlI8uG9uc6iAO8SonYQ3e1W";
|
|
@@ -299983,6 +300158,24 @@ function readWebSocketUpgradeToken(params) {
|
|
|
299983
300158
|
}
|
|
299984
300159
|
//#endregion
|
|
299985
300160
|
//#region src/auth/Layers/ForgeCloudOwnerAuthentication.ts
|
|
300161
|
+
const BEARER_PREFIX = "Bearer ";
|
|
300162
|
+
/**
|
|
300163
|
+
* Reads a Forge Cloud owner assertion presented as `Authorization: Bearer
|
|
300164
|
+
* <assertion>`, alongside the cookie read below.
|
|
300165
|
+
*
|
|
300166
|
+
* Deliberately its own parser rather than a shared one: `ServerAuth.ts`
|
|
300167
|
+
* carries a bearer reader for the local bootstrap/pairing/desktop lanes, and
|
|
300168
|
+
* those must stay untouched by this change. This function is only ever
|
|
300169
|
+
* called from `authenticateForgeCloudOwnerRequest`, which itself only runs
|
|
300170
|
+
* when `serverConfig.forgeCloudShellMode === true` — so a non-cloud server's
|
|
300171
|
+
* bearer lanes never pass through here.
|
|
300172
|
+
*/
|
|
300173
|
+
function readForgeCloudOwnerBearerToken(request) {
|
|
300174
|
+
const header = request.headers["authorization"];
|
|
300175
|
+
if (typeof header !== "string" || !header.startsWith(BEARER_PREFIX)) return null;
|
|
300176
|
+
const token = header.slice(7).trim();
|
|
300177
|
+
return token.length > 0 ? token : null;
|
|
300178
|
+
}
|
|
299986
300179
|
function assertionVerifierConfig(config) {
|
|
299987
300180
|
const publicKey = config.forgeCloudOwnerAssertionPublicKey;
|
|
299988
300181
|
const keyId = config.forgeCloudOwnerAssertionKeyId;
|
|
@@ -300010,7 +300203,7 @@ function authenticateForgeCloudOwnerRequest(request, config) {
|
|
|
300010
300203
|
message: "Forge Cloud owner assertion verification is misconfigured.",
|
|
300011
300204
|
status: 500
|
|
300012
300205
|
});
|
|
300013
|
-
const token = request.cookies[
|
|
300206
|
+
const token = request.cookies["forge_cloud_owner_assertion"] ?? readForgeCloudOwnerBearerToken(request);
|
|
300014
300207
|
if (!token) return yield* new AuthError({
|
|
300015
300208
|
message: "Authentication required.",
|
|
300016
300209
|
status: 401
|
|
@@ -321069,7 +321262,7 @@ const MiscRuntimeLayerLive = mergeAll(AnalyticsServiceLayerLive, OpenLive, Serve
|
|
|
321069
321262
|
const RuntimeDependenciesWithoutPullRequestsLive = ReactorLayerLive.pipe(provideMerge(CheckpointingLayerLive), provideMerge(GitLayerLive), provideMerge(OrchestrationLayerLive), provideMerge(ProviderLayerLive), provideMerge(TerminalLayerLive), provideMerge(AgentBrowserManagerLive), provideMerge(mergeAll(ThreadDevRuntimeLive, DevTunnelLive.pipe(provide$1(ThreadDevRuntimeLive)))), provideMerge(PersistenceLayerLive), provideMerge(KeybindingsLive), provideMerge(ProviderRegistryLive), provideMerge(mergeAll(ServerSettingsLive, UsageServiceLive.pipe(provide$1(ServerSettingsLive), provide$1(UsageSessionLookupLive), provide$1(UsageRecordedSourceLive.pipe(provide$1(OrchestrationLayerLive), provide$1(RuntimeReplicationOutboxLive), provide$1(ProviderSessionRuntimeRepositoryLive), provide$1(TextGenerationSessionRepositoryLive)))))), provideMerge(WorkspaceLayerLive), provideMerge(WorkspaceReadinessStateLive), provideMerge(ProjectFaviconResolverLive), provideMerge(RepositoryIdentityResolverLive), provideMerge(ServerEnvironmentLive), provideMerge(AuthLayerLive), provideMerge(ServerSecretStoreLayerLive), provideMerge(mergeAll(CloudRedisCoordinationLive, CloudStateSyncLive.pipe(provide$1(PersistenceLayerLive)))), provideMerge(MiscRuntimeLayerLive));
|
|
321070
321263
|
const RuntimeDependenciesLive = pullRequestServiceFactoryLayer.pipe(provideMerge(RuntimeDependenciesWithoutPullRequestsLive), provideMerge(cmmdProjectIdentityResolverLive.pipe(provide$1(RuntimeDependenciesWithoutPullRequestsLive))), provideMerge(pullRequestProviderRuntimeLive));
|
|
321071
321264
|
const RuntimeServicesLive = ServerRuntimeStartupLive.pipe(provideMerge(RuntimeDependenciesLive));
|
|
321072
|
-
const makeRoutesLayer = mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRenewRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdForgeRuntimesStatusRouteLayer, cmmdForgeRuntimesStartRouteLayer, cmmdForgeRuntimesStopRouteLayer, cmmdForgeRuntimesRecycleRouteLayer, cmmdForgeRuntimesUpdateCommitRouteLayer, cmmdForgeRuntimesDestroyRouteLayer, mobileProviderCatalogRouteLayer, attachmentsRouteLayer, orchestrationDispatchRouteLayer, orchestrationCmmdWorkRunEventRouteLayer, orchestrationAutomationStartThreadRouteLayer, orchestrationAutomationRebindRouteLayer, orchestrationAutomationTerminalReconcileRouteLayer, mobileGitRunStackedActionRouteLayer, pullRequestDiffRouteLayer, orchestrationSnapshotRouteLayer, orchestrationThreadSnapshotRouteLayer, localAutomationListRouteLayer, localAutomationCreateRouteLayer, localAutomationUpdateRouteLayer, localAutomationDeleteRouteLayer, localAutomationRunRouteLayer, otlpTracesProxyRouteLayer, devTunnelProxyRouteLayer, devTunnelLocalPortProxyRouteLayer, projectFaviconRouteLayer, serverEnvironmentRouteLayer, browserWebRtcReadinessRouteLayer, staticAndDevRouteLayer, githubStatusRouteLayer, githubAuthorizeUrlRouteLayer, githubDisconnectRouteLayer, githubReposRouteLayer, githubCloneRouteLayer, githubCloneJobStartRouteLayer, githubCloneJobStatusRouteLayer, githubRefreshCredentialsRouteLayer, githubToolConfirmationApproveRouteLayer, projectLinkGetRouteLayer, projectLinkListRouteLayer, projectLinkSetRouteLayer, projectLinkDeleteRouteLayer, cmmdWorkspacesRouteLayer, cmmdCampaignsRouteLayer, cmmdProjectLinksRouteLayer, cmmdEnsureGitHubProjectContextRouteLayer, cmmdProjectRepositoryEnsureRouteLayer, cmmdProjectsGetRouteLayer, cmmdProjectsCreateRouteLayer, workspaceReadinessRouteLayer, runtimeLivenessRouteLayer, runtimeFilesystemHealthRouteLayer, claudeNativeGenerationProofRouteLayer, claudeUsageRouteLayer, codexRuntimeProviderReadinessRouteLayer, runtimeDiagnosticsRouteLayer, cloudFoldersRouteLayer, cloudProjectCreateRouteLayer, gitUrlCloneRouteLayer, websocketRpcRouteLayer).pipe(provide$1(browserApiCorsLayer));
|
|
321265
|
+
const makeRoutesLayer = mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRenewRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, cloudOwnerSessionRedeemRouteLayer, cloudOwnerSessionRevokeRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdForgeRuntimesStatusRouteLayer, cmmdForgeRuntimesStartRouteLayer, cmmdForgeRuntimesStopRouteLayer, cmmdForgeRuntimesRecycleRouteLayer, cmmdForgeRuntimesUpdateCommitRouteLayer, cmmdForgeRuntimesDestroyRouteLayer, mobileProviderCatalogRouteLayer, attachmentsRouteLayer, orchestrationDispatchRouteLayer, orchestrationCmmdWorkRunEventRouteLayer, orchestrationAutomationStartThreadRouteLayer, orchestrationAutomationRebindRouteLayer, orchestrationAutomationTerminalReconcileRouteLayer, mobileGitRunStackedActionRouteLayer, pullRequestDiffRouteLayer, orchestrationSnapshotRouteLayer, orchestrationThreadSnapshotRouteLayer, localAutomationListRouteLayer, localAutomationCreateRouteLayer, localAutomationUpdateRouteLayer, localAutomationDeleteRouteLayer, localAutomationRunRouteLayer, otlpTracesProxyRouteLayer, devTunnelProxyRouteLayer, devTunnelLocalPortProxyRouteLayer, projectFaviconRouteLayer, serverEnvironmentRouteLayer, browserWebRtcReadinessRouteLayer, staticAndDevRouteLayer, githubStatusRouteLayer, githubAuthorizeUrlRouteLayer, githubDisconnectRouteLayer, githubReposRouteLayer, githubCloneRouteLayer, githubCloneJobStartRouteLayer, githubCloneJobStatusRouteLayer, githubRefreshCredentialsRouteLayer, githubToolConfirmationApproveRouteLayer, projectLinkGetRouteLayer, projectLinkListRouteLayer, projectLinkSetRouteLayer, projectLinkDeleteRouteLayer, cmmdWorkspacesRouteLayer, cmmdCampaignsRouteLayer, cmmdProjectLinksRouteLayer, cmmdEnsureGitHubProjectContextRouteLayer, cmmdProjectRepositoryEnsureRouteLayer, cmmdProjectsGetRouteLayer, cmmdProjectsCreateRouteLayer, workspaceReadinessRouteLayer, runtimeLivenessRouteLayer, runtimeFilesystemHealthRouteLayer, claudeNativeGenerationProofRouteLayer, claudeUsageRouteLayer, codexRuntimeProviderReadinessRouteLayer, runtimeDiagnosticsRouteLayer, cloudFoldersRouteLayer, cloudProjectCreateRouteLayer, gitUrlCloneRouteLayer, websocketRpcRouteLayer).pipe(provide$1(browserApiCorsLayer));
|
|
321073
321266
|
const runServer = launch$1(unwrap(gen(function* () {
|
|
321074
321267
|
const config = yield* ServerConfig;
|
|
321075
321268
|
fixPath({ preferProcessPath: config.forgeCmmdRuntimeMode === true });
|
|
@@ -321999,7 +322192,7 @@ const connectLoginCommand = make$34("login", {
|
|
|
321999
322192
|
})).json();
|
|
322000
322193
|
},
|
|
322001
322194
|
openBrowser: async (url) => {
|
|
322002
|
-
const open = (await import("./open-
|
|
322195
|
+
const open = (await import("./open-FIgTDosf.mjs")).default;
|
|
322003
322196
|
await open(url);
|
|
322004
322197
|
return true;
|
|
322005
322198
|
},
|