mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-07 06:39:10 +08:00
[feat] add acknowledged remote clipboard read and write
This commit is contained in:
@@ -173,6 +173,8 @@ export class NativeClient {
|
||||
if (this.pending.size >= 32 || this.child.stdin!.writableLength > 262144)
|
||||
return Promise.reject(new Failure("BUSY", false, true));
|
||||
const id = ++this.sequence;
|
||||
const sideEffect = method === "action" || method === "unlock" ||
|
||||
(method === "clipboard" && args.operation === "set");
|
||||
const line = JSON.stringify({ ...args, id, method }) + "\n";
|
||||
if (Buffer.byteLength(line) > 65536)
|
||||
return Promise.reject(new Failure("MESSAGE_TOO_LARGE"));
|
||||
@@ -190,7 +192,7 @@ export class NativeClient {
|
||||
request_id: id,
|
||||
}) + "\n",
|
||||
);
|
||||
reject(new Failure(code, method === "action" || method === "unlock"));
|
||||
reject(new Failure(code, sideEffect));
|
||||
};
|
||||
const abort = () => cancel("CANCELLED");
|
||||
const timer = setTimeout(() => cancel("NATIVE_TIMEOUT"), timeout);
|
||||
@@ -198,7 +200,7 @@ export class NativeClient {
|
||||
this.pending.set(id, {
|
||||
resolve,
|
||||
reject,
|
||||
action: method === "action" || method === "unlock",
|
||||
action: sideEffect,
|
||||
clean: () => {
|
||||
clearTimeout(timer);
|
||||
signal?.removeEventListener("abort", abort);
|
||||
|
||||
@@ -312,6 +312,14 @@ export function createServer(
|
||||
"Enable or disable control within the user-authorized scope. Cannot override human pause or upgrade an observe-only connection.",
|
||||
async (p) => output(await sessions.control(p.session_id, p.enabled)),
|
||||
);
|
||||
add("crossdesk_clipboard_get", z.strictObject({ session_id: id }),
|
||||
"Read the remote text clipboard within the authorized control session.",
|
||||
async (p, ctx) => output(await sessions.clipboard({ ...p, operation: "get" }, ctx.mcpReq.signal)),
|
||||
{ readOnly: true });
|
||||
add("crossdesk_clipboard_set", z.strictObject({ session_id: id, operation_id: id,
|
||||
text: z.string().max(16384).refine((text) => Buffer.byteLength(text) <= 16384 && !text.includes("\0")) }),
|
||||
"Set or clear the remote text clipboard. Reuse the same operation_id to query the original result. Returns applied after the remote clipboard update.",
|
||||
async (p, ctx) => output(await sessions.clipboard({ ...p, operation: "set" }, ctx.mcpReq.signal)));
|
||||
add(
|
||||
"crossdesk_select_display",
|
||||
z.strictObject({ session_id: id, display_index: z.int().min(0).max(63) }),
|
||||
|
||||
@@ -466,6 +466,13 @@ export class Sessions {
|
||||
if (result.matched) result.observation = await this.screenshot({ session_id }, signal);
|
||||
return result;
|
||||
}
|
||||
async clipboard(p: Data, signal?: AbortSignal): Promise<Data> {
|
||||
const { session_id, ...args } = p;
|
||||
const s = this.session(session_id);
|
||||
this.checkUnlock(s);
|
||||
if (s.unlock && this.unlockPending(s.unlock)) throw new Failure("UNLOCK_PENDING");
|
||||
return s.native.request("clipboard", args, 7000, signal);
|
||||
}
|
||||
async control(id: string, enabled: boolean) {
|
||||
if (!enabled) this.cancelUnlock(this.session(id));
|
||||
return this.session(id).native.request("control", { enabled });
|
||||
|
||||
Reference in New Issue
Block a user