jules-orchestrator-kit 0.52.1 → 0.52.3
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/README.md +1 -1
- package/package.json +1 -1
- package/src/coverage.mjs +10 -18
- package/src/git.mjs +46 -0
- package/src/mutation.mjs +12 -18
- package/src/perf.mjs +10 -14
- package/src/provider.mjs +5 -3
- package/src/remediation.mjs +1 -1
- package/src/session-ops.mjs +18 -10
- package/src/stack-detector.mjs +57 -4
- package/src/webhook.mjs +9 -7
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
143
143
|
* **Fail-Closed Security & Secret Redaction:** Evaluates explicit Deny rules before Allow rules against canonicalized, case-folded paths. Redacts high-entropy keys and base64-encoded credentials (such as Kubernetes `Secret` manifests).
|
|
144
144
|
* **Complexity & Cost Router:** Zero-dependency heuristic classifier (`src/router.mjs`) routing mechanical tasks to lightweight models while reserving primary models for complex refactors, with a `node --check` syntax-verification gate that transparently escalates a FAST-tier result to the primary provider if it left broken JS on disk.
|
|
145
145
|
* **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
|
|
146
|
-
* **Verified Test Suite:** Tested with **
|
|
146
|
+
* **Verified Test Suite:** Tested with **809 unit tests across 91 suites passing in < 10.0s**.
|
|
147
147
|
|
|
148
148
|
<br/>
|
|
149
149
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.3",
|
|
4
4
|
"description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for Google Jules (jules) autonomous agents.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/coverage.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync, readdirSync, mkdtempSync, rmSync, existsSync, realpathSyn
|
|
|
2
2
|
import { join, resolve, relative, isAbsolute } from "node:path";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
5
|
+
import { runCmd } from "./git.mjs";
|
|
6
6
|
import { resolveVerify } from "./config.mjs";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -136,23 +136,15 @@ export function runV8Coverage(testCmd, options = {}) {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
149
|
-
encoding: "utf-8",
|
|
150
|
-
});
|
|
151
|
-
} catch (err) {
|
|
152
|
-
exitCode = err.status || 1;
|
|
153
|
-
stdout = err.stdout ? String(err.stdout) : "";
|
|
154
|
-
stderr = err.stderr ? String(err.stderr) : err.message;
|
|
155
|
-
}
|
|
139
|
+
const res = runCmd(cmd, {
|
|
140
|
+
cwd: root,
|
|
141
|
+
env,
|
|
142
|
+
timeout: timeoutMs,
|
|
143
|
+
ignoreError: true,
|
|
144
|
+
});
|
|
145
|
+
const exitCode = res.status;
|
|
146
|
+
const stdout = res.stdout;
|
|
147
|
+
const stderr = res.stderr;
|
|
156
148
|
|
|
157
149
|
const coverageByFile = new Map();
|
|
158
150
|
const files = existsSync(tempCoverageDir) ? readdirSync(tempCoverageDir) : [];
|
package/src/git.mjs
CHANGED
|
@@ -412,3 +412,49 @@ export function worktreeRemove(root = process.cwd(), targetDir = "") {
|
|
|
412
412
|
export function worktreePrune(root = process.cwd()) {
|
|
413
413
|
return git(["worktree", "prune"], { cwd: root });
|
|
414
414
|
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Extract 'owner/repo' from a git remote URL if it points to GitHub.
|
|
418
|
+
* Handles SSH (git@github.com:owner/repo.git), HTTPS (https://github.com/owner/repo.git),
|
|
419
|
+
* protocol prefixes (ssh://, git://) and URL-embedded credentials.
|
|
420
|
+
* @param {string} remoteUrl
|
|
421
|
+
* @returns {string} 'owner/repo' or '' if invalid/not GitHub
|
|
422
|
+
*/
|
|
423
|
+
export function parseGitHubRepo(remoteUrl) {
|
|
424
|
+
if (!remoteUrl || typeof remoteUrl !== "string") return "";
|
|
425
|
+
const trimmed = remoteUrl.trim();
|
|
426
|
+
|
|
427
|
+
// Match git@github.com:owner/repo(.git)
|
|
428
|
+
const sshMatch = trimmed.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i);
|
|
429
|
+
if (sshMatch) {
|
|
430
|
+
return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Match https://github.com/owner/repo(.git), http, ssh://, git://
|
|
434
|
+
try {
|
|
435
|
+
const urlObj = new URL(trimmed.startsWith("git@") ? `ssh://${trimmed}` : trimmed);
|
|
436
|
+
if (urlObj.hostname.toLowerCase() === "github.com") {
|
|
437
|
+
const parts = urlObj.pathname.replace(/^\/+/, "").replace(/\.git$/i, "").split("/");
|
|
438
|
+
if (parts.length >= 2 && parts[0] && parts[1]) {
|
|
439
|
+
return `${parts[0]}/${parts[1]}`;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} catch (_) {}
|
|
443
|
+
|
|
444
|
+
return "";
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Attempt to resolve 'owner/repo' from the local repository's git remote origin.
|
|
449
|
+
* @param {string} [root=process.cwd()]
|
|
450
|
+
* @returns {string}
|
|
451
|
+
*/
|
|
452
|
+
export function resolveGitRemoteOrigin(root = process.cwd()) {
|
|
453
|
+
try {
|
|
454
|
+
const raw = git(["config", "--get", "remote.origin.url"], { cwd: root, ignoreError: true });
|
|
455
|
+
return parseGitHubRepo(raw);
|
|
456
|
+
} catch (_) {
|
|
457
|
+
return "";
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
package/src/mutation.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { resolve, isAbsolute } from "node:path";
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
4
3
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { diffText } from "./git.mjs";
|
|
4
|
+
import { diffText, runCmd } from "./git.mjs";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* @typedef {Object} MutationCandidate
|
|
@@ -574,22 +573,17 @@ export function executeMutant(mutant, options = {}) {
|
|
|
574
573
|
stdout = execResult.stdout || "";
|
|
575
574
|
stderr = execResult.stderr || "";
|
|
576
575
|
} else {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
stdout = execErr.stdout ? execErr.stdout.toString() : "";
|
|
589
|
-
stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
590
|
-
if (execErr.code === "ETIMEDOUT" || execErr.killed) {
|
|
591
|
-
status = "TIMEOUT";
|
|
592
|
-
}
|
|
576
|
+
const execResult = runCmd(testCmd, {
|
|
577
|
+
cwd: root,
|
|
578
|
+
timeout: timeoutMs,
|
|
579
|
+
env: { ...process.env, CI: "true", JULES_MUTATION_RUN: "true" },
|
|
580
|
+
ignoreError: true,
|
|
581
|
+
});
|
|
582
|
+
exitCode = execResult.status;
|
|
583
|
+
stdout = execResult.stdout;
|
|
584
|
+
stderr = execResult.stderr;
|
|
585
|
+
if (execResult.status === 124 || stderr.includes("ETIMEDOUT")) {
|
|
586
|
+
status = "TIMEOUT";
|
|
593
587
|
}
|
|
594
588
|
}
|
|
595
589
|
|
package/src/perf.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
2
|
-
import {
|
|
2
|
+
import { runCmd } from "./git.mjs";
|
|
3
3
|
import { resolveVerify } from "./config.mjs";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -41,19 +41,15 @@ export function measureEventLoopDelay(target, options = {}) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
exitCode = err.status || 1;
|
|
54
|
-
stdout = err.stdout ? String(err.stdout) : "";
|
|
55
|
-
stderr = err.stderr ? String(err.stderr) : err.message;
|
|
56
|
-
}
|
|
44
|
+
const execResult = runCmd(cmd, {
|
|
45
|
+
cwd: root,
|
|
46
|
+
env,
|
|
47
|
+
timeout: timeoutMs,
|
|
48
|
+
ignoreError: true,
|
|
49
|
+
});
|
|
50
|
+
exitCode = execResult.status;
|
|
51
|
+
stdout = execResult.stdout;
|
|
52
|
+
stderr = execResult.stderr;
|
|
57
53
|
}
|
|
58
54
|
} finally {
|
|
59
55
|
histogram.disable();
|
package/src/provider.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { join, extname } from "node:path";
|
|
4
4
|
import { redactSecrets } from "./security.mjs";
|
|
5
|
-
import { resolveWindowsSpawn } from "./git.mjs";
|
|
5
|
+
import { resolveWindowsSpawn, resolveGitRemoteOrigin } from "./git.mjs";
|
|
6
6
|
|
|
7
7
|
// Extensions node --check can parse as a script. .mjs/.cjs are unambiguous;
|
|
8
8
|
// plain .js is checked as a script too — a stray top-level ESM import would
|
|
@@ -301,7 +301,9 @@ export function createProvider(spec = "jules", config = {}) {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
const isRepoless = Boolean(task.repoless || ctx.repoless);
|
|
304
|
-
const
|
|
304
|
+
const root = ctx.root || config.root || process.cwd();
|
|
305
|
+
const detectedRepo = resolveGitRemoteOrigin(root);
|
|
306
|
+
const rawRepo = task.source || ctx.source || config.source || process.env.JULES_REPO || detectedRepo || "";
|
|
305
307
|
if (!rawRepo && !ctx.dryRun && providerSpec.name === "jules" && !isRepoless) {
|
|
306
308
|
throw new Error("Missing connected Jules repository source. Set JULES_REPO or pass source/repoless option.");
|
|
307
309
|
}
|
|
@@ -951,7 +953,7 @@ export function createProvider(spec = "jules", config = {}) {
|
|
|
951
953
|
if (ctx.pageSize) params.set("pageSize", String(ctx.pageSize));
|
|
952
954
|
if (ctx.pageToken) params.set("pageToken", String(ctx.pageToken));
|
|
953
955
|
const url = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
|
954
|
-
const res = await this.getSession(
|
|
956
|
+
const res = await this.getSession(sessionId, { ...ctx, customUrl: url });
|
|
955
957
|
return {
|
|
956
958
|
activities: res?.raw?.activities || [],
|
|
957
959
|
nextPageToken: res?.raw?.nextPageToken,
|
package/src/remediation.mjs
CHANGED
|
@@ -285,7 +285,7 @@ export function createWhackAMoleDetector(opts = {}) {
|
|
|
285
285
|
const oscillatingTests = [...cycleTests];
|
|
286
286
|
|
|
287
287
|
const testSummary = oscillatingTests.length > 0 ? oscillatingTests.join(" <-> ") : "tests";
|
|
288
|
-
const promptDirective = `[WHACK_A_MOLE_WARNING] You are trapped in a local optimization cycle where fixing one test breaks another (
|
|
288
|
+
const promptDirective = `[WHACK_A_MOLE_WARNING] You are trapped in a local optimization cycle where fixing one test breaks another (<UNTRUSTED>${testSummary}</UNTRUSTED>). Do not add more conditional edge-case band-aids. Revert recent patches and refactor the core logic cleanly.`;
|
|
289
289
|
|
|
290
290
|
return {
|
|
291
291
|
whackAMole: true,
|
package/src/session-ops.mjs
CHANGED
|
@@ -68,17 +68,25 @@ export async function extractSessionPatch(sessionId, opts = {}) {
|
|
|
68
68
|
// 2. Inspect activity artifacts and logs if patch wasn't in top-level output
|
|
69
69
|
for (const act of activities) {
|
|
70
70
|
if (act.pullRequest) pr = act.pullRequest;
|
|
71
|
-
if (act.gitPatch || act.patch || act.diff) {
|
|
72
|
-
patch = patch || act.gitPatch || act.patch || act.diff;
|
|
71
|
+
if (act.gitPatch || act.patch || act.diff || act.changeSet?.gitPatch?.unidiffPatch) {
|
|
72
|
+
patch = patch || act.gitPatch || act.patch || act.diff || act.changeSet?.gitPatch?.unidiffPatch;
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
const artifactList = [
|
|
75
|
+
...(Array.isArray(act.artifacts) ? act.artifacts : []),
|
|
76
|
+
...(Array.isArray(act.artifactsCreated?.artifacts) ? act.artifactsCreated.artifacts : []),
|
|
77
|
+
];
|
|
78
|
+
for (const art of artifactList) {
|
|
79
|
+
const artPatch =
|
|
80
|
+
art.patch ||
|
|
81
|
+
art.diff ||
|
|
82
|
+
art.changeSet?.gitPatch?.unidiffPatch ||
|
|
83
|
+
art.gitPatch?.unidiffPatch ||
|
|
84
|
+
(typeof art.content === "string" && art.content.startsWith("diff --git") ? art.content : "");
|
|
85
|
+
if (artPatch) {
|
|
86
|
+
patch = patch || artPatch;
|
|
87
|
+
}
|
|
88
|
+
if (art.path || art.filename) {
|
|
89
|
+
files.add(art.path || art.filename);
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
92
|
if (act.changedFiles && Array.isArray(act.changedFiles)) {
|
package/src/stack-detector.mjs
CHANGED
|
@@ -663,8 +663,28 @@ export function bootstrapZeroTestRepo(root = process.cwd(), options = {}) {
|
|
|
663
663
|
const detected = detectPolyglotStack(root);
|
|
664
664
|
const configPath = join(root, ".agent", "config.yml");
|
|
665
665
|
const hasConfig = existsSync(configPath);
|
|
666
|
+
let existingTestCmd = "";
|
|
667
|
+
if (hasConfig) {
|
|
668
|
+
try {
|
|
669
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
670
|
+
const match = raw.match(/^\s*test:\s*["']?(.*?)["']?\s*$/m) || raw.match(/^\s*test_cmd:\s*["']?(.*?)["']?\s*$/m);
|
|
671
|
+
if (match) {
|
|
672
|
+
existingTestCmd = match[1].trim();
|
|
673
|
+
}
|
|
674
|
+
} catch (_) {}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let hasPkgTest = false;
|
|
678
|
+
if (existsSync(join(root, "package.json"))) {
|
|
679
|
+
try {
|
|
680
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
|
|
681
|
+
hasPkgTest = Boolean(pkg?.scripts?.test);
|
|
682
|
+
} catch (_) {}
|
|
683
|
+
}
|
|
684
|
+
|
|
666
685
|
const hasRealTestSuite =
|
|
667
|
-
hasConfig ||
|
|
686
|
+
(hasConfig && Boolean(existingTestCmd)) ||
|
|
687
|
+
hasPkgTest ||
|
|
668
688
|
existsSync(join(root, "phpunit.xml")) ||
|
|
669
689
|
existsSync(join(root, "pest.php")) ||
|
|
670
690
|
existsSync(join(root, "turbo.json")) ||
|
|
@@ -672,7 +692,7 @@ export function bootstrapZeroTestRepo(root = process.cwd(), options = {}) {
|
|
|
672
692
|
existsSync(join(root, "nx.json"));
|
|
673
693
|
|
|
674
694
|
if (!options.force && hasRealTestSuite) {
|
|
675
|
-
return { bootstrapped: false, reason: "EXISTING_VERIFICATION_ORACLE", testCmd: detected.testCmd };
|
|
695
|
+
return { bootstrapped: false, reason: "EXISTING_VERIFICATION_ORACLE", testCmd: existingTestCmd || (hasPkgTest ? detected.testCmd : "") };
|
|
676
696
|
}
|
|
677
697
|
|
|
678
698
|
let testCmd = "";
|
|
@@ -697,8 +717,41 @@ export function bootstrapZeroTestRepo(root = process.cwd(), options = {}) {
|
|
|
697
717
|
mkdirSync(join(root, ".agent"), { recursive: true });
|
|
698
718
|
} catch (_) {}
|
|
699
719
|
|
|
700
|
-
|
|
701
|
-
|
|
720
|
+
if (hasConfig) {
|
|
721
|
+
try {
|
|
722
|
+
let rawConfig = readFileSync(configPath, "utf-8");
|
|
723
|
+
if (/^\s*test:\s*.*$/m.test(rawConfig)) {
|
|
724
|
+
rawConfig = rawConfig.replace(/^\s*test:\s*.*$/m, ` test: "${testCmd}"`);
|
|
725
|
+
} else if (/^\s*verify:\s*$/m.test(rawConfig)) {
|
|
726
|
+
rawConfig = rawConfig.replace(/^\s*verify:\s*$/m, `verify:\n test: "${testCmd}"`);
|
|
727
|
+
} else {
|
|
728
|
+
rawConfig += `\nverify:\n test: "${testCmd}"\n`;
|
|
729
|
+
}
|
|
730
|
+
if (detected.buildCmd && /^\s*build:\s*["']?["']?\s*$/m.test(rawConfig)) {
|
|
731
|
+
rawConfig = rawConfig.replace(/^\s*build:\s*.*$/m, ` build: "${detected.buildCmd}"`);
|
|
732
|
+
}
|
|
733
|
+
writeFileSync(configPath, rawConfig, "utf-8");
|
|
734
|
+
} catch (_) {}
|
|
735
|
+
} else {
|
|
736
|
+
const cfg = `version: 1\nprovider: jules\ntier: free\nverify:\n test: "${testCmd}"\n build: "${detected.buildCmd || ""}"\nlimits:\n diff_kb: 75\n daily_tasks: 15\n repair_attempts: 3\nbranch_prefix: agent/\nbase_branch: main\n`;
|
|
737
|
+
writeFileSync(configPath, cfg, "utf-8");
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const julesPath = join(root, ".agent", "jules.yml");
|
|
741
|
+
if (existsSync(julesPath)) {
|
|
742
|
+
try {
|
|
743
|
+
let rawJules = readFileSync(julesPath, "utf-8");
|
|
744
|
+
if (/^\s*test_cmd:\s*.*$/m.test(rawJules)) {
|
|
745
|
+
rawJules = rawJules.replace(/^\s*test_cmd:\s*.*$/m, `test_cmd: "${testCmd}"`);
|
|
746
|
+
} else {
|
|
747
|
+
rawJules += `\ntest_cmd: "${testCmd}"\n`;
|
|
748
|
+
}
|
|
749
|
+
if (detected.buildCmd && /^\s*build_cmd:\s*["']?["']?\s*$/m.test(rawJules)) {
|
|
750
|
+
rawJules = rawJules.replace(/^\s*build_cmd:\s*.*$/m, `build_cmd: "${detected.buildCmd}"`);
|
|
751
|
+
}
|
|
752
|
+
writeFileSync(julesPath, rawJules, "utf-8");
|
|
753
|
+
} catch (_) {}
|
|
754
|
+
}
|
|
702
755
|
|
|
703
756
|
return { bootstrapped: true, stack: detected.stack, testCmd, configPath };
|
|
704
757
|
}
|
package/src/webhook.mjs
CHANGED
|
@@ -480,25 +480,27 @@ export function verifySignature(payload, signatureHeader, secret) {
|
|
|
480
480
|
if (!/^[0-9a-fA-F]{64}$/.test(hexSignature)) return false;
|
|
481
481
|
|
|
482
482
|
const hmac = createHmac("sha256", secret);
|
|
483
|
-
const
|
|
483
|
+
const encoder = new TextEncoder();
|
|
484
|
+
const bodyBuf = payload instanceof Uint8Array ? payload : encoder.encode(payload);
|
|
484
485
|
const digest = `sha256=${hmac.update(bodyBuf).digest("hex")}`;
|
|
485
486
|
|
|
486
|
-
const sigBuf =
|
|
487
|
-
const digestBuf =
|
|
487
|
+
const sigBuf = encoder.encode(signatureHeader);
|
|
488
|
+
const digestBuf = encoder.encode(digest);
|
|
488
489
|
|
|
489
|
-
if (sigBuf.
|
|
490
|
+
if (sigBuf.byteLength !== digestBuf.byteLength) return false;
|
|
490
491
|
return timingSafeEqual(sigBuf, digestBuf);
|
|
491
492
|
}
|
|
492
493
|
|
|
493
494
|
/**
|
|
494
495
|
* Parse incoming webhook body buffer into a JS object.
|
|
495
|
-
* @param {Buffer} bodyBuf
|
|
496
|
+
* @param {Buffer|Uint8Array|string} bodyBuf
|
|
496
497
|
* @param {string} contentType
|
|
497
498
|
* @returns {object} Parsed JSON payload
|
|
498
499
|
*/
|
|
499
500
|
export function parseWebhookPayload(bodyBuf, contentType = "application/json") {
|
|
500
|
-
if (!bodyBuf || bodyBuf.length === 0) return {};
|
|
501
|
-
const
|
|
501
|
+
if (!bodyBuf || (bodyBuf.length === 0 && bodyBuf.byteLength === 0)) return {};
|
|
502
|
+
const decoder = new TextDecoder();
|
|
503
|
+
const str = (bodyBuf instanceof Uint8Array ? decoder.decode(bodyBuf) : String(bodyBuf)).trim();
|
|
502
504
|
if (!str) return {};
|
|
503
505
|
|
|
504
506
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|