mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
feat: finish the ai plugin (B18)
This commit is contained in:
1 parent
6b4d1432ab
commit
21937a6b02
193 files changed
+23429
-4244
No files matched your search
@@ -254,6 +254,35 @@ export function createPluginHosts({ manifest, bag, audit }: Deps): PluginHosts {
|
||||
: null;
|
||||
},
|
||||
|
||||
delete: async (hostId: number): Promise<boolean> => {
|
||||
try {
|
||||
await requireWrite();
|
||||
const userId = actingUser();
|
||||
const { PermissionManager } =
|
||||
await import("../utils/permission-manager.js");
|
||||
if (
|
||||
!(await PermissionManager.getInstance().hasPermission(
|
||||
userId,
|
||||
"hosts.delete",
|
||||
))
|
||||
) {
|
||||
throw new Error("The acting user may not delete hosts");
|
||||
}
|
||||
const { deleteOwnedHost } = await import("../hosts/delete-host.js");
|
||||
const deleted = await deleteOwnedHost(userId, hostId);
|
||||
await audit("hosts_delete", `host ${hostId}`, {
|
||||
success: deleted !== null,
|
||||
});
|
||||
return deleted !== null;
|
||||
} catch (error) {
|
||||
await audit("hosts_delete", `host ${hostId}`, {
|
||||
success: false,
|
||||
errorMessage: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
listOwned: async (): Promise<PluginHostRecord[]> => {
|
||||
await requireWrite();
|
||||
const userId = actingUser();
|
||||
|
||||
@@ -132,6 +132,11 @@ export function createPluginFetch(deps: Deps): PluginFetch {
|
||||
() => controller.abort(),
|
||||
Math.max(init.timeoutMs ?? 30_000, 1),
|
||||
);
|
||||
// The caller's signal keeps working after the headers arrive, so it
|
||||
// can stop a streamed body; the timeout only covers the wait for them.
|
||||
const onAbort = () => controller.abort();
|
||||
if (init.signal?.aborted) controller.abort();
|
||||
init.signal?.addEventListener("abort", onAbort, { once: true });
|
||||
try {
|
||||
return await safeOutboundFetch(
|
||||
url,
|
||||
|
||||
@@ -181,6 +181,23 @@ export function createPluginContext(
|
||||
const logContext = { operation: `plugin:${pluginId}` };
|
||||
const declared = manifest.capabilities;
|
||||
|
||||
// ctx.secrets rows live in plugin_settings under the "secret" scope, one
|
||||
// per user, so they go with the user and with the plugin.
|
||||
const secretsRepository = async () => {
|
||||
const { createCurrentPluginSettingsRepository } =
|
||||
await import("../database/repositories/factory.js");
|
||||
return createCurrentPluginSettingsRepository();
|
||||
};
|
||||
const secretOwner = (): string => {
|
||||
const actor = getActor();
|
||||
if (!actor) {
|
||||
throw new Error(
|
||||
"ctx.secrets needs an acting user: call it inside a request or ctx.asUser",
|
||||
);
|
||||
}
|
||||
return actor;
|
||||
};
|
||||
|
||||
const kvGet = guarded(
|
||||
manifest,
|
||||
"kv:own",
|
||||
@@ -537,6 +554,52 @@ export function createPluginContext(
|
||||
},
|
||||
|
||||
secrets: {
|
||||
get: async (key) => {
|
||||
await assertCapability(pluginId, "secrets:own", declared);
|
||||
const row = await (
|
||||
await secretsRepository()
|
||||
).get(pluginId, "secret", secretOwner(), key);
|
||||
if (!row?.value) return null;
|
||||
const { decryptSystemSecret } =
|
||||
await import("../utils/system-secret-crypto.js");
|
||||
return decryptSystemSecret(JSON.parse(row.value) as string);
|
||||
},
|
||||
|
||||
set: guarded(
|
||||
manifest,
|
||||
"secrets:own",
|
||||
async (key: string, value: string | null) => {
|
||||
const repository = await secretsRepository();
|
||||
const owner = secretOwner();
|
||||
if (value === null || value === "") {
|
||||
await repository.delete(pluginId, "secret", owner, key);
|
||||
return;
|
||||
}
|
||||
const { encryptSystemSecret } =
|
||||
await import("../utils/system-secret-crypto.js");
|
||||
await repository.set(
|
||||
pluginId,
|
||||
"secret",
|
||||
owner,
|
||||
key,
|
||||
JSON.stringify(await encryptSystemSecret(value)),
|
||||
true,
|
||||
);
|
||||
},
|
||||
{ action: "secret_set", details: () => "stored a secret" },
|
||||
),
|
||||
|
||||
delete: guarded(
|
||||
manifest,
|
||||
"secrets:own",
|
||||
async (key: string) => {
|
||||
await (
|
||||
await secretsRepository()
|
||||
).delete(pluginId, "secret", secretOwner(), key);
|
||||
},
|
||||
{ action: "secret_delete", details: () => "deleted a secret" },
|
||||
),
|
||||
|
||||
offer: (key, resolve) => {
|
||||
const entry = manifest.providesSecret?.find(
|
||||
(candidate) => candidate.key === key,
|
||||
|
||||
Reference in new issue
Block a user