@runfusion/fusion 0.2.5 → 0.2.7
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.js +329 -53
- package/dist/client/assets/{AgentDetailView-CkuuGA1O.js → AgentDetailView-B4lRk--v.js} +1 -1
- package/dist/client/assets/{AgentsView-CWFLMIDP.js → AgentsView-yCYBY2km.js} +3 -3
- package/dist/client/assets/{ChatView-C_T91ebd.js → ChatView-CH9T0dDs.js} +1 -1
- package/dist/client/assets/{DevServerView-ChWTzTvy.js → DevServerView-jXXtoQUx.js} +1 -1
- package/dist/client/assets/DirectoryPicker-izgMlS27.js +1 -0
- package/dist/client/assets/{DocumentsView-Co9to4Zp.css → DocumentsView-DV2DrCZb.css} +1 -1
- package/dist/client/assets/{DocumentsView-CjfVl8mZ.js → DocumentsView-DkkoHRwL.js} +1 -1
- package/dist/client/assets/{InsightsView-Rb735C9_.js → InsightsView-DaRtUPHX.js} +1 -1
- package/dist/client/assets/{MemoryView-LLc_uNtG.js → MemoryView-85NKuU3h.js} +1 -1
- package/dist/client/assets/{NodesView-BviqBWRA.js → NodesView-BsUk_oiU.js} +1 -1
- package/dist/client/assets/PiExtensionsManager-BF5pxrSE.js +11 -0
- package/dist/client/assets/PiExtensionsManager-K7HQ08L4.css +1 -0
- package/dist/client/assets/PluginManager-ccq3uK50.css +1 -0
- package/dist/client/assets/{PluginManager-BywTPbLB.js → PluginManager-s6btydh5.js} +1 -1
- package/dist/client/assets/{RoadmapsView-Dhl--4vY.js → RoadmapsView-SQol126Y.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-CVtmwoJC.js → SetupWizardModal-CQc1uGSq.js} +1 -1
- package/dist/client/assets/{SkillsView-CG9y4fsE.js → SkillsView-BtUhs_QW.js} +1 -1
- package/dist/client/assets/{folder-open-BVDq27HP.js → folder-open-CI4TCD7P.js} +1 -1
- package/dist/client/assets/index-Ct-OqLpP.css +1 -0
- package/dist/client/assets/index-rNf7s96d.js +649 -0
- package/dist/client/assets/{upload-BDvpReDO.js → upload-CAlKC4qI.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/extension.js +75 -8
- package/dist/pi-claude-cli/package.json +1 -1
- package/package.json +5 -5
- package/dist/client/assets/DirectoryPicker-BMJIT7HD.js +0 -1
- package/dist/client/assets/PiExtensionsManager-CPgmJgDk.css +0 -1
- package/dist/client/assets/PiExtensionsManager-CriZBkQe.js +0 -11
- package/dist/client/assets/PluginManager-D64RIzmL.css +0 -1
- package/dist/client/assets/index-CikysL-d.js +0 -644
- package/dist/client/assets/index-Da1qmtc7.css +0 -1
package/dist/bin.js
CHANGED
|
@@ -91,7 +91,10 @@ var init_settings_schema = __esm({
|
|
|
91
91
|
settingsSyncConflictResolution: "last-write-wins",
|
|
92
92
|
// Dashboard session state (persisted to global settings for PWA/offline restore)
|
|
93
93
|
dashboardCurrentNodeId: void 0,
|
|
94
|
-
dashboardCurrentProjectIdByNode: void 0
|
|
94
|
+
dashboardCurrentProjectIdByNode: void 0,
|
|
95
|
+
// Dashboard TUI memory guard
|
|
96
|
+
vitestAutoKillEnabled: true,
|
|
97
|
+
vitestKillThresholdPct: 90
|
|
95
98
|
};
|
|
96
99
|
DEFAULT_PROJECT_SETTINGS = {
|
|
97
100
|
globalPause: false,
|
|
@@ -65592,6 +65595,63 @@ async function validateDiffScope(store, taskId, diffStat, strict = false) {
|
|
|
65592
65595
|
}
|
|
65593
65596
|
return result;
|
|
65594
65597
|
}
|
|
65598
|
+
async function resolveTaskDiffBaseRef({
|
|
65599
|
+
cwd,
|
|
65600
|
+
headRef,
|
|
65601
|
+
baseBranch,
|
|
65602
|
+
baseCommitSha
|
|
65603
|
+
}) {
|
|
65604
|
+
const resolvedBaseBranch = baseBranch?.trim() || "main";
|
|
65605
|
+
const quotedHeadRef = quoteArg(headRef);
|
|
65606
|
+
let mergeBase;
|
|
65607
|
+
try {
|
|
65608
|
+
try {
|
|
65609
|
+
const { stdout } = await execAsync2(`git merge-base ${quotedHeadRef} ${quoteArg(resolvedBaseBranch)}`, {
|
|
65610
|
+
cwd,
|
|
65611
|
+
encoding: "utf-8"
|
|
65612
|
+
});
|
|
65613
|
+
mergeBase = stdout.trim() || void 0;
|
|
65614
|
+
} catch {
|
|
65615
|
+
const { stdout } = await execAsync2(`git merge-base ${quotedHeadRef} ${quoteArg(`origin/${resolvedBaseBranch}`)}`, {
|
|
65616
|
+
cwd,
|
|
65617
|
+
encoding: "utf-8"
|
|
65618
|
+
});
|
|
65619
|
+
mergeBase = stdout.trim() || void 0;
|
|
65620
|
+
}
|
|
65621
|
+
} catch {
|
|
65622
|
+
}
|
|
65623
|
+
if (mergeBase) {
|
|
65624
|
+
try {
|
|
65625
|
+
const { stdout } = await execAsync2(`git rev-parse ${quotedHeadRef}`, {
|
|
65626
|
+
cwd,
|
|
65627
|
+
encoding: "utf-8"
|
|
65628
|
+
});
|
|
65629
|
+
const headSha = stdout.trim();
|
|
65630
|
+
if (headSha && headSha !== mergeBase) return mergeBase;
|
|
65631
|
+
} catch {
|
|
65632
|
+
return mergeBase;
|
|
65633
|
+
}
|
|
65634
|
+
}
|
|
65635
|
+
if (baseCommitSha) {
|
|
65636
|
+
try {
|
|
65637
|
+
await execAsync2(`git merge-base --is-ancestor ${quoteArg(baseCommitSha)} ${quotedHeadRef}`, {
|
|
65638
|
+
cwd,
|
|
65639
|
+
encoding: "utf-8"
|
|
65640
|
+
});
|
|
65641
|
+
return baseCommitSha;
|
|
65642
|
+
} catch {
|
|
65643
|
+
}
|
|
65644
|
+
}
|
|
65645
|
+
try {
|
|
65646
|
+
const { stdout } = await execAsync2(`git rev-parse ${quoteArg(`${headRef}~1`)}`, {
|
|
65647
|
+
cwd,
|
|
65648
|
+
encoding: "utf-8"
|
|
65649
|
+
});
|
|
65650
|
+
return stdout.trim() || void 0;
|
|
65651
|
+
} catch {
|
|
65652
|
+
return void 0;
|
|
65653
|
+
}
|
|
65654
|
+
}
|
|
65595
65655
|
async function getConflictedFiles(cwd) {
|
|
65596
65656
|
try {
|
|
65597
65657
|
const { stdout } = await execAsync2("git diff --name-only --diff-filter=U", {
|
|
@@ -66208,10 +66268,17 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
66208
66268
|
mergerLog.warn(`${taskId}: pre-merge rebase pipeline failed (${msg}) \u2014 proceeding without rebase`);
|
|
66209
66269
|
}
|
|
66210
66270
|
}
|
|
66271
|
+
const diffBaseRef = await resolveTaskDiffBaseRef({
|
|
66272
|
+
cwd: rootDir,
|
|
66273
|
+
headRef: branch,
|
|
66274
|
+
baseBranch: task.baseBranch,
|
|
66275
|
+
baseCommitSha: task.baseCommitSha
|
|
66276
|
+
});
|
|
66277
|
+
const contextDiffRange = diffBaseRef ? `${diffBaseRef}..${branch}` : `HEAD..${branch}`;
|
|
66211
66278
|
let commitLog = "";
|
|
66212
66279
|
let diffStat = "";
|
|
66213
66280
|
try {
|
|
66214
|
-
const { stdout: logOutput } = await execAsync2(`git log
|
|
66281
|
+
const { stdout: logOutput } = await execAsync2(`git log ${contextDiffRange} --format="- %s"`, {
|
|
66215
66282
|
cwd: rootDir,
|
|
66216
66283
|
encoding: "utf-8"
|
|
66217
66284
|
});
|
|
@@ -66220,12 +66287,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
66220
66287
|
commitLog = "(unable to read commit log)";
|
|
66221
66288
|
}
|
|
66222
66289
|
try {
|
|
66223
|
-
const { stdout:
|
|
66224
|
-
cwd: rootDir,
|
|
66225
|
-
encoding: "utf-8"
|
|
66226
|
-
});
|
|
66227
|
-
const mergeBase = mergeBaseOutput.trim();
|
|
66228
|
-
const { stdout: diffOutput } = await execAsync2(`git diff ${mergeBase}..${branch} --stat`, {
|
|
66290
|
+
const { stdout: diffOutput } = await execAsync2(`git diff ${contextDiffRange} --stat`, {
|
|
66229
66291
|
cwd: rootDir,
|
|
66230
66292
|
encoding: "utf-8"
|
|
66231
66293
|
});
|
|
@@ -85752,6 +85814,11 @@ async function createPlanningAgent(session, rootDir, modelProvider, modelId, pro
|
|
|
85752
85814
|
},
|
|
85753
85815
|
onText: (delta) => {
|
|
85754
85816
|
session.thinkingOutput += delta;
|
|
85817
|
+
persistThinking2(session.id, session.thinkingOutput);
|
|
85818
|
+
planningStreamManager.broadcast(session.id, {
|
|
85819
|
+
type: "thinking",
|
|
85820
|
+
data: delta
|
|
85821
|
+
});
|
|
85755
85822
|
}
|
|
85756
85823
|
});
|
|
85757
85824
|
}
|
|
@@ -87837,6 +87904,9 @@ function detectAngularLine(line) {
|
|
|
87837
87904
|
}
|
|
87838
87905
|
return null;
|
|
87839
87906
|
}
|
|
87907
|
+
function isNodeInspectorLine(line) {
|
|
87908
|
+
return /\binspector\b/i.test(line);
|
|
87909
|
+
}
|
|
87840
87910
|
function detectGenericUrl(line) {
|
|
87841
87911
|
const genericUrlMatch = line.match(/((?:https?:\/\/)?(?:localhost|127\.0\.0\.1):\d{2,5}(?:\/\S*)?)/i);
|
|
87842
87912
|
if (!genericUrlMatch) {
|
|
@@ -87844,6 +87914,9 @@ function detectGenericUrl(line) {
|
|
|
87844
87914
|
}
|
|
87845
87915
|
return withSource("generic-url", genericUrlMatch[1]);
|
|
87846
87916
|
}
|
|
87917
|
+
function isInspectorDiagnosticLine(line) {
|
|
87918
|
+
return /\b(?:inspector|debugger)\b/i.test(line) || /\b(node:)?\s*--inspect(?:-brk)?\b/i.test(line) || /\bws:\/\/(?:127\.0\.0\.1|localhost):\d{2,5}\b/i.test(line);
|
|
87919
|
+
}
|
|
87847
87920
|
function detectGenericPortLine(line) {
|
|
87848
87921
|
const keywordPortMatch = line.match(/\b(?:ready|listening|started|available|compiled|running|server)\b[^\d]{0,50}(?:on\s+)?(?:port\s*[:=]?\s*)?(\d{2,5})\b/i);
|
|
87849
87922
|
if (!keywordPortMatch) {
|
|
@@ -87870,7 +87943,10 @@ function detectPortFromLogLine(line) {
|
|
|
87870
87943
|
return null;
|
|
87871
87944
|
}
|
|
87872
87945
|
const cleanLine = stripAnsi(line).trim();
|
|
87873
|
-
if (!cleanLine) {
|
|
87946
|
+
if (!cleanLine || isInspectorDiagnosticLine(cleanLine)) {
|
|
87947
|
+
return null;
|
|
87948
|
+
}
|
|
87949
|
+
if (isNodeInspectorLine(cleanLine)) {
|
|
87874
87950
|
return null;
|
|
87875
87951
|
}
|
|
87876
87952
|
return detectViteLine(cleanLine) ?? detectNextLine(cleanLine) ?? detectStorybookLine(cleanLine) ?? detectAngularLine(cleanLine) ?? detectGenericUrl(cleanLine) ?? detectGenericPortLine(cleanLine);
|
|
@@ -115277,6 +115353,38 @@ async function runGitCommand(args, cwd, timeout2 = 1e4) {
|
|
|
115277
115353
|
}
|
|
115278
115354
|
return "";
|
|
115279
115355
|
}
|
|
115356
|
+
async function resolveDiffBase(task, cwd, headRef = "HEAD", runGit = runGitCommand) {
|
|
115357
|
+
const baseBranch = task.baseBranch ?? "main";
|
|
115358
|
+
let mergeBase;
|
|
115359
|
+
try {
|
|
115360
|
+
try {
|
|
115361
|
+
mergeBase = (await runGit(["merge-base", headRef, baseBranch], cwd, 5e3)).trim() || void 0;
|
|
115362
|
+
} catch {
|
|
115363
|
+
mergeBase = (await runGit(["merge-base", headRef, `origin/${baseBranch}`], cwd, 5e3)).trim() || void 0;
|
|
115364
|
+
}
|
|
115365
|
+
} catch {
|
|
115366
|
+
}
|
|
115367
|
+
if (mergeBase) {
|
|
115368
|
+
try {
|
|
115369
|
+
const head = (await runGit(["rev-parse", headRef], cwd, 5e3)).trim();
|
|
115370
|
+
if (head && head !== mergeBase) return mergeBase;
|
|
115371
|
+
} catch {
|
|
115372
|
+
return mergeBase;
|
|
115373
|
+
}
|
|
115374
|
+
}
|
|
115375
|
+
if (task.baseCommitSha) {
|
|
115376
|
+
try {
|
|
115377
|
+
await runGit(["merge-base", "--is-ancestor", task.baseCommitSha, headRef], cwd, 5e3);
|
|
115378
|
+
return task.baseCommitSha;
|
|
115379
|
+
} catch {
|
|
115380
|
+
}
|
|
115381
|
+
}
|
|
115382
|
+
try {
|
|
115383
|
+
return (await runGit(["rev-parse", `${headRef}~1`], cwd, 5e3)).trim() || void 0;
|
|
115384
|
+
} catch {
|
|
115385
|
+
return void 0;
|
|
115386
|
+
}
|
|
115387
|
+
}
|
|
115280
115388
|
function slugifyPresetName(name) {
|
|
115281
115389
|
const slug = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 32);
|
|
115282
115390
|
return slug || "preset";
|
|
@@ -118110,38 +118218,6 @@ function createApiRoutes(store, options) {
|
|
|
118110
118218
|
}
|
|
118111
118219
|
}
|
|
118112
118220
|
});
|
|
118113
|
-
async function resolveDiffBase(task, cwd) {
|
|
118114
|
-
const baseBranch = task.baseBranch ?? "main";
|
|
118115
|
-
let mergeBase;
|
|
118116
|
-
try {
|
|
118117
|
-
try {
|
|
118118
|
-
mergeBase = (await runGitCommand(["merge-base", "HEAD", baseBranch], cwd, 5e3)).trim() || void 0;
|
|
118119
|
-
} catch {
|
|
118120
|
-
mergeBase = (await runGitCommand(["merge-base", "HEAD", `origin/${baseBranch}`], cwd, 5e3)).trim() || void 0;
|
|
118121
|
-
}
|
|
118122
|
-
} catch {
|
|
118123
|
-
}
|
|
118124
|
-
if (mergeBase) {
|
|
118125
|
-
try {
|
|
118126
|
-
const head = (await runGitCommand(["rev-parse", "HEAD"], cwd, 5e3)).trim();
|
|
118127
|
-
if (head && head !== mergeBase) return mergeBase;
|
|
118128
|
-
} catch {
|
|
118129
|
-
return mergeBase;
|
|
118130
|
-
}
|
|
118131
|
-
}
|
|
118132
|
-
if (task.baseCommitSha) {
|
|
118133
|
-
try {
|
|
118134
|
-
await runGitCommand(["merge-base", "--is-ancestor", task.baseCommitSha, "HEAD"], cwd, 5e3);
|
|
118135
|
-
return task.baseCommitSha;
|
|
118136
|
-
} catch {
|
|
118137
|
-
}
|
|
118138
|
-
}
|
|
118139
|
-
try {
|
|
118140
|
-
return (await runGitCommand(["rev-parse", "HEAD~1"], cwd, 5e3)).trim() || void 0;
|
|
118141
|
-
} catch {
|
|
118142
|
-
return void 0;
|
|
118143
|
-
}
|
|
118144
|
-
}
|
|
118145
118221
|
router.get("/tasks/:id/session-files", async (req, res) => {
|
|
118146
118222
|
try {
|
|
118147
118223
|
const { store: scopedStore } = await getProjectContext2(req);
|
|
@@ -118662,7 +118738,7 @@ function createApiRoutes(store, options) {
|
|
|
118662
118738
|
router.patch("/tasks/:id", async (req, res) => {
|
|
118663
118739
|
try {
|
|
118664
118740
|
const { store: scopedStore } = await getProjectContext2(req);
|
|
118665
|
-
const { title, description, prompt, dependencies, enabledWorkflowSteps, modelProvider, modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, thinkingLevel, assigneeUserId, reviewLevel, executionMode } = req.body;
|
|
118741
|
+
const { title, description, prompt, dependencies, enabledWorkflowSteps, modelProvider, modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, thinkingLevel, assigneeUserId, reviewLevel, executionMode, sourceIssue } = req.body;
|
|
118666
118742
|
const hasBodyField = (field) => Object.prototype.hasOwnProperty.call(req.body, field);
|
|
118667
118743
|
const validateModelField = (value, name) => {
|
|
118668
118744
|
if (value === void 0) return void 0;
|
|
@@ -118697,6 +118773,40 @@ function createApiRoutes(store, options) {
|
|
|
118697
118773
|
throw new Error("enabledWorkflowSteps must be an array of strings");
|
|
118698
118774
|
}
|
|
118699
118775
|
}
|
|
118776
|
+
let validatedSourceIssue;
|
|
118777
|
+
if (hasBodyField("sourceIssue")) {
|
|
118778
|
+
if (sourceIssue === null) {
|
|
118779
|
+
validatedSourceIssue = null;
|
|
118780
|
+
} else if (sourceIssue === void 0) {
|
|
118781
|
+
validatedSourceIssue = void 0;
|
|
118782
|
+
} else if (typeof sourceIssue === "object") {
|
|
118783
|
+
const candidate = sourceIssue;
|
|
118784
|
+
if (typeof candidate.provider !== "string" || candidate.provider.trim().length === 0) {
|
|
118785
|
+
throw new Error("sourceIssue.provider must be a non-empty string");
|
|
118786
|
+
}
|
|
118787
|
+
if (typeof candidate.repository !== "string" || candidate.repository.trim().length === 0) {
|
|
118788
|
+
throw new Error("sourceIssue.repository must be a non-empty string");
|
|
118789
|
+
}
|
|
118790
|
+
if (typeof candidate.externalIssueId !== "string" || candidate.externalIssueId.trim().length === 0) {
|
|
118791
|
+
throw new Error("sourceIssue.externalIssueId must be a non-empty string");
|
|
118792
|
+
}
|
|
118793
|
+
if (typeof candidate.issueNumber !== "number" || !Number.isFinite(candidate.issueNumber) || !Number.isInteger(candidate.issueNumber) || candidate.issueNumber <= 0) {
|
|
118794
|
+
throw new Error("sourceIssue.issueNumber must be a positive integer");
|
|
118795
|
+
}
|
|
118796
|
+
if (candidate.url !== void 0 && candidate.url !== null && typeof candidate.url !== "string") {
|
|
118797
|
+
throw new Error("sourceIssue.url must be a string when provided");
|
|
118798
|
+
}
|
|
118799
|
+
validatedSourceIssue = {
|
|
118800
|
+
provider: candidate.provider.trim(),
|
|
118801
|
+
repository: candidate.repository.trim(),
|
|
118802
|
+
externalIssueId: candidate.externalIssueId.trim(),
|
|
118803
|
+
issueNumber: candidate.issueNumber,
|
|
118804
|
+
...typeof candidate.url === "string" && candidate.url.trim().length > 0 ? { url: candidate.url.trim() } : {}
|
|
118805
|
+
};
|
|
118806
|
+
} else {
|
|
118807
|
+
throw new Error("sourceIssue must be an object or null");
|
|
118808
|
+
}
|
|
118809
|
+
}
|
|
118700
118810
|
const updates = {};
|
|
118701
118811
|
if (title !== void 0) updates.title = title;
|
|
118702
118812
|
if (description !== void 0) updates.description = description;
|
|
@@ -118713,13 +118823,14 @@ function createApiRoutes(store, options) {
|
|
|
118713
118823
|
if (hasBodyField("assigneeUserId")) updates.assigneeUserId = validatedAssigneeUserId;
|
|
118714
118824
|
if (hasBodyField("reviewLevel")) updates.reviewLevel = reviewLevel;
|
|
118715
118825
|
if (hasBodyField("executionMode")) updates.executionMode = executionMode === null ? null : executionMode;
|
|
118826
|
+
if (hasBodyField("sourceIssue")) updates.sourceIssue = validatedSourceIssue === void 0 ? void 0 : validatedSourceIssue;
|
|
118716
118827
|
const task = await scopedStore.updateTask(req.params.id, updates);
|
|
118717
118828
|
res.json(task);
|
|
118718
118829
|
} catch (err) {
|
|
118719
118830
|
if (err instanceof ApiError) {
|
|
118720
118831
|
throw err;
|
|
118721
118832
|
}
|
|
118722
|
-
const status = (err instanceof Error ? err.message : String(err)).includes("must be a string") || (err instanceof Error ? err.message : String(err)).includes("must be an array of strings") || (err instanceof Error ? err.message : String(err)).includes("thinkingLevel must be one of") || (err instanceof Error ? err.message : String(err)).includes("reviewLevel must be an integer") || (err instanceof Error ? err.message : String(err)).includes("executionMode must be one of") ? 400 : 500;
|
|
118833
|
+
const status = (err instanceof Error ? err.message : String(err)).includes("must be a string") || (err instanceof Error ? err.message : String(err)).includes("must be an array of strings") || (err instanceof Error ? err.message : String(err)).includes("thinkingLevel must be one of") || (err instanceof Error ? err.message : String(err)).includes("reviewLevel must be an integer") || (err instanceof Error ? err.message : String(err)).includes("executionMode must be one of") || (err instanceof Error ? err.message : String(err)).includes("sourceIssue") ? 400 : 500;
|
|
118723
118834
|
throw new ApiError(status, err instanceof Error ? err.message : String(err));
|
|
118724
118835
|
}
|
|
118725
118836
|
});
|
|
@@ -121747,6 +121858,12 @@ ${qaSection}` : `Source: ${session.initialPlan.slice(0, 200)}`;
|
|
|
121747
121858
|
return;
|
|
121748
121859
|
}
|
|
121749
121860
|
}
|
|
121861
|
+
} else if (lastEventId === void 0) {
|
|
121862
|
+
const buffered = planningStreamManager2.getBufferedEvents(sessionId, 0);
|
|
121863
|
+
if (!replayBufferedSSE2(res, buffered)) {
|
|
121864
|
+
res.end();
|
|
121865
|
+
return;
|
|
121866
|
+
}
|
|
121750
121867
|
}
|
|
121751
121868
|
const unsubscribe = planningStreamManager2.subscribe(sessionId, (event, eventId) => {
|
|
121752
121869
|
const data = event.data;
|
|
@@ -136701,7 +136818,11 @@ function StatsPanel({ state, isFocused }) {
|
|
|
136701
136818
|
/* @__PURE__ */ jsx(Text, { color: sysMemColor(sys.systemTotalMem - sys.systemFreeMem, sys.systemTotalMem), children: formatBytes2(sys.systemTotalMem - sys.systemFreeMem) }),
|
|
136702
136819
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "used" }),
|
|
136703
136820
|
/* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.systemFreeMem) }),
|
|
136704
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "free" })
|
|
136821
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "free" }),
|
|
136822
|
+
sys.systemTotalMem > 0 && /* @__PURE__ */ jsxs(Text, { color: sysMemColor(sys.systemTotalMem - sys.systemFreeMem, sys.systemTotalMem), children: [
|
|
136823
|
+
((sys.systemTotalMem - sys.systemFreeMem) / sys.systemTotalMem * 100).toFixed(1),
|
|
136824
|
+
"%"
|
|
136825
|
+
] })
|
|
136705
136826
|
] }),
|
|
136706
136827
|
/* @__PURE__ */ jsxs(StatRow, { label: "Cores", children: [
|
|
136707
136828
|
/* @__PURE__ */ jsx(Text, { children: sys.cpuCount }),
|
|
@@ -136860,11 +136981,16 @@ function ExpandedLog({ entry, index: index2, total }) {
|
|
|
136860
136981
|
/* @__PURE__ */ jsx(Text, { wrap: "wrap", children: entry.message })
|
|
136861
136982
|
] });
|
|
136862
136983
|
}
|
|
136863
|
-
function UtilitiesPanel({ isFocused }) {
|
|
136984
|
+
function UtilitiesPanel({ state, isFocused }) {
|
|
136985
|
+
const autoKill = state.autoKillVitestOnPressure;
|
|
136986
|
+
const thresholdPct = Math.round(state.vitestKillThreshold * 100);
|
|
136864
136987
|
const actions = [
|
|
136865
136988
|
{ key: "r", label: "Refresh Stats" },
|
|
136866
136989
|
{ key: "c", label: "Clear Logs" },
|
|
136867
136990
|
{ key: "t", label: "Toggle Engine Pause" },
|
|
136991
|
+
{ key: "k", label: "Kill Vitest Processes" },
|
|
136992
|
+
{ key: "v", label: `Auto-Kill Vitest >${thresholdPct}% Mem: ${autoKill ? "ON" : "OFF"}` },
|
|
136993
|
+
{ key: "+/-", label: `Adjust Threshold (${thresholdPct}%)` },
|
|
136868
136994
|
{ key: "?", label: "Help" }
|
|
136869
136995
|
];
|
|
136870
136996
|
return /* @__PURE__ */ jsx(Panel, { title: "Utilities", isFocused, flexGrow: 1, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actions.map((action) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
@@ -136892,6 +137018,9 @@ function HelpOverlay() {
|
|
|
136892
137018
|
["[\u2190] / [p]", "Previous panel (Main)"],
|
|
136893
137019
|
["[r]", "Refresh stats (Utilities)"],
|
|
136894
137020
|
["[c]", "Clear logs (Utilities)"],
|
|
137021
|
+
["[k]", "Kill all vitest processes (Utilities)"],
|
|
137022
|
+
["[v]", "Toggle auto-kill vitest on memory pressure (Utilities)"],
|
|
137023
|
+
["[+/-]", "Adjust vitest kill memory threshold (Utilities)"],
|
|
136895
137024
|
["[\u2191/\u2193/k/j]", "Navigate list / log entries"],
|
|
136896
137025
|
["[Home / G]", "First / last log entry (Logs)"],
|
|
136897
137026
|
["[Enter/Space]", "Expand log entry (Logs)"],
|
|
@@ -136950,7 +137079,7 @@ function StatusModeGrid({
|
|
|
136950
137079
|
}
|
|
136951
137080
|
),
|
|
136952
137081
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", overflow: "hidden", children: [
|
|
136953
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: focused === "utilities" }) }),
|
|
137082
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(UtilitiesPanel, { state, isFocused: focused === "utilities" }) }),
|
|
136954
137083
|
/* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(SettingsPanel, { state, isFocused: focused === "settings" }) })
|
|
136955
137084
|
] })
|
|
136956
137085
|
] })
|
|
@@ -136970,7 +137099,7 @@ function StatusModeSingle({
|
|
|
136970
137099
|
case "logs":
|
|
136971
137100
|
return /* @__PURE__ */ jsx(LogsPanel, { state, isFocused: true, availableRows: Math.max(4, (process.stdout.rows ?? 24) - 8) });
|
|
136972
137101
|
case "utilities":
|
|
136973
|
-
return /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: true });
|
|
137102
|
+
return /* @__PURE__ */ jsx(UtilitiesPanel, { state, isFocused: true });
|
|
136974
137103
|
case "stats":
|
|
136975
137104
|
return /* @__PURE__ */ jsx(StatsPanel, { state, isFocused: true });
|
|
136976
137105
|
case "settings":
|
|
@@ -136989,7 +137118,7 @@ function StatusBar({ state, controller: _controller }) {
|
|
|
136989
137118
|
if (activeSection === "logs") {
|
|
136990
137119
|
hotkeys.push("\u2191\u2193 navigate", "w wrap", "f filter", "Enter expand");
|
|
136991
137120
|
} else if (activeSection === "utilities") {
|
|
136992
|
-
hotkeys.push("r refresh", "c clear logs", "t toggle pause");
|
|
137121
|
+
hotkeys.push("r refresh", "c clear logs", "t toggle pause", "k kill vitest", "v auto-kill", "+/- threshold");
|
|
136993
137122
|
} else {
|
|
136994
137123
|
hotkeys.push("Tab cycle panel", "1-5 jump");
|
|
136995
137124
|
}
|
|
@@ -139289,6 +139418,7 @@ var init_app = __esm({
|
|
|
139289
139418
|
// src/commands/dashboard-tui/controller.ts
|
|
139290
139419
|
import os3 from "node:os";
|
|
139291
139420
|
import v8 from "node:v8";
|
|
139421
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
139292
139422
|
var DashboardTUI;
|
|
139293
139423
|
var init_controller = __esm({
|
|
139294
139424
|
"src/commands/dashboard-tui/controller.ts"() {
|
|
@@ -139319,6 +139449,15 @@ var init_controller = __esm({
|
|
|
139319
139449
|
logsViewportStart = 0;
|
|
139320
139450
|
loadingStatus = "Starting\u2026";
|
|
139321
139451
|
mode = "status";
|
|
139452
|
+
// When true, sampleSystemStats() kills any running vitest processes if
|
|
139453
|
+
// system memory usage crosses 90%. Toggled by [v] in the Utilities panel.
|
|
139454
|
+
autoKillVitestOnPressure = true;
|
|
139455
|
+
// System-memory ratio (0..1) at which auto-kill triggers. Adjustable from
|
|
139456
|
+
// the Utilities panel via [+]/[-] in 5% steps. Clamped to [0.5, 0.99].
|
|
139457
|
+
vitestKillThreshold = 0.9;
|
|
139458
|
+
// Throttle so we don't spam kills while the sampler keeps firing during
|
|
139459
|
+
// sustained pressure (sampler runs every 2s).
|
|
139460
|
+
lastAutoKillAt = 0;
|
|
139322
139461
|
interactiveData = null;
|
|
139323
139462
|
interactiveView = "board";
|
|
139324
139463
|
// Subscribers registered by the Ink App component.
|
|
@@ -139366,7 +139505,9 @@ var init_controller = __esm({
|
|
|
139366
139505
|
loadingStatus: this.loadingStatus,
|
|
139367
139506
|
mode: this.mode,
|
|
139368
139507
|
interactiveData: this.interactiveData,
|
|
139369
|
-
interactiveView: this.interactiveView
|
|
139508
|
+
interactiveView: this.interactiveView,
|
|
139509
|
+
autoKillVitestOnPressure: this.autoKillVitestOnPressure,
|
|
139510
|
+
vitestKillThreshold: this.vitestKillThreshold
|
|
139370
139511
|
};
|
|
139371
139512
|
return this.cachedSnapshot;
|
|
139372
139513
|
}
|
|
@@ -139439,6 +139580,82 @@ var init_controller = __esm({
|
|
|
139439
139580
|
nodeVersion: process.version,
|
|
139440
139581
|
platform: `${process.platform}/${process.arch}`
|
|
139441
139582
|
});
|
|
139583
|
+
if (this.autoKillVitestOnPressure) {
|
|
139584
|
+
const total = os3.totalmem();
|
|
139585
|
+
const free = os3.freemem();
|
|
139586
|
+
if (total > 0) {
|
|
139587
|
+
const usedRatio = (total - free) / total;
|
|
139588
|
+
if (usedRatio > this.vitestKillThreshold && now - this.lastAutoKillAt > 3e4) {
|
|
139589
|
+
this.lastAutoKillAt = now;
|
|
139590
|
+
const result = this.killVitestProcesses();
|
|
139591
|
+
if (result.killed > 0) {
|
|
139592
|
+
this.warn(
|
|
139593
|
+
`Auto-killed ${result.killed} vitest process${result.killed === 1 ? "" : "es"} (system memory at ${Math.round(usedRatio * 100)}%, threshold ${Math.round(this.vitestKillThreshold * 100)}%)`,
|
|
139594
|
+
"memory-guard"
|
|
139595
|
+
);
|
|
139596
|
+
}
|
|
139597
|
+
}
|
|
139598
|
+
}
|
|
139599
|
+
}
|
|
139600
|
+
}
|
|
139601
|
+
/**
|
|
139602
|
+
* Find and SIGKILL any running vitest processes, excluding this dashboard
|
|
139603
|
+
* itself. Returns a count of pids signalled (best-effort — a pid may be
|
|
139604
|
+
* gone by the time we send the signal).
|
|
139605
|
+
*/
|
|
139606
|
+
killVitestProcesses() {
|
|
139607
|
+
const selfPid = process.pid;
|
|
139608
|
+
let pids = [];
|
|
139609
|
+
try {
|
|
139610
|
+
const out = execSync2("pgrep -f vitest", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
139611
|
+
pids = out.split("\n").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n) && n > 0 && n !== selfPid);
|
|
139612
|
+
} catch {
|
|
139613
|
+
return { killed: 0, pids: [] };
|
|
139614
|
+
}
|
|
139615
|
+
let killed = 0;
|
|
139616
|
+
for (const pid of pids) {
|
|
139617
|
+
try {
|
|
139618
|
+
process.kill(pid, "SIGKILL");
|
|
139619
|
+
killed += 1;
|
|
139620
|
+
} catch {
|
|
139621
|
+
}
|
|
139622
|
+
}
|
|
139623
|
+
return { killed, pids };
|
|
139624
|
+
}
|
|
139625
|
+
adjustVitestKillThreshold(deltaPct) {
|
|
139626
|
+
const next = this.vitestKillThreshold + deltaPct / 100;
|
|
139627
|
+
this.vitestKillThreshold = Math.max(0.5, Math.min(0.99, Math.round(next * 100) / 100));
|
|
139628
|
+
this.notify();
|
|
139629
|
+
void this.persistVitestKillSettings({ thresholdPct: Math.round(this.vitestKillThreshold * 100) });
|
|
139630
|
+
return this.vitestKillThreshold;
|
|
139631
|
+
}
|
|
139632
|
+
toggleAutoKillVitest() {
|
|
139633
|
+
this.autoKillVitestOnPressure = !this.autoKillVitestOnPressure;
|
|
139634
|
+
if (!this.autoKillVitestOnPressure) {
|
|
139635
|
+
this.lastAutoKillAt = 0;
|
|
139636
|
+
}
|
|
139637
|
+
this.notify();
|
|
139638
|
+
void this.persistVitestKillSettings({ enabled: this.autoKillVitestOnPressure });
|
|
139639
|
+
return this.autoKillVitestOnPressure;
|
|
139640
|
+
}
|
|
139641
|
+
/** Apply persisted values from global settings on startup. Does not
|
|
139642
|
+
* trigger a write-back. */
|
|
139643
|
+
hydrateVitestKillSettings(values) {
|
|
139644
|
+
if (typeof values.enabled === "boolean") {
|
|
139645
|
+
this.autoKillVitestOnPressure = values.enabled;
|
|
139646
|
+
}
|
|
139647
|
+
if (typeof values.thresholdPct === "number" && Number.isFinite(values.thresholdPct)) {
|
|
139648
|
+
const ratio = values.thresholdPct / 100;
|
|
139649
|
+
this.vitestKillThreshold = Math.max(0.5, Math.min(0.99, ratio));
|
|
139650
|
+
}
|
|
139651
|
+
this.notify();
|
|
139652
|
+
}
|
|
139653
|
+
async persistVitestKillSettings(partial) {
|
|
139654
|
+
if (!this.callbacks?.onPersistVitestKillSettings) return;
|
|
139655
|
+
try {
|
|
139656
|
+
await this.callbacks.onPersistVitestKillSettings(partial);
|
|
139657
|
+
} catch {
|
|
139658
|
+
}
|
|
139442
139659
|
}
|
|
139443
139660
|
setSettings(settings) {
|
|
139444
139661
|
this.settings = settings;
|
|
@@ -139552,6 +139769,38 @@ var init_controller = __esm({
|
|
|
139552
139769
|
this.setSettings(newSettings);
|
|
139553
139770
|
}
|
|
139554
139771
|
break;
|
|
139772
|
+
case "k": {
|
|
139773
|
+
const result = this.killVitestProcesses();
|
|
139774
|
+
if (result.killed === 0) {
|
|
139775
|
+
this.log("No vitest processes found.", "kill-vitest");
|
|
139776
|
+
} else {
|
|
139777
|
+
this.warn(
|
|
139778
|
+
`Killed ${result.killed} vitest process${result.killed === 1 ? "" : "es"}: ${result.pids.join(", ")}`,
|
|
139779
|
+
"kill-vitest"
|
|
139780
|
+
);
|
|
139781
|
+
}
|
|
139782
|
+
break;
|
|
139783
|
+
}
|
|
139784
|
+
case "v": {
|
|
139785
|
+
const enabled = this.toggleAutoKillVitest();
|
|
139786
|
+
this.log(
|
|
139787
|
+
`Auto-kill vitest on memory pressure (>${Math.round(this.vitestKillThreshold * 100)}%): ${enabled ? "ON" : "OFF"}`,
|
|
139788
|
+
"memory-guard"
|
|
139789
|
+
);
|
|
139790
|
+
break;
|
|
139791
|
+
}
|
|
139792
|
+
case "+":
|
|
139793
|
+
case "=": {
|
|
139794
|
+
const v = this.adjustVitestKillThreshold(5);
|
|
139795
|
+
this.log(`Vitest kill threshold: ${Math.round(v * 100)}%`, "memory-guard");
|
|
139796
|
+
break;
|
|
139797
|
+
}
|
|
139798
|
+
case "-":
|
|
139799
|
+
case "_": {
|
|
139800
|
+
const v = this.adjustVitestKillThreshold(-5);
|
|
139801
|
+
this.log(`Vitest kill threshold: ${Math.round(v * 100)}%`, "memory-guard");
|
|
139802
|
+
break;
|
|
139803
|
+
}
|
|
139555
139804
|
}
|
|
139556
139805
|
}
|
|
139557
139806
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -140224,6 +140473,18 @@ async function runDashboard(port, opts = {}) {
|
|
|
140224
140473
|
enginePaused: paused,
|
|
140225
140474
|
globalPause: false
|
|
140226
140475
|
};
|
|
140476
|
+
},
|
|
140477
|
+
onPersistVitestKillSettings: async (partial) => {
|
|
140478
|
+
if (!store) return;
|
|
140479
|
+
const patch = {};
|
|
140480
|
+
if (typeof partial.enabled === "boolean") {
|
|
140481
|
+
patch.vitestAutoKillEnabled = partial.enabled;
|
|
140482
|
+
}
|
|
140483
|
+
if (typeof partial.thresholdPct === "number") {
|
|
140484
|
+
patch.vitestKillThresholdPct = partial.thresholdPct;
|
|
140485
|
+
}
|
|
140486
|
+
if (Object.keys(patch).length === 0) return;
|
|
140487
|
+
await store.getGlobalSettingsStore().updateSettings(patch);
|
|
140227
140488
|
}
|
|
140228
140489
|
});
|
|
140229
140490
|
await tui.start();
|
|
@@ -140960,6 +141221,14 @@ async function runDashboard(port, opts = {}) {
|
|
|
140960
141221
|
enginePaused: settings.enginePaused ?? false,
|
|
140961
141222
|
globalPause: settings.globalPause ?? false
|
|
140962
141223
|
});
|
|
141224
|
+
try {
|
|
141225
|
+
const globalSettings = await store.getGlobalSettingsStore().getSettings();
|
|
141226
|
+
tui.hydrateVitestKillSettings({
|
|
141227
|
+
enabled: typeof globalSettings.vitestAutoKillEnabled === "boolean" ? globalSettings.vitestAutoKillEnabled : void 0,
|
|
141228
|
+
thresholdPct: typeof globalSettings.vitestKillThresholdPct === "number" ? globalSettings.vitestKillThresholdPct : void 0
|
|
141229
|
+
});
|
|
141230
|
+
} catch {
|
|
141231
|
+
}
|
|
140963
141232
|
const tasks = await store.listTasks({ slim: true, includeArchived: false });
|
|
140964
141233
|
const counts = /* @__PURE__ */ new Map();
|
|
140965
141234
|
for (const task of tasks) {
|
|
@@ -147452,6 +147721,7 @@ import { existsSync as existsSync47, mkdtempSync as mkdtempSync2, readFileSync a
|
|
|
147452
147721
|
import { createRequire as createRequire4 } from "node:module";
|
|
147453
147722
|
import { join as join59, dirname as dirname24 } from "node:path";
|
|
147454
147723
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
147724
|
+
import { performance as performance3 } from "node:perf_hooks";
|
|
147455
147725
|
var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
147456
147726
|
function configurePiPackage() {
|
|
147457
147727
|
if (process.env.PI_PACKAGE_DIR) {
|
|
@@ -147484,6 +147754,10 @@ function configurePiPackage() {
|
|
|
147484
147754
|
process.env.PI_PACKAGE_DIR = tmp;
|
|
147485
147755
|
}
|
|
147486
147756
|
configurePiPackage();
|
|
147757
|
+
setInterval(() => {
|
|
147758
|
+
performance3.clearMeasures();
|
|
147759
|
+
performance3.clearMarks();
|
|
147760
|
+
}, 3e4).unref();
|
|
147487
147761
|
function loadEnvFile(path4) {
|
|
147488
147762
|
if (!existsSync47(path4)) return;
|
|
147489
147763
|
const contents = readFileSync17(path4, "utf-8");
|
|
@@ -147700,7 +147974,8 @@ Usage:
|
|
|
147700
147974
|
fn backup --restore <file> Restore database from a backup file
|
|
147701
147975
|
fn backup --cleanup Remove old backups exceeding retention limit
|
|
147702
147976
|
fn plugin list | ls List installed plugins
|
|
147703
|
-
fn plugin install <path>
|
|
147977
|
+
fn plugin install <path-or-package> Install a plugin from path or package
|
|
147978
|
+
fn plugin add <path-or-package> Alias for plugin install
|
|
147704
147979
|
fn plugin uninstall <id> [--force] Uninstall a plugin
|
|
147705
147980
|
fn plugin enable <id> Enable a plugin
|
|
147706
147981
|
fn plugin disable <id> Disable a plugin
|
|
@@ -148602,10 +148877,11 @@ async function main() {
|
|
|
148602
148877
|
case "ls":
|
|
148603
148878
|
await runPluginList2(projectName);
|
|
148604
148879
|
break;
|
|
148605
|
-
case "install":
|
|
148880
|
+
case "install":
|
|
148881
|
+
case "add": {
|
|
148606
148882
|
const source = args[2];
|
|
148607
148883
|
if (!source) {
|
|
148608
|
-
console.error("Usage: fn plugin install <path-or-package>");
|
|
148884
|
+
console.error("Usage: fn plugin install <path-or-package> (alias: fn plugin add <path-or-package>)");
|
|
148609
148885
|
process.exit(1);
|
|
148610
148886
|
}
|
|
148611
148887
|
await runPluginInstall2(source, { projectName });
|
|
@@ -148650,7 +148926,7 @@ async function main() {
|
|
|
148650
148926
|
}
|
|
148651
148927
|
default:
|
|
148652
148928
|
console.error(`Unknown subcommand: plugin ${sub || ""}`);
|
|
148653
|
-
console.log("Try: fn plugin list | install | uninstall | enable | disable | create");
|
|
148929
|
+
console.log("Try: fn plugin list | install | add (alias for install) | uninstall | enable | disable | create");
|
|
148654
148930
|
process.exit(1);
|
|
148655
148931
|
}
|
|
148656
148932
|
break;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as a,j as e}from"./vendor-react-K0fH_qHe.js";import{c as xe,c2 as ks,c3 as Cs,c4 as ws,c5 as Rs,L as O,R as ze,af as ts,Z as Ms,ah as Te,I as Ce,K as hs,D as gs,c6 as Fs,c7 as Ts,c8 as As,c9 as Ls,p as W,ca as Ds,s as as,B as ns,E as Ne,J as is,aK as ge,X as zs,F as we,A as Se,v as Ee,cb as Es,b_ as $s,ar as Ps,b5 as Bs,ai as fs,M as Is,U as Os,W as _s,cc as Le,cd as Hs,y as Vs,ce as Us,cf as Js,cg as Ws,ch as qs,ci as Gs,cj as $e,a3 as Pe,a5 as Be,a6 as Ie,C as X,a1 as Ks,ck as Ys,cl as Zs,i as Qs,h as Xs,j as et,cm as De,V as st,cn as tt,co as at,cp as nt,cq as it,cr as rt,cs as lt,ct as ot,cu as ct,Q as dt,cv as ut,N as mt}from"./index-
|
|
1
|
+
import{r as a,j as e}from"./vendor-react-K0fH_qHe.js";import{c as xe,c2 as ks,c3 as Cs,c4 as ws,c5 as Rs,L as O,R as ze,af as ts,Z as Ms,ah as Te,I as Ce,K as hs,D as gs,c6 as Fs,c7 as Ts,c8 as As,c9 as Ls,p as W,ca as Ds,s as as,B as ns,E as Ne,J as is,aK as ge,X as zs,F as we,A as Se,v as Ee,cb as Es,b_ as $s,ar as Ps,b5 as Bs,ai as fs,M as Is,U as Os,W as _s,cc as Le,cd as Hs,y as Vs,ce as Us,cf as Js,cg as Ws,ch as qs,ci as Gs,cj as $e,a3 as Pe,a5 as Be,a6 as Ie,C as X,a1 as Ks,ck as Ys,cl as Zs,i as Qs,h as Xs,j as et,cm as De,V as st,cn as tt,co as at,cp as nt,cq as it,cr as rt,cs as lt,ct as ot,cu as ct,Q as dt,cv as ut,N as mt}from"./index-rNf7s96d.js";import{S as ht}from"./AgentsView-yCYBY2km.js";import"./vendor-xterm-DzcZoU0P.js";import"./upload-CAlKC4qI.js";import"./folder-open-CI4TCD7P.js";/**
|
|
2
2
|
* @license lucide-react v1.7.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|