@klarkxy/dsh-git-commit 0.3.3 → 0.3.5
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/README.md +2 -1
- package/docs/README.zh-CN.md +2 -1
- package/lib/client.inner.cjs +574 -93
- package/lib/client.inner.cjs.map +1 -1
- package/lib/client.js +574 -93
- package/lib/contracts.d.ts +76 -28
- package/lib/contracts.js +2 -2
- package/lib/contracts2.d.ts +2 -2
- package/lib/contracts2.js +1 -2
- package/lib/contracts2.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +828 -63
- package/lib/index.js.map +1 -1
- package/package.json +10 -9
package/lib/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as normalizeFallback, b as normalizeModelRoute, c as MAX_UNTRACKED_PREVIEW_CHARS, g as defaultSettings, h as SETTINGS_KEY, l as PLUGIN_NAME, o as MAX_MANIFEST_CHARS, p as RPC_CHANNEL, t as COMMIT_PLAN_FEATURE, y as modelRouteOverride } from "./contracts2.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { appendFileSync, constants, existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, unlinkSync } from "node:fs";
|
|
5
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
3
8
|
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
4
9
|
import { execFile } from "node:child_process";
|
|
5
10
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { constants } from "node:fs";
|
|
7
11
|
import { copyFile, lstat, mkdtemp, open, readFile, readlink, realpath, rm } from "node:fs/promises";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
10
12
|
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
11
13
|
import { z } from "zod";
|
|
12
14
|
//#region ../dsh-plugin-kit/src/contracts.ts
|
|
@@ -60,12 +62,648 @@ function getAuxiliaryModel(host) {
|
|
|
60
62
|
return modelRouteOverride(shared.snapshot().model ?? void 0);
|
|
61
63
|
}
|
|
62
64
|
//#endregion
|
|
65
|
+
//#region ../dsh-plugin-kit/src/log-shared.ts
|
|
66
|
+
const PLUGIN_LOG_LEVELS = [
|
|
67
|
+
"debug",
|
|
68
|
+
"info",
|
|
69
|
+
"warn",
|
|
70
|
+
"error",
|
|
71
|
+
"fault"
|
|
72
|
+
];
|
|
73
|
+
const DEFAULT_PLUGIN_LOG_LEVEL = "info";
|
|
74
|
+
const PLUGIN_LOG_MAX_MESSAGE_CHARS = 16e3;
|
|
75
|
+
const SOURCE_RE = /^[a-z][a-z0-9-]{0,63}$/;
|
|
76
|
+
const EVENT_RE = /^[a-z][a-z0-9._-]{0,127}$/;
|
|
77
|
+
const CONTEXT_KEY_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/;
|
|
78
|
+
const SECRET_KEY_RE = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|bearer|authorization|cookie|set[_-]?cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|z_c0|d_c0)$/i;
|
|
79
|
+
const CHAT_PAYLOAD_RE = /"role"\s*:\s*"(?:system|user|assistant|tool)"|"(?:messages|content|prompt|input_text|input_image|image_url)"\s*:\s*[\[{"']/i;
|
|
80
|
+
const CREDENTIAL_BODY_RE = /-----BEGIN (?:RSA |EC |OPENSSH |PRIVATE )?PRIVATE KEY-----|"type"\s*:\s*"service_account"|credentials\.json|\.dsh[/\\](?:credentials|auth)/i;
|
|
81
|
+
const AUTH_HEADER_RE = /\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie)\s*:\s*[^\r\n]*/gi;
|
|
82
|
+
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+=/]{8,}/g;
|
|
83
|
+
const SECRET_FIELD_RE = /(["']?(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|bearer|authorization|cookie|set-cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|password|z_c0|d_c0)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,&;}]+)/gi;
|
|
84
|
+
const SECRET_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|password|bootstrap|code|sig|signature|credential|password)=)[^\s&#"']+/gi;
|
|
85
|
+
const URL_CREDENTIAL_RE = /\b(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
|
|
86
|
+
const BOOTSTRAP_RE = /\/bootstrap\/[^\s"'?&#]+/gi;
|
|
87
|
+
const KNOWN_TOKEN_RE = /\b(?:sk-[A-Za-z0-9_-]{10,}|gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{20,}|tvly-[A-Za-z0-9_-]{10,}|AKIA[0-9A-Z]{16})\b/g;
|
|
88
|
+
const PATH_RE = /(?:[A-Za-z]:\\|\\\\)[^\s"'`<>|]+|(?:\/(?:home|Users|tmp|var|root)\/|~\/)[^\s"'`<>|]+/g;
|
|
89
|
+
function isPluginLogLevel(value) {
|
|
90
|
+
return typeof value === "string" && PLUGIN_LOG_LEVELS.includes(value);
|
|
91
|
+
}
|
|
92
|
+
function pluginLogLevelEnabled(level, minimum) {
|
|
93
|
+
return PLUGIN_LOG_LEVELS.indexOf(level) >= PLUGIN_LOG_LEVELS.indexOf(minimum);
|
|
94
|
+
}
|
|
95
|
+
function normalizePluginLogSource(raw) {
|
|
96
|
+
const stripped = String(raw || "").trim().replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
97
|
+
const bounded = (/^[a-z]/.test(stripped) ? stripped : `p-${stripped}`).slice(0, 64).replace(/-+$/g, "");
|
|
98
|
+
return SOURCE_RE.test(bounded) ? bounded : "dsh-plugin";
|
|
99
|
+
}
|
|
100
|
+
function normalizePluginLogEvent(raw) {
|
|
101
|
+
const cleaned = String(raw || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 128);
|
|
102
|
+
return EVENT_RE.test(cleaned) ? cleaned : "plugin.event";
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse a level specification like `debug` or `dsh-zhihu=debug,dsh-dot:warn,info`.
|
|
106
|
+
* Source-specific rules win over a bare fallback level.
|
|
107
|
+
*/
|
|
108
|
+
function parsePluginLogLevel(spec, source, fallback = DEFAULT_PLUGIN_LOG_LEVEL) {
|
|
109
|
+
if (!spec || typeof spec !== "string") return fallback;
|
|
110
|
+
const normalizedSource = source ? normalizePluginLogSource(source) : void 0;
|
|
111
|
+
let wildcard;
|
|
112
|
+
for (const token of spec.split(",").map((part) => part.trim()).filter(Boolean)) {
|
|
113
|
+
const lowered = token.toLowerCase();
|
|
114
|
+
if (isPluginLogLevel(lowered)) {
|
|
115
|
+
wildcard = lowered;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const match = /^([^=:]+)[=:]([a-z]+)$/i.exec(token);
|
|
119
|
+
if (!match) continue;
|
|
120
|
+
const target = match[1].trim();
|
|
121
|
+
const candidate = match[2].trim().toLowerCase();
|
|
122
|
+
if (!isPluginLogLevel(candidate)) continue;
|
|
123
|
+
if (target === "*") {
|
|
124
|
+
wildcard = candidate;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (normalizedSource && normalizePluginLogSource(target) === normalizedSource) return candidate;
|
|
128
|
+
}
|
|
129
|
+
return wildcard ?? fallback;
|
|
130
|
+
}
|
|
131
|
+
function sanitizePluginLogText(text, maxChars = PLUGIN_LOG_MAX_MESSAGE_CHARS) {
|
|
132
|
+
if (!text) return "";
|
|
133
|
+
if (CREDENTIAL_BODY_RE.test(text)) return "[omitted credential material]";
|
|
134
|
+
if (CHAT_PAYLOAD_RE.test(text)) return "[omitted chat payload]";
|
|
135
|
+
return truncateLogText(text.replace(AUTH_HEADER_RE, "$1: [redacted]").replace(BEARER_RE, "Bearer [redacted]").replace(SECRET_FIELD_RE, (full, prefix) => {
|
|
136
|
+
const value = full.slice(prefix.length);
|
|
137
|
+
if (value.startsWith("\"")) return `${prefix}"[redacted]"`;
|
|
138
|
+
if (value.startsWith("'")) return `${prefix}'[redacted]'`;
|
|
139
|
+
return `${prefix}[redacted]`;
|
|
140
|
+
}).replace(SECRET_QUERY_RE, "$1[redacted]").replace(URL_CREDENTIAL_RE, "$1[redacted]@").replace(BOOTSTRAP_RE, "/bootstrap/[redacted]").replace(KNOWN_TOKEN_RE, "[redacted]").replace(PATH_RE, "[path]"), maxChars);
|
|
141
|
+
}
|
|
142
|
+
function sanitizePluginLogContext(input) {
|
|
143
|
+
if (!input) return void 0;
|
|
144
|
+
const output = {};
|
|
145
|
+
for (const [key, value] of Object.entries(input).slice(0, 32)) {
|
|
146
|
+
if (value === void 0 || !CONTEXT_KEY_RE.test(key)) continue;
|
|
147
|
+
if (typeof value === "number" && !Number.isFinite(value)) continue;
|
|
148
|
+
if (SECRET_KEY_RE.test(key)) {
|
|
149
|
+
output[key] = "[redacted]";
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
output[key] = typeof value === "string" ? sanitizePluginLogText(value, 1e3) : value;
|
|
153
|
+
}
|
|
154
|
+
if (!Object.keys(output).length) return void 0;
|
|
155
|
+
if (JSON.stringify(output).length <= 8e3) return output;
|
|
156
|
+
return {
|
|
157
|
+
omitted: true,
|
|
158
|
+
reason: "Diagnostic context exceeded its size limit."
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function sanitizePluginLogError(error) {
|
|
162
|
+
if (error === void 0) return void 0;
|
|
163
|
+
const row = error && typeof error === "object" ? error : {};
|
|
164
|
+
const rawMessage = typeof row.message === "string" ? row.message : String(error);
|
|
165
|
+
const result = {
|
|
166
|
+
name: typeof row.name === "string" && row.name.trim() ? truncateLogText(row.name.trim(), 100) : "Error",
|
|
167
|
+
message: sanitizePluginLogText(rawMessage) || "Unknown error"
|
|
168
|
+
};
|
|
169
|
+
if (typeof row.stack === "string" && row.stack.trim()) result.stack = sanitizePluginLogText(row.stack, 24e3);
|
|
170
|
+
if (typeof row.code === "string" && /^[A-Za-z0-9_./:-]{1,100}$/.test(row.code)) result.code = row.code;
|
|
171
|
+
else if (typeof row.code === "number" && Number.isFinite(row.code)) result.code = String(row.code);
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
function mergePluginLogContext(base, extra) {
|
|
175
|
+
if (!base) return extra;
|
|
176
|
+
if (!extra) return base;
|
|
177
|
+
return {
|
|
178
|
+
...base,
|
|
179
|
+
...extra
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function buildPluginLogEntry(input, meta) {
|
|
183
|
+
const entry = {
|
|
184
|
+
version: 1,
|
|
185
|
+
at: meta.at.toISOString(),
|
|
186
|
+
level: isPluginLogLevel(input.level) ? input.level : "info",
|
|
187
|
+
source: normalizePluginLogSource(meta.source),
|
|
188
|
+
surface: meta.surface,
|
|
189
|
+
...meta.profile ? { profile: meta.profile } : {},
|
|
190
|
+
event: normalizePluginLogEvent(input.event),
|
|
191
|
+
message: sanitizePluginLogText(input.message || "No diagnostic message was provided.", PLUGIN_LOG_MAX_MESSAGE_CHARS),
|
|
192
|
+
...meta.pid !== void 0 ? { pid: meta.pid } : {}
|
|
193
|
+
};
|
|
194
|
+
const context = sanitizePluginLogContext(mergePluginLogContext(meta.correlation, input.context));
|
|
195
|
+
if (context) entry.context = context;
|
|
196
|
+
const error = sanitizePluginLogError(input.error);
|
|
197
|
+
if (error) entry.error = error;
|
|
198
|
+
return entry;
|
|
199
|
+
}
|
|
200
|
+
function createChildPluginLogger(parent, bound) {
|
|
201
|
+
return {
|
|
202
|
+
get source() {
|
|
203
|
+
return parent.source;
|
|
204
|
+
},
|
|
205
|
+
get file() {
|
|
206
|
+
return parent.file;
|
|
207
|
+
},
|
|
208
|
+
get lastWriteError() {
|
|
209
|
+
return parent.lastWriteError;
|
|
210
|
+
},
|
|
211
|
+
debug: (event, message, context) => parent.debug(event, message, mergePluginLogContext(bound, context)),
|
|
212
|
+
info: (event, message, context) => parent.info(event, message, mergePluginLogContext(bound, context)),
|
|
213
|
+
warn: (event, message, error, context) => parent.warn(event, message, error, mergePluginLogContext(bound, context)),
|
|
214
|
+
error: (event, message, error, context) => parent.error(event, message, error, mergePluginLogContext(bound, context)),
|
|
215
|
+
fault: (event, message, error, context) => parent.fault(event, message, error, mergePluginLogContext(bound, context)),
|
|
216
|
+
write: (input) => parent.write({
|
|
217
|
+
...input,
|
|
218
|
+
context: mergePluginLogContext(bound, input.context)
|
|
219
|
+
}),
|
|
220
|
+
child: (next) => createChildPluginLogger(parent, mergePluginLogContext(bound, next) ?? {})
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function truncateLogText(text, maxChars) {
|
|
224
|
+
if (text.length <= maxChars) return text;
|
|
225
|
+
const marker = "[truncated]\n";
|
|
226
|
+
if (maxChars <= 12) return text.slice(0, Math.max(0, maxChars));
|
|
227
|
+
return `${marker}${text.slice(-(maxChars - 12))}`;
|
|
228
|
+
}
|
|
229
|
+
const PROFILE_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
230
|
+
const ARCHIVE_RE = /^[a-z][a-z0-9-]{0,63}\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z\.\d+\.\d+\.jsonl$/;
|
|
231
|
+
const correlationStorage = new AsyncLocalStorage();
|
|
232
|
+
const processFileLoggers = /* @__PURE__ */ new Map();
|
|
233
|
+
let operationSequence = 0;
|
|
234
|
+
function safeProfileName(profile) {
|
|
235
|
+
const trimmed = String(profile ?? "").trim();
|
|
236
|
+
if (PROFILE_RE.test(trimmed)) return trimmed.toLowerCase();
|
|
237
|
+
const cleaned = trimmed.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
238
|
+
return cleaned && PROFILE_RE.test(cleaned) ? cleaned : "default";
|
|
239
|
+
}
|
|
240
|
+
function pluginLogDirectory(home, profile = "default") {
|
|
241
|
+
return join(resolve(home), "logs", "plugins", safeProfileName(profile));
|
|
242
|
+
}
|
|
243
|
+
function resolveHostLogTarget(ctx, options = {}) {
|
|
244
|
+
const env = options.env ?? process.env;
|
|
245
|
+
if (options.directory?.trim()) {
|
|
246
|
+
const dir = resolve(options.directory.trim());
|
|
247
|
+
return {
|
|
248
|
+
directory: dir,
|
|
249
|
+
anchor: dir,
|
|
250
|
+
...options.profile ? { profile: safeProfileName(options.profile) } : {}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (options.home?.trim()) {
|
|
254
|
+
const home = resolve(options.home.trim());
|
|
255
|
+
const profile = safeProfileName(options.profile);
|
|
256
|
+
return {
|
|
257
|
+
directory: pluginLogDirectory(home, profile),
|
|
258
|
+
anchor: home,
|
|
259
|
+
profile
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (env.DSH_PLUGIN_LOG_DIR?.trim()) {
|
|
263
|
+
const dir = resolve(env.DSH_PLUGIN_LOG_DIR.trim());
|
|
264
|
+
return {
|
|
265
|
+
directory: dir,
|
|
266
|
+
anchor: dir,
|
|
267
|
+
...options.profile ? { profile: safeProfileName(options.profile) } : {}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const host = ctx && typeof ctx === "object" ? ctx : void 0;
|
|
271
|
+
let profileCtx;
|
|
272
|
+
try {
|
|
273
|
+
profileCtx = host?.get?.("profileContext", false);
|
|
274
|
+
} catch {
|
|
275
|
+
try {
|
|
276
|
+
profileCtx = host?.get?.("profileContext");
|
|
277
|
+
} catch {
|
|
278
|
+
profileCtx = void 0;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (typeof profileCtx?.home === "string" && profileCtx.home.trim() && typeof profileCtx.dir === "string" && profileCtx.dir.trim()) {
|
|
282
|
+
const home = resolve(profileCtx.home.trim());
|
|
283
|
+
const dir = resolve(profileCtx.dir.trim());
|
|
284
|
+
if (isInside(home, dir)) {
|
|
285
|
+
const profile = safeProfileName(typeof profileCtx.name === "string" ? profileCtx.name : basename(dir));
|
|
286
|
+
return {
|
|
287
|
+
directory: pluginLogDirectory(home, profile),
|
|
288
|
+
anchor: home,
|
|
289
|
+
profile
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!Boolean(env.VITEST || env.NODE_TEST_CONTEXT) && typeof host?.baseUrl === "string" && host.baseUrl.trim()) {
|
|
294
|
+
const basePath = pathFromBaseUrl(host.baseUrl);
|
|
295
|
+
const configuredHome = resolve((env.DSH_HOME?.trim() || join(homedir(), ".dsh")).trim());
|
|
296
|
+
const profilesRoot = join(configuredHome, "profiles");
|
|
297
|
+
if (basePath && isInside(profilesRoot, basePath) && !samePath(profilesRoot, basePath)) {
|
|
298
|
+
const firstSegment = basePath.slice(profilesRoot.length).replace(/^[/\\]+/, "").split(/[/\\]/)[0];
|
|
299
|
+
if (firstSegment) {
|
|
300
|
+
const profile = safeProfileName(firstSegment);
|
|
301
|
+
return {
|
|
302
|
+
directory: pluginLogDirectory(configuredHome, profile),
|
|
303
|
+
anchor: configuredHome,
|
|
304
|
+
profile
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
directory: null,
|
|
311
|
+
anchor: null,
|
|
312
|
+
...options.profile ? { profile: safeProfileName(options.profile) } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Synchronous, append-only rotating JSONL logger modeled on DSH Spaces
|
|
317
|
+
* diagnostics: 7-day or 100 MiB active rotation, 7-day / 100 MiB archive
|
|
318
|
+
* retention, secret and path redaction, and non-throwing write resilience.
|
|
319
|
+
*/
|
|
320
|
+
var RotatingPluginLogger = class {
|
|
321
|
+
source;
|
|
322
|
+
file;
|
|
323
|
+
lastError = null;
|
|
324
|
+
size = 0;
|
|
325
|
+
openedAt = 0;
|
|
326
|
+
initialized = false;
|
|
327
|
+
writing = false;
|
|
328
|
+
sequence = 0;
|
|
329
|
+
loggedWriteFailure = false;
|
|
330
|
+
recent = [];
|
|
331
|
+
directory;
|
|
332
|
+
anchor;
|
|
333
|
+
profile;
|
|
334
|
+
minimum;
|
|
335
|
+
now;
|
|
336
|
+
pid;
|
|
337
|
+
maxFileBytes;
|
|
338
|
+
maxArchiveBytes;
|
|
339
|
+
maxAgeMs;
|
|
340
|
+
recentLimit;
|
|
341
|
+
constructor(source, options = {}) {
|
|
342
|
+
this.source = normalizePluginLogSource(source);
|
|
343
|
+
const env = options.env ?? process.env;
|
|
344
|
+
const resolved = options.directory !== void 0 || options.home !== void 0 ? resolveHostLogTarget(void 0, options) : {
|
|
345
|
+
directory: null,
|
|
346
|
+
anchor: options.anchor ?? null,
|
|
347
|
+
profile: options.profile
|
|
348
|
+
};
|
|
349
|
+
this.directory = resolved.directory;
|
|
350
|
+
this.anchor = options.anchor ?? resolved.anchor;
|
|
351
|
+
this.profile = options.profile ?? resolved.profile;
|
|
352
|
+
this.file = this.directory ? join(this.directory, `${this.source}.jsonl`) : null;
|
|
353
|
+
this.minimum = options.minimumLevel ?? parsePluginLogLevel(env.DSH_PLUGIN_LOG_LEVEL, this.source, "info");
|
|
354
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
355
|
+
this.pid = options.pid ?? process.pid;
|
|
356
|
+
this.maxFileBytes = options.maxFileBytes ?? 104857600;
|
|
357
|
+
this.maxArchiveBytes = options.maxArchiveBytes ?? 104857600;
|
|
358
|
+
this.maxAgeMs = options.maxAgeMs ?? 6048e5;
|
|
359
|
+
this.recentLimit = Math.max(1, options.recentLimit ?? 200);
|
|
360
|
+
}
|
|
361
|
+
get lastWriteError() {
|
|
362
|
+
return this.lastError;
|
|
363
|
+
}
|
|
364
|
+
recentEntries(limit = this.recentLimit) {
|
|
365
|
+
return this.recent.slice(-Math.max(0, limit));
|
|
366
|
+
}
|
|
367
|
+
debug(event, message, context) {
|
|
368
|
+
return this.write({
|
|
369
|
+
level: "debug",
|
|
370
|
+
event,
|
|
371
|
+
message,
|
|
372
|
+
...context ? { context } : {}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
info(event, message, context) {
|
|
376
|
+
return this.write({
|
|
377
|
+
level: "info",
|
|
378
|
+
event,
|
|
379
|
+
message,
|
|
380
|
+
...context ? { context } : {}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
warn(event, message, error, context) {
|
|
384
|
+
return this.write({
|
|
385
|
+
level: "warn",
|
|
386
|
+
event,
|
|
387
|
+
message,
|
|
388
|
+
...error !== void 0 ? { error } : {},
|
|
389
|
+
...context ? { context } : {}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
error(event, message, error, context) {
|
|
393
|
+
return this.write({
|
|
394
|
+
level: "error",
|
|
395
|
+
event,
|
|
396
|
+
message,
|
|
397
|
+
...error !== void 0 ? { error } : {},
|
|
398
|
+
...context ? { context } : {}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
fault(event, message, error, context) {
|
|
402
|
+
return this.write({
|
|
403
|
+
level: "fault",
|
|
404
|
+
event,
|
|
405
|
+
message,
|
|
406
|
+
...error !== void 0 ? { error } : {},
|
|
407
|
+
...context ? { context } : {}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
child(context) {
|
|
411
|
+
return createChildPluginLogger(this, context);
|
|
412
|
+
}
|
|
413
|
+
write(input) {
|
|
414
|
+
if (!pluginLogLevelEnabled(input.level, this.minimum) || this.writing) return false;
|
|
415
|
+
const at = this.now();
|
|
416
|
+
let entry;
|
|
417
|
+
let line;
|
|
418
|
+
try {
|
|
419
|
+
entry = buildPluginLogEntry(input, {
|
|
420
|
+
at,
|
|
421
|
+
source: this.source,
|
|
422
|
+
surface: "host",
|
|
423
|
+
profile: this.profile,
|
|
424
|
+
pid: this.pid,
|
|
425
|
+
correlation: correlationStorage.getStore()
|
|
426
|
+
});
|
|
427
|
+
line = `${JSON.stringify(entry)}\n`;
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
this.pushRecent(entry);
|
|
432
|
+
if (!this.directory || !this.file) return true;
|
|
433
|
+
this.writing = true;
|
|
434
|
+
try {
|
|
435
|
+
this.openManagedDirectory();
|
|
436
|
+
this.prepare(at);
|
|
437
|
+
if (this.size > 0 && (this.size + Buffer.byteLength(line) > this.maxFileBytes || at.getTime() - this.openedAt >= this.maxAgeMs)) this.rotate(at);
|
|
438
|
+
appendFileSync(this.file, line, {
|
|
439
|
+
encoding: "utf8",
|
|
440
|
+
mode: 384
|
|
441
|
+
});
|
|
442
|
+
this.size += Buffer.byteLength(line);
|
|
443
|
+
this.prune(at);
|
|
444
|
+
this.lastError = null;
|
|
445
|
+
this.loggedWriteFailure = false;
|
|
446
|
+
return true;
|
|
447
|
+
} catch (error) {
|
|
448
|
+
const detail = sanitizePluginLogText(error instanceof Error ? error.message : String(error), 500);
|
|
449
|
+
this.lastError = `The plugin diagnostic log could not be written: ${detail}`;
|
|
450
|
+
this.initialized = false;
|
|
451
|
+
if (!this.loggedWriteFailure) {
|
|
452
|
+
this.loggedWriteFailure = true;
|
|
453
|
+
try {
|
|
454
|
+
process.stderr.write(`DSH Plugin (${this.source}): ${this.lastError}. The original operation continues unchanged.\n`);
|
|
455
|
+
} catch {}
|
|
456
|
+
}
|
|
457
|
+
return false;
|
|
458
|
+
} finally {
|
|
459
|
+
this.writing = false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
pushRecent(entry) {
|
|
463
|
+
this.recent.push(entry);
|
|
464
|
+
const overflow = this.recent.length - this.recentLimit;
|
|
465
|
+
if (overflow > 0) this.recent.splice(0, overflow);
|
|
466
|
+
}
|
|
467
|
+
prepare(at) {
|
|
468
|
+
if (this.initialized || !this.file) return;
|
|
469
|
+
this.openManagedDirectory();
|
|
470
|
+
const stat = this.regularFile(this.file);
|
|
471
|
+
this.size = stat?.size ?? 0;
|
|
472
|
+
const stamps = stat ? [stat.birthtimeMs, stat.mtimeMs].filter((value) => value > 0) : [];
|
|
473
|
+
this.openedAt = stamps.length ? Math.min(...stamps) : at.getTime();
|
|
474
|
+
this.initialized = true;
|
|
475
|
+
}
|
|
476
|
+
rotate(at) {
|
|
477
|
+
if (!this.directory || !this.file) return;
|
|
478
|
+
const stamp = at.toISOString().replace(/:/g, "-");
|
|
479
|
+
const name = `${this.source}.${stamp}.${this.pid}.${this.sequence++}.jsonl`;
|
|
480
|
+
if (!ARCHIVE_RE.test(name)) throw new Error("Diagnostic log archive name is invalid.");
|
|
481
|
+
const archive = join(this.directory, name);
|
|
482
|
+
if (lexists(archive)) throw new Error("Diagnostic log archive already exists.");
|
|
483
|
+
try {
|
|
484
|
+
renameSync(this.file, archive);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
if (error.code === "ENOENT") {
|
|
487
|
+
this.size = 0;
|
|
488
|
+
this.openedAt = at.getTime();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
this.size = 0;
|
|
494
|
+
this.openedAt = at.getTime();
|
|
495
|
+
}
|
|
496
|
+
rotateStaleActive(at) {
|
|
497
|
+
if (!this.file) return;
|
|
498
|
+
const stat = this.regularFile(this.file);
|
|
499
|
+
if (!stat || stat.size === 0) return;
|
|
500
|
+
if (at.getTime() - stat.mtimeMs < this.maxAgeMs) return;
|
|
501
|
+
this.rotate(at);
|
|
502
|
+
}
|
|
503
|
+
prune(at) {
|
|
504
|
+
if (!this.directory) return;
|
|
505
|
+
this.rotateStaleActive(at);
|
|
506
|
+
const archives = [];
|
|
507
|
+
let total = 0;
|
|
508
|
+
for (const name of readdirSync(this.directory)) {
|
|
509
|
+
if (!ARCHIVE_RE.test(name) || !name.startsWith(`${this.source}.`)) continue;
|
|
510
|
+
const file = join(this.directory, name);
|
|
511
|
+
const stat = lstatSync(file);
|
|
512
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log archive is not a regular file.");
|
|
513
|
+
if (at.getTime() - stat.mtimeMs >= this.maxAgeMs) {
|
|
514
|
+
try {
|
|
515
|
+
unlinkSync(file);
|
|
516
|
+
} catch {}
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
archives.push({
|
|
520
|
+
name,
|
|
521
|
+
size: stat.size,
|
|
522
|
+
mtime: stat.mtimeMs
|
|
523
|
+
});
|
|
524
|
+
total += stat.size;
|
|
525
|
+
}
|
|
526
|
+
archives.sort((left, right) => left.mtime - right.mtime || left.name.localeCompare(right.name));
|
|
527
|
+
while (total > this.maxArchiveBytes && archives.length) {
|
|
528
|
+
const oldest = archives.shift();
|
|
529
|
+
try {
|
|
530
|
+
unlinkSync(join(this.directory, oldest.name));
|
|
531
|
+
} catch {}
|
|
532
|
+
total -= oldest.size;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
openManagedDirectory() {
|
|
536
|
+
if (!this.directory) return;
|
|
537
|
+
if (this.anchor) {
|
|
538
|
+
if ((existsSync(this.anchor) ? lstatSync(this.anchor) : void 0)?.isSymbolicLink()) throw new Error("Diagnostic log anchor cannot be a symbolic link.");
|
|
539
|
+
}
|
|
540
|
+
mkdirSync(this.directory, {
|
|
541
|
+
recursive: true,
|
|
542
|
+
mode: 448
|
|
543
|
+
});
|
|
544
|
+
const stat = lstatSync(this.directory);
|
|
545
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Diagnostic log directory is not a real directory.");
|
|
546
|
+
const realDir = realpathSync(this.directory);
|
|
547
|
+
if (this.anchor) {
|
|
548
|
+
if (!isInside(realpathSync(this.anchor), realDir)) throw new Error("Diagnostic log directory escaped its managed root.");
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
regularFile(file) {
|
|
552
|
+
try {
|
|
553
|
+
const stat = lstatSync(file);
|
|
554
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error("Diagnostic log is not a regular file.");
|
|
555
|
+
const expected = join(realpathSync(dirname(file)), basename(file));
|
|
556
|
+
if (!samePath(realpathSync(file), expected)) throw new Error("Diagnostic log path could not be verified.");
|
|
557
|
+
return stat;
|
|
558
|
+
} catch (error) {
|
|
559
|
+
if (error.code === "ENOENT") return void 0;
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function createPluginLogger(ctxOrSource, maybeSource, maybeOptions) {
|
|
565
|
+
const hasCtx = typeof ctxOrSource !== "string";
|
|
566
|
+
const ctx = hasCtx ? ctxOrSource : void 0;
|
|
567
|
+
const source = hasCtx ? String(maybeSource ?? "dsh-plugin") : ctxOrSource;
|
|
568
|
+
const options = (hasCtx ? maybeOptions : maybeSource) ?? {};
|
|
569
|
+
const normalized = normalizePluginLogSource(source);
|
|
570
|
+
const target = resolveHostLogTarget(ctx, options);
|
|
571
|
+
let backing;
|
|
572
|
+
if (Boolean(target.directory && options.minimumLevel === void 0 && options.now === void 0 && options.pid === void 0 && options.maxFileBytes === void 0 && options.maxArchiveBytes === void 0 && options.maxAgeMs === void 0) && target.directory) {
|
|
573
|
+
const key = resolve(target.directory, `${normalized}.jsonl`);
|
|
574
|
+
let shared = processFileLoggers.get(key);
|
|
575
|
+
if (!shared) {
|
|
576
|
+
shared = new RotatingPluginLogger(normalized, {
|
|
577
|
+
...options,
|
|
578
|
+
directory: target.directory,
|
|
579
|
+
anchor: target.anchor,
|
|
580
|
+
profile: target.profile
|
|
581
|
+
});
|
|
582
|
+
processFileLoggers.set(key, shared);
|
|
583
|
+
}
|
|
584
|
+
backing = shared;
|
|
585
|
+
} else backing = new RotatingPluginLogger(normalized, {
|
|
586
|
+
...options,
|
|
587
|
+
...target.directory ? {
|
|
588
|
+
directory: target.directory,
|
|
589
|
+
anchor: target.anchor
|
|
590
|
+
} : {},
|
|
591
|
+
...target.profile ? { profile: target.profile } : {}
|
|
592
|
+
});
|
|
593
|
+
if (options.forwardHostWarnError === false || !ctx || typeof ctx !== "object") return backing;
|
|
594
|
+
return wrapWithHostForwarding(backing, ctx);
|
|
595
|
+
}
|
|
596
|
+
function wrapWithHostForwarding(backing, host) {
|
|
597
|
+
const forward = (input) => {
|
|
598
|
+
if (input.level !== "warn" && input.level !== "error" && input.level !== "fault") return;
|
|
599
|
+
try {
|
|
600
|
+
const hostLogger = host.logger;
|
|
601
|
+
if (!hostLogger) return;
|
|
602
|
+
const detail = input.error instanceof Error ? `${input.message} (${input.error.message})` : input.error !== void 0 ? `${input.message} (${String(input.error)})` : input.message;
|
|
603
|
+
const text = sanitizePluginLogText(`${backing.source} [${input.event}]: ${detail}`, 1e3);
|
|
604
|
+
if (input.level === "warn") {
|
|
605
|
+
if (typeof hostLogger.warn === "function") hostLogger.warn(text);
|
|
606
|
+
} else if (typeof hostLogger.error === "function") hostLogger.error(text);
|
|
607
|
+
else if (typeof hostLogger.warn === "function") hostLogger.warn(text);
|
|
608
|
+
} catch {}
|
|
609
|
+
};
|
|
610
|
+
const wrapped = {
|
|
611
|
+
get source() {
|
|
612
|
+
return backing.source;
|
|
613
|
+
},
|
|
614
|
+
get file() {
|
|
615
|
+
return backing.file;
|
|
616
|
+
},
|
|
617
|
+
get lastWriteError() {
|
|
618
|
+
return backing.lastWriteError;
|
|
619
|
+
},
|
|
620
|
+
debug: (event, message, context) => backing.debug(event, message, context),
|
|
621
|
+
info: (event, message, context) => backing.info(event, message, context),
|
|
622
|
+
warn: (event, message, error, context) => {
|
|
623
|
+
const wrote = backing.warn(event, message, error, context);
|
|
624
|
+
forward({
|
|
625
|
+
level: "warn",
|
|
626
|
+
event,
|
|
627
|
+
message,
|
|
628
|
+
error,
|
|
629
|
+
context
|
|
630
|
+
});
|
|
631
|
+
return wrote;
|
|
632
|
+
},
|
|
633
|
+
error: (event, message, error, context) => {
|
|
634
|
+
const wrote = backing.error(event, message, error, context);
|
|
635
|
+
forward({
|
|
636
|
+
level: "error",
|
|
637
|
+
event,
|
|
638
|
+
message,
|
|
639
|
+
error,
|
|
640
|
+
context
|
|
641
|
+
});
|
|
642
|
+
return wrote;
|
|
643
|
+
},
|
|
644
|
+
fault: (event, message, error, context) => {
|
|
645
|
+
const wrote = backing.fault(event, message, error, context);
|
|
646
|
+
forward({
|
|
647
|
+
level: "fault",
|
|
648
|
+
event,
|
|
649
|
+
message,
|
|
650
|
+
error,
|
|
651
|
+
context
|
|
652
|
+
});
|
|
653
|
+
return wrote;
|
|
654
|
+
},
|
|
655
|
+
write: (input) => {
|
|
656
|
+
const wrote = backing.write(input);
|
|
657
|
+
forward(input);
|
|
658
|
+
return wrote;
|
|
659
|
+
},
|
|
660
|
+
child: (context) => createChildPluginLogger(wrapped, context)
|
|
661
|
+
};
|
|
662
|
+
return wrapped;
|
|
663
|
+
}
|
|
664
|
+
function nextPluginOperationId(prefix = "op") {
|
|
665
|
+
return `${normalizePluginLogSource(prefix)}-${process.pid}-${++operationSequence}`;
|
|
666
|
+
}
|
|
667
|
+
function withPluginLogContext(context, action) {
|
|
668
|
+
const merged = mergePluginLogContext(correlationStorage.getStore(), context) ?? {};
|
|
669
|
+
return correlationStorage.run(merged, action);
|
|
670
|
+
}
|
|
671
|
+
function lexists(file) {
|
|
672
|
+
try {
|
|
673
|
+
lstatSync(file);
|
|
674
|
+
return true;
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (error.code === "ENOENT") return false;
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function pathFromBaseUrl(baseUrl) {
|
|
681
|
+
try {
|
|
682
|
+
return baseUrl.includes("://") ? resolve(fileURLToPath(baseUrl)) : resolve(baseUrl);
|
|
683
|
+
} catch {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function samePath(left, right) {
|
|
688
|
+
const a = resolve(left);
|
|
689
|
+
const b = resolve(right);
|
|
690
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
691
|
+
}
|
|
692
|
+
function isInside(root, target) {
|
|
693
|
+
const rootResolved = resolve(root);
|
|
694
|
+
const targetResolved = resolve(target);
|
|
695
|
+
if (samePath(rootResolved, targetResolved)) return true;
|
|
696
|
+
const prefix = rootResolved.endsWith(sep) ? rootResolved : rootResolved + sep;
|
|
697
|
+
return process.platform === "win32" ? targetResolved.toLowerCase().startsWith(prefix.toLowerCase()) : targetResolved.startsWith(prefix);
|
|
698
|
+
}
|
|
699
|
+
//#endregion
|
|
63
700
|
//#region ../dsh-plugin-kit/src/host-rpc.ts
|
|
64
701
|
/** The shared authenticated HTTP boundary. Each plugin still owns its RPC envelope semantics. */
|
|
65
702
|
function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
|
|
66
703
|
const bodyLimit = options.maxBodyBytes ?? 65536;
|
|
67
704
|
if (!Number.isSafeInteger(bodyLimit) || bodyLimit <= 0) throw new Error("Invalid RPC body limit");
|
|
68
705
|
const isEndpoint = options.isEndpoint ?? ((endpoint) => /^[a-z.]+$/.test(endpoint));
|
|
706
|
+
const log = options.logger;
|
|
69
707
|
return ctx.webServer.register({
|
|
70
708
|
kind: "prefix",
|
|
71
709
|
path: channel,
|
|
@@ -76,29 +714,54 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
|
|
|
76
714
|
else res.writeHead(status);
|
|
77
715
|
res.end(override ?? options.errorBody?.(status, error) ?? (status === 500 ? "rpc request failed" : ""));
|
|
78
716
|
};
|
|
717
|
+
let endpoint = "";
|
|
79
718
|
try {
|
|
80
719
|
if (typeof ctx.connection.requestRejection !== "function") {
|
|
720
|
+
log?.error("rpc.admission.unavailable", "Host RPC admission policy is unavailable.", void 0, {
|
|
721
|
+
channel,
|
|
722
|
+
status: 503
|
|
723
|
+
});
|
|
81
724
|
reject(503);
|
|
82
725
|
return;
|
|
83
726
|
}
|
|
84
727
|
const rejection = ctx.connection.requestRejection(req);
|
|
85
728
|
if (rejection !== void 0) {
|
|
729
|
+
log?.warn("rpc.admission.rejected", "Host RPC request rejected by connection policy.", void 0, {
|
|
730
|
+
channel,
|
|
731
|
+
status: rejection
|
|
732
|
+
});
|
|
86
733
|
reject(rejection);
|
|
87
734
|
return;
|
|
88
735
|
}
|
|
89
736
|
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
|
90
|
-
|
|
737
|
+
endpoint = pathname.startsWith(`${channel}/`) ? pathname.slice(channel.length + 1) : "";
|
|
91
738
|
if (req.method !== "POST" || !isEndpoint(endpoint)) {
|
|
739
|
+
log?.debug("rpc.route.rejected", "Host RPC route or method rejected.", {
|
|
740
|
+
channel,
|
|
741
|
+
endpoint: endpoint || null,
|
|
742
|
+
method: req.method ?? null,
|
|
743
|
+
status: 404
|
|
744
|
+
});
|
|
92
745
|
reject(404);
|
|
93
746
|
return;
|
|
94
747
|
}
|
|
95
748
|
const contentType = String(req.headers["content-type"] ?? "").split(";", 1)[0]?.trim();
|
|
96
749
|
if ((options.caseInsensitiveContentType ? contentType.toLowerCase() : contentType) !== "application/json") {
|
|
750
|
+
log?.debug("rpc.content-type.rejected", "Host RPC content-type rejected.", {
|
|
751
|
+
channel,
|
|
752
|
+
endpoint,
|
|
753
|
+
status: 415
|
|
754
|
+
});
|
|
97
755
|
reject(415);
|
|
98
756
|
return;
|
|
99
757
|
}
|
|
100
758
|
const guarded = options.beforeBody?.(req, endpoint);
|
|
101
759
|
if (guarded) {
|
|
760
|
+
log?.debug("rpc.guard.rejected", "Host RPC request rejected before body read.", {
|
|
761
|
+
channel,
|
|
762
|
+
endpoint,
|
|
763
|
+
status: guarded.status
|
|
764
|
+
});
|
|
102
765
|
reject(guarded.status, void 0, guarded.body);
|
|
103
766
|
return;
|
|
104
767
|
}
|
|
@@ -112,6 +775,11 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
|
|
|
112
775
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
113
776
|
size += buffer.length;
|
|
114
777
|
if (size > bodyLimit) {
|
|
778
|
+
log?.warn("rpc.body.too-large", "Host RPC request exceeded body limit.", void 0, {
|
|
779
|
+
channel,
|
|
780
|
+
endpoint,
|
|
781
|
+
maxBodyBytes: bodyLimit
|
|
782
|
+
});
|
|
115
783
|
reject(413);
|
|
116
784
|
req.destroy();
|
|
117
785
|
return;
|
|
@@ -122,11 +790,26 @@ function registerHostRpcTransport(ctx, channel, dispatch, options = {}) {
|
|
|
122
790
|
try {
|
|
123
791
|
body = JSON.parse(Buffer.concat(chunks).toString("utf8") || (options.emptyBodyAsNull ? "null" : ""));
|
|
124
792
|
} catch {
|
|
793
|
+
log?.debug("rpc.body.invalid-json", "Host RPC request body is not valid JSON.", {
|
|
794
|
+
channel,
|
|
795
|
+
endpoint,
|
|
796
|
+
status: 400
|
|
797
|
+
});
|
|
125
798
|
reject(400);
|
|
126
799
|
return;
|
|
127
800
|
}
|
|
128
|
-
|
|
801
|
+
if (log) await withPluginLogContext({
|
|
802
|
+
rpcChannel: channel,
|
|
803
|
+
rpcEndpoint: endpoint,
|
|
804
|
+
operationId: nextPluginOperationId("rpc")
|
|
805
|
+
}, () => dispatch(endpoint, body, controller.signal, res));
|
|
806
|
+
else await dispatch(endpoint, body, controller.signal, res);
|
|
129
807
|
} catch (error) {
|
|
808
|
+
log?.error("rpc.dispatch.failed", "Unhandled host RPC handler error.", error, {
|
|
809
|
+
channel,
|
|
810
|
+
endpoint: endpoint || null,
|
|
811
|
+
status: 500
|
|
812
|
+
});
|
|
130
813
|
reject(500, error);
|
|
131
814
|
}
|
|
132
815
|
}
|
|
@@ -147,19 +830,41 @@ function sendHostRpcResponse(response, rpcId, result, noStore = true) {
|
|
|
147
830
|
}
|
|
148
831
|
/** Safe host RPC registration. Missing requestRejection fails closed. */
|
|
149
832
|
function registerHostRpc(ctx, channel, call, options = {}) {
|
|
833
|
+
const log = options.logger;
|
|
150
834
|
return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {
|
|
151
835
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
836
|
+
log?.debug("rpc.envelope.invalid", "Host RPC envelope must be a JSON object.", {
|
|
837
|
+
channel,
|
|
838
|
+
endpoint
|
|
839
|
+
});
|
|
152
840
|
res.writeHead(400);
|
|
153
841
|
res.end();
|
|
154
842
|
return;
|
|
155
843
|
}
|
|
156
844
|
const request = body;
|
|
157
845
|
if (request.type !== "client-request" || request.method !== endpoint || typeof request.rpcId !== "string" || options.requirePayload && !Object.hasOwn(request, "payload")) {
|
|
846
|
+
log?.debug("rpc.envelope.invalid", "Host RPC envelope failed validation.", {
|
|
847
|
+
channel,
|
|
848
|
+
endpoint
|
|
849
|
+
});
|
|
158
850
|
res.writeHead(400);
|
|
159
851
|
res.end();
|
|
160
852
|
return;
|
|
161
853
|
}
|
|
854
|
+
const started = Date.now();
|
|
162
855
|
const raw = await call(endpoint, request.payload, signal);
|
|
856
|
+
const durationMs = Date.now() - started;
|
|
857
|
+
if (raw.ok) log?.debug("rpc.call.completed", "Host RPC call succeeded.", {
|
|
858
|
+
channel,
|
|
859
|
+
endpoint,
|
|
860
|
+
durationMs
|
|
861
|
+
});
|
|
862
|
+
else log?.warn("rpc.call.error", "Host RPC call returned an application error.", void 0, {
|
|
863
|
+
channel,
|
|
864
|
+
endpoint,
|
|
865
|
+
code: raw.error?.code ?? null,
|
|
866
|
+
durationMs
|
|
867
|
+
});
|
|
163
868
|
const result = raw.ok ? raw : {
|
|
164
869
|
...raw,
|
|
165
870
|
error: {
|
|
@@ -465,7 +1170,13 @@ function setSharedModel(state, ctx, input, expectedRevision) {
|
|
|
465
1170
|
if ((Boolean(row.provider) || Boolean(row.model)) && !modelRouteOverride(normalized)) return Promise.reject(/* @__PURE__ */ new Error("Invalid model route"));
|
|
466
1171
|
const commit = state.tail.then(async () => {
|
|
467
1172
|
if (state.closed || !state.domain) throw new Error("Auxiliary model settings are unloading; retry in a moment");
|
|
468
|
-
if (expectedRevision !== state.data.revision)
|
|
1173
|
+
if (expectedRevision !== state.data.revision) {
|
|
1174
|
+
createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).warn("auxiliary-model.conflict", "Auxiliary model update rejected due to revision conflict.", void 0, {
|
|
1175
|
+
expectedRevision: Number(expectedRevision),
|
|
1176
|
+
currentRevision: state.data.revision
|
|
1177
|
+
});
|
|
1178
|
+
throw conflictError("Auxiliary model settings changed; reload and retry");
|
|
1179
|
+
}
|
|
469
1180
|
const next = {
|
|
470
1181
|
revision: state.data.revision + 1,
|
|
471
1182
|
model: modelRouteOverride(normalized) ?? null
|
|
@@ -475,6 +1186,11 @@ function setSharedModel(state, ctx, input, expectedRevision) {
|
|
|
475
1186
|
model: next.model
|
|
476
1187
|
});
|
|
477
1188
|
state.data = next;
|
|
1189
|
+
createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).info("auxiliary-model.updated", "Updated global auxiliary model default.", {
|
|
1190
|
+
revision: next.revision,
|
|
1191
|
+
provider: next.model?.provider ?? null,
|
|
1192
|
+
model: next.model?.model ?? null
|
|
1193
|
+
});
|
|
478
1194
|
notifyModelSettingsChanged(ctx.root, AUXILIARY_MODEL_NS, next.revision);
|
|
479
1195
|
return next;
|
|
480
1196
|
});
|
|
@@ -482,6 +1198,7 @@ function setSharedModel(state, ctx, input, expectedRevision) {
|
|
|
482
1198
|
return commit;
|
|
483
1199
|
}
|
|
484
1200
|
function initScope(state, ctx, scope) {
|
|
1201
|
+
const log = createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false });
|
|
485
1202
|
const generation = ++state.generation;
|
|
486
1203
|
let disposed = false;
|
|
487
1204
|
scope.effect(() => () => {
|
|
@@ -513,6 +1230,11 @@ function initScope(state, ctx, scope) {
|
|
|
513
1230
|
};
|
|
514
1231
|
state.error = void 0;
|
|
515
1232
|
state.available = true;
|
|
1233
|
+
log.info("auxiliary-model.opened", "Shared auxiliary model store opened.", {
|
|
1234
|
+
generation,
|
|
1235
|
+
revision: state.data.revision,
|
|
1236
|
+
hasModel: Boolean(state.data.model)
|
|
1237
|
+
});
|
|
516
1238
|
scope.effect(() => registerModelSettings(scope, {
|
|
517
1239
|
ns: AUXILIARY_MODEL_NS,
|
|
518
1240
|
title: {
|
|
@@ -596,7 +1318,10 @@ function initScope(state, ctx, scope) {
|
|
|
596
1318
|
message: `Unknown endpoint: ${endpoint}`
|
|
597
1319
|
}
|
|
598
1320
|
};
|
|
599
|
-
}, {
|
|
1321
|
+
}, {
|
|
1322
|
+
isEndpoint: (endpoint) => /^[a-z.]+$/.test(endpoint),
|
|
1323
|
+
logger: log
|
|
1324
|
+
}));
|
|
600
1325
|
createModelSettingsChanges(scope, AUXILIARY_MODEL_CHANGES_IDENTITY, AUXILIARY_MODEL_NS);
|
|
601
1326
|
scope.effect(() => () => {
|
|
602
1327
|
if (state.generation === generation) {
|
|
@@ -605,6 +1330,8 @@ function initScope(state, ctx, scope) {
|
|
|
605
1330
|
}
|
|
606
1331
|
});
|
|
607
1332
|
})().catch((error) => {
|
|
1333
|
+
if (error && typeof error === "object" && error[RECOVERABLE]) log.debug("auxiliary-model.superseded", "Shared auxiliary model init superseded during open.", { generation });
|
|
1334
|
+
else log.error("auxiliary-model.init-failed", "Shared auxiliary model store failed to open.", error, { generation });
|
|
608
1335
|
if (error && typeof error === "object") error.auxGeneration = generation;
|
|
609
1336
|
if (state.generation === generation) {
|
|
610
1337
|
state.available = false;
|
|
@@ -628,6 +1355,7 @@ function teardown(state, ctx) {
|
|
|
628
1355
|
try {
|
|
629
1356
|
await state.fiber?.dispose();
|
|
630
1357
|
} catch (error) {
|
|
1358
|
+
createPluginLogger(ctx, "dsh-plugin-kit", { forwardHostWarnError: false }).fault("auxiliary-model.dispose-failed", "Shared auxiliary model owner fiber failed to dispose.", error);
|
|
631
1359
|
try {
|
|
632
1360
|
ctx.logger?.warn("auxiliary-model: fiber dispose failed", error);
|
|
633
1361
|
} catch {}
|
|
@@ -730,7 +1458,7 @@ async function retainAuxiliaryModel(ctx) {
|
|
|
730
1458
|
}
|
|
731
1459
|
}
|
|
732
1460
|
//#endregion
|
|
733
|
-
//#region ../../node_modules
|
|
1461
|
+
//#region ../../node_modules/@deepseek-ai/dsh-util-values/lib/index.js
|
|
734
1462
|
/**
|
|
735
1463
|
* Lazily scanned view of one JSON object's top-level fields, built from text
|
|
736
1464
|
* that may still be streaming or from an already parsed object. Nothing is
|
|
@@ -1407,7 +2135,7 @@ function deepFreeze(value) {
|
|
|
1407
2135
|
return value;
|
|
1408
2136
|
}
|
|
1409
2137
|
//#endregion
|
|
1410
|
-
//#region ../../node_modules
|
|
2138
|
+
//#region ../../node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
|
|
1411
2139
|
/**
|
|
1412
2140
|
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
1413
2141
|
* @returns the UUID string.
|
|
@@ -1420,7 +2148,7 @@ function randomUUID() {
|
|
|
1420
2148
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1421
2149
|
}
|
|
1422
2150
|
//#endregion
|
|
1423
|
-
//#region ../../node_modules
|
|
2151
|
+
//#region ../../node_modules/@deepseek-ai/dsh-brand/lib/index.js
|
|
1424
2152
|
/**
|
|
1425
2153
|
* Duplicate-install-safe nominal primitive helpers.
|
|
1426
2154
|
*
|
|
@@ -1443,7 +2171,7 @@ function brandString(value) {
|
|
|
1443
2171
|
return value;
|
|
1444
2172
|
}
|
|
1445
2173
|
//#endregion
|
|
1446
|
-
//#region ../../node_modules
|
|
2174
|
+
//#region ../../node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
1447
2175
|
/** Return true when a value is `null` or `undefined`. */
|
|
1448
2176
|
function isNullable(value) {
|
|
1449
2177
|
return value === null || value === void 0;
|
|
@@ -1697,7 +2425,7 @@ var Time;
|
|
|
1697
2425
|
Time.template = template;
|
|
1698
2426
|
})(Time || (Time = {}));
|
|
1699
2427
|
//#endregion
|
|
1700
|
-
//#region ../../node_modules
|
|
2428
|
+
//#region ../../node_modules/@deepseek-ai/schemastery/lib/index.mjs
|
|
1701
2429
|
const kSchema = Symbol.for("schemastery");
|
|
1702
2430
|
const kValidationError = Symbol.for("ValidationError");
|
|
1703
2431
|
globalThis.__schemastery_index__ ??= 0;
|
|
@@ -2330,11 +3058,11 @@ defineMethod("transform", [
|
|
|
2330
3058
|
"preserve"
|
|
2331
3059
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
2332
3060
|
//#endregion
|
|
2333
|
-
//#region ../../node_modules
|
|
3061
|
+
//#region ../../node_modules/@deepseek-ai/dsh-timeout/lib/index.js
|
|
2334
3062
|
/** Largest delay Node schedules without clamping it to one millisecond. */
|
|
2335
3063
|
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
2336
3064
|
//#endregion
|
|
2337
|
-
//#region ../../node_modules
|
|
3065
|
+
//#region ../../node_modules/@deepseek-ai/dsh-llm/lib/index.js
|
|
2338
3066
|
/**
|
|
2339
3067
|
* Detach and deep-freeze a message whose identity already exists.
|
|
2340
3068
|
* @param message - complete message, including its stable identity.
|
|
@@ -3411,12 +4139,17 @@ var LlmError = class extends HarnessError {
|
|
|
3411
4139
|
if (typeof model.provider !== "string" || model.provider !== provider || typeof model.id !== "string" || model.id.length === 0 || typeof model.name !== "string" || model.name.length === 0 || model.description !== void 0 && typeof model.description !== "string" || seen.has(model.id)) throw new LlmError(`adapter returned invalid or duplicate model metadata for provider "${provider}"`, "INVALID_CATALOG");
|
|
3412
4140
|
seen.add(model.id);
|
|
3413
4141
|
const inputModalities = this.detachedModalities(model.inputModalities);
|
|
4142
|
+
const badge = model.badge;
|
|
3414
4143
|
return {
|
|
3415
4144
|
provider: model.provider,
|
|
3416
4145
|
id: model.id,
|
|
3417
4146
|
name: model.name,
|
|
3418
4147
|
...model.description === void 0 ? {} : { description: model.description },
|
|
3419
|
-
...inputModalities === void 0 ? {} : { inputModalities }
|
|
4148
|
+
...inputModalities === void 0 ? {} : { inputModalities },
|
|
4149
|
+
...badge === void 0 ? {} : { badge: {
|
|
4150
|
+
text: typeof badge.text === "string" ? badge.text : { ...badge.text },
|
|
4151
|
+
tone: badge.tone
|
|
4152
|
+
} }
|
|
3420
4153
|
};
|
|
3421
4154
|
});
|
|
3422
4155
|
}
|
|
@@ -3838,56 +4571,89 @@ async function callLlmText(llm, request) {
|
|
|
3838
4571
|
request.signal.throwIfAborted();
|
|
3839
4572
|
const live = () => request.isCurrent?.() !== false && !request.signal.aborted;
|
|
3840
4573
|
if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
|
|
3841
|
-
const
|
|
3842
|
-
const
|
|
4574
|
+
const started = Date.now();
|
|
4575
|
+
const logBase = {
|
|
4576
|
+
plugin: request.plugin,
|
|
4577
|
+
feature: request.feature ?? null,
|
|
3843
4578
|
provider: request.route.provider,
|
|
3844
4579
|
model: request.route.model,
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
}, request.signal, request.configure);
|
|
3848
|
-
if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
|
|
3849
|
-
const options = {
|
|
3850
|
-
provider: prepared.config.provider,
|
|
3851
|
-
model: prepared.config.model,
|
|
3852
|
-
...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
|
|
3853
|
-
...prepared.config.maxTokens !== void 0 ? { maxTokens: Math.min(request.maxTokens, prepared.config.maxTokens) } : { maxTokens: request.maxTokens },
|
|
3854
|
-
...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
|
|
3855
|
-
...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
|
|
3856
|
-
system: request.system,
|
|
3857
|
-
messages: [createUserMessage({
|
|
3858
|
-
source: producerMessageSource(request.plugin),
|
|
3859
|
-
content: [{
|
|
3860
|
-
type: "text",
|
|
3861
|
-
text: buildAuxiliaryUserText(request)
|
|
3862
|
-
}]
|
|
3863
|
-
})],
|
|
3864
|
-
signal: request.signal,
|
|
3865
|
-
...request.purpose ? { purpose: request.purpose } : {},
|
|
3866
|
-
...request.sessionId ? { sessionId: request.sessionId } : {}
|
|
4580
|
+
purpose: request.purpose ?? null,
|
|
4581
|
+
sessionId: request.sessionId ?? null
|
|
3867
4582
|
};
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
4583
|
+
request.logger?.debug("llm.call.started", "Auxiliary model call started.", logBase);
|
|
4584
|
+
try {
|
|
4585
|
+
const effort = request.route.reasoningEffort?.trim();
|
|
4586
|
+
const prepared = await llm.prepareCall({
|
|
4587
|
+
provider: request.route.provider,
|
|
4588
|
+
model: request.route.model,
|
|
4589
|
+
...effort ? { reasoningEffort: ReasoningEffortId(effort) } : {},
|
|
4590
|
+
...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {}
|
|
4591
|
+
}, request.signal, request.configure);
|
|
4592
|
+
if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
|
|
4593
|
+
const maxTokens = request.maxTokens === void 0 ? prepared.config.maxTokens : Math.min(request.maxTokens, prepared.config.maxTokens ?? request.maxTokens);
|
|
4594
|
+
const options = {
|
|
4595
|
+
provider: prepared.config.provider,
|
|
4596
|
+
model: prepared.config.model,
|
|
4597
|
+
...prepared.config.reasoningEffort ? { reasoningEffort: prepared.config.reasoningEffort } : {},
|
|
4598
|
+
...maxTokens !== void 0 ? { maxTokens } : {},
|
|
4599
|
+
...prepared.config.temperature !== void 0 ? { temperature: prepared.config.temperature } : {},
|
|
4600
|
+
...prepared.config.stop ? { stop: [...prepared.config.stop] } : {},
|
|
4601
|
+
system: request.system,
|
|
4602
|
+
messages: [createUserMessage({
|
|
4603
|
+
source: producerMessageSource(request.plugin),
|
|
4604
|
+
content: [{
|
|
4605
|
+
type: "text",
|
|
4606
|
+
text: buildAuxiliaryUserText(request)
|
|
4607
|
+
}]
|
|
4608
|
+
})],
|
|
4609
|
+
signal: request.signal,
|
|
4610
|
+
...request.purpose ? { purpose: request.purpose } : {},
|
|
4611
|
+
...request.sessionId ? { sessionId: request.sessionId } : {}
|
|
4612
|
+
};
|
|
4613
|
+
const assembler = await readStream(prepared.stream(options), request.signal, live);
|
|
4614
|
+
if (request.signal.aborted || assembler.finish.kind === "aborted") throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
|
|
4615
|
+
if (!live()) throw Object.assign(/* @__PURE__ */ new Error("调用已取消。"), { name: "AbortError" });
|
|
4616
|
+
if (assembler.finish.kind === "error") {
|
|
4617
|
+
const code = assembler.finish.failure.code;
|
|
4618
|
+
throw Object.assign(new Error(assembler.finish.failure.message || "模型调用失败。"), { code });
|
|
4619
|
+
}
|
|
4620
|
+
if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
|
|
4621
|
+
if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
|
|
4622
|
+
const usage = assembler.usage;
|
|
4623
|
+
request.logger?.debug("llm.call.completed", "Auxiliary model call completed.", {
|
|
4624
|
+
...logBase,
|
|
4625
|
+
provider: prepared.config.provider,
|
|
4626
|
+
model: prepared.config.model,
|
|
4627
|
+
durationMs: Date.now() - started,
|
|
4628
|
+
...usage?.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
|
|
4629
|
+
...usage?.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
|
|
4630
|
+
...usage?.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {}
|
|
4631
|
+
});
|
|
4632
|
+
return {
|
|
4633
|
+
text: joinText(assembler),
|
|
4634
|
+
provider: prepared.config.provider,
|
|
4635
|
+
model: prepared.config.model,
|
|
4636
|
+
...usage ? {
|
|
4637
|
+
inputTokens: usage.inputTokens,
|
|
4638
|
+
outputTokens: usage.outputTokens,
|
|
4639
|
+
...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
|
|
4640
|
+
...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
|
|
4641
|
+
...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
|
|
4642
|
+
...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
|
|
4643
|
+
} : {}
|
|
4644
|
+
};
|
|
4645
|
+
} catch (error) {
|
|
4646
|
+
const durationMs = Date.now() - started;
|
|
4647
|
+
if (request.signal.aborted || error instanceof Error && error.name === "AbortError") request.logger?.debug("llm.call.cancelled", "Auxiliary model call cancelled.", {
|
|
4648
|
+
...logBase,
|
|
4649
|
+
durationMs
|
|
4650
|
+
});
|
|
4651
|
+
else request.logger?.warn("llm.call.failed", "Auxiliary model call failed.", error, {
|
|
4652
|
+
...logBase,
|
|
4653
|
+
durationMs
|
|
4654
|
+
});
|
|
4655
|
+
throw error;
|
|
3874
4656
|
}
|
|
3875
|
-
if (assembler.blocks().some((block) => block.type === "tool-call") || assembler.finish.kind === "tool-calls") throw new Error("这次调用不使用工具。");
|
|
3876
|
-
if (assembler.finish.kind !== "stop") throw new Error("模型调用未完成。");
|
|
3877
|
-
const usage = assembler.usage;
|
|
3878
|
-
return {
|
|
3879
|
-
text: joinText(assembler),
|
|
3880
|
-
provider: prepared.config.provider,
|
|
3881
|
-
model: prepared.config.model,
|
|
3882
|
-
...usage ? {
|
|
3883
|
-
inputTokens: usage.inputTokens,
|
|
3884
|
-
outputTokens: usage.outputTokens,
|
|
3885
|
-
...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {},
|
|
3886
|
-
...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {},
|
|
3887
|
-
...usage.cacheWriteTokens !== void 0 ? { cacheWriteTokens: usage.cacheWriteTokens } : {},
|
|
3888
|
-
...usage.reasoningTokens !== void 0 ? { reasoningTokens: usage.reasoningTokens } : {}
|
|
3889
|
-
} : {}
|
|
3890
|
-
};
|
|
3891
4657
|
}
|
|
3892
4658
|
//#endregion
|
|
3893
4659
|
//#region src/git.ts
|
|
@@ -4786,7 +5552,6 @@ var GitCommitService = class {
|
|
|
4786
5552
|
text: buildPlanText(planInput, Math.max(1024, MAX_MANIFEST_CHARS - envelopeBase.length)),
|
|
4787
5553
|
route: planned.route,
|
|
4788
5554
|
system: planSystemPrompt(),
|
|
4789
|
-
maxTokens: DEFAULT_MAX_OUTPUT_TOKENS,
|
|
4790
5555
|
signal,
|
|
4791
5556
|
sessionId
|
|
4792
5557
|
});
|