@dungsil/dsh-plugin-ui-vcs 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +448 -141
- package/dist/index.js +510 -112
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
3
|
-
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
3
|
+
import { Session, SessionId } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
5
5
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { homedir } from "node:os";
|
|
6
8
|
// \0rolldown/runtime.js
|
|
7
9
|
var __defProp = Object.defineProperty;
|
|
8
10
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -899,19 +901,82 @@ defineMethod("transform", [
|
|
|
899
901
|
"callback",
|
|
900
902
|
"preserve"
|
|
901
903
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
904
|
+
// src/inspect-session.ts
|
|
905
|
+
async function inspectSession(ctx, id, signal) {
|
|
906
|
+
signal.throwIfAborted();
|
|
907
|
+
const live = ctx.get("agents")?.get(id);
|
|
908
|
+
if (live) return live.session;
|
|
909
|
+
const inspection = await ctx.sessionController.inspect(id, signal);
|
|
910
|
+
signal.throwIfAborted();
|
|
911
|
+
return Session.create(id, inspection.events, inspection.meta, inspection.inheritedEventCount);
|
|
912
|
+
}
|
|
913
|
+
// ../../tools/async/src/index.ts
|
|
914
|
+
function createTaskQueue({ concurrency, maxPending }) {
|
|
915
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1 || !Number.isSafeInteger(maxPending) || maxPending < 0) throw new RangeError("Invalid task queue limits");
|
|
916
|
+
let active = 0;
|
|
917
|
+
const pending = /* @__PURE__ */ new Set();
|
|
918
|
+
const drain = () => {
|
|
919
|
+
while (active < concurrency && pending.size) pending.values().next().value.start();
|
|
920
|
+
};
|
|
921
|
+
return (signal, task) => {
|
|
922
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
923
|
+
if (active >= concurrency && pending.size >= maxPending) return Promise.reject(/* @__PURE__ */ new Error("busy"));
|
|
924
|
+
return new Promise((resolve, reject) => {
|
|
925
|
+
const entry = {
|
|
926
|
+
start() {
|
|
927
|
+
pending.delete(entry);
|
|
928
|
+
signal.removeEventListener("abort", entry.cancel);
|
|
929
|
+
active++;
|
|
930
|
+
(async () => {
|
|
931
|
+
try {
|
|
932
|
+
signal.throwIfAborted();
|
|
933
|
+
resolve(await task());
|
|
934
|
+
} catch (error) {
|
|
935
|
+
reject(error);
|
|
936
|
+
} finally {
|
|
937
|
+
active--;
|
|
938
|
+
drain();
|
|
939
|
+
}
|
|
940
|
+
})();
|
|
941
|
+
},
|
|
942
|
+
cancel() {
|
|
943
|
+
pending.delete(entry);
|
|
944
|
+
signal.removeEventListener("abort", entry.cancel);
|
|
945
|
+
reject(signal.reason);
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
pending.add(entry);
|
|
949
|
+
signal.addEventListener("abort", entry.cancel, { once: true });
|
|
950
|
+
drain();
|
|
951
|
+
});
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
// src/read-queue.ts
|
|
955
|
+
const queues = /* @__PURE__ */ new WeakMap();
|
|
956
|
+
function readQueue(provider) {
|
|
957
|
+
let queue = queues.get(provider);
|
|
958
|
+
if (!queue) {
|
|
959
|
+
queue = createTaskQueue({
|
|
960
|
+
concurrency: 2,
|
|
961
|
+
maxPending: 128
|
|
962
|
+
});
|
|
963
|
+
queues.set(provider, queue);
|
|
964
|
+
}
|
|
965
|
+
return queue;
|
|
966
|
+
}
|
|
902
967
|
// src/pull-requests-protocol.ts
|
|
903
968
|
const channel$1 = "/api";
|
|
904
969
|
const method = "dungsil-vcs/pull-requests/list";
|
|
905
970
|
const actionMethod = "dungsil-vcs/pull-requests/action";
|
|
906
971
|
// src/pull-requests.ts
|
|
907
972
|
var pull_requests_exports = /* @__PURE__ */ __exportAll({
|
|
908
|
-
Config: () => Config,
|
|
909
|
-
apply: () => apply$
|
|
910
|
-
inject: () => inject$
|
|
911
|
-
name: () => name$
|
|
973
|
+
Config: () => Config$1,
|
|
974
|
+
apply: () => apply$3,
|
|
975
|
+
inject: () => inject$3,
|
|
976
|
+
name: () => name$3
|
|
912
977
|
});
|
|
913
|
-
const name$
|
|
914
|
-
const inject$
|
|
978
|
+
const name$3 = "dungsil-vcs-pull-requests";
|
|
979
|
+
const inject$3 = [
|
|
915
980
|
"connection",
|
|
916
981
|
"sessionController",
|
|
917
982
|
"workingDirectory",
|
|
@@ -919,7 +984,7 @@ const inject$2 = [
|
|
|
919
984
|
"sandbox",
|
|
920
985
|
"sandboxPolicy"
|
|
921
986
|
];
|
|
922
|
-
const Config = Schema.object({ provider: Schema.union([
|
|
987
|
+
const Config$1 = Schema.object({ provider: Schema.union([
|
|
923
988
|
"auto",
|
|
924
989
|
"github",
|
|
925
990
|
"gitlab",
|
|
@@ -971,12 +1036,13 @@ function actionInput$1(value) {
|
|
|
971
1036
|
].includes(input.strategy)) || input.action === "merge" && input.body !== void 0) throw new Error("invalid_payload");
|
|
972
1037
|
return input;
|
|
973
1038
|
}
|
|
974
|
-
function apply$
|
|
1039
|
+
function apply$3(ctx, config) {
|
|
975
1040
|
const lifetime = new AbortController();
|
|
976
1041
|
const actionsInFlight = /* @__PURE__ */ new Set();
|
|
1042
|
+
const read = readQueue(ctx.subprocess);
|
|
977
1043
|
ctx.effect(() => () => lifetime.abort());
|
|
978
|
-
const list = async (
|
|
979
|
-
const cwd = ctx.workingDirectory.get(
|
|
1044
|
+
const list = async (session, signal, action, requested, summary = false) => {
|
|
1045
|
+
const cwd = ctx.workingDirectory.get(session);
|
|
980
1046
|
const run = async (executable, args, failure = "command_failed", allowed = [0], trim = true) => {
|
|
981
1047
|
let resolved;
|
|
982
1048
|
try {
|
|
@@ -985,7 +1051,7 @@ function apply$2(ctx, config) {
|
|
|
985
1051
|
throw new Error(executable === "git" ? "repository" : "cli_missing");
|
|
986
1052
|
}
|
|
987
1053
|
const argv = [resolved, ...args];
|
|
988
|
-
const policy = ctx.sandboxPolicy.resolve({ session
|
|
1054
|
+
const policy = ctx.sandboxPolicy.resolve({ session });
|
|
989
1055
|
const command = policy.mode === "danger-full-access" ? argv : (await ctx.sandbox.confine(argv, {
|
|
990
1056
|
...policy,
|
|
991
1057
|
mode: policy.mode
|
|
@@ -1118,7 +1184,7 @@ function apply$2(ctx, config) {
|
|
|
1118
1184
|
if (requested.origin !== new URL(origin).origin || provider === "github" && repository.split("/").length !== 2 || !match || !Number.isSafeInteger(Number(match[2]))) throw new Error("unsupported");
|
|
1119
1185
|
if (provider === "github" ? match[1].toLowerCase() !== prefix.toLowerCase() : match[1] !== prefix) {
|
|
1120
1186
|
if (provider === "gitlab") throw new Error("unsupported");
|
|
1121
|
-
const associated = await list(
|
|
1187
|
+
const associated = await list(session, signal, void 0, void 0, summary);
|
|
1122
1188
|
const linkedUrl = `${requested.origin}${match[1]}/${provider === "github" ? "pull" : forge ? "pulls" : "-/merge_requests"}/${match[2]}`;
|
|
1123
1189
|
if ("request" in associated && associated.request && (provider === "github" ? associated.request.url.toLowerCase() === linkedUrl.toLowerCase() : associated.request.url === linkedUrl)) return associated;
|
|
1124
1190
|
throw new Error("unsupported");
|
|
@@ -1713,14 +1779,24 @@ function apply$2(ctx, config) {
|
|
|
1713
1779
|
inFlight = action.sessionId;
|
|
1714
1780
|
}
|
|
1715
1781
|
const requested = route === "dungsil-vcs/pull-requests/list" && input.requestUrl !== void 0 ? lookupUrl(input.requestUrl) : void 0;
|
|
1716
|
-
const resolved = await ctx.sessionController.resolveAgent(SessionId(input.sessionId));
|
|
1717
|
-
if ("error" in resolved) throw new Error("unavailable");
|
|
1718
1782
|
const signal = AbortSignal.any([
|
|
1719
1783
|
request.signal,
|
|
1720
1784
|
lifetime.signal,
|
|
1721
1785
|
AbortSignal.timeout(2e4)
|
|
1722
1786
|
]);
|
|
1723
|
-
const
|
|
1787
|
+
const sessionId = SessionId(input.sessionId);
|
|
1788
|
+
const execute = async () => {
|
|
1789
|
+
signal.throwIfAborted();
|
|
1790
|
+
let session;
|
|
1791
|
+
if (action) {
|
|
1792
|
+
const resolved = await ctx.sessionController.resolveAgent(sessionId);
|
|
1793
|
+
if ("error" in resolved) throw new Error("unavailable");
|
|
1794
|
+
signal.throwIfAborted();
|
|
1795
|
+
session = resolved.agent.session;
|
|
1796
|
+
} else session = await inspectSession(ctx, sessionId, signal);
|
|
1797
|
+
return list(session, signal, action, requested, input.summary === true);
|
|
1798
|
+
};
|
|
1799
|
+
const value = action ? await execute() : await read(signal, execute);
|
|
1724
1800
|
if (action && !("completed" in value)) throw new Error("stale_request");
|
|
1725
1801
|
return reply({
|
|
1726
1802
|
ok: true,
|
|
@@ -4514,14 +4590,23 @@ async function generateTexts(ctx, agent, operation, context, signal) {
|
|
|
4514
4590
|
// src/protocol.ts
|
|
4515
4591
|
const channel = "/api";
|
|
4516
4592
|
const executeMethod = "dungsil-vcs/execute";
|
|
4593
|
+
function validCommitSettings(value) {
|
|
4594
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4595
|
+
const input = value;
|
|
4596
|
+
return Object.keys(input).every((key) => [
|
|
4597
|
+
"authorName",
|
|
4598
|
+
"authorEmail",
|
|
4599
|
+
"noSign"
|
|
4600
|
+
].includes(key)) && typeof input.authorName === "string" && input.authorName.length <= 200 && !/[<>]/.test(input.authorName) && !Array.from(input.authorName).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) && typeof input.authorEmail === "string" && input.authorEmail.length <= 254 && (input.authorEmail === "" || /^[^\s<>@]+@[^\s<>@]+$/.test(input.authorEmail)) && !Array.from(input.authorEmail).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) && (input.authorName === "" ? input.authorEmail === "" : !!input.authorName.trim() && !!input.authorEmail) && typeof input.noSign === "boolean";
|
|
4601
|
+
}
|
|
4517
4602
|
// src/commands.ts
|
|
4518
4603
|
var commands_exports = /* @__PURE__ */ __exportAll({
|
|
4519
|
-
apply: () => apply$
|
|
4520
|
-
inject: () => inject$
|
|
4521
|
-
name: () => name$
|
|
4604
|
+
apply: () => apply$2,
|
|
4605
|
+
inject: () => inject$2,
|
|
4606
|
+
name: () => name$2
|
|
4522
4607
|
});
|
|
4523
|
-
const name$
|
|
4524
|
-
const inject$
|
|
4608
|
+
const name$2 = "dungsil-vcs-commands";
|
|
4609
|
+
const inject$2 = [
|
|
4525
4610
|
"connection",
|
|
4526
4611
|
"sessionController",
|
|
4527
4612
|
"workingDirectory",
|
|
@@ -4546,7 +4631,7 @@ function actionInput(value) {
|
|
|
4546
4631
|
].includes(input.operation) || typeof input.requestId !== "string" || !/^[a-zA-Z0-9-]{16,100}$/.test(input.requestId)) throw new Error("invalid");
|
|
4547
4632
|
return input;
|
|
4548
4633
|
}
|
|
4549
|
-
function apply$
|
|
4634
|
+
function apply$2(ctx, config) {
|
|
4550
4635
|
const lifetime = new AbortController();
|
|
4551
4636
|
const pending = /* @__PURE__ */ new Set();
|
|
4552
4637
|
const repositories = /* @__PURE__ */ new Set();
|
|
@@ -4561,6 +4646,12 @@ function apply$1(ctx, config) {
|
|
|
4561
4646
|
}
|
|
4562
4647
|
});
|
|
4563
4648
|
const execute = async (agent, input, signal) => {
|
|
4649
|
+
const settings = {
|
|
4650
|
+
authorName: config.authorName ?? "",
|
|
4651
|
+
authorEmail: config.authorEmail ?? "",
|
|
4652
|
+
noSign: config.noSign ?? false
|
|
4653
|
+
};
|
|
4654
|
+
if (!validCommitSettings(settings)) throw new Error("invalid");
|
|
4564
4655
|
if (ctx.sandboxPolicy.resolve({ session: agent.session }).mode === "read-only") throw new Error("read_only");
|
|
4565
4656
|
const cwd = await ctx.workingDirectory.ensure(agent, signal);
|
|
4566
4657
|
if (cwd !== input.cwd) throw new Error("conflict");
|
|
@@ -4810,6 +4901,8 @@ function apply$1(ctx, config) {
|
|
|
4810
4901
|
]);
|
|
4811
4902
|
await git([
|
|
4812
4903
|
"commit",
|
|
4904
|
+
...settings.authorName ? [`--author=${settings.authorName} <${settings.authorEmail}>`] : [],
|
|
4905
|
+
...settings.noSign ? ["--no-gpg-sign"] : [],
|
|
4813
4906
|
"--only",
|
|
4814
4907
|
"-m",
|
|
4815
4908
|
texts.commitMessage,
|
|
@@ -4906,6 +4999,303 @@ function apply$1(ctx, config) {
|
|
|
4906
4999
|
}
|
|
4907
5000
|
}), "ui-vcs: owner-only commands");
|
|
4908
5001
|
}
|
|
5002
|
+
// src/cli-status.ts
|
|
5003
|
+
function object(value) {
|
|
5004
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid_status");
|
|
5005
|
+
return value;
|
|
5006
|
+
}
|
|
5007
|
+
function parse(output) {
|
|
5008
|
+
if (output.length > 1048576) throw new Error("invalid_status");
|
|
5009
|
+
return JSON.parse(output);
|
|
5010
|
+
}
|
|
5011
|
+
function safe(value) {
|
|
5012
|
+
return typeof value === "string" && !/(?:gh[pousr]_|github_pat_|gl(?:pat|oas|rt|cbt|ptt|ft|soat|dt)-|Bearer|eyJ)/i.test(value) && !/[a-f0-9]{20,}/i.test(value) && !/[a-z0-9_+-]{32,}/i.test(value);
|
|
5013
|
+
}
|
|
5014
|
+
function host(value) {
|
|
5015
|
+
if (!safe(value) || value.length > 253 || !/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?::[0-9]{1,5})?$/i.test(value)) throw new Error("invalid_identity");
|
|
5016
|
+
return value.toLowerCase();
|
|
5017
|
+
}
|
|
5018
|
+
function account(hostname, login, source) {
|
|
5019
|
+
if (!safe(login) || !/^[a-z0-9][a-z0-9_.-]{0,99}$/i.test(login)) throw new Error("invalid_identity");
|
|
5020
|
+
const result = {
|
|
5021
|
+
host: host(hostname),
|
|
5022
|
+
login
|
|
5023
|
+
};
|
|
5024
|
+
if ([
|
|
5025
|
+
"GH_TOKEN",
|
|
5026
|
+
"GITHUB_TOKEN",
|
|
5027
|
+
"GH_ENTERPRISE_TOKEN",
|
|
5028
|
+
"GITHUB_ENTERPRISE_TOKEN",
|
|
5029
|
+
"environment"
|
|
5030
|
+
].includes(String(source))) result.method = "environment";
|
|
5031
|
+
else if (source === "keyring") result.method = "keyring";
|
|
5032
|
+
else if (source === "config_file" || typeof source === "string" && /(?:^|[/\\])hosts\.yml$/.test(source)) result.method = "config-file";
|
|
5033
|
+
return result;
|
|
5034
|
+
}
|
|
5035
|
+
async function inspectAuth(cli, run) {
|
|
5036
|
+
const data = object(parse(await run(cli, cli === "gh" ? [
|
|
5037
|
+
"auth",
|
|
5038
|
+
"status",
|
|
5039
|
+
"--json",
|
|
5040
|
+
"hosts"
|
|
5041
|
+
] : [
|
|
5042
|
+
"auth",
|
|
5043
|
+
"status",
|
|
5044
|
+
"--all",
|
|
5045
|
+
"--output",
|
|
5046
|
+
"json"
|
|
5047
|
+
])));
|
|
5048
|
+
const rows = cli === "gh" ? Object.values(object(data.hosts)).flatMap((entries) => {
|
|
5049
|
+
if (!Array.isArray(entries)) throw new Error("invalid_status");
|
|
5050
|
+
return entries;
|
|
5051
|
+
}) : data.hosts;
|
|
5052
|
+
if (!Array.isArray(rows)) throw new Error("invalid_status");
|
|
5053
|
+
const accounts = [];
|
|
5054
|
+
let failed = false;
|
|
5055
|
+
for (const row of rows) {
|
|
5056
|
+
const entry = object(row);
|
|
5057
|
+
if (entry.state === "success") try {
|
|
5058
|
+
accounts.push(account(entry.host, cli === "gh" ? entry.login : entry.user, cli === "gh" ? entry.tokenSource : entry.token_source));
|
|
5059
|
+
} catch {
|
|
5060
|
+
failed = true;
|
|
5061
|
+
}
|
|
5062
|
+
else if (entry.state !== "refresh_expired") failed = true;
|
|
5063
|
+
}
|
|
5064
|
+
return {
|
|
5065
|
+
cli,
|
|
5066
|
+
status: failed ? "error" : accounts.length ? "authenticated" : "unauthenticated",
|
|
5067
|
+
accounts
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
async function inspectTea(run) {
|
|
5071
|
+
const logins = parse(await run("tea", [
|
|
5072
|
+
"login",
|
|
5073
|
+
"list",
|
|
5074
|
+
"--output",
|
|
5075
|
+
"json"
|
|
5076
|
+
]));
|
|
5077
|
+
if (!Array.isArray(logins)) throw new Error("invalid_status");
|
|
5078
|
+
const accounts = [];
|
|
5079
|
+
let failed = false;
|
|
5080
|
+
for (const row of logins) try {
|
|
5081
|
+
const entry = object(row);
|
|
5082
|
+
if (typeof entry.name !== "string" || !entry.name || entry.name.length > 256 || entry.name.split("").some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) throw new Error("invalid_status");
|
|
5083
|
+
if (typeof entry.url !== "string") throw new Error("invalid_status");
|
|
5084
|
+
const url = new URL(entry.url);
|
|
5085
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("invalid_identity");
|
|
5086
|
+
const hostname = host(url.host);
|
|
5087
|
+
const user = object(parse(await run("tea", [
|
|
5088
|
+
"api",
|
|
5089
|
+
"--method",
|
|
5090
|
+
"GET",
|
|
5091
|
+
"--login",
|
|
5092
|
+
entry.name,
|
|
5093
|
+
"user"
|
|
5094
|
+
])));
|
|
5095
|
+
if (typeof user.id !== "number" || !Number.isSafeInteger(user.id) || user.id <= 0) throw new Error("invalid_user");
|
|
5096
|
+
accounts.push(account(hostname, user.login));
|
|
5097
|
+
} catch (error) {
|
|
5098
|
+
if (error instanceof Error && error.message === "cli_missing") throw error;
|
|
5099
|
+
failed = true;
|
|
5100
|
+
}
|
|
5101
|
+
return {
|
|
5102
|
+
cli: "tea",
|
|
5103
|
+
status: failed ? "error" : accounts.length ? "authenticated" : "unauthenticated",
|
|
5104
|
+
accounts
|
|
5105
|
+
};
|
|
5106
|
+
}
|
|
5107
|
+
/** The runner must reject resolution failures with Error('cli_missing'), and
|
|
5108
|
+
* reject nonzero exits, policy failures and timeouts with any other error.
|
|
5109
|
+
* It must bound execution/output and disable prompts. No stdout/stderr is ever
|
|
5110
|
+
* returned, including when older CLIs do not support these structured queries.
|
|
5111
|
+
*/
|
|
5112
|
+
async function inspectCliStatus(run) {
|
|
5113
|
+
return Promise.all([
|
|
5114
|
+
"gh",
|
|
5115
|
+
"glab",
|
|
5116
|
+
"tea"
|
|
5117
|
+
].map(async (cli) => {
|
|
5118
|
+
try {
|
|
5119
|
+
return await (cli === "tea" ? inspectTea(run) : inspectAuth(cli, run));
|
|
5120
|
+
} catch (error) {
|
|
5121
|
+
return {
|
|
5122
|
+
cli,
|
|
5123
|
+
status: error instanceof Error && error.message === "cli_missing" ? "unavailable" : "error",
|
|
5124
|
+
accounts: []
|
|
5125
|
+
};
|
|
5126
|
+
}
|
|
5127
|
+
}));
|
|
5128
|
+
}
|
|
5129
|
+
// src/settings.ts
|
|
5130
|
+
var settings_exports = /* @__PURE__ */ __exportAll({
|
|
5131
|
+
Config: () => Config,
|
|
5132
|
+
apply: () => apply$1,
|
|
5133
|
+
inject: () => inject$1,
|
|
5134
|
+
name: () => name$1
|
|
5135
|
+
});
|
|
5136
|
+
const name$1 = "dungsil-vcs-settings";
|
|
5137
|
+
const inject$1 = [
|
|
5138
|
+
"connection",
|
|
5139
|
+
"configEditor",
|
|
5140
|
+
"subprocess",
|
|
5141
|
+
"sandbox",
|
|
5142
|
+
"sandboxPolicy"
|
|
5143
|
+
];
|
|
5144
|
+
const Config = Schema.object({
|
|
5145
|
+
provider: Schema.union([
|
|
5146
|
+
"auto",
|
|
5147
|
+
"github",
|
|
5148
|
+
"gitlab",
|
|
5149
|
+
"gitea",
|
|
5150
|
+
"forgejo"
|
|
5151
|
+
]).default("auto"),
|
|
5152
|
+
authorName: Schema.string().default(""),
|
|
5153
|
+
authorEmail: Schema.string().default(""),
|
|
5154
|
+
noSign: Schema.boolean().default(false)
|
|
5155
|
+
});
|
|
5156
|
+
function apply$1(ctx) {
|
|
5157
|
+
const editor = ctx.configEditor;
|
|
5158
|
+
const lifetime = new AbortController();
|
|
5159
|
+
ctx.effect(() => () => lifetime.abort());
|
|
5160
|
+
const entry = () => {
|
|
5161
|
+
const entries = editor.entries().filter((item) => item.options.name === "@dungsil/dsh-plugin-ui-vcs");
|
|
5162
|
+
if (entries.length !== 1) throw new Error("unavailable");
|
|
5163
|
+
return entries[0];
|
|
5164
|
+
};
|
|
5165
|
+
const snapshot = () => {
|
|
5166
|
+
const current = entry().options.config;
|
|
5167
|
+
return {
|
|
5168
|
+
revision: createHash("sha256").update(JSON.stringify(current ?? {})).digest("hex"),
|
|
5169
|
+
settings: {
|
|
5170
|
+
authorName: current?.authorName ?? "",
|
|
5171
|
+
authorEmail: current?.authorEmail ?? "",
|
|
5172
|
+
noSign: current?.noSign ?? false
|
|
5173
|
+
}
|
|
5174
|
+
};
|
|
5175
|
+
};
|
|
5176
|
+
for (const endpoint of [
|
|
5177
|
+
"settings",
|
|
5178
|
+
"save-settings",
|
|
5179
|
+
"cli-status"
|
|
5180
|
+
]) {
|
|
5181
|
+
const method = `dungsil-vcs/${endpoint}`;
|
|
5182
|
+
ctx.effect(() => ctx.connection.fetch.register({
|
|
5183
|
+
path: `${channel}/${method}`,
|
|
5184
|
+
methods: ["POST"],
|
|
5185
|
+
requestBody: "buffered",
|
|
5186
|
+
async fetch(request) {
|
|
5187
|
+
let rpcId = "";
|
|
5188
|
+
let result;
|
|
5189
|
+
try {
|
|
5190
|
+
const body = clientRequestSchema.parse(await request.json());
|
|
5191
|
+
rpcId = body.rpcId;
|
|
5192
|
+
if (body.method !== method) throw new Error("invalid");
|
|
5193
|
+
const signal = AbortSignal.any([
|
|
5194
|
+
request.signal,
|
|
5195
|
+
lifetime.signal,
|
|
5196
|
+
AbortSignal.timeout(3e4)
|
|
5197
|
+
]);
|
|
5198
|
+
signal.throwIfAborted();
|
|
5199
|
+
if (endpoint === "settings") result = {
|
|
5200
|
+
ok: true,
|
|
5201
|
+
value: snapshot()
|
|
5202
|
+
};
|
|
5203
|
+
else if (endpoint === "save-settings") {
|
|
5204
|
+
const input = body.payload;
|
|
5205
|
+
if (!input || typeof input.revision !== "string" || !validCommitSettings(input.settings)) throw new Error("invalid");
|
|
5206
|
+
const settings = input.settings;
|
|
5207
|
+
await editor.edit(entry(), (current) => {
|
|
5208
|
+
if (snapshot().revision !== input.revision) throw new Error("conflict");
|
|
5209
|
+
return {
|
|
5210
|
+
...current,
|
|
5211
|
+
...settings
|
|
5212
|
+
};
|
|
5213
|
+
});
|
|
5214
|
+
result = {
|
|
5215
|
+
ok: true,
|
|
5216
|
+
value: snapshot()
|
|
5217
|
+
};
|
|
5218
|
+
} else {
|
|
5219
|
+
const value = await inspectCliStatus(async (executable, args) => {
|
|
5220
|
+
const bounded = AbortSignal.any([signal, AbortSignal.timeout(1e4)]);
|
|
5221
|
+
let path;
|
|
5222
|
+
try {
|
|
5223
|
+
path = await ctx.subprocess.resolveExecutable(executable, void 0, bounded);
|
|
5224
|
+
} catch (error) {
|
|
5225
|
+
if (error instanceof Error && error.name === "SubprocessExecutableNotFoundError") throw new Error("cli_missing");
|
|
5226
|
+
throw error;
|
|
5227
|
+
}
|
|
5228
|
+
const argv = [path, ...args];
|
|
5229
|
+
const policy = ctx.sandboxPolicy.resolve();
|
|
5230
|
+
const command = policy.mode === "danger-full-access" ? argv : (await ctx.sandbox.confine(argv, {
|
|
5231
|
+
...policy,
|
|
5232
|
+
mode: policy.mode
|
|
5233
|
+
}, bounded)).argv;
|
|
5234
|
+
const child = ctx.subprocess.spawn({
|
|
5235
|
+
argv: command,
|
|
5236
|
+
cwd: homedir(),
|
|
5237
|
+
signal: bounded,
|
|
5238
|
+
graceMs: 1e3,
|
|
5239
|
+
env: {
|
|
5240
|
+
GH_PROMPT_DISABLED: "1",
|
|
5241
|
+
GLAB_PROMPT_DISABLED: "1",
|
|
5242
|
+
NO_COLOR: "1",
|
|
5243
|
+
GH_HOST: void 0,
|
|
5244
|
+
GH_REPO: void 0,
|
|
5245
|
+
GITLAB_HOST: void 0,
|
|
5246
|
+
GITLAB_REPO: void 0,
|
|
5247
|
+
TEA_LOGIN: void 0,
|
|
5248
|
+
TEA_REPO: void 0,
|
|
5249
|
+
...executable === "tea" ? {
|
|
5250
|
+
GH_TOKEN: void 0,
|
|
5251
|
+
GITEA_TOKEN: void 0,
|
|
5252
|
+
GITEA_INSTANCE_URL: void 0,
|
|
5253
|
+
GITEA_INSTANCE_SSH_HOST: void 0,
|
|
5254
|
+
GITEA_INSTANCE_INSECURE: void 0
|
|
5255
|
+
} : {}
|
|
5256
|
+
},
|
|
5257
|
+
stdio: {
|
|
5258
|
+
stdin: "ignore",
|
|
5259
|
+
stdout: { maxBytes: 131072 },
|
|
5260
|
+
stderr: { maxBytes: 16384 }
|
|
5261
|
+
}
|
|
5262
|
+
});
|
|
5263
|
+
try {
|
|
5264
|
+
const outcome = await child.done;
|
|
5265
|
+
bounded.throwIfAborted();
|
|
5266
|
+
const output = child.collected.stdout.readFrom(0);
|
|
5267
|
+
if (outcome.exitCode !== 0 || output.lossy) throw new Error("command_failed");
|
|
5268
|
+
return output.text;
|
|
5269
|
+
} finally {
|
|
5270
|
+
child.terminate();
|
|
5271
|
+
await child.waitForExit();
|
|
5272
|
+
}
|
|
5273
|
+
});
|
|
5274
|
+
signal.throwIfAborted();
|
|
5275
|
+
result = {
|
|
5276
|
+
ok: true,
|
|
5277
|
+
value
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5280
|
+
} catch (error) {
|
|
5281
|
+
result = {
|
|
5282
|
+
ok: false,
|
|
5283
|
+
error: {
|
|
5284
|
+
code: error instanceof Error && ["invalid", "conflict"].includes(error.message) ? error.message : "unavailable",
|
|
5285
|
+
message: "Could not confirm VCS settings.",
|
|
5286
|
+
details: {}
|
|
5287
|
+
}
|
|
5288
|
+
};
|
|
5289
|
+
}
|
|
5290
|
+
return Response.json({
|
|
5291
|
+
type: "server-response",
|
|
5292
|
+
rpcId,
|
|
5293
|
+
result
|
|
5294
|
+
}, { headers: { "Cache-Control": "no-store" } });
|
|
5295
|
+
}
|
|
5296
|
+
}), `ui-vcs: owner-only ${endpoint}`);
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
4909
5299
|
// src/index.ts
|
|
4910
5300
|
const name = "dungsil-ui-vcs";
|
|
4911
5301
|
const inject = [
|
|
@@ -4917,8 +5307,12 @@ const inject = [
|
|
|
4917
5307
|
"workingDirectory"
|
|
4918
5308
|
];
|
|
4919
5309
|
function apply(ctx, config) {
|
|
5310
|
+
const lifetime = new AbortController();
|
|
5311
|
+
const read = readQueue(ctx.subprocess);
|
|
5312
|
+
ctx.effect(() => () => lifetime.abort(), "ui-vcs: cancel context reads");
|
|
4920
5313
|
ctx.plugin(pull_requests_exports, config);
|
|
4921
5314
|
ctx.plugin(commands_exports, config);
|
|
5315
|
+
ctx.plugin(settings_exports);
|
|
4922
5316
|
ctx.effect(() => ctx.connection.fetch.register({
|
|
4923
5317
|
path: "/api/dungsil-vcs/describe",
|
|
4924
5318
|
methods: ["POST"],
|
|
@@ -4940,100 +5334,104 @@ function apply(ctx, config) {
|
|
|
4940
5334
|
if (body.method !== "dungsil-vcs/describe") throw new Error("Invalid method");
|
|
4941
5335
|
const sessionId = body.payload?.sessionId;
|
|
4942
5336
|
if (typeof sessionId !== "string" || !sessionId) throw new Error("Invalid session");
|
|
4943
|
-
const
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
const cwd = await ctx.workingDirectory.ensure(agent, signal);
|
|
4948
|
-
const git = async (args) => {
|
|
4949
|
-
const argv = [
|
|
4950
|
-
await ctx.subprocess.resolveExecutable("git", void 0, signal),
|
|
4951
|
-
"-c",
|
|
4952
|
-
"core.fsmonitor=false",
|
|
4953
|
-
"-c",
|
|
4954
|
-
"core.hooksPath=/dev/null",
|
|
4955
|
-
...args
|
|
4956
|
-
];
|
|
4957
|
-
const policy = ctx.sandboxPolicy.resolve({ session: agent.session });
|
|
4958
|
-
const command = policy.mode === "danger-full-access" ? argv : (await ctx.sandbox.confine(argv, {
|
|
4959
|
-
...policy,
|
|
4960
|
-
mode: policy.mode
|
|
4961
|
-
}, signal)).argv;
|
|
4962
|
-
const child = ctx.subprocess.spawn({
|
|
4963
|
-
argv: command,
|
|
4964
|
-
cwd,
|
|
4965
|
-
signal,
|
|
4966
|
-
graceMs: 1e3,
|
|
4967
|
-
env: {
|
|
4968
|
-
...Object.fromEntries(Object.keys(process.env).filter((key) => key.startsWith("GIT_")).map((key) => [key, void 0])),
|
|
4969
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
4970
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
4971
|
-
GIT_NO_LAZY_FETCH: "1"
|
|
4972
|
-
},
|
|
4973
|
-
stdio: {
|
|
4974
|
-
stdin: "ignore",
|
|
4975
|
-
stdout: { maxBytes: 65536 },
|
|
4976
|
-
stderr: { maxBytes: 4096 }
|
|
4977
|
-
}
|
|
4978
|
-
});
|
|
4979
|
-
try {
|
|
4980
|
-
const outcome = await child.done;
|
|
4981
|
-
signal.throwIfAborted();
|
|
4982
|
-
const output = child.collected.stdout.readFrom(0);
|
|
4983
|
-
if (outcome.exitCode !== 0 || output.lossy) throw new Error("Git read failed");
|
|
4984
|
-
return output.text.replace(/\r?\n$/, "");
|
|
4985
|
-
} finally {
|
|
4986
|
-
child.terminate();
|
|
4987
|
-
await child.waitForExit();
|
|
4988
|
-
}
|
|
4989
|
-
};
|
|
4990
|
-
const gitDir = await git([
|
|
4991
|
-
"rev-parse",
|
|
4992
|
-
"--path-format=absolute",
|
|
4993
|
-
"--git-dir"
|
|
4994
|
-
]);
|
|
4995
|
-
const commonDir = await git([
|
|
4996
|
-
"rev-parse",
|
|
4997
|
-
"--path-format=absolute",
|
|
4998
|
-
"--git-common-dir"
|
|
5337
|
+
const signal = AbortSignal.any([
|
|
5338
|
+
request.signal,
|
|
5339
|
+
lifetime.signal,
|
|
5340
|
+
AbortSignal.timeout(1e4)
|
|
4999
5341
|
]);
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
const
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5342
|
+
result = await read(signal, async () => {
|
|
5343
|
+
const session = await inspectSession(ctx, SessionId(sessionId), signal);
|
|
5344
|
+
const cwd = ctx.workingDirectory.get(session);
|
|
5345
|
+
const git = async (args) => {
|
|
5346
|
+
const argv = [
|
|
5347
|
+
await ctx.subprocess.resolveExecutable("git", void 0, signal),
|
|
5348
|
+
"-c",
|
|
5349
|
+
"core.fsmonitor=false",
|
|
5350
|
+
"-c",
|
|
5351
|
+
"core.hooksPath=/dev/null",
|
|
5352
|
+
...args
|
|
5353
|
+
];
|
|
5354
|
+
const policy = ctx.sandboxPolicy.resolve({ session });
|
|
5355
|
+
const command = policy.mode === "danger-full-access" ? argv : (await ctx.sandbox.confine(argv, {
|
|
5356
|
+
...policy,
|
|
5357
|
+
mode: policy.mode
|
|
5358
|
+
}, signal)).argv;
|
|
5359
|
+
const child = ctx.subprocess.spawn({
|
|
5360
|
+
argv: command,
|
|
5361
|
+
cwd,
|
|
5362
|
+
signal,
|
|
5363
|
+
graceMs: 1e3,
|
|
5364
|
+
env: {
|
|
5365
|
+
...Object.fromEntries(Object.keys(process.env).filter((key) => key.startsWith("GIT_")).map((key) => [key, void 0])),
|
|
5366
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
5367
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
5368
|
+
GIT_NO_LAZY_FETCH: "1"
|
|
5369
|
+
},
|
|
5370
|
+
stdio: {
|
|
5371
|
+
stdin: "ignore",
|
|
5372
|
+
stdout: { maxBytes: 65536 },
|
|
5373
|
+
stderr: { maxBytes: 4096 }
|
|
5374
|
+
}
|
|
5375
|
+
});
|
|
5376
|
+
try {
|
|
5377
|
+
const outcome = await child.done;
|
|
5378
|
+
signal.throwIfAborted();
|
|
5379
|
+
const output = child.collected.stdout.readFrom(0);
|
|
5380
|
+
if (outcome.exitCode !== 0 || output.lossy) throw new Error("Git read failed");
|
|
5381
|
+
return output.text.replace(/\r?\n$/, "");
|
|
5382
|
+
} finally {
|
|
5383
|
+
child.terminate();
|
|
5384
|
+
await child.waitForExit();
|
|
5385
|
+
}
|
|
5386
|
+
};
|
|
5387
|
+
const gitDir = await git([
|
|
5388
|
+
"rev-parse",
|
|
5389
|
+
"--path-format=absolute",
|
|
5390
|
+
"--git-dir"
|
|
5007
5391
|
]);
|
|
5008
|
-
const
|
|
5009
|
-
"
|
|
5010
|
-
"--format
|
|
5011
|
-
|
|
5392
|
+
const commonDir = await git([
|
|
5393
|
+
"rev-parse",
|
|
5394
|
+
"--path-format=absolute",
|
|
5395
|
+
"--git-common-dir"
|
|
5012
5396
|
]);
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5397
|
+
let operation = "pr";
|
|
5398
|
+
if (gitDir === commonDir) {
|
|
5399
|
+
const branch = await git([
|
|
5400
|
+
"symbolic-ref",
|
|
5401
|
+
"--quiet",
|
|
5402
|
+
"--short",
|
|
5403
|
+
"HEAD"
|
|
5404
|
+
]);
|
|
5405
|
+
const upstream = await git([
|
|
5406
|
+
"for-each-ref",
|
|
5407
|
+
"--format=%(upstream:remotename)",
|
|
5408
|
+
`refs/heads/${branch}`
|
|
5409
|
+
]);
|
|
5410
|
+
const remotes = (await git(["remote"])).split("\n").filter(Boolean);
|
|
5411
|
+
const remote = upstream && upstream !== "." ? upstream : remotes.includes("origin") ? "origin" : remotes.length === 1 ? remotes[0] : void 0;
|
|
5412
|
+
if (!remote) throw new Error("Default remote unknown");
|
|
5413
|
+
operation = await git([
|
|
5414
|
+
"symbolic-ref",
|
|
5415
|
+
"--quiet",
|
|
5416
|
+
`refs/remotes/${remote}/HEAD`
|
|
5417
|
+
]) === `refs/remotes/${remote}/${branch}` ? "push" : "pr";
|
|
5034
5418
|
}
|
|
5035
|
-
|
|
5036
|
-
|
|
5419
|
+
const headSha = await git(["rev-parse", "HEAD"]);
|
|
5420
|
+
const dirty = !!await git([
|
|
5421
|
+
"status",
|
|
5422
|
+
"--porcelain",
|
|
5423
|
+
"--untracked-files=normal"
|
|
5424
|
+
]);
|
|
5425
|
+
if (ctx.workingDirectory.get(session) !== cwd) throw new Error("Working directory changed");
|
|
5426
|
+
return {
|
|
5427
|
+
ok: true,
|
|
5428
|
+
value: {
|
|
5429
|
+
operation,
|
|
5430
|
+
headSha,
|
|
5431
|
+
dirty
|
|
5432
|
+
}
|
|
5433
|
+
};
|
|
5434
|
+
});
|
|
5037
5435
|
} catch {
|
|
5038
5436
|
result = failure;
|
|
5039
5437
|
}
|