mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35: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
@@ -219,6 +219,19 @@ export interface PluginServices {
|
||||
}
|
||||
|
||||
export interface PluginSecrets {
|
||||
/**
|
||||
* The acting user's own copy of a secret this plugin stored, decrypted, or
|
||||
* null when nothing is stored. Needs secrets:own and an actor. Not audited:
|
||||
* a plugin reads its keys on every request that uses them.
|
||||
*/
|
||||
get: (key: string) => Promise<string | null>;
|
||||
/**
|
||||
* Stores a secret for the acting user, encrypted at rest. Null clears it.
|
||||
* Needs secrets:own and an actor. Audited without the value.
|
||||
*/
|
||||
set: (key: string, value: string | null) => Promise<void>;
|
||||
/** Removes the acting user's secret. Needs secrets:own. Audited. */
|
||||
delete: (key: string) => Promise<void>;
|
||||
offer: (
|
||||
key: string,
|
||||
resolve: (userId: string) => Promise<string | null> | string | null,
|
||||
@@ -580,6 +593,14 @@ export interface PluginHosts {
|
||||
hostId: number,
|
||||
patch: PluginHostUpdateInput,
|
||||
) => Promise<PluginHostRecord | null>;
|
||||
/**
|
||||
* Deletes a host the acting user owns, the same way the host editor's
|
||||
* delete does: its access grants, activity and plugin settings go with it
|
||||
* and remote sync gets a tombstone. Needs hosts:write, and the acting user
|
||||
* needs the core hosts.delete permission. False when the host does not
|
||||
* exist or is not theirs.
|
||||
*/
|
||||
delete: (hostId: number) => Promise<boolean>;
|
||||
/**
|
||||
* Every host the acting user owns, decrypted and wide (PluginHostRecord,
|
||||
* not the narrow list() summary). For a plugin that scans its own hosts in
|
||||
@@ -1317,6 +1338,8 @@ export interface PluginFetchInit {
|
||||
* private is refused, and redirects are never followed.
|
||||
*/
|
||||
allowPrivateHosts?: readonly string[];
|
||||
/** Aborts the request, and a streamed body, when the caller gives up. */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -600,6 +600,11 @@ export interface TermixApp extends TermixAppInfo {
|
||||
* desktop app's connected remote server, anything else is `api`.
|
||||
*/
|
||||
apiFor: (origin?: unknown) => PluginApiClient;
|
||||
/**
|
||||
* A raw fetch on /plugin-api/<id>/<path> with core's auth, for a response
|
||||
* read as a stream (server-sent events), which `api` cannot do.
|
||||
*/
|
||||
fetch: (path: string, init?: RequestInit) => Promise<Response>;
|
||||
/** WebSocket URL and auth subprotocols for /plugin-ws/<id>/<path>. */
|
||||
wsUrl: (
|
||||
path: string,
|
||||
|
||||
@@ -185,6 +185,8 @@ export interface FakePluginContext {
|
||||
channelIds: number[];
|
||||
notification: PluginNotification;
|
||||
}>;
|
||||
/** Backing store behind ctx.secrets.get/set, keyed "<userId>:<key>". */
|
||||
secretStore: Map<string, string>;
|
||||
/** Every ctx.fetch call, in order. */
|
||||
fetches: Array<{ url: string; init?: PluginFetchInit }>;
|
||||
/** Every ctx.audit.record entry, in order. */
|
||||
@@ -290,6 +292,7 @@ export function createFakeContext(
|
||||
const credentialReads: FakePluginContext["credentialReads"] = [];
|
||||
const notifications: FakePluginContext["notifications"] = [];
|
||||
const fetches: FakePluginContext["fetches"] = [];
|
||||
const secretStore = new Map<string, string>();
|
||||
const audits: FakePluginContext["audits"] = [];
|
||||
const activities: FakePluginContext["activities"] = [];
|
||||
const trackedSessions: number[] = [];
|
||||
@@ -491,6 +494,14 @@ export function createFakeContext(
|
||||
},
|
||||
|
||||
secrets: {
|
||||
get: async (key) => secretStore.get(`${actor ?? ""}:${key}`) ?? null,
|
||||
set: async (key, value) => {
|
||||
if (value === null) secretStore.delete(`${actor ?? ""}:${key}`);
|
||||
else secretStore.set(`${actor ?? ""}:${key}`, value);
|
||||
},
|
||||
delete: async (key) => {
|
||||
secretStore.delete(`${actor ?? ""}:${key}`);
|
||||
},
|
||||
offer: () => {},
|
||||
withdraw: () => false,
|
||||
getShared: async () => null,
|
||||
@@ -629,6 +640,11 @@ export function createFakeContext(
|
||||
hostRecordsById.set(hostId, updated);
|
||||
return updated;
|
||||
},
|
||||
delete: async (hostId: number): Promise<boolean> => {
|
||||
const existed =
|
||||
hostRecordsById.delete(hostId) || hostsById.delete(hostId);
|
||||
return existed;
|
||||
},
|
||||
listOwned: async (): Promise<PluginHostRecord[]> => [
|
||||
...hostRecordsById.values(),
|
||||
],
|
||||
@@ -836,6 +852,7 @@ export function createFakeContext(
|
||||
credentialReads,
|
||||
notifications,
|
||||
fetches,
|
||||
secretStore,
|
||||
audits,
|
||||
activities,
|
||||
trackedSessions,
|
||||
@@ -1076,6 +1093,10 @@ export function createMockCtx(
|
||||
require("hosts:write");
|
||||
return ctx.hosts.update(hostId, patch);
|
||||
},
|
||||
delete: async (hostId) => {
|
||||
require("hosts:write");
|
||||
return ctx.hosts.delete(hostId);
|
||||
},
|
||||
listOwned: async () => {
|
||||
require("hosts:write");
|
||||
return ctx.hosts.listOwned();
|
||||
@@ -1220,6 +1241,22 @@ export function createMockCtx(
|
||||
return ctx.fetch(url, init);
|
||||
},
|
||||
|
||||
secrets: {
|
||||
...ctx.secrets,
|
||||
get: async (key) => {
|
||||
require("secrets:own");
|
||||
return ctx.secrets.get(key);
|
||||
},
|
||||
set: async (key, value) => {
|
||||
require("secrets:own");
|
||||
return ctx.secrets.set(key, value);
|
||||
},
|
||||
delete: async (key) => {
|
||||
require("secrets:own");
|
||||
return ctx.secrets.delete(key);
|
||||
},
|
||||
},
|
||||
|
||||
capabilities: {
|
||||
has: async (capability) => granted.has(capability),
|
||||
require: async (capability) => {
|
||||
|
||||
Reference in new issue
Block a user