101 lines
3.3 KiB
JavaScript
101 lines
3.3 KiB
JavaScript
// @dsh-local/portal-entry — PoC host plugin for the dsh web profile.
|
|||
|
|
//
|
||
|
|
// Goal: prove the official profile-plugin mechanism on dsh 0.1.2-rc.1
|
||
|
|
// (install -> load -> persist across restart -> remove), with zero risk to
|
||
|
|
// the running profile: every side effect is guarded, nothing can throw out
|
||
|
|
// of apply().
|
||
|
|
//
|
||
|
|
// v1 evidence channel: an append-only marker file
|
||
|
|
// $DSH_HOME/.dsh-poc-portal-entry.log
|
||
|
|
// written when the row is instantiated. Tool registration (defineTool from
|
||
|
|
// @deepseek-ai/dsh-tools) is attempted defensively; failure is logged, never
|
||
|
|
// fatal.
|
||
|
|
|
||
|
|
import { appendFileSync } from "node:fs";
|
||
|
|
import { join } from "node:path";
|
||
|
|
|
||
|
|
const MARKER = ".dsh-poc-portal-entry.log";
|
||
|
|
const PORTAL_ROOT = process.env.DSH_SERVER_LOGIN_URL ?? "http://127.0.0.1:3080";
|
||
|
|
|
||
|
|
/** Append one line to the marker file. Never throws. */
|
||
|
|
function log(line) {
|
||
|
|
try {
|
||
|
|
const home = process.env.DSH_HOME || process.env.HOME || ".";
|
||
|
|
appendFileSync(join(home, MARKER), `${new Date().toISOString()} ${line}\n`);
|
||
|
|
} catch {
|
||
|
|
// marker writes must never take the profile down
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Build the portal_ping tool definition (lazy import of defineTool). */
|
||
|
|
async function portalPingDefinition() {
|
||
|
|
const { defineTool } = await import("@deepseek-ai/dsh-tools");
|
||
|
|
return defineTool({
|
||
|
|
name: "portal_ping",
|
||
|
|
description:
|
||
|
|
"PoC tool: probe the dsh-server-login portal reachable from this instance. Returns HTTP status and body size of the portal root.",
|
||
|
|
parameters: {},
|
||
|
|
output: {
|
||
|
|
schema: {
|
||
|
|
type: "object",
|
||
|
|
additionalProperties: false,
|
||
|
|
properties: {
|
||
|
|
ok: { type: "boolean", required: true },
|
||
|
|
status: { type: "number" },
|
||
|
|
bytes: { type: "number" },
|
||
|
|
note: { type: "string" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
isConcurrencySafe: () => true,
|
||
|
|
async execute() {
|
||
|
|
try {
|
||
|
|
const res = await fetch(PORTAL_ROOT, { redirect: "manual" });
|
||
|
|
const body = await res.arrayBuffer();
|
||
|
|
return {
|
||
|
|
ok: true,
|
||
|
|
status: res.status,
|
||
|
|
bytes: body.byteLength,
|
||
|
|
note: `portal root reachable from instance (${PORTAL_ROOT})`
|
||
|
|
};
|
||
|
|
} catch (err) {
|
||
|
|
return { ok: false, note: `portal unreachable: ${String(err?.message ?? err)}` };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Cordis-style plugin apply. Row declares inject:[tools], so ctx.tools is
|
||
|
|
* available; every failure is still contained and logged. */
|
||
|
|
export function apply(ctx) {
|
||
|
|
log(`apply pid=${process.pid} dshHome=${process.env.DSH_HOME ?? ""} home=${process.env.HOME ?? ""}`);
|
||
|
|
|
||
|
|
const register = () => {
|
||
|
|
try {
|
||
|
|
if (!ctx.tools || typeof ctx.tools.register !== "function") {
|
||
|
|
log("tools service unavailable at apply time");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
portalPingDefinition()
|
||
|
|
.then((def) => {
|
||
|
|
try {
|
||
|
|
ctx.tools.register(def);
|
||
|
|
log("tool portal_ping registered");
|
||
|
|
} catch (err) {
|
||
|
|
log(`tool register threw: ${String(err?.message ?? err)}`);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch((err) => log(`tool build failed: ${String(err?.message ?? err)}`));
|
||
|
|
} catch (err) {
|
||
|
|
log(`tools access failed: ${String(err?.message ?? err)}`);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
register();
|
||
|
|
} catch (err) {
|
||
|
|
log(`register wrapper failed: ${String(err?.message ?? err)}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default apply;
|