@keyou007/agent-bot 0.1.14-alpha.1 → 0.1.14-alpha.2
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/CHANGELOG.md +14 -1
- package/README.md +42 -12
- package/README.zh.md +42 -12
- package/config.example.yaml +1 -1
- package/dist/cli/AutostartManager.js +431 -0
- package/dist/cli/AutostartManager.js.map +1 -0
- package/dist/cli/FeishuAppConfiguration.js +17 -13
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/Initializer.js +35 -0
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js +552 -0
- package/dist/cli/SelfUpdateRunner.js.map +1 -0
- package/dist/cli/SelfUpdater.js +431 -0
- package/dist/cli/SelfUpdater.js.map +1 -0
- package/dist/cli/autostartOutput.js +63 -0
- package/dist/cli/autostartOutput.js.map +1 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +92 -18
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +14 -11
- package/dist/cli/taskGroupOptions.js.map +1 -1
- package/dist/cli/taskListOutput.js +3 -0
- package/dist/cli/taskListOutput.js.map +1 -1
- package/dist/cli/taskTarget.js +99 -0
- package/dist/cli/taskTarget.js.map +1 -0
- package/dist/cli.js +643 -58
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +8 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +7 -1
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +172 -23
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +25 -1
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/index.js +134 -2
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +436 -12
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +18 -0
- package/dist/state/StateStore.js.map +1 -1
- package/docs/technical-reference.md +17 -7
- package/docs/technical-reference.zh.md +17 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/skills/agent-bot/SKILL.md +107 -188
- package/src/cli/AutostartManager.ts +579 -0
- package/src/cli/FeishuAppConfiguration.ts +22 -7
- package/src/cli/Initializer.ts +57 -0
- package/src/cli/SelfUpdateRunner.ts +817 -0
- package/src/cli/SelfUpdater.ts +688 -0
- package/src/cli/autostartOutput.ts +75 -0
- package/src/cli/controlProtocol.ts +111 -1
- package/src/cli/help.ts +92 -18
- package/src/cli/taskGroupOptions.ts +27 -17
- package/src/cli/taskListOutput.ts +10 -0
- package/src/cli/taskTarget.ts +168 -0
- package/src/cli.ts +781 -65
- package/src/codex/CodexRuntime.ts +8 -2
- package/src/commands/CommandRouter.ts +6 -1
- package/src/commands/commandTypes.ts +1 -0
- package/src/feishu/CardRenderer.ts +199 -23
- package/src/feishu/FeishuMarkdown.ts +27 -1
- package/src/index.ts +172 -2
- package/src/proxy/ProxySessionController.ts +532 -15
- package/src/runtime/types.ts +1 -0
- package/src/state/StateStore.ts +19 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { truncateText } from "../utils/markdown.js";
|
|
4
4
|
import { localCardImage } from "./LocalCardImage.js";
|
|
5
5
|
const DIRECTORY_BROWSER_ROW_COUNT = 16;
|
|
6
6
|
export class CardRenderer {
|
|
@@ -630,15 +630,22 @@ export class CardRenderer {
|
|
|
630
630
|
}
|
|
631
631
|
renderActivityHistory(state, requestedPage) {
|
|
632
632
|
const grouped = this.thinkingCardLayout === "grouped";
|
|
633
|
+
const activities = turnActivities(state);
|
|
634
|
+
const allGroups = grouped ? groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity) : [];
|
|
635
|
+
const historyGroups = grouped
|
|
636
|
+
? groupedLiveActivityPage(groupedActivityPagesFromGroups(allGroups, state.projectCwd), state.projectCwd).historyGroups
|
|
637
|
+
: [];
|
|
638
|
+
const usesHistoricalPrefixPages = historyGroups.length > 0;
|
|
633
639
|
const pages = grouped
|
|
634
|
-
?
|
|
640
|
+
? groupedActivityPagesFromGroups(usesHistoricalPrefixPages ? historyGroups : allGroups, state.projectCwd, {
|
|
635
641
|
fullActivityText: true,
|
|
636
642
|
includeReasoningHistory: true,
|
|
637
643
|
})
|
|
638
|
-
: activityPages(
|
|
644
|
+
: activityPages(activities);
|
|
639
645
|
if (pages.length === 0) {
|
|
640
646
|
return this.renderSectionsCard("思考活动历史", [{ lines: ["没有保存到活动记录。"] }]);
|
|
641
647
|
}
|
|
648
|
+
const totalPages = usesHistoricalPrefixPages ? pages.length + 1 : pages.length;
|
|
642
649
|
const page = Math.max(0, Math.min(Math.trunc(requestedPage), pages.length - 1));
|
|
643
650
|
const actions = [
|
|
644
651
|
...(pages.length > 1 ? [{
|
|
@@ -649,7 +656,7 @@ export class CardRenderer {
|
|
|
649
656
|
text: "上一页",
|
|
650
657
|
value: { action: "activity_history", turnId: state.turnId, page: String(page - 1) },
|
|
651
658
|
}] : []),
|
|
652
|
-
...(page < pages.length - 2 ? [{
|
|
659
|
+
...(page < pages.length - (usesHistoricalPrefixPages ? 1 : 2) ? [{
|
|
653
660
|
text: "下一页",
|
|
654
661
|
value: { action: "activity_history", turnId: state.turnId, page: String(page + 1) },
|
|
655
662
|
}] : []),
|
|
@@ -663,7 +670,7 @@ export class CardRenderer {
|
|
|
663
670
|
includeReasoningHistory: true,
|
|
664
671
|
})
|
|
665
672
|
: renderActivities(pages[page] ?? [], state.projectCwd, true)));
|
|
666
|
-
return sectionCard(`思考活动历史 · ${page + 1}/${
|
|
673
|
+
return sectionCard(`思考活动历史 · ${page + 1}/${totalPages}`, elements.length > 0 ? elements : [markdown("无")]);
|
|
667
674
|
}
|
|
668
675
|
renderSessionStarted(session) {
|
|
669
676
|
return this.baseCard("ACP 会话已创建", "green", [
|
|
@@ -953,26 +960,26 @@ function renderGroupedTurnElements(state, assistantTextMode) {
|
|
|
953
960
|
const elements = [];
|
|
954
961
|
const allActivities = turnActivities(state);
|
|
955
962
|
const livePages = groupedActivityPages(allActivities, state.projectCwd);
|
|
956
|
-
const
|
|
963
|
+
const visible = groupedLiveActivityPage(livePages, state.projectCwd);
|
|
964
|
+
const historyPages = groupedActivityPagesFromGroups(visible.historyGroups, state.projectCwd, {
|
|
957
965
|
fullActivityText: true,
|
|
958
966
|
includeReasoningHistory: true,
|
|
959
967
|
});
|
|
960
|
-
const visibleGroups = livePages.at(-1) ?? [];
|
|
961
968
|
if (state.plan.length > 0)
|
|
962
969
|
elements.push(planPanel(state.plan));
|
|
963
|
-
if (historyPages.length >
|
|
970
|
+
if (historyPages.length > 0) {
|
|
964
971
|
elements.push(taskActionRow([{
|
|
965
|
-
text: `查看历史思考(共 ${historyPages.length} 页)`,
|
|
972
|
+
text: `查看历史思考(共 ${historyPages.length + 1} 页)`,
|
|
966
973
|
value: {
|
|
967
974
|
action: "activity_history",
|
|
968
975
|
turnId: state.turnId,
|
|
969
|
-
page: String(historyPages.length -
|
|
976
|
+
page: String(historyPages.length - 1),
|
|
970
977
|
},
|
|
971
978
|
}]));
|
|
972
979
|
}
|
|
973
|
-
if (state.activitiesTruncated
|
|
980
|
+
if (state.activitiesTruncated && visible.groups[0]?.kind !== "gap")
|
|
974
981
|
elements.push(markdown("…"));
|
|
975
|
-
elements.push(...renderGroupedActivityGroups(
|
|
982
|
+
elements.push(...renderGroupedActivityGroups(visible.groups, state.projectCwd));
|
|
976
983
|
if (state.fileSummary.length > 0)
|
|
977
984
|
elements.push(fileSummaryPanel(state));
|
|
978
985
|
if (state.approval) {
|
|
@@ -1101,6 +1108,8 @@ function renderActivities(activities, projectCwd, fullAssistantText = false) {
|
|
|
1101
1108
|
}
|
|
1102
1109
|
function renderGroupedActivityGroups(groups, projectCwd, options = {}) {
|
|
1103
1110
|
return groups.flatMap((group) => {
|
|
1111
|
+
if (group.kind === "gap")
|
|
1112
|
+
return [markdown("…")];
|
|
1104
1113
|
if (group.kind === "activity") {
|
|
1105
1114
|
return renderActivity(group.activity, projectCwd, options.fullActivityText === true);
|
|
1106
1115
|
}
|
|
@@ -1169,19 +1178,17 @@ function executionActivityPanel(group, projectCwd, includeReasoningHistory = fal
|
|
|
1169
1178
|
return collapsiblePanel(executionActivityTitle(group, status), elements, {
|
|
1170
1179
|
elementId: executionActivityPanelElementId(group.id),
|
|
1171
1180
|
expanded: false,
|
|
1172
|
-
borderColor: status === "
|
|
1181
|
+
borderColor: status === "running" ? "blue" : "grey",
|
|
1173
1182
|
compact: true,
|
|
1174
1183
|
});
|
|
1175
1184
|
}
|
|
1176
1185
|
function executionActivityStatus(tools) {
|
|
1177
1186
|
if (tools.some((tool) => tool.status === "running"))
|
|
1178
1187
|
return "running";
|
|
1179
|
-
if (tools.some((tool) => tool.status === "failed"))
|
|
1180
|
-
return "failed";
|
|
1181
1188
|
return "completed";
|
|
1182
1189
|
}
|
|
1183
1190
|
function executionActivityTitle(group, status) {
|
|
1184
|
-
const icon = status === "running" ? "⏳" :
|
|
1191
|
+
const icon = status === "running" ? "⏳" : "💭";
|
|
1185
1192
|
const latestReasoning = group.latestReasoning?.text
|
|
1186
1193
|
? removeMarkdownBold(group.latestReasoning.text).replace(/\s+/g, " ").trim()
|
|
1187
1194
|
: "";
|
|
@@ -1189,9 +1196,7 @@ function executionActivityTitle(group, status) {
|
|
|
1189
1196
|
? truncateText(latestReasoning, 90)
|
|
1190
1197
|
: status === "running"
|
|
1191
1198
|
? "正在执行工具"
|
|
1192
|
-
:
|
|
1193
|
-
? "工具执行失败"
|
|
1194
|
-
: "已执行工具";
|
|
1199
|
+
: "已执行工具";
|
|
1195
1200
|
return `${icon} ${summary} · ${group.tools.length} 个工具`;
|
|
1196
1201
|
}
|
|
1197
1202
|
function executionActivityPanelElementId(activityId) {
|
|
@@ -1217,6 +1222,7 @@ const ACTIVITIES_PER_PAGE = 40;
|
|
|
1217
1222
|
const GROUPED_TOOLS_PER_PANEL = 8;
|
|
1218
1223
|
const GROUPED_PAGE_ACTIVITY_BYTES = 24 * 1024;
|
|
1219
1224
|
const GROUPED_PAGE_ACTIVITY_COMPONENTS = 160;
|
|
1225
|
+
const PINNED_LIVE_COMMENTARIES = 3;
|
|
1220
1226
|
const MAX_LIVE_ASSISTANT_TEXT = 2_000;
|
|
1221
1227
|
const ACTIVITY_TEXT_CHUNK = 2_500;
|
|
1222
1228
|
function activityPages(activities) {
|
|
@@ -1234,6 +1240,9 @@ function activityPages(activities) {
|
|
|
1234
1240
|
}
|
|
1235
1241
|
function groupedActivityPages(activities, projectCwd, renderOptions = {}) {
|
|
1236
1242
|
const groups = groupTurnActivities(activities).flatMap(splitGroupedExecutionActivity);
|
|
1243
|
+
return groupedActivityPagesFromGroups(groups, projectCwd, renderOptions);
|
|
1244
|
+
}
|
|
1245
|
+
function groupedActivityPagesFromGroups(groups, projectCwd, renderOptions = {}) {
|
|
1237
1246
|
if (groups.length === 0)
|
|
1238
1247
|
return [];
|
|
1239
1248
|
const newestFirst = [];
|
|
@@ -1256,6 +1265,69 @@ function groupedActivityPageFits(groups, projectCwd, renderOptions) {
|
|
|
1256
1265
|
return Buffer.byteLength(JSON.stringify(elements), "utf8") <= GROUPED_PAGE_ACTIVITY_BYTES
|
|
1257
1266
|
&& countCardComponents(elements) <= GROUPED_PAGE_ACTIVITY_COMPONENTS;
|
|
1258
1267
|
}
|
|
1268
|
+
function groupedLiveActivityPage(pages, projectCwd) {
|
|
1269
|
+
const latest = pages.at(-1) ?? [];
|
|
1270
|
+
if (pages.length <= 1)
|
|
1271
|
+
return { groups: latest, historyGroups: [] };
|
|
1272
|
+
const groups = pages.flat();
|
|
1273
|
+
const latestStart = groups.length - latest.length;
|
|
1274
|
+
const commentaryIndexes = groups
|
|
1275
|
+
.map((group, index) => isCommentaryGroup(group) ? index : -1)
|
|
1276
|
+
.filter((index) => index >= 0);
|
|
1277
|
+
if (commentaryIndexes.length === 0) {
|
|
1278
|
+
return { groups: latest, historyGroups: groups.slice(0, latestStart) };
|
|
1279
|
+
}
|
|
1280
|
+
const selected = new Set();
|
|
1281
|
+
const pinnedCommentaries = commentaryIndexes.slice(0, PINNED_LIVE_COMMENTARIES);
|
|
1282
|
+
for (const index of pinnedCommentaries)
|
|
1283
|
+
selected.add(index);
|
|
1284
|
+
const latestCommentary = commentaryIndexes.at(-1);
|
|
1285
|
+
for (let index = latestCommentary; index < groups.length; index += 1)
|
|
1286
|
+
selected.add(index);
|
|
1287
|
+
const renderSelection = (selection) => ({
|
|
1288
|
+
groups: groupsWithGaps(groups, selection),
|
|
1289
|
+
historyGroups: groups.filter((_group, index) => index < latestCommentary || !selection.has(index)),
|
|
1290
|
+
});
|
|
1291
|
+
const fits = (selection) => {
|
|
1292
|
+
const view = renderSelection(selection);
|
|
1293
|
+
return groupedActivityPageFits(view.groups, projectCwd, {});
|
|
1294
|
+
};
|
|
1295
|
+
if (!fits(selected)) {
|
|
1296
|
+
const latestExecutions = [...selected]
|
|
1297
|
+
.filter((index) => index > latestCommentary && groups[index]?.kind === "execution")
|
|
1298
|
+
.sort((left, right) => left - right);
|
|
1299
|
+
const newestExecution = latestExecutions.at(-1);
|
|
1300
|
+
for (const index of latestExecutions) {
|
|
1301
|
+
if (fits(selected) || index === newestExecution)
|
|
1302
|
+
break;
|
|
1303
|
+
selected.delete(index);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return renderSelection(selected);
|
|
1307
|
+
}
|
|
1308
|
+
function isCommentaryGroup(group) {
|
|
1309
|
+
return group.kind === "activity" && isCommentaryActivity(group.activity);
|
|
1310
|
+
}
|
|
1311
|
+
function groupsWithGaps(groups, selected) {
|
|
1312
|
+
const indexes = [...selected].sort((left, right) => left - right);
|
|
1313
|
+
const visible = [];
|
|
1314
|
+
const append = (group) => {
|
|
1315
|
+
if (group.kind === "gap" && visible.at(-1)?.kind === "gap")
|
|
1316
|
+
return;
|
|
1317
|
+
visible.push(group);
|
|
1318
|
+
};
|
|
1319
|
+
let previous = -1;
|
|
1320
|
+
for (const index of indexes) {
|
|
1321
|
+
if (previous >= 0 && index > previous + 1) {
|
|
1322
|
+
append({ kind: "gap", id: `gap:${previous}:${index}` });
|
|
1323
|
+
}
|
|
1324
|
+
const group = groups[index];
|
|
1325
|
+
if (group)
|
|
1326
|
+
append(group);
|
|
1327
|
+
previous = index;
|
|
1328
|
+
}
|
|
1329
|
+
return visible;
|
|
1330
|
+
}
|
|
1259
1331
|
function countCardComponents(value) {
|
|
1260
1332
|
if (Array.isArray(value)) {
|
|
1261
1333
|
return value.reduce((total, entry) => total + countCardComponents(entry), 0);
|
|
@@ -1415,12 +1487,21 @@ function renderToolDetails(tool, projectCwd) {
|
|
|
1415
1487
|
const result = tool.error ?? tool.output ?? fileSummary;
|
|
1416
1488
|
const normalizedCommand = stripAnsi(command).trim();
|
|
1417
1489
|
const displayCommand = tool.kind === "command"
|
|
1418
|
-
?
|
|
1490
|
+
? unwrapShellCommand(normalizedCommand) ?? normalizedCommand
|
|
1419
1491
|
: normalizedCommand;
|
|
1420
|
-
const commandText = truncateText(displayCommand,
|
|
1421
|
-
const resultText = result ?
|
|
1492
|
+
const commandText = truncateText(displayCommand, 600);
|
|
1493
|
+
const resultText = result ? truncateToolResult(stripAnsi(result).trim()) : undefined;
|
|
1422
1494
|
return codeBlock([`$ ${commandText}`, resultText].filter((part) => part !== undefined).join("\n"), 2_003);
|
|
1423
1495
|
}
|
|
1496
|
+
function truncateToolResult(result) {
|
|
1497
|
+
const maxLength = 900;
|
|
1498
|
+
const marker = "\n...\n";
|
|
1499
|
+
const tailLength = 600;
|
|
1500
|
+
if (result.length <= maxLength)
|
|
1501
|
+
return result;
|
|
1502
|
+
const headLength = maxLength - marker.length - tailLength;
|
|
1503
|
+
return `${result.slice(0, headLength)}${marker}${result.slice(-tailLength)}`;
|
|
1504
|
+
}
|
|
1424
1505
|
function displayFilePath(filePath, projectCwd) {
|
|
1425
1506
|
if (!projectCwd)
|
|
1426
1507
|
return filePath;
|
|
@@ -1447,7 +1528,7 @@ function toolPanelTitle(tool) {
|
|
|
1447
1528
|
const command = stripAnsi(tool.command ?? tool.title).trim();
|
|
1448
1529
|
const meaningfulCommand = tool.kind === "web_search"
|
|
1449
1530
|
? tool.title
|
|
1450
|
-
:
|
|
1531
|
+
: unwrapShellCommand(command) ?? tool.title;
|
|
1451
1532
|
const duration = toolDuration(tool);
|
|
1452
1533
|
const prefix = `${icon} `;
|
|
1453
1534
|
const suffix = duration ? ` · ${duration}` : "";
|
|
@@ -1494,6 +1575,9 @@ function formatTurnDuration(durationMs) {
|
|
|
1494
1575
|
return `${String(totalMinutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
1495
1576
|
return `${seconds}s`;
|
|
1496
1577
|
}
|
|
1578
|
+
function unwrapShellCommand(command) {
|
|
1579
|
+
return unwrapPowerShellCommand(command) ?? unwrapPosixShellCommand(command);
|
|
1580
|
+
}
|
|
1497
1581
|
function unwrapPowerShellCommand(command) {
|
|
1498
1582
|
const executable = command.match(/^(?:"[^"]*(?:pwsh|powershell)\.exe"|(?:\S*[\\/])?(?:pwsh|powershell)(?:\.exe)?)(?=\s|$)/i);
|
|
1499
1583
|
if (!executable)
|
|
@@ -1505,6 +1589,71 @@ function unwrapPowerShellCommand(command) {
|
|
|
1505
1589
|
const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
|
|
1506
1590
|
if (!payload)
|
|
1507
1591
|
return undefined;
|
|
1592
|
+
return unwrapQuotedPayload(payload);
|
|
1593
|
+
}
|
|
1594
|
+
function unwrapPosixShellCommand(command) {
|
|
1595
|
+
let executable = readCommandWord(command, 0);
|
|
1596
|
+
if (!executable)
|
|
1597
|
+
return undefined;
|
|
1598
|
+
if (posixExecutableName(executable.value) === "env") {
|
|
1599
|
+
let cursor = executable.end;
|
|
1600
|
+
while (true) {
|
|
1601
|
+
const candidate = readCommandWord(command, cursor);
|
|
1602
|
+
if (!candidate)
|
|
1603
|
+
return undefined;
|
|
1604
|
+
const name = posixExecutableName(candidate.value);
|
|
1605
|
+
if (isPosixShell(name)) {
|
|
1606
|
+
executable = candidate;
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
if (!candidate.value.startsWith("-") && !/^[A-Za-z_][A-Za-z0-9_]*=/.test(candidate.value)) {
|
|
1610
|
+
return undefined;
|
|
1611
|
+
}
|
|
1612
|
+
cursor = candidate.end;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
else if (!isPosixShell(posixExecutableName(executable.value))) {
|
|
1616
|
+
return undefined;
|
|
1617
|
+
}
|
|
1618
|
+
const args = command.slice(executable.end);
|
|
1619
|
+
const commandFlag = /(?:^|\s)(?:-[A-Za-z]*c[A-Za-z]*|--command)(?:\s+|=)/.exec(args);
|
|
1620
|
+
if (!commandFlag)
|
|
1621
|
+
return undefined;
|
|
1622
|
+
const payload = args.slice(commandFlag.index + commandFlag[0].length).trim();
|
|
1623
|
+
if (!payload)
|
|
1624
|
+
return undefined;
|
|
1625
|
+
return unwrapQuotedPayload(payload);
|
|
1626
|
+
}
|
|
1627
|
+
function readCommandWord(command, start) {
|
|
1628
|
+
let cursor = start;
|
|
1629
|
+
while (cursor < command.length && /\s/.test(command[cursor] ?? ""))
|
|
1630
|
+
cursor += 1;
|
|
1631
|
+
if (cursor >= command.length)
|
|
1632
|
+
return undefined;
|
|
1633
|
+
const quote = command[cursor];
|
|
1634
|
+
if (quote === "\"" || quote === "'") {
|
|
1635
|
+
const endQuote = command.indexOf(quote, cursor + 1);
|
|
1636
|
+
if (endQuote < 0)
|
|
1637
|
+
return undefined;
|
|
1638
|
+
return { value: command.slice(cursor + 1, endQuote), end: endQuote + 1 };
|
|
1639
|
+
}
|
|
1640
|
+
const word = /^\S+/.exec(command.slice(cursor));
|
|
1641
|
+
if (!word)
|
|
1642
|
+
return undefined;
|
|
1643
|
+
return { value: word[0], end: cursor + word[0].length };
|
|
1644
|
+
}
|
|
1645
|
+
function posixExecutableName(executable) {
|
|
1646
|
+
return executable.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase() ?? "";
|
|
1647
|
+
}
|
|
1648
|
+
function isPosixShell(executable) {
|
|
1649
|
+
return executable === "sh"
|
|
1650
|
+
|| executable === "bash"
|
|
1651
|
+
|| executable === "zsh"
|
|
1652
|
+
|| executable === "dash"
|
|
1653
|
+
|| executable === "ksh"
|
|
1654
|
+
|| executable === "fish";
|
|
1655
|
+
}
|
|
1656
|
+
function unwrapQuotedPayload(payload) {
|
|
1508
1657
|
const quote = payload[0];
|
|
1509
1658
|
if ((quote === "\"" || quote === "'") && payload.at(-1) === quote)
|
|
1510
1659
|
return payload.slice(1, -1).trim();
|