laohuang 0.9.1 → 0.9.2
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/dist/bin.js +1153 -241
- package/dist/bin.js.map +4 -4
- package/package.json +4 -1
package/dist/bin.js
CHANGED
|
@@ -7369,8 +7369,8 @@ var require_dist = __commonJS({
|
|
|
7369
7369
|
});
|
|
7370
7370
|
|
|
7371
7371
|
// src/main.ts
|
|
7372
|
-
import { existsSync as
|
|
7373
|
-
import { dirname as dirname13, join as
|
|
7372
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
7373
|
+
import { dirname as dirname13, join as join20 } from "node:path";
|
|
7374
7374
|
import { fileURLToPath } from "node:url";
|
|
7375
7375
|
|
|
7376
7376
|
// ../../packages/session/session-context/dist/conversation-history.js
|
|
@@ -7415,13 +7415,14 @@ var DefaultTokenEstimator = class {
|
|
|
7415
7415
|
};
|
|
7416
7416
|
}
|
|
7417
7417
|
estimateMessage(message) {
|
|
7418
|
+
const attachmentTokens = message.role === "user" || message.role === "tool-result" ? (message.attachments ?? []).reduce((sum, block, index) => sum + (block.type === "image" && !message.omittedImageIndexes?.includes(index) ? 85 + 170 * Math.ceil(block.ref.width / 512) * Math.ceil(block.ref.height / 512) : 32), 0) : 0;
|
|
7418
7419
|
if (message.role === "system" && message.toolDefinitions !== void 0)
|
|
7419
7420
|
return 0;
|
|
7420
7421
|
if (message.role === "system" || message.role === "user") {
|
|
7421
|
-
return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content);
|
|
7422
|
+
return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
|
|
7422
7423
|
}
|
|
7423
7424
|
if (message.role === "tool-result") {
|
|
7424
|
-
return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content);
|
|
7425
|
+
return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
|
|
7425
7426
|
}
|
|
7426
7427
|
return MESSAGE_OVERHEAD + message.content.reduce((total, block) => {
|
|
7427
7428
|
if (block.type === "text" || block.type === "reasoning") {
|
|
@@ -7489,7 +7490,7 @@ var ContextUsage = class {
|
|
|
7489
7490
|
}
|
|
7490
7491
|
/** Histories are append-only and ordered; process each committed entry once. */
|
|
7491
7492
|
append(entry) {
|
|
7492
|
-
if (entry.entryType === "compaction") {
|
|
7493
|
+
if (entry.entryType === "compaction" || entry.entryType === "image_offload") {
|
|
7493
7494
|
this.#tokens = null;
|
|
7494
7495
|
return;
|
|
7495
7496
|
}
|
|
@@ -7549,14 +7550,14 @@ var ConversationHistory = class _ConversationHistory {
|
|
|
7549
7550
|
}
|
|
7550
7551
|
static fromReplay(replay, journal) {
|
|
7551
7552
|
const history = new _ConversationHistory(replay.items.filter((item) => item.kind === "entry"), journal);
|
|
7552
|
-
for (const
|
|
7553
|
+
for (const open6 of replay.openToolCalls) {
|
|
7553
7554
|
const entry = history.appendToolResults({
|
|
7554
7555
|
requestId: "recovered",
|
|
7555
7556
|
recovered: true,
|
|
7556
7557
|
messages: [{
|
|
7557
7558
|
role: "tool-result",
|
|
7558
|
-
toolCallId:
|
|
7559
|
-
toolName:
|
|
7559
|
+
toolCallId: open6.toolCallId,
|
|
7560
|
+
toolName: open6.toolName,
|
|
7560
7561
|
content: JSON.stringify({
|
|
7561
7562
|
ok: false,
|
|
7562
7563
|
status: "interrupted",
|
|
@@ -7569,7 +7570,7 @@ var ConversationHistory = class _ConversationHistory {
|
|
|
7569
7570
|
recordType: "error",
|
|
7570
7571
|
payload: {
|
|
7571
7572
|
type: "interrupted_tool_call",
|
|
7572
|
-
toolCallId:
|
|
7573
|
+
toolCallId: open6.toolCallId,
|
|
7573
7574
|
repairedEntryId: entry?.id ?? null
|
|
7574
7575
|
}
|
|
7575
7576
|
});
|
|
@@ -7661,6 +7662,12 @@ var ConversationHistory = class _ConversationHistory {
|
|
|
7661
7662
|
entries() {
|
|
7662
7663
|
return [...this.#entries];
|
|
7663
7664
|
}
|
|
7665
|
+
offloadImages(keys2) {
|
|
7666
|
+
if (keys2.length === 0)
|
|
7667
|
+
return;
|
|
7668
|
+
this.append({ entryType: "image_offload", payload: { keys: [...keys2] } });
|
|
7669
|
+
this.#journal.flush();
|
|
7670
|
+
}
|
|
7664
7671
|
get contextTokens() {
|
|
7665
7672
|
return this.#contextUsage.tokens;
|
|
7666
7673
|
}
|
|
@@ -7728,6 +7735,14 @@ function clampReasoningEffort(supported, requested) {
|
|
|
7728
7735
|
}
|
|
7729
7736
|
return "off";
|
|
7730
7737
|
}
|
|
7738
|
+
function projectImageOffloads(message, keys2) {
|
|
7739
|
+
if (message.role !== "user" && message.role !== "tool-result" || !message.attachments?.length)
|
|
7740
|
+
return message;
|
|
7741
|
+
const omittedImageIndexes = message.attachments.flatMap((block, index) => block.type === "image" && keys2.has(`${message.attachmentKey}:${index}`) ? [index] : []);
|
|
7742
|
+
if (!omittedImageIndexes.length)
|
|
7743
|
+
return message;
|
|
7744
|
+
return { ...message, omittedImageIndexes };
|
|
7745
|
+
}
|
|
7731
7746
|
var ModelError = class extends ModelStreamError {
|
|
7732
7747
|
kind;
|
|
7733
7748
|
constructor(message, options) {
|
|
@@ -7813,6 +7828,7 @@ var ModelRuntime = class {
|
|
|
7813
7828
|
const maxAttempts = validateMaxAttempts(request.maxAttempts ?? 3);
|
|
7814
7829
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
7815
7830
|
const adapterRequest = {
|
|
7831
|
+
...request.imageContext === void 0 ? {} : { imageContext: request.imageContext },
|
|
7816
7832
|
provider: request.provider,
|
|
7817
7833
|
model: request.model,
|
|
7818
7834
|
...request.baseUrl === void 0 ? {} : { baseUrl: request.baseUrl },
|
|
@@ -7921,7 +7937,7 @@ async function defaultSleep(delayMs, cancelToken) {
|
|
|
7921
7937
|
throw new ModelStreamCancelled(cancelToken?.reason || "cancelled");
|
|
7922
7938
|
}
|
|
7923
7939
|
if (cancelToken === null) {
|
|
7924
|
-
await new Promise((
|
|
7940
|
+
await new Promise((resolve15) => setTimeout(resolve15, delayMs));
|
|
7925
7941
|
}
|
|
7926
7942
|
}
|
|
7927
7943
|
function errorMessage(error) {
|
|
@@ -7970,8 +7986,9 @@ ${activeCompaction.payload.summary}
|
|
|
7970
7986
|
}
|
|
7971
7987
|
}
|
|
7972
7988
|
validateToolPairs(selected);
|
|
7989
|
+
const offloaded = new Set(sorted.flatMap((entry) => entry.entryType === "image_offload" ? entry.payload.keys : []));
|
|
7973
7990
|
return {
|
|
7974
|
-
messages: selected.map((item) => item.message),
|
|
7991
|
+
messages: selected.map((item) => projectImageOffloads(item.message, offloaded)),
|
|
7975
7992
|
sourceEntryIds: selected.map((item) => item.entryId),
|
|
7976
7993
|
activeCompactionId: activeCompaction?.id ?? null
|
|
7977
7994
|
};
|
|
@@ -8084,12 +8101,15 @@ function selectCompactionPlan(input) {
|
|
|
8084
8101
|
if (retained.length === 0 && units.length > 0) {
|
|
8085
8102
|
retained.push(units.at(-1));
|
|
8086
8103
|
}
|
|
8087
|
-
const
|
|
8104
|
+
const currentTurn = input.protectedFromSeq ?? [...input.entries].reverse().find((entry) => entry.entryType === "user_message")?.seq ?? 0;
|
|
8105
|
+
const hasCurrentImages = input.entries.some((entry) => entry.seq >= currentTurn && "message" in entry.payload && "attachments" in entry.payload.message && entry.payload.message.attachments?.some((block) => block.type === "image"));
|
|
8106
|
+
const retainedFirst = Math.min(retained[0]?.entries[0]?.seq ?? Number.MAX_SAFE_INTEGER, hasCurrentImages ? currentTurn : Number.MAX_SAFE_INTEGER);
|
|
8107
|
+
const retainedUnits = units.filter((unit) => unit.entries[0].seq >= retainedFirst);
|
|
8088
8108
|
const summarized = units.filter((unit) => unit.entries[0].seq < retainedFirst);
|
|
8089
8109
|
return {
|
|
8090
8110
|
summarizedEntries: summarized.flatMap((unit) => unit.entries),
|
|
8091
|
-
retainedEntries:
|
|
8092
|
-
retainedFromSeq:
|
|
8111
|
+
retainedEntries: retainedUnits.flatMap((unit) => unit.entries),
|
|
8112
|
+
retainedFromSeq: retainedUnits[0]?.entries[0]?.seq ?? 1
|
|
8093
8113
|
};
|
|
8094
8114
|
}
|
|
8095
8115
|
function serializeConversation(entries) {
|
|
@@ -8111,6 +8131,10 @@ function serializeConversation(entries) {
|
|
|
8111
8131
|
} else if (entry.entryType === "user_message" || entry.entryType === "skill_context" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
|
|
8112
8132
|
lines2.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
|
|
8113
8133
|
}
|
|
8134
|
+
if ("message" in entry.payload && "attachments" in entry.payload.message) {
|
|
8135
|
+
for (const block of entry.payload.message.attachments ?? [])
|
|
8136
|
+
lines2.push(`[attachment metadata only; not visual interpretation] ${JSON.stringify(block)}`);
|
|
8137
|
+
}
|
|
8114
8138
|
lines2.push("</entry>");
|
|
8115
8139
|
}
|
|
8116
8140
|
lines2.push("</conversation>");
|
|
@@ -8123,7 +8147,7 @@ function semanticUnits(entries) {
|
|
|
8123
8147
|
const entry = entries[index];
|
|
8124
8148
|
if (groupedDefinitions.has(entry.id))
|
|
8125
8149
|
continue;
|
|
8126
|
-
if (entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
|
|
8150
|
+
if (entry.entryType === "image_offload" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
|
|
8127
8151
|
continue;
|
|
8128
8152
|
}
|
|
8129
8153
|
if (entry.entryType === "assistant_message") {
|
|
@@ -8208,6 +8232,7 @@ var ContextGovernor = class {
|
|
|
8208
8232
|
async compact(input) {
|
|
8209
8233
|
const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
|
|
8210
8234
|
const plan = selectCompactionPlan({
|
|
8235
|
+
protectedFromSeq: input.protectedFromSeq,
|
|
8211
8236
|
entries: input.entries,
|
|
8212
8237
|
// A search preflight reserves the pending call/result/definition unit.
|
|
8213
8238
|
// Keep the latest completed unit and summarize older units to make room.
|
|
@@ -8282,6 +8307,66 @@ function calculateModelBudget(input) {
|
|
|
8282
8307
|
return { hardInputLimit, safetyTokens, autoTrigger, retainTokens };
|
|
8283
8308
|
}
|
|
8284
8309
|
|
|
8310
|
+
// ../../packages/attachment/attachment/dist/errors.js
|
|
8311
|
+
var AttachmentError = class extends Error {
|
|
8312
|
+
code;
|
|
8313
|
+
constructor(code, message, options) {
|
|
8314
|
+
super(message, options);
|
|
8315
|
+
this.name = "AttachmentError";
|
|
8316
|
+
this.code = code;
|
|
8317
|
+
}
|
|
8318
|
+
};
|
|
8319
|
+
|
|
8320
|
+
// ../../packages/attachment/attachment/dist/types.js
|
|
8321
|
+
var DEFAULT_IMAGE_LIMITS = Object.freeze({
|
|
8322
|
+
maxBytes: 20 * 1024 ** 2,
|
|
8323
|
+
maxImages: 20,
|
|
8324
|
+
maxBatchBytes: 200 * 1024 ** 2,
|
|
8325
|
+
maxPixels: 64e6,
|
|
8326
|
+
maxDimension: 8192
|
|
8327
|
+
});
|
|
8328
|
+
var RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
8329
|
+
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
8330
|
+
function assertAttachmentContent(value) {
|
|
8331
|
+
if (!value || typeof value !== "object")
|
|
8332
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment content");
|
|
8333
|
+
const block = value;
|
|
8334
|
+
if (block.type !== "image" && block.type !== "file")
|
|
8335
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment kind");
|
|
8336
|
+
assertFileRef(block.ref);
|
|
8337
|
+
if (block.type === "image") {
|
|
8338
|
+
const ref = block.ref;
|
|
8339
|
+
if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(String(ref.mimeType)) || !Number.isSafeInteger(ref.width) || Number(ref.width) < 1 || !Number.isSafeInteger(ref.height) || Number(ref.height) < 1 || typeof ref.animated !== "boolean") {
|
|
8340
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid image reference");
|
|
8341
|
+
}
|
|
8342
|
+
if (block.view !== void 0)
|
|
8343
|
+
assertImageView(block.view);
|
|
8344
|
+
}
|
|
8345
|
+
}
|
|
8346
|
+
function assertFileRef(value) {
|
|
8347
|
+
const ref = value;
|
|
8348
|
+
if (!ref || typeof ref !== "object" || typeof ref.id !== "string" || !/^sha256:[a-f0-9]{64}$/.test(ref.id) || !Number.isSafeInteger(ref.bytes) || Number(ref.bytes) < 0 || typeof ref.name !== "string") {
|
|
8349
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment reference");
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
function assertImageView(value) {
|
|
8353
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8354
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid image view");
|
|
8355
|
+
const view = value;
|
|
8356
|
+
if (view.fullResolution !== void 0 && typeof view.fullResolution !== "boolean")
|
|
8357
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid fullResolution");
|
|
8358
|
+
if (view.region !== void 0) {
|
|
8359
|
+
const r = view.region;
|
|
8360
|
+
if (!r || typeof r !== "object" || !["x", "y", "width", "height"].every((k) => Number.isSafeInteger(r[k])) || Number(r.x) < 0 || Number(r.y) < 0 || Number(r.width) < 1 || Number(r.height) < 1) {
|
|
8361
|
+
throw new AttachmentError("INVALID_REFERENCE", "Invalid crop region");
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
}
|
|
8365
|
+
|
|
8366
|
+
// ../../packages/attachment/attachment/dist/attachment-store.js
|
|
8367
|
+
var AttachmentStore = class {
|
|
8368
|
+
};
|
|
8369
|
+
|
|
8285
8370
|
// ../../packages/session/session-store/dist/session-metadata.js
|
|
8286
8371
|
function normalizeSessionTitle(input) {
|
|
8287
8372
|
if (new RegExp("\\p{Cc}|\\u2028|\\u2029", "u").test(input)) {
|
|
@@ -8312,6 +8397,7 @@ function sessionTitleAt(items, throughSeq = Infinity) {
|
|
|
8312
8397
|
|
|
8313
8398
|
// ../../packages/session/session-store/dist/schema.js
|
|
8314
8399
|
var ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
8400
|
+
"image_offload",
|
|
8315
8401
|
"skill_context",
|
|
8316
8402
|
"tool_definitions",
|
|
8317
8403
|
"tool_catalog",
|
|
@@ -8420,7 +8506,21 @@ function parseSessionItem(value, expectedSessionId) {
|
|
|
8420
8506
|
if (!ENTRY_TYPES.has(entryType)) {
|
|
8421
8507
|
throw new SessionSchemaError("invalid session entry type");
|
|
8422
8508
|
}
|
|
8423
|
-
objectOf(input["payload"], "entry payload");
|
|
8509
|
+
const entryPayload = objectOf(input["payload"], "entry payload");
|
|
8510
|
+
if (entryType === "image_offload" && (!Array.isArray(entryPayload.keys) || entryPayload.keys.some((key) => typeof key !== "string"))) {
|
|
8511
|
+
throw new SessionSchemaError("invalid image offload keys");
|
|
8512
|
+
}
|
|
8513
|
+
if (entryPayload.message !== void 0) {
|
|
8514
|
+
const message = objectOf(entryPayload.message, "message");
|
|
8515
|
+
if (message.attachments !== void 0) {
|
|
8516
|
+
if (message.role !== "user" && message.role !== "tool-result" || !Array.isArray(message.attachments))
|
|
8517
|
+
throw new SessionSchemaError("invalid message attachments");
|
|
8518
|
+
for (const attachment of message.attachments)
|
|
8519
|
+
assertAttachmentContent(attachment);
|
|
8520
|
+
if (typeof message.attachmentKey !== "string" || !message.attachmentKey)
|
|
8521
|
+
throw new SessionSchemaError("missing attachment occurrence key");
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8424
8524
|
if (entryType === "skill_context") {
|
|
8425
8525
|
const payload = objectOf(input["payload"], "skill payload");
|
|
8426
8526
|
const message = objectOf(payload["message"], "skill message");
|
|
@@ -8516,35 +8616,9 @@ function requirePositiveInteger(input, key) {
|
|
|
8516
8616
|
return value;
|
|
8517
8617
|
}
|
|
8518
8618
|
|
|
8519
|
-
// ../../packages/session/session-store/dist/
|
|
8520
|
-
import {
|
|
8521
|
-
import {
|
|
8522
|
-
import { basename, join, resolve } from "node:path";
|
|
8523
|
-
function canonicalProjectRoot(projectRoot) {
|
|
8524
|
-
try {
|
|
8525
|
-
return realpathSync.native(projectRoot);
|
|
8526
|
-
} catch {
|
|
8527
|
-
return resolve(projectRoot);
|
|
8528
|
-
}
|
|
8529
|
-
}
|
|
8530
|
-
function projectKeyForRoot(projectRoot) {
|
|
8531
|
-
const canonical = canonicalProjectRoot(projectRoot);
|
|
8532
|
-
const digest = createHash2("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
8533
|
-
return `${sanitizePathPart(basename(canonical) || "project")}-${digest}`;
|
|
8534
|
-
}
|
|
8535
|
-
function ensureSessionDirectory(sessionsRoot, projectKey) {
|
|
8536
|
-
const directory = join(sessionsRoot, projectKey);
|
|
8537
|
-
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
8538
|
-
return directory;
|
|
8539
|
-
}
|
|
8540
|
-
function sessionPathForHeader(sessionsRoot, projectKey, createdAt, sessionId) {
|
|
8541
|
-
const safeCreatedAt = createdAt.replaceAll(":", "").replaceAll(".", "");
|
|
8542
|
-
return join(ensureSessionDirectory(sessionsRoot, projectKey), `${safeCreatedAt}_${sessionId}.jsonl`);
|
|
8543
|
-
}
|
|
8544
|
-
function sanitizePathPart(value) {
|
|
8545
|
-
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8546
|
-
return sanitized || "project";
|
|
8547
|
-
}
|
|
8619
|
+
// ../../packages/session/session-store/dist/attachment-references.js
|
|
8620
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
8621
|
+
import { join } from "node:path";
|
|
8548
8622
|
|
|
8549
8623
|
// ../../packages/session/session-store/dist/session-reader.js
|
|
8550
8624
|
import { readFileSync } from "node:fs";
|
|
@@ -8636,6 +8710,78 @@ function findOpenToolCalls(items) {
|
|
|
8636
8710
|
return [...calls.values()];
|
|
8637
8711
|
}
|
|
8638
8712
|
|
|
8713
|
+
// ../../packages/session/session-store/dist/attachment-references.js
|
|
8714
|
+
function attachmentReferences(entries) {
|
|
8715
|
+
const result = /* @__PURE__ */ new Set();
|
|
8716
|
+
for (const entry of entries) {
|
|
8717
|
+
if (entry.entryType === "compaction" || entry.entryType === "image_offload")
|
|
8718
|
+
continue;
|
|
8719
|
+
if (!("message" in entry.payload) || !entry.payload.message || typeof entry.payload.message !== "object")
|
|
8720
|
+
throw new Error("Incomplete session message during attachment scan");
|
|
8721
|
+
const message = entry.payload.message;
|
|
8722
|
+
if (!("attachments" in message))
|
|
8723
|
+
continue;
|
|
8724
|
+
for (const block of message.attachments ?? []) {
|
|
8725
|
+
assertAttachmentContent(block);
|
|
8726
|
+
result.add(block.ref.id);
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
return result;
|
|
8730
|
+
}
|
|
8731
|
+
function scanAttachmentReferences(sessionsRoot) {
|
|
8732
|
+
const result = /* @__PURE__ */ new Set();
|
|
8733
|
+
const visit = (directory) => {
|
|
8734
|
+
for (const file of readdirSync(directory, { withFileTypes: true })) {
|
|
8735
|
+
const path5 = join(directory, file.name);
|
|
8736
|
+
if (file.isSymbolicLink())
|
|
8737
|
+
throw new Error(`Cannot scan symbolic session path: ${path5}`);
|
|
8738
|
+
if (file.isDirectory())
|
|
8739
|
+
visit(path5);
|
|
8740
|
+
else if (file.name.endsWith(".jsonl")) {
|
|
8741
|
+
if (!lstatSync(path5).isFile())
|
|
8742
|
+
throw new Error(`Invalid session file: ${path5}`);
|
|
8743
|
+
const replay = readSessionFile(path5);
|
|
8744
|
+
if (replay.ignoredTornTail)
|
|
8745
|
+
throw new Error(`Incomplete session journal: ${path5}`);
|
|
8746
|
+
for (const id of attachmentReferences(replay.items.filter((item) => item.kind === "entry")))
|
|
8747
|
+
result.add(id);
|
|
8748
|
+
}
|
|
8749
|
+
}
|
|
8750
|
+
};
|
|
8751
|
+
visit(sessionsRoot);
|
|
8752
|
+
return result;
|
|
8753
|
+
}
|
|
8754
|
+
|
|
8755
|
+
// ../../packages/session/session-store/dist/session-paths.js
|
|
8756
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8757
|
+
import { mkdirSync, realpathSync } from "node:fs";
|
|
8758
|
+
import { basename, join as join2, resolve } from "node:path";
|
|
8759
|
+
function canonicalProjectRoot(projectRoot) {
|
|
8760
|
+
try {
|
|
8761
|
+
return realpathSync.native(projectRoot);
|
|
8762
|
+
} catch {
|
|
8763
|
+
return resolve(projectRoot);
|
|
8764
|
+
}
|
|
8765
|
+
}
|
|
8766
|
+
function projectKeyForRoot(projectRoot) {
|
|
8767
|
+
const canonical = canonicalProjectRoot(projectRoot);
|
|
8768
|
+
const digest = createHash2("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
8769
|
+
return `${sanitizePathPart(basename(canonical) || "project")}-${digest}`;
|
|
8770
|
+
}
|
|
8771
|
+
function ensureSessionDirectory(sessionsRoot, projectKey) {
|
|
8772
|
+
const directory = join2(sessionsRoot, projectKey);
|
|
8773
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
8774
|
+
return directory;
|
|
8775
|
+
}
|
|
8776
|
+
function sessionPathForHeader(sessionsRoot, projectKey, createdAt, sessionId) {
|
|
8777
|
+
const safeCreatedAt = createdAt.replaceAll(":", "").replaceAll(".", "");
|
|
8778
|
+
return join2(ensureSessionDirectory(sessionsRoot, projectKey), `${safeCreatedAt}_${sessionId}.jsonl`);
|
|
8779
|
+
}
|
|
8780
|
+
function sanitizePathPart(value) {
|
|
8781
|
+
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8782
|
+
return sanitized || "project";
|
|
8783
|
+
}
|
|
8784
|
+
|
|
8639
8785
|
// ../../packages/session/session-store/dist/session-journal.js
|
|
8640
8786
|
import { closeSync, existsSync, fsyncSync, openSync, readFileSync as readFileSync2, unlinkSync, writeSync } from "node:fs";
|
|
8641
8787
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -8646,6 +8792,7 @@ var SessionJournalLockError = class extends Error {
|
|
|
8646
8792
|
}
|
|
8647
8793
|
};
|
|
8648
8794
|
var FileSessionJournal = class {
|
|
8795
|
+
attachments;
|
|
8649
8796
|
header;
|
|
8650
8797
|
path;
|
|
8651
8798
|
#fd;
|
|
@@ -8653,6 +8800,7 @@ var FileSessionJournal = class {
|
|
|
8653
8800
|
#nextSeq;
|
|
8654
8801
|
#closed = false;
|
|
8655
8802
|
constructor(input) {
|
|
8803
|
+
this.attachments = input.attachments;
|
|
8656
8804
|
this.header = input.header;
|
|
8657
8805
|
this.path = input.path;
|
|
8658
8806
|
this.#nextSeq = input.nextSeq;
|
|
@@ -8670,7 +8818,21 @@ var FileSessionJournal = class {
|
|
|
8670
8818
|
entryType: input.entryType,
|
|
8671
8819
|
payload: input.payload
|
|
8672
8820
|
};
|
|
8673
|
-
|
|
8821
|
+
const message = "message" in entry.payload ? entry.payload.message : void 0;
|
|
8822
|
+
const attachments = message && "attachments" in message ? message.attachments : void 0;
|
|
8823
|
+
if (attachments?.length) {
|
|
8824
|
+
for (const attachment of attachments)
|
|
8825
|
+
assertAttachmentContent(attachment);
|
|
8826
|
+
if (!this.attachments)
|
|
8827
|
+
throw new Error("Attachment persistence requires an attachment store");
|
|
8828
|
+
if (!("attachmentKey" in message) || !message.attachmentKey)
|
|
8829
|
+
throw new Error("Attachment message requires an occurrence key");
|
|
8830
|
+
this.attachments.commit(attachments.map((block) => block.ref), () => {
|
|
8831
|
+
this.writeItem(entry);
|
|
8832
|
+
this.flush();
|
|
8833
|
+
});
|
|
8834
|
+
} else
|
|
8835
|
+
this.writeItem(entry);
|
|
8674
8836
|
if (entry.entryType === "compaction") {
|
|
8675
8837
|
this.flush();
|
|
8676
8838
|
}
|
|
@@ -8736,14 +8898,15 @@ function createSessionJournal(options) {
|
|
|
8736
8898
|
if (!existing) {
|
|
8737
8899
|
writeLine(fd, header);
|
|
8738
8900
|
fsyncSync(fd);
|
|
8739
|
-
return new FileSessionJournal({ header, path: path5, nextSeq: 1, fd });
|
|
8901
|
+
return new FileSessionJournal({ header, path: path5, nextSeq: 1, fd, attachments: options.attachments });
|
|
8740
8902
|
}
|
|
8741
8903
|
const replay = readSessionFile(path5);
|
|
8742
8904
|
return new FileSessionJournal({
|
|
8743
8905
|
header: replay.header,
|
|
8744
8906
|
path: path5,
|
|
8745
8907
|
nextSeq: replay.lastSeq + 1,
|
|
8746
|
-
fd
|
|
8908
|
+
fd,
|
|
8909
|
+
attachments: options.attachments
|
|
8747
8910
|
});
|
|
8748
8911
|
} catch (error) {
|
|
8749
8912
|
releaseLock(lockPath);
|
|
@@ -8819,8 +8982,8 @@ function required(value, label) {
|
|
|
8819
8982
|
}
|
|
8820
8983
|
|
|
8821
8984
|
// ../../packages/session/session-store/dist/session-manager.js
|
|
8822
|
-
import { existsSync as existsSync2, readdirSync, statSync } from "node:fs";
|
|
8823
|
-
import { join as
|
|
8985
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "node:fs";
|
|
8986
|
+
import { join as join3 } from "node:path";
|
|
8824
8987
|
|
|
8825
8988
|
// ../../packages/session/session-store/dist/session-tree.js
|
|
8826
8989
|
function buildSessionTree(sessions) {
|
|
@@ -8848,14 +9011,17 @@ function buildSessionTree(sessions) {
|
|
|
8848
9011
|
|
|
8849
9012
|
// ../../packages/session/session-store/dist/session-manager.js
|
|
8850
9013
|
var SessionManager = class {
|
|
9014
|
+
#attachments;
|
|
8851
9015
|
#sessionsRoot;
|
|
8852
9016
|
#appVersion;
|
|
8853
9017
|
constructor(options) {
|
|
9018
|
+
this.#attachments = options.attachments;
|
|
8854
9019
|
this.#sessionsRoot = options.sessionsRoot;
|
|
8855
9020
|
this.#appVersion = options.appVersion;
|
|
8856
9021
|
}
|
|
8857
9022
|
create(options) {
|
|
8858
9023
|
const journal = createSessionJournal({
|
|
9024
|
+
attachments: this.#attachments,
|
|
8859
9025
|
sessionsRoot: this.#sessionsRoot,
|
|
8860
9026
|
projectRoot: options.projectRoot,
|
|
8861
9027
|
initialCwd: options.initialCwd,
|
|
@@ -8884,6 +9050,7 @@ var SessionManager = class {
|
|
|
8884
9050
|
}
|
|
8885
9051
|
const replay = readSessionFile(summary.path);
|
|
8886
9052
|
const journal = createSessionJournal({
|
|
9053
|
+
attachments: this.#attachments,
|
|
8887
9054
|
sessionsRoot: this.#sessionsRoot,
|
|
8888
9055
|
header: replay.header,
|
|
8889
9056
|
path: summary.path
|
|
@@ -8911,6 +9078,7 @@ var SessionManager = class {
|
|
|
8911
9078
|
throw new Error("fork target must be a user_message entry");
|
|
8912
9079
|
}
|
|
8913
9080
|
const child = createSessionJournal({
|
|
9081
|
+
attachments: this.#attachments,
|
|
8914
9082
|
sessionsRoot: this.#sessionsRoot,
|
|
8915
9083
|
projectRoot: parent.header.projectRoot,
|
|
8916
9084
|
initialCwd: parent.header.initialCwd,
|
|
@@ -8941,6 +9109,7 @@ var SessionManager = class {
|
|
|
8941
9109
|
const parent = this.parentReplay(options.parentSessionId);
|
|
8942
9110
|
const lastEntry = [...parent.items].reverse().find((item) => item.kind === "entry");
|
|
8943
9111
|
const child = createSessionJournal({
|
|
9112
|
+
attachments: this.#attachments,
|
|
8944
9113
|
sessionsRoot: this.#sessionsRoot,
|
|
8945
9114
|
projectRoot: parent.header.projectRoot,
|
|
8946
9115
|
initialCwd: parent.header.initialCwd,
|
|
@@ -8974,8 +9143,8 @@ var SessionManager = class {
|
|
|
8974
9143
|
}
|
|
8975
9144
|
const paths = [];
|
|
8976
9145
|
const visit = (directory) => {
|
|
8977
|
-
for (const entry of
|
|
8978
|
-
const absolute =
|
|
9146
|
+
for (const entry of readdirSync2(directory)) {
|
|
9147
|
+
const absolute = join3(directory, entry);
|
|
8979
9148
|
const stat6 = statSync(absolute);
|
|
8980
9149
|
if (stat6.isDirectory()) {
|
|
8981
9150
|
visit(absolute);
|
|
@@ -9331,14 +9500,14 @@ function toolsOf(request) {
|
|
|
9331
9500
|
}
|
|
9332
9501
|
return request.tools.map(toolOf);
|
|
9333
9502
|
}
|
|
9334
|
-
function toPiContext(request) {
|
|
9503
|
+
function toPiContext(request, attachments) {
|
|
9335
9504
|
const systems = request.messages.filter((message) => message.role === "system" && message.toolDefinitions === void 0);
|
|
9336
9505
|
if (systems.length > 1) {
|
|
9337
9506
|
throw new ModelError("model history contains more than one system message", {
|
|
9338
9507
|
kind: "protocol"
|
|
9339
9508
|
});
|
|
9340
9509
|
}
|
|
9341
|
-
const messages = request.messages.filter((message) => message.role !== "system").map((message) => toPiMessage(message, request.provider, request.model));
|
|
9510
|
+
const messages = request.messages.filter((message) => message.role !== "system").map((message) => toPiMessage(message, request.provider, request.model, attachments?.get(message)));
|
|
9342
9511
|
const tools = toolsOf(request);
|
|
9343
9512
|
return {
|
|
9344
9513
|
...systems[0] === void 0 ? {} : { systemPrompt: systems[0].content },
|
|
@@ -9346,9 +9515,12 @@ function toPiContext(request) {
|
|
|
9346
9515
|
...tools === void 0 ? {} : { tools }
|
|
9347
9516
|
};
|
|
9348
9517
|
}
|
|
9349
|
-
function toPiMessage(message, provider2, model2) {
|
|
9518
|
+
function toPiMessage(message, provider2, model2, attachments) {
|
|
9519
|
+
if ((message.role === "user" || message.role === "tool-result") && message.attachments?.length && attachments === void 0) {
|
|
9520
|
+
throw new ModelError("Attachments must be prepared before serializing model context", { kind: "protocol" });
|
|
9521
|
+
}
|
|
9350
9522
|
if (message.role === "user") {
|
|
9351
|
-
return { role: "user", content: message.content, timestamp: 0 };
|
|
9523
|
+
return { role: "user", content: attachments?.length ? [{ type: "text", text: message.content }, ...attachments] : message.content, timestamp: 0 };
|
|
9352
9524
|
}
|
|
9353
9525
|
if (message.role === "assistant") {
|
|
9354
9526
|
return toPiAssistant(message, { provider: provider2, model: model2 });
|
|
@@ -9357,7 +9529,7 @@ function toPiMessage(message, provider2, model2) {
|
|
|
9357
9529
|
role: "toolResult",
|
|
9358
9530
|
toolCallId: message.toolCallId,
|
|
9359
9531
|
toolName: message.toolName,
|
|
9360
|
-
content: [{ type: "text", text: message.content || "(no output)" }],
|
|
9532
|
+
content: [{ type: "text", text: message.content || "(no output)" }, ...attachments ?? []],
|
|
9361
9533
|
isError: message.isError,
|
|
9362
9534
|
timestamp: 0
|
|
9363
9535
|
};
|
|
@@ -9543,13 +9715,88 @@ function ensureActive(request, hadDelta) {
|
|
|
9543
9715
|
}
|
|
9544
9716
|
}
|
|
9545
9717
|
|
|
9718
|
+
// ../../packages/llm/llm-pi-ai/dist/images.js
|
|
9719
|
+
function defaultImagePolicy(model2) {
|
|
9720
|
+
const anthropic = model2.api === "anthropic-messages";
|
|
9721
|
+
const google = model2.api === "google-generative-ai" || model2.api === "google-vertex";
|
|
9722
|
+
return {
|
|
9723
|
+
target: {
|
|
9724
|
+
maxPixels: anthropic ? 115e4 : google ? 4194304 : 2097152,
|
|
9725
|
+
maxDimension: anthropic ? 1568 : 2048,
|
|
9726
|
+
maxBytes: 4 * 1024 ** 2,
|
|
9727
|
+
formats: ["image/png", "image/jpeg", "image/webp"]
|
|
9728
|
+
},
|
|
9729
|
+
maxImages: 20,
|
|
9730
|
+
maxTotalBytes: (google ? 12 : 16) * 1024 ** 2
|
|
9731
|
+
};
|
|
9732
|
+
}
|
|
9733
|
+
async function prepareRequestImages(request, model2, options) {
|
|
9734
|
+
const offloaded = request.imageContext?.offloaded ?? /* @__PURE__ */ new Set();
|
|
9735
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
9736
|
+
const images = [];
|
|
9737
|
+
const policy = (options?.policy ?? defaultImagePolicy)(model2);
|
|
9738
|
+
if (![policy.maxImages, policy.maxTotalBytes].every((n) => Number.isSafeInteger(n) && n > 0))
|
|
9739
|
+
throw new ModelError("Invalid image request budget", { kind: "protocol" });
|
|
9740
|
+
for (const message of request.messages) {
|
|
9741
|
+
if (message.role !== "user" && message.role !== "tool-result")
|
|
9742
|
+
continue;
|
|
9743
|
+
const output = [];
|
|
9744
|
+
blocks.set(message, output);
|
|
9745
|
+
for (const [index, block] of (message.attachments ?? []).entries()) {
|
|
9746
|
+
assertAttachmentContent(block);
|
|
9747
|
+
if (block.type === "file") {
|
|
9748
|
+
output.push({ type: "text", text: `Attached file ${JSON.stringify(block.ref.name)} (${block.ref.id}, ${block.ref.bytes} bytes). File bytes have not been read or parsed.` });
|
|
9749
|
+
continue;
|
|
9750
|
+
}
|
|
9751
|
+
const key = `${message.attachmentKey}:${index}`;
|
|
9752
|
+
if (offloaded.has(key) || message.omittedImageIndexes?.includes(index)) {
|
|
9753
|
+
output.push({ type: "text", text: `[Image ${block.ref.id} omitted from context; use read_image to inspect it again.]` });
|
|
9754
|
+
continue;
|
|
9755
|
+
}
|
|
9756
|
+
if (!model2.input.includes("image"))
|
|
9757
|
+
throw new ModelError(`Model ${model2.provider}/${model2.id} does not declare image input`, { kind: "protocol" });
|
|
9758
|
+
const store = options?.store();
|
|
9759
|
+
if (!store)
|
|
9760
|
+
throw new ModelError("Image request requires an attachment store", { kind: "protocol" });
|
|
9761
|
+
const prepared = await store.prepareImage(block.ref, { ...policy.target, ...block.view }, request.cancelToken?.signal);
|
|
9762
|
+
const content = { type: "image", data: Buffer.from(prepared.data).toString("base64"), mimeType: prepared.mimeType };
|
|
9763
|
+
const note = { type: "text", text: `Image ${block.ref.id}: ${prepared.note}` };
|
|
9764
|
+
output.push(note, content);
|
|
9765
|
+
images.push({ key, owner: message, content, note, bytes: prepared.data.byteLength });
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
let count = images.length;
|
|
9769
|
+
let bytes = images.reduce((sum, image) => sum + image.bytes, 0);
|
|
9770
|
+
const evicted = [];
|
|
9771
|
+
for (const image of images) {
|
|
9772
|
+
if (count <= policy.maxImages && bytes <= policy.maxTotalBytes)
|
|
9773
|
+
break;
|
|
9774
|
+
if (!request.imageContext || request.imageContext.protectedKeys.has(image.key) || !("attachmentKey" in image.owner) || !image.owner.attachmentKey)
|
|
9775
|
+
continue;
|
|
9776
|
+
const output = blocks.get(image.owner);
|
|
9777
|
+
output.splice(output.indexOf(image.content), 1);
|
|
9778
|
+
image.note.text = `[Image omitted from context to meet request budget; original remains stored. Use read_image to inspect it again.]`;
|
|
9779
|
+
evicted.push(image.key);
|
|
9780
|
+
count--;
|
|
9781
|
+
bytes -= image.bytes;
|
|
9782
|
+
}
|
|
9783
|
+
if (count > policy.maxImages || bytes > policy.maxTotalBytes)
|
|
9784
|
+
throw new ModelError("Current-turn images exceed the request image budget; reduce the number or crop the images", { kind: "protocol" });
|
|
9785
|
+
request.cancelToken?.signal.throwIfAborted();
|
|
9786
|
+
if (evicted.length)
|
|
9787
|
+
request.imageContext.persistOffload(evicted);
|
|
9788
|
+
return blocks;
|
|
9789
|
+
}
|
|
9790
|
+
|
|
9546
9791
|
// ../../packages/llm/llm-pi-ai/dist/adapter.js
|
|
9547
9792
|
var PiAiAdapter = class {
|
|
9793
|
+
attachments;
|
|
9548
9794
|
name = "pi-ai";
|
|
9549
9795
|
models;
|
|
9550
9796
|
eligibleProviderIds;
|
|
9551
9797
|
constructor(options, models) {
|
|
9552
9798
|
this.eligibleProviderIds = options.eligibleProviderIds;
|
|
9799
|
+
this.attachments = options.attachments;
|
|
9553
9800
|
this.models = models;
|
|
9554
9801
|
}
|
|
9555
9802
|
async runAttempt(request) {
|
|
@@ -9564,9 +9811,6 @@ var PiAiAdapter = class {
|
|
|
9564
9811
|
throw new ModelError(`unknown model route: ${request.provider}/${request.model}`, { kind: "protocol" });
|
|
9565
9812
|
}
|
|
9566
9813
|
this.checkActive(request);
|
|
9567
|
-
if (request.onRequestOpened?.() === false) {
|
|
9568
|
-
throw new ModelStreamCancelled("model request was cancelled before opening");
|
|
9569
|
-
}
|
|
9570
9814
|
const requestModel = request.baseUrl === void 0 ? model2 : { ...model2, baseUrl: request.baseUrl };
|
|
9571
9815
|
const options = {
|
|
9572
9816
|
...request.cancelToken === void 0 ? {} : { signal: request.cancelToken.signal },
|
|
@@ -9577,16 +9821,26 @@ var PiAiAdapter = class {
|
|
|
9577
9821
|
maxRetries: 0
|
|
9578
9822
|
};
|
|
9579
9823
|
try {
|
|
9580
|
-
const
|
|
9824
|
+
const images = await prepareRequestImages(request, model2, this.attachments).catch((error) => {
|
|
9825
|
+
this.checkActive(request);
|
|
9826
|
+
if (error instanceof ModelError)
|
|
9827
|
+
throw error;
|
|
9828
|
+
throw new ModelError(errorMessage2(error), { kind: "protocol", cause: error });
|
|
9829
|
+
});
|
|
9830
|
+
this.checkActive(request);
|
|
9831
|
+
if (request.onRequestOpened?.() === false)
|
|
9832
|
+
throw new ModelStreamCancelled("model request was cancelled before opening");
|
|
9833
|
+
const stream = this.models.streamSimple(requestModel, toPiContext(request, images), options);
|
|
9581
9834
|
const result = await consumePiEvents(stream, request, request.onEvent);
|
|
9582
9835
|
this.checkActive(request);
|
|
9583
9836
|
return result;
|
|
9584
9837
|
} catch (error) {
|
|
9838
|
+
this.checkActive(request);
|
|
9585
9839
|
if (error instanceof ModelError || error instanceof ModelStreamCancelled) {
|
|
9586
9840
|
throw error;
|
|
9587
9841
|
}
|
|
9588
9842
|
throw new ModelError(errorMessage2(error), {
|
|
9589
|
-
kind: piModelErrorKind(error),
|
|
9843
|
+
kind: error instanceof AttachmentError ? "protocol" : piModelErrorKind(error),
|
|
9590
9844
|
cause: error
|
|
9591
9845
|
});
|
|
9592
9846
|
}
|
|
@@ -17855,6 +18109,7 @@ async function createPiAiPlatform(options) {
|
|
|
17855
18109
|
for (const id of custom.providerIds)
|
|
17856
18110
|
verified.delete(id);
|
|
17857
18111
|
return new PiAiPlatform(models, {
|
|
18112
|
+
attachments: options.attachments,
|
|
17858
18113
|
excludedProviderIds: options.excludedProviderIds,
|
|
17859
18114
|
verifiedProviderIds: verified,
|
|
17860
18115
|
reload: async () => {
|
|
@@ -17881,7 +18136,7 @@ var PiAiPlatform = class {
|
|
|
17881
18136
|
this.models = models;
|
|
17882
18137
|
this.eligibleIds = new Set(eligibleProviderIds(models, options.excludedProviderIds));
|
|
17883
18138
|
this.verifiedProviderIds = options.verifiedProviderIds;
|
|
17884
|
-
this.adapter = new PiAiAdapter({ eligibleProviderIds: this.eligibleIds }, models);
|
|
18139
|
+
this.adapter = new PiAiAdapter({ eligibleProviderIds: this.eligibleIds, attachments: options.attachments }, models);
|
|
17885
18140
|
this.catalog = new PiAiCatalog(this.models, this.eligibleIds, this.verifiedProviderIds, async () => {
|
|
17886
18141
|
await options.reload?.();
|
|
17887
18142
|
this.eligibleIds.clear();
|
|
@@ -18086,7 +18341,7 @@ function errorMessage3(error) {
|
|
|
18086
18341
|
// ../../packages/storage/local-config/dist/config.js
|
|
18087
18342
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18088
18343
|
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
|
|
18089
|
-
import { basename as basename2, dirname, join as
|
|
18344
|
+
import { basename as basename2, dirname, join as join4 } from "node:path";
|
|
18090
18345
|
|
|
18091
18346
|
// ../../packages/fs/local-paths/dist/index.js
|
|
18092
18347
|
import { execFileSync } from "node:child_process";
|
|
@@ -18184,8 +18439,8 @@ function defaultConfigPath(environ = process.env) {
|
|
|
18184
18439
|
return normalizeLocalPath(explicit, { env: environ });
|
|
18185
18440
|
}
|
|
18186
18441
|
const configHome = environ["XDG_CONFIG_HOME"];
|
|
18187
|
-
const root = configHome ? normalizeLocalPath(configHome, { env: environ }) :
|
|
18188
|
-
return
|
|
18442
|
+
const root = configHome ? normalizeLocalPath(configHome, { env: environ }) : join4(getHomeDirectory({ env: environ }), ".config");
|
|
18443
|
+
return join4(root, "laohuang", "config.json");
|
|
18189
18444
|
}
|
|
18190
18445
|
var ConfigManager = class {
|
|
18191
18446
|
path;
|
|
@@ -18287,7 +18542,7 @@ var ConfigManager = class {
|
|
|
18287
18542
|
const parent = dirname(this.path);
|
|
18288
18543
|
mkdirSync2(parent, { recursive: true, mode: 448 });
|
|
18289
18544
|
const content = JSON.stringify(document, null, 2) + "\n";
|
|
18290
|
-
const temporary =
|
|
18545
|
+
const temporary = join4(parent, `.${basename2(this.path)}.${process.pid}.${randomUUID3()}.tmp`);
|
|
18291
18546
|
writeFileSync(temporary, content, { encoding: "utf8", mode: 384 });
|
|
18292
18547
|
chmodSync(temporary, 384);
|
|
18293
18548
|
renameSync(temporary, this.path);
|
|
@@ -18363,7 +18618,7 @@ var CustomModelsStore = class {
|
|
|
18363
18618
|
// ../../packages/storage/local-config/dist/credentials.js
|
|
18364
18619
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
18365
18620
|
import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18366
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
18621
|
+
import { basename as basename3, dirname as dirname2, join as join5 } from "node:path";
|
|
18367
18622
|
function isPlainObject2(value) {
|
|
18368
18623
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18369
18624
|
}
|
|
@@ -18527,7 +18782,7 @@ var CredentialStore = class {
|
|
|
18527
18782
|
version: 2,
|
|
18528
18783
|
providers: document.providers
|
|
18529
18784
|
}, null, 2) + "\n";
|
|
18530
|
-
const temporary =
|
|
18785
|
+
const temporary = join5(parent, `.${basename3(this.path)}.${process.pid}.${randomUUID4()}.tmp`);
|
|
18531
18786
|
writeFileSync2(temporary, content, { encoding: "utf8", mode: 384 });
|
|
18532
18787
|
chmodSync2(temporary, 384);
|
|
18533
18788
|
renameSync2(temporary, this.path);
|
|
@@ -18544,7 +18799,7 @@ function normalizeStoredCredential(credential) {
|
|
|
18544
18799
|
// ../../packages/storage/local-config/dist/model-catalog-store.js
|
|
18545
18800
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
18546
18801
|
import { chmodSync as chmodSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
18547
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
18802
|
+
import { basename as basename4, dirname as dirname3, join as join6 } from "node:path";
|
|
18548
18803
|
function isPlainObject3(value) {
|
|
18549
18804
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18550
18805
|
}
|
|
@@ -18608,7 +18863,7 @@ var ModelCatalogStore = class {
|
|
|
18608
18863
|
chmodSync3(parent, 448);
|
|
18609
18864
|
}
|
|
18610
18865
|
const content = JSON.stringify(document, null, 2) + "\n";
|
|
18611
|
-
const temporary =
|
|
18866
|
+
const temporary = join6(parent, `.${basename4(this.path)}.${process.pid}.${randomUUID5()}.tmp`);
|
|
18612
18867
|
writeFileSync3(temporary, content, { encoding: "utf8", mode: 384 });
|
|
18613
18868
|
chmodSync3(temporary, 384);
|
|
18614
18869
|
renameSync3(temporary, this.path);
|
|
@@ -18652,7 +18907,7 @@ function normalizeEntry(providerId, entry) {
|
|
|
18652
18907
|
// ../../packages/storage/local-config/dist/mcp-config.js
|
|
18653
18908
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
18654
18909
|
import { mkdir, readFile as readFile2, rename, unlink, writeFile } from "node:fs/promises";
|
|
18655
|
-
import { basename as basename5, dirname as dirname4, join as
|
|
18910
|
+
import { basename as basename5, dirname as dirname4, join as join7 } from "node:path";
|
|
18656
18911
|
var McpConfigStore = class {
|
|
18657
18912
|
path;
|
|
18658
18913
|
constructor(path5) {
|
|
@@ -18684,7 +18939,7 @@ var McpConfigStore = class {
|
|
|
18684
18939
|
throw new Error("MCP storage requires JSON data");
|
|
18685
18940
|
const parent = dirname4(this.path);
|
|
18686
18941
|
await mkdir(parent, { recursive: true, mode: 448 });
|
|
18687
|
-
const temporary =
|
|
18942
|
+
const temporary = join7(parent, `.${basename5(this.path)}.${randomUUID6()}.tmp`);
|
|
18688
18943
|
try {
|
|
18689
18944
|
await writeFile(temporary, `${content}
|
|
18690
18945
|
`, { mode: 384, flag: "wx" });
|
|
@@ -18703,7 +18958,7 @@ var McpConfigStore = class {
|
|
|
18703
18958
|
// ../../packages/storage/local-config/dist/mcp-credentials.js
|
|
18704
18959
|
import { createHash as createHash3 } from "node:crypto";
|
|
18705
18960
|
import { unlink as unlink2 } from "node:fs/promises";
|
|
18706
|
-
import { join as
|
|
18961
|
+
import { join as join8, resolve as resolve2 } from "node:path";
|
|
18707
18962
|
var operations = /* @__PURE__ */ new Map();
|
|
18708
18963
|
var McpCredentialFileStore = class {
|
|
18709
18964
|
directory;
|
|
@@ -18711,7 +18966,7 @@ var McpCredentialFileStore = class {
|
|
|
18711
18966
|
this.directory = resolve2(directory);
|
|
18712
18967
|
}
|
|
18713
18968
|
path(key) {
|
|
18714
|
-
return
|
|
18969
|
+
return join8(this.directory, `${createHash3("sha256").update(key).digest("hex")}.json`);
|
|
18715
18970
|
}
|
|
18716
18971
|
async read(key) {
|
|
18717
18972
|
const path5 = this.path(key);
|
|
@@ -18800,8 +19055,8 @@ var CancelToken = class {
|
|
|
18800
19055
|
continue;
|
|
18801
19056
|
}
|
|
18802
19057
|
}
|
|
18803
|
-
for (const
|
|
18804
|
-
|
|
19058
|
+
for (const resolve15 of this.#waiters) {
|
|
19059
|
+
resolve15();
|
|
18805
19060
|
}
|
|
18806
19061
|
this.#waiters.clear();
|
|
18807
19062
|
return true;
|
|
@@ -18814,20 +19069,20 @@ var CancelToken = class {
|
|
|
18814
19069
|
if (this.#cancelled) {
|
|
18815
19070
|
return Promise.resolve(true);
|
|
18816
19071
|
}
|
|
18817
|
-
return new Promise((
|
|
19072
|
+
return new Promise((resolve15) => {
|
|
18818
19073
|
let timer;
|
|
18819
19074
|
const onCancel = () => {
|
|
18820
19075
|
if (timer !== void 0) {
|
|
18821
19076
|
clearTimeout(timer);
|
|
18822
19077
|
}
|
|
18823
19078
|
this.#waiters.delete(onCancel);
|
|
18824
|
-
|
|
19079
|
+
resolve15(true);
|
|
18825
19080
|
};
|
|
18826
19081
|
this.#waiters.add(onCancel);
|
|
18827
19082
|
if (timeoutMs !== void 0) {
|
|
18828
19083
|
timer = setTimeout(() => {
|
|
18829
19084
|
this.#waiters.delete(onCancel);
|
|
18830
|
-
|
|
19085
|
+
resolve15(false);
|
|
18831
19086
|
}, timeoutMs);
|
|
18832
19087
|
}
|
|
18833
19088
|
});
|
|
@@ -19423,8 +19678,8 @@ var SubscriberMailbox = class _SubscriberMailbox {
|
|
|
19423
19678
|
const deadline = timeoutMs === void 0 ? void 0 : Date.now() + timeoutMs;
|
|
19424
19679
|
while (this.unfinished > 0) {
|
|
19425
19680
|
if (deadline === void 0) {
|
|
19426
|
-
await new Promise((
|
|
19427
|
-
this.flushWaiters.push(
|
|
19681
|
+
await new Promise((resolve15) => {
|
|
19682
|
+
this.flushWaiters.push(resolve15);
|
|
19428
19683
|
});
|
|
19429
19684
|
continue;
|
|
19430
19685
|
}
|
|
@@ -19432,12 +19687,12 @@ var SubscriberMailbox = class _SubscriberMailbox {
|
|
|
19432
19687
|
if (remaining <= 0) {
|
|
19433
19688
|
return false;
|
|
19434
19689
|
}
|
|
19435
|
-
const waiter = new Promise((
|
|
19436
|
-
this.flushWaiters.push(
|
|
19690
|
+
const waiter = new Promise((resolve15) => {
|
|
19691
|
+
this.flushWaiters.push(resolve15);
|
|
19437
19692
|
});
|
|
19438
19693
|
let timer;
|
|
19439
|
-
const timeout = new Promise((
|
|
19440
|
-
timer = setTimeout(
|
|
19694
|
+
const timeout = new Promise((resolve15) => {
|
|
19695
|
+
timer = setTimeout(resolve15, remaining);
|
|
19441
19696
|
});
|
|
19442
19697
|
await Promise.race([waiter, timeout]);
|
|
19443
19698
|
clearTimeout(timer);
|
|
@@ -19466,22 +19721,22 @@ var SubscriberMailbox = class _SubscriberMailbox {
|
|
|
19466
19721
|
wake() {
|
|
19467
19722
|
const waiters = this.wakeWaiters;
|
|
19468
19723
|
this.wakeWaiters = [];
|
|
19469
|
-
for (const
|
|
19470
|
-
|
|
19724
|
+
for (const resolve15 of waiters) {
|
|
19725
|
+
resolve15();
|
|
19471
19726
|
}
|
|
19472
19727
|
}
|
|
19473
19728
|
notifyFlushWaiters() {
|
|
19474
19729
|
const waiters = this.flushWaiters;
|
|
19475
19730
|
this.flushWaiters = [];
|
|
19476
|
-
for (const
|
|
19477
|
-
|
|
19731
|
+
for (const resolve15 of waiters) {
|
|
19732
|
+
resolve15();
|
|
19478
19733
|
}
|
|
19479
19734
|
}
|
|
19480
19735
|
async run() {
|
|
19481
19736
|
for (; ; ) {
|
|
19482
19737
|
while (this.items.length === 0 && !this.closed) {
|
|
19483
|
-
await new Promise((
|
|
19484
|
-
this.wakeWaiters.push(
|
|
19738
|
+
await new Promise((resolve15) => {
|
|
19739
|
+
this.wakeWaiters.push(resolve15);
|
|
19485
19740
|
});
|
|
19486
19741
|
}
|
|
19487
19742
|
if (this.items.length === 0 && this.closed) {
|
|
@@ -19629,8 +19884,8 @@ var EventBus = class {
|
|
|
19629
19884
|
if (this.closed) {
|
|
19630
19885
|
return Promise.reject(new EventBusClosedError());
|
|
19631
19886
|
}
|
|
19632
|
-
return new Promise((
|
|
19633
|
-
const waiter = { resolve:
|
|
19887
|
+
return new Promise((resolve15, reject) => {
|
|
19888
|
+
const waiter = { resolve: resolve15, reject };
|
|
19634
19889
|
if (timeoutMs !== void 0) {
|
|
19635
19890
|
waiter.timer = setTimeout(() => {
|
|
19636
19891
|
const index = this.bufferWaiters.indexOf(waiter);
|
|
@@ -19729,7 +19984,7 @@ function makeCancelIntent(reason, source) {
|
|
|
19729
19984
|
|
|
19730
19985
|
// ../../packages/context/project-instructions/dist/project-instructions.js
|
|
19731
19986
|
import { createHash as createHash4 } from "node:crypto";
|
|
19732
|
-
import { closeSync as closeSync2, existsSync as existsSync7, fstatSync, lstatSync, openSync as openSync2, readSync, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
19987
|
+
import { closeSync as closeSync2, existsSync as existsSync7, fstatSync, lstatSync as lstatSync2, openSync as openSync2, readSync, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
19733
19988
|
import path from "node:path";
|
|
19734
19989
|
var INSTRUCTION_FILENAMES = [
|
|
19735
19990
|
"AGENTS.md",
|
|
@@ -19913,7 +20168,7 @@ function scopeChain(root, cwd) {
|
|
|
19913
20168
|
function readInstructionFile(rootReal, candidate, options) {
|
|
19914
20169
|
let stats;
|
|
19915
20170
|
try {
|
|
19916
|
-
stats =
|
|
20171
|
+
stats = lstatSync2(candidate);
|
|
19917
20172
|
} catch {
|
|
19918
20173
|
return null;
|
|
19919
20174
|
}
|
|
@@ -21496,20 +21751,20 @@ var AgentSession = class {
|
|
|
21496
21751
|
if (timeoutMs !== void 0 && timeoutMs <= 0) {
|
|
21497
21752
|
return Promise.resolve(false);
|
|
21498
21753
|
}
|
|
21499
|
-
return new Promise((
|
|
21754
|
+
return new Promise((resolve15) => {
|
|
21500
21755
|
let timer;
|
|
21501
21756
|
const onIdle = () => {
|
|
21502
21757
|
if (timer !== void 0) {
|
|
21503
21758
|
clearTimeout(timer);
|
|
21504
21759
|
}
|
|
21505
21760
|
this.#idleWaiters.delete(onIdle);
|
|
21506
|
-
|
|
21761
|
+
resolve15(true);
|
|
21507
21762
|
};
|
|
21508
21763
|
this.#idleWaiters.add(onIdle);
|
|
21509
21764
|
if (timeoutMs !== void 0) {
|
|
21510
21765
|
timer = setTimeout(() => {
|
|
21511
21766
|
this.#idleWaiters.delete(onIdle);
|
|
21512
|
-
|
|
21767
|
+
resolve15(false);
|
|
21513
21768
|
}, timeoutMs);
|
|
21514
21769
|
timer.unref();
|
|
21515
21770
|
}
|
|
@@ -21823,8 +22078,8 @@ var AgentSession = class {
|
|
|
21823
22078
|
if (value) {
|
|
21824
22079
|
const waiters = [...this.#idleWaiters];
|
|
21825
22080
|
this.#idleWaiters.clear();
|
|
21826
|
-
for (const
|
|
21827
|
-
|
|
22081
|
+
for (const resolve15 of waiters) {
|
|
22082
|
+
resolve15();
|
|
21828
22083
|
}
|
|
21829
22084
|
}
|
|
21830
22085
|
}
|
|
@@ -22054,13 +22309,13 @@ function inputTimeout(kind, env = process.env) {
|
|
|
22054
22309
|
async function drainTerminalInput(input, maxMs = 1e3, idleMs = 50) {
|
|
22055
22310
|
if (!input?.off)
|
|
22056
22311
|
return;
|
|
22057
|
-
await new Promise((
|
|
22312
|
+
await new Promise((resolve15) => {
|
|
22058
22313
|
let idle;
|
|
22059
22314
|
const finish = () => {
|
|
22060
22315
|
clearTimeout(idle);
|
|
22061
22316
|
clearTimeout(deadline);
|
|
22062
22317
|
input.off?.("data", onData);
|
|
22063
|
-
|
|
22318
|
+
resolve15();
|
|
22064
22319
|
};
|
|
22065
22320
|
const onData = () => {
|
|
22066
22321
|
clearTimeout(idle);
|
|
@@ -22608,7 +22863,7 @@ var BoundedTerminalWriter = class _BoundedTerminalWriter {
|
|
|
22608
22863
|
// ../../packages/terminal/tui/dist/tui/render-diagnostics.js
|
|
22609
22864
|
import { mkdtempSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22610
22865
|
import { tmpdir } from "node:os";
|
|
22611
|
-
import { join as
|
|
22866
|
+
import { join as join9 } from "node:path";
|
|
22612
22867
|
var RenderWidthError = class extends Error {
|
|
22613
22868
|
expected;
|
|
22614
22869
|
actual;
|
|
@@ -22626,8 +22881,8 @@ function recordRenderFailure(error) {
|
|
|
22626
22881
|
if (!(error instanceof RenderWidthError))
|
|
22627
22882
|
return error;
|
|
22628
22883
|
try {
|
|
22629
|
-
const directory = mkdtempSync(
|
|
22630
|
-
const path5 =
|
|
22884
|
+
const directory = mkdtempSync(join9(tmpdir(), "laohuang-tui-error-"));
|
|
22885
|
+
const path5 = join9(directory, "render.json");
|
|
22631
22886
|
writeFileSync4(path5, JSON.stringify({
|
|
22632
22887
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22633
22888
|
source: error.source,
|
|
@@ -26843,19 +27098,19 @@ var ViewHost = class {
|
|
|
26843
27098
|
this.#overlays = overlays;
|
|
26844
27099
|
}
|
|
26845
27100
|
openSelection(request) {
|
|
26846
|
-
return new Promise((
|
|
27101
|
+
return new Promise((resolve15) => {
|
|
26847
27102
|
const component = this.#selectionComponent(request);
|
|
26848
|
-
this.#open({ id: request.id, component, resolve:
|
|
27103
|
+
this.#open({ id: request.id, component, resolve: resolve15 }, "selector", request.floating);
|
|
26849
27104
|
});
|
|
26850
27105
|
}
|
|
26851
27106
|
openPrompt(request) {
|
|
26852
|
-
return new Promise((
|
|
27107
|
+
return new Promise((resolve15) => {
|
|
26853
27108
|
const component = new AuthDialog({
|
|
26854
27109
|
request,
|
|
26855
27110
|
onSubmit: (value) => this.#close(request.id, value),
|
|
26856
27111
|
onCancel: () => this.#close(request.id, null)
|
|
26857
27112
|
});
|
|
26858
|
-
this.#open({ id: request.id, component, resolve:
|
|
27113
|
+
this.#open({ id: request.id, component, resolve: resolve15 }, "modal", request.floating);
|
|
26859
27114
|
});
|
|
26860
27115
|
}
|
|
26861
27116
|
handleInput(event) {
|
|
@@ -27379,8 +27634,8 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27379
27634
|
try {
|
|
27380
27635
|
input?.resume?.();
|
|
27381
27636
|
if (!this.#exitRequested) {
|
|
27382
|
-
await new Promise((
|
|
27383
|
-
this.#exitResolve =
|
|
27637
|
+
await new Promise((resolve15) => {
|
|
27638
|
+
this.#exitResolve = resolve15;
|
|
27384
27639
|
});
|
|
27385
27640
|
}
|
|
27386
27641
|
} finally {
|
|
@@ -27747,18 +28002,18 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
|
|
|
27747
28002
|
if (this.#closed || !this.#running) {
|
|
27748
28003
|
return Promise.resolve(null);
|
|
27749
28004
|
}
|
|
27750
|
-
return new Promise((
|
|
28005
|
+
return new Promise((resolve15) => {
|
|
27751
28006
|
if (type === "open_selection") {
|
|
27752
28007
|
this.#work.push({
|
|
27753
28008
|
type,
|
|
27754
28009
|
request,
|
|
27755
|
-
resolve:
|
|
28010
|
+
resolve: resolve15
|
|
27756
28011
|
});
|
|
27757
28012
|
} else {
|
|
27758
28013
|
this.#work.push({
|
|
27759
28014
|
type,
|
|
27760
28015
|
request,
|
|
27761
|
-
resolve:
|
|
28016
|
+
resolve: resolve15
|
|
27762
28017
|
});
|
|
27763
28018
|
}
|
|
27764
28019
|
this.#scheduleWakeup();
|
|
@@ -29086,8 +29341,8 @@ async function executeEdit(args, execution, resolvePath, io) {
|
|
|
29086
29341
|
async function withFileMutationLock(key, fn) {
|
|
29087
29342
|
const previous = mutationLocks.get(key) ?? Promise.resolve();
|
|
29088
29343
|
let release;
|
|
29089
|
-
const current = new Promise((
|
|
29090
|
-
release =
|
|
29344
|
+
const current = new Promise((resolve15) => {
|
|
29345
|
+
release = resolve15;
|
|
29091
29346
|
});
|
|
29092
29347
|
const tail2 = previous.then(() => current);
|
|
29093
29348
|
mutationLocks.set(key, tail2);
|
|
@@ -29170,6 +29425,98 @@ function editsArgument(args) {
|
|
|
29170
29425
|
});
|
|
29171
29426
|
}
|
|
29172
29427
|
|
|
29428
|
+
// ../../packages/fs/tool-fs/dist/read-image.js
|
|
29429
|
+
import { open } from "node:fs/promises";
|
|
29430
|
+
import { basename as basename6 } from "node:path";
|
|
29431
|
+
function createReadImageTool(options) {
|
|
29432
|
+
return {
|
|
29433
|
+
spec: {
|
|
29434
|
+
name: "read_image",
|
|
29435
|
+
description: "View a PNG, JPEG, WebP or GIF image. Use path to read a local image file. Use attachment_id only to re-read an image attachment reference already returned in the current session; do not guess IDs. Provide exactly one of path or attachment_id. Optional crop coordinates use the oriented original pixels. Animations show only their first frame. full_resolution prevents downscaling and may exceed model limits.",
|
|
29436
|
+
parameters: { type: "object", properties: {
|
|
29437
|
+
path: {
|
|
29438
|
+
type: "string",
|
|
29439
|
+
description: "Local image file path. Absolute paths are accepted; relative paths resolve against the default working directory, and ~/ expands to the user's home directory. Use this for local files. Provide exactly one of path or attachment_id."
|
|
29440
|
+
},
|
|
29441
|
+
attachment_id: {
|
|
29442
|
+
type: "string",
|
|
29443
|
+
description: "Exact image attachment ID (sha256:...) already returned in the current session. Use only to re-read an existing session attachment, not to locate a local file. Do not guess IDs. Images not referenced by the current session are rejected. Provide exactly one of path or attachment_id."
|
|
29444
|
+
},
|
|
29445
|
+
region: {
|
|
29446
|
+
type: "object",
|
|
29447
|
+
description: "Optional rectangle in original-image pixels after orientation correction, with the origin at the top-left. Omit to view the whole image. Use a crop to inspect details that are unclear in a downscaled view. Out-of-bounds regions are rejected, not clipped. The crop may still be downscaled unless full_resolution is true.",
|
|
29448
|
+
properties: {
|
|
29449
|
+
x: { type: "integer", minimum: 0, description: "Zero-based left edge in the oriented original image, in pixels." },
|
|
29450
|
+
y: { type: "integer", minimum: 0, description: "Zero-based top edge in the oriented original image, in pixels." },
|
|
29451
|
+
width: { type: "integer", minimum: 1, description: "Crop width in original-image pixels; must be positive." },
|
|
29452
|
+
height: { type: "integer", minimum: 1, description: "Crop height in original-image pixels; must be positive." }
|
|
29453
|
+
},
|
|
29454
|
+
required: ["x", "y", "width", "height"],
|
|
29455
|
+
additionalProperties: false
|
|
29456
|
+
},
|
|
29457
|
+
full_resolution: {
|
|
29458
|
+
type: "boolean",
|
|
29459
|
+
description: "Defaults to false: the whole image or crop may be downscaled to fit model request limits. Set true to preserve its pixel dimensions; orientation correction and re-encoding may still occur. If model limits cannot be met, request preparation fails instead of silently downscaling. Use a smaller region in that case."
|
|
29460
|
+
}
|
|
29461
|
+
}, additionalProperties: false },
|
|
29462
|
+
promptGuidelines: ["Use read_image for visual image inspection. Use read for text. Image crop coordinates refer to the oriented original; follow the returned coordinate mapping."]
|
|
29463
|
+
},
|
|
29464
|
+
async execute(args, execution) {
|
|
29465
|
+
if (typeof args.path === "string" === (typeof args.attachment_id === "string"))
|
|
29466
|
+
throw new Error("Supply exactly one of path or attachment_id");
|
|
29467
|
+
const view = { ...args.region === void 0 ? {} : { region: args.region }, ...args.full_resolution === void 0 ? {} : { fullResolution: args.full_resolution } };
|
|
29468
|
+
assertImageView(view);
|
|
29469
|
+
const release = options.store.protect();
|
|
29470
|
+
try {
|
|
29471
|
+
let ref;
|
|
29472
|
+
let target;
|
|
29473
|
+
if (typeof args.path === "string") {
|
|
29474
|
+
target = resolveLocalPath(args.path, options.projectRoot, options.pathOptions);
|
|
29475
|
+
const handle = await open(target, "r");
|
|
29476
|
+
let bytes;
|
|
29477
|
+
try {
|
|
29478
|
+
const stat6 = await handle.stat();
|
|
29479
|
+
if (!stat6.isFile() || stat6.size > options.store.imageLimits.maxBytes)
|
|
29480
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Image must be a regular file within the source byte limit");
|
|
29481
|
+
const chunks = [];
|
|
29482
|
+
let total = 0;
|
|
29483
|
+
while (true) {
|
|
29484
|
+
execution.signal?.throwIfAborted();
|
|
29485
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
29486
|
+
const { bytesRead } = await handle.read(buffer);
|
|
29487
|
+
if (!bytesRead)
|
|
29488
|
+
break;
|
|
29489
|
+
total += bytesRead;
|
|
29490
|
+
if (total > options.store.imageLimits.maxBytes)
|
|
29491
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Image exceeds source byte limit");
|
|
29492
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
29493
|
+
}
|
|
29494
|
+
bytes = Buffer.concat(chunks);
|
|
29495
|
+
} finally {
|
|
29496
|
+
await handle.close();
|
|
29497
|
+
}
|
|
29498
|
+
ref = (await options.store.saveImages([{ data: bytes, name: basename6(target) }], execution.signal))[0];
|
|
29499
|
+
} else {
|
|
29500
|
+
const found = options.resolveReference(String(args.attachment_id));
|
|
29501
|
+
if (!found)
|
|
29502
|
+
throw new AttachmentError("ACCESS_DENIED", "Image is not referenced by the current session");
|
|
29503
|
+
ref = found;
|
|
29504
|
+
for await (const _chunk of options.store.readFile(ref, execution.signal)) {
|
|
29505
|
+
}
|
|
29506
|
+
}
|
|
29507
|
+
if (view.region && (view.region.x + view.region.width > ref.width || view.region.y + view.region.height > ref.height))
|
|
29508
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Crop is outside the oriented image");
|
|
29509
|
+
if (execution.isCancelled())
|
|
29510
|
+
return cancelledResult(execution);
|
|
29511
|
+
const result = { ok: true, content: `Image ${ref.id}, oriented original ${ref.width}x${ref.height}.`, attachmentContent: [{ type: "image", ref, view }] };
|
|
29512
|
+
return target ? withTouchedPath(result, target) : result;
|
|
29513
|
+
} finally {
|
|
29514
|
+
release();
|
|
29515
|
+
}
|
|
29516
|
+
}
|
|
29517
|
+
};
|
|
29518
|
+
}
|
|
29519
|
+
|
|
29173
29520
|
// ../../packages/shell/tool-bash/dist/tool-bash.js
|
|
29174
29521
|
import { promises as fs2 } from "node:fs";
|
|
29175
29522
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
@@ -29239,9 +29586,9 @@ function resolveBashPath(options = {}) {
|
|
|
29239
29586
|
}
|
|
29240
29587
|
|
|
29241
29588
|
// ../../packages/shell/bash-local/dist/bash-output.js
|
|
29242
|
-
import { mkdir as mkdir2, mkdtemp, open, readdir, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
29589
|
+
import { mkdir as mkdir2, mkdtemp, open as open2, readdir, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
29243
29590
|
import { homedir as homedir3 } from "node:os";
|
|
29244
|
-
import { join as
|
|
29591
|
+
import { join as join10, resolve as resolve3 } from "node:path";
|
|
29245
29592
|
var BASH_OUTPUT_MAX_BYTES = 50 * 1024;
|
|
29246
29593
|
var BASH_OUTPUT_MAX_LINES = 2e3;
|
|
29247
29594
|
var OUTPUT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -29291,7 +29638,7 @@ var BashOutput = class {
|
|
|
29291
29638
|
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
29292
29639
|
throw new RangeError("Bash output limits must be positive integers");
|
|
29293
29640
|
}
|
|
29294
|
-
this.#directory = resolve3(options.outputDirectory ??
|
|
29641
|
+
this.#directory = resolve3(options.outputDirectory ?? join10(homedir3(), ".laohuang", "tool-output"));
|
|
29295
29642
|
}
|
|
29296
29643
|
append(stream, text3) {
|
|
29297
29644
|
const next = this.#pending.then(() => this.#append(stream, text3));
|
|
@@ -29328,13 +29675,13 @@ var BashOutput = class {
|
|
|
29328
29675
|
try {
|
|
29329
29676
|
await mkdir2(this.#directory, { recursive: true, mode: 448 });
|
|
29330
29677
|
await this.#removeExpired();
|
|
29331
|
-
this.#archiveDirectory = await mkdtemp(
|
|
29678
|
+
this.#archiveDirectory = await mkdtemp(join10(this.#directory, "bash-"));
|
|
29332
29679
|
const files = {
|
|
29333
|
-
stdout:
|
|
29334
|
-
stderr:
|
|
29680
|
+
stdout: join10(this.#archiveDirectory, "stdout.log"),
|
|
29681
|
+
stderr: join10(this.#archiveDirectory, "stderr.log")
|
|
29335
29682
|
};
|
|
29336
29683
|
for (const stream of ["stdout", "stderr"])
|
|
29337
|
-
this.#handles[stream] = await
|
|
29684
|
+
this.#handles[stream] = await open2(files[stream], "wx", 384);
|
|
29338
29685
|
this.files = files;
|
|
29339
29686
|
} catch (error) {
|
|
29340
29687
|
this.fileError = `Cannot save Bash output: ${String(error)}`;
|
|
@@ -29370,7 +29717,7 @@ var BashOutput = class {
|
|
|
29370
29717
|
}
|
|
29371
29718
|
if (this.#archiveDirectory !== null) {
|
|
29372
29719
|
try {
|
|
29373
|
-
await writeFile2(
|
|
29720
|
+
await writeFile2(join10(this.#archiveDirectory, "completed"), "", { mode: 384 });
|
|
29374
29721
|
} catch {
|
|
29375
29722
|
}
|
|
29376
29723
|
}
|
|
@@ -29379,9 +29726,9 @@ var BashOutput = class {
|
|
|
29379
29726
|
for (const entry of await readdir(this.#directory, { withFileTypes: true })) {
|
|
29380
29727
|
if (!entry.isDirectory() || !entry.name.startsWith("bash-"))
|
|
29381
29728
|
continue;
|
|
29382
|
-
const directory =
|
|
29729
|
+
const directory = join10(this.#directory, entry.name);
|
|
29383
29730
|
try {
|
|
29384
|
-
if (Date.now() - (await stat(
|
|
29731
|
+
if (Date.now() - (await stat(join10(directory, "completed"))).mtimeMs > OUTPUT_RETENTION_MS) {
|
|
29385
29732
|
await rm(directory, { recursive: true, force: true });
|
|
29386
29733
|
}
|
|
29387
29734
|
} catch {
|
|
@@ -29563,7 +29910,7 @@ var TerminalSanitizer = class {
|
|
|
29563
29910
|
}
|
|
29564
29911
|
};
|
|
29565
29912
|
function sleep(ms) {
|
|
29566
|
-
return new Promise((
|
|
29913
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
29567
29914
|
}
|
|
29568
29915
|
function errnoCode(error) {
|
|
29569
29916
|
return error?.code;
|
|
@@ -29574,7 +29921,7 @@ async function terminateProcessTree(child, graceMs = TERMINATION_GRACE_MS) {
|
|
|
29574
29921
|
return;
|
|
29575
29922
|
if (process.platform === "win32") {
|
|
29576
29923
|
const taskkill = win323.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe");
|
|
29577
|
-
await new Promise((
|
|
29924
|
+
await new Promise((resolve15, reject) => {
|
|
29578
29925
|
execFile(taskkill, ["/F", "/T", "/PID", String(pid)], {
|
|
29579
29926
|
windowsHide: true,
|
|
29580
29927
|
timeout: TASKKILL_TIMEOUT_MS,
|
|
@@ -29583,7 +29930,7 @@ async function terminateProcessTree(child, graceMs = TERMINATION_GRACE_MS) {
|
|
|
29583
29930
|
if (error)
|
|
29584
29931
|
reject(new Error(`taskkill failed: ${error.message}`));
|
|
29585
29932
|
else
|
|
29586
|
-
|
|
29933
|
+
resolve15();
|
|
29587
29934
|
});
|
|
29588
29935
|
});
|
|
29589
29936
|
return;
|
|
@@ -29686,9 +30033,9 @@ async function runBash(command, options) {
|
|
|
29686
30033
|
});
|
|
29687
30034
|
return await finish(result2);
|
|
29688
30035
|
}
|
|
29689
|
-
const spawnError = await new Promise((
|
|
29690
|
-
child.once("spawn", () =>
|
|
29691
|
-
child.once("error", (error) =>
|
|
30036
|
+
const spawnError = await new Promise((resolve15) => {
|
|
30037
|
+
child.once("spawn", () => resolve15(null));
|
|
30038
|
+
child.once("error", (error) => resolve15(error));
|
|
29692
30039
|
});
|
|
29693
30040
|
if (spawnError) {
|
|
29694
30041
|
const result2 = new BashResult({
|
|
@@ -29765,8 +30112,8 @@ async function runBash(command, options) {
|
|
|
29765
30112
|
const stdoutDone = readPipe("stdout", child.stdout);
|
|
29766
30113
|
const stderrDone = readPipe("stderr", child.stderr);
|
|
29767
30114
|
let resolveExit;
|
|
29768
|
-
const exitInfo = new Promise((
|
|
29769
|
-
resolveExit =
|
|
30115
|
+
const exitInfo = new Promise((resolve15) => {
|
|
30116
|
+
resolveExit = resolve15;
|
|
29770
30117
|
});
|
|
29771
30118
|
const onExit = (code2, signal2) => {
|
|
29772
30119
|
exited = true;
|
|
@@ -31723,6 +32070,7 @@ var SessionController = class {
|
|
|
31723
32070
|
constructor(options) {
|
|
31724
32071
|
this.#sessionsRoot = options.sessionsRoot;
|
|
31725
32072
|
this.#manager = new SessionManager({
|
|
32073
|
+
attachments: options.attachments,
|
|
31726
32074
|
sessionsRoot: options.sessionsRoot,
|
|
31727
32075
|
appVersion: options.appVersion
|
|
31728
32076
|
});
|
|
@@ -32352,6 +32700,7 @@ function errorMessage10(error) {
|
|
|
32352
32700
|
}
|
|
32353
32701
|
|
|
32354
32702
|
// ../../packages/core/agent-runtime/dist/core/history-committer.js
|
|
32703
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
32355
32704
|
var HistoryCommitter = class {
|
|
32356
32705
|
messages;
|
|
32357
32706
|
context;
|
|
@@ -32366,7 +32715,10 @@ var HistoryCommitter = class {
|
|
|
32366
32715
|
this.conversationHistory = options.conversationHistory ?? null;
|
|
32367
32716
|
}
|
|
32368
32717
|
commitInput(content) {
|
|
32369
|
-
const message = typeof content === "string" ? { role: "user", content } :
|
|
32718
|
+
const message = typeof content === "string" ? { role: "user", content } : {
|
|
32719
|
+
...content,
|
|
32720
|
+
...content.attachments?.length ? { attachmentKey: content.attachmentKey ?? randomUUID11() } : {}
|
|
32721
|
+
};
|
|
32370
32722
|
const commitInput = this.context?.commitInput;
|
|
32371
32723
|
if (typeof commitInput === "function") {
|
|
32372
32724
|
return this.commitContextMessage((append, rollback) => commitInput.call(this.context, append, rollback), message);
|
|
@@ -32385,7 +32737,10 @@ var HistoryCommitter = class {
|
|
|
32385
32737
|
if (content === "") {
|
|
32386
32738
|
return true;
|
|
32387
32739
|
}
|
|
32388
|
-
const message = prepared
|
|
32740
|
+
const message = prepared ? {
|
|
32741
|
+
...prepared,
|
|
32742
|
+
...prepared.attachments?.length ? { attachmentKey: prepared.attachmentKey ?? randomUUID11() } : {}
|
|
32743
|
+
} : { role: "user", content };
|
|
32389
32744
|
const commitPending = this.context?.commitPending;
|
|
32390
32745
|
if (typeof commitPending === "function") {
|
|
32391
32746
|
return this.commitContextMessage((append, rollback) => commitPending.call(this.context, batch, append, rollback), message);
|
|
@@ -32434,21 +32789,23 @@ var HistoryCommitter = class {
|
|
|
32434
32789
|
if (call === void 0 || result === void 0) {
|
|
32435
32790
|
continue;
|
|
32436
32791
|
}
|
|
32792
|
+
const { attachmentContent: attachments, ...textResult } = result;
|
|
32437
32793
|
const message = {
|
|
32438
32794
|
role: "tool-result",
|
|
32439
32795
|
toolCallId: call.id,
|
|
32440
32796
|
toolName: call.name,
|
|
32441
|
-
content: JSON.stringify(
|
|
32797
|
+
content: JSON.stringify(textResult),
|
|
32798
|
+
...attachments?.length ? { attachments, attachmentKey: randomUUID11() } : {},
|
|
32442
32799
|
isError: result.ok !== true
|
|
32443
32800
|
};
|
|
32444
32801
|
messages.push(message);
|
|
32445
|
-
this.messages.push(message);
|
|
32446
32802
|
}
|
|
32447
32803
|
this.conversationHistory?.appendToolResults({
|
|
32448
32804
|
requestId: "tool-results",
|
|
32449
32805
|
messages,
|
|
32450
32806
|
recovered: false
|
|
32451
32807
|
});
|
|
32808
|
+
this.messages.push(...messages);
|
|
32452
32809
|
}
|
|
32453
32810
|
commitReminder(content) {
|
|
32454
32811
|
this.raiseIfCancelled();
|
|
@@ -32942,7 +33299,32 @@ function resultText(result) {
|
|
|
32942
33299
|
function createSessionRuntime(options) {
|
|
32943
33300
|
let route = { ...options.route };
|
|
32944
33301
|
let selectedModel = modelInfo2(options.catalog, route);
|
|
32945
|
-
|
|
33302
|
+
let attachmentTurnStart;
|
|
33303
|
+
const adapter = {
|
|
33304
|
+
name: options.modelAdapter.name,
|
|
33305
|
+
runAttempt: (request) => {
|
|
33306
|
+
const history = options.sessionController.history;
|
|
33307
|
+
const entries = history?.entries() ?? [];
|
|
33308
|
+
const boundary = attachmentTurnStart ?? [...entries].reverse().find((entry) => entry.entryType === "user_message")?.seq ?? 0;
|
|
33309
|
+
const protectedKeys = /* @__PURE__ */ new Set();
|
|
33310
|
+
for (const entry of entries) {
|
|
33311
|
+
if (entry.seq < boundary || !("message" in entry.payload)) continue;
|
|
33312
|
+
const message = entry.payload.message;
|
|
33313
|
+
if (!("attachments" in message)) continue;
|
|
33314
|
+
for (const [index] of (message.attachments ?? []).entries()) protectedKeys.add(`${message.attachmentKey}:${index}`);
|
|
33315
|
+
}
|
|
33316
|
+
return options.modelAdapter.runAttempt({ ...request, imageContext: {
|
|
33317
|
+
offloaded: new Set(entries.flatMap((entry) => entry.entryType === "image_offload" ? entry.payload.keys : [])),
|
|
33318
|
+
protectedKeys,
|
|
33319
|
+
persistOffload: (keys2) => {
|
|
33320
|
+
if (!history) throw new Error("Cannot persist image offload without an active session");
|
|
33321
|
+
history.offloadImages(keys2);
|
|
33322
|
+
}
|
|
33323
|
+
} });
|
|
33324
|
+
}
|
|
33325
|
+
};
|
|
33326
|
+
const modelRuntime = new ModelRuntime(adapter);
|
|
33327
|
+
let releaseAttachments;
|
|
32946
33328
|
const activeConversationHistory = {
|
|
32947
33329
|
appendSkillContext: (input) => activeHistory(options.sessionController).appendSkillContext(input),
|
|
32948
33330
|
appendToolDefinitions: (input) => activeHistory(options.sessionController).appendToolDefinitions(input),
|
|
@@ -32977,20 +33359,30 @@ function createSessionRuntime(options) {
|
|
|
32977
33359
|
}
|
|
32978
33360
|
});
|
|
32979
33361
|
const agent = new CodingAgent({
|
|
32980
|
-
resources:
|
|
33362
|
+
resources: {
|
|
32981
33363
|
prepareInput: async (input, signal, inputs) => {
|
|
33364
|
+
attachmentTurnStart ??= (options.sessionController.history?.entries().at(-1)?.seq ?? 0) + 1;
|
|
33365
|
+
releaseAttachments ??= options.attachments?.protect();
|
|
32982
33366
|
try {
|
|
32983
|
-
return await options.skills
|
|
33367
|
+
return await options.skills?.prepareInput(input, signal, inputs) ?? { role: "user", content: input };
|
|
32984
33368
|
} catch (error) {
|
|
32985
33369
|
if (!signal?.aborted) options.skillInputFailed?.(inputs?.join("\n\n") ?? input, error);
|
|
32986
33370
|
throw error;
|
|
32987
33371
|
}
|
|
32988
33372
|
},
|
|
32989
|
-
prepareContext: (messages, signal) => options.skills
|
|
32990
|
-
committed: (messages) => options.skills
|
|
32991
|
-
finishTurn: () =>
|
|
32992
|
-
|
|
32993
|
-
|
|
33373
|
+
prepareContext: (messages, signal) => options.skills?.prepareContext(messages, signal) ?? Promise.resolve([]),
|
|
33374
|
+
committed: (messages) => options.skills?.committed(messages),
|
|
33375
|
+
finishTurn: () => {
|
|
33376
|
+
try {
|
|
33377
|
+
options.skills?.finishTurn();
|
|
33378
|
+
} finally {
|
|
33379
|
+
releaseAttachments?.();
|
|
33380
|
+
releaseAttachments = void 0;
|
|
33381
|
+
attachmentTurnStart = void 0;
|
|
33382
|
+
}
|
|
33383
|
+
}
|
|
33384
|
+
},
|
|
33385
|
+
modelAdapter: adapter,
|
|
32994
33386
|
model: route.model,
|
|
32995
33387
|
tools: options.tools,
|
|
32996
33388
|
prepareTools: options.prepareTools,
|
|
@@ -33010,6 +33402,7 @@ function createSessionRuntime(options) {
|
|
|
33010
33402
|
const entries = history.entries();
|
|
33011
33403
|
const pendingAssistant = pendingToolCall ? [...entries].reverse().find((entry) => entry.entryType === "assistant_message") : void 0;
|
|
33012
33404
|
const prepared = await createGovernor(history).prepare({
|
|
33405
|
+
protectedFromSeq: attachmentTurnStart,
|
|
33013
33406
|
entries: entries.filter((entry) => entry !== pendingAssistant),
|
|
33014
33407
|
currentProvider: route.provider,
|
|
33015
33408
|
currentModel: route.model,
|
|
@@ -33177,8 +33570,8 @@ function defaultContextPolicy() {
|
|
|
33177
33570
|
}
|
|
33178
33571
|
|
|
33179
33572
|
// src/create-mcp-runtime.ts
|
|
33180
|
-
import { randomUUID as
|
|
33181
|
-
import { dirname as dirname6, join as
|
|
33573
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
33574
|
+
import { dirname as dirname6, join as join12, resolve as resolve6 } from "node:path";
|
|
33182
33575
|
|
|
33183
33576
|
// ../../packages/core/mcp/dist/config.js
|
|
33184
33577
|
import { resolve as resolve4 } from "node:path";
|
|
@@ -33354,14 +33747,14 @@ function createMcpClient(config, version, changed) {
|
|
|
33354
33747
|
function waitWithSignal(work, signal) {
|
|
33355
33748
|
if (signal === void 0)
|
|
33356
33749
|
return work;
|
|
33357
|
-
return new Promise((
|
|
33750
|
+
return new Promise((resolve15, reject) => {
|
|
33358
33751
|
const abort = () => {
|
|
33359
33752
|
signal.removeEventListener("abort", abort);
|
|
33360
33753
|
reject(signal.reason);
|
|
33361
33754
|
};
|
|
33362
33755
|
work.then((value) => {
|
|
33363
33756
|
signal.removeEventListener("abort", abort);
|
|
33364
|
-
|
|
33757
|
+
resolve15(value);
|
|
33365
33758
|
}, (error) => {
|
|
33366
33759
|
signal.removeEventListener("abort", abort);
|
|
33367
33760
|
reject(error);
|
|
@@ -33447,9 +33840,9 @@ function privateRedirectFetch(base, privateHeaders) {
|
|
|
33447
33840
|
import { createHash as createHash6 } from "node:crypto";
|
|
33448
33841
|
|
|
33449
33842
|
// ../../packages/core/mcp/dist/output.js
|
|
33450
|
-
import { randomUUID as
|
|
33843
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
33451
33844
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
33452
|
-
import { join as
|
|
33845
|
+
import { join as join11 } from "node:path";
|
|
33453
33846
|
function bounded(result, limit) {
|
|
33454
33847
|
if (Buffer.byteLength(JSON.stringify(result), "utf8") <= limit)
|
|
33455
33848
|
return result;
|
|
@@ -33510,13 +33903,13 @@ async function adaptMcpResult(result, options) {
|
|
|
33510
33903
|
if (!oversized && media.length === 0)
|
|
33511
33904
|
return preview;
|
|
33512
33905
|
try {
|
|
33513
|
-
const directory =
|
|
33906
|
+
const directory = join11(options.artifactRoot, randomUUID12());
|
|
33514
33907
|
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
33515
|
-
const path5 =
|
|
33908
|
+
const path5 = join11(directory, "result.json");
|
|
33516
33909
|
await writeFile3(path5, JSON.stringify(business), { mode: 384, flag: "wx" });
|
|
33517
33910
|
const attachments = [];
|
|
33518
33911
|
for (const [index, item] of media.entries()) {
|
|
33519
|
-
const path6 =
|
|
33912
|
+
const path6 = join11(directory, `attachment-${index}.bin`);
|
|
33520
33913
|
const decoded = Buffer.from(item.data, "base64");
|
|
33521
33914
|
if (decoded.toString("base64").replace(/=+$/, "") !== item.data.replace(/=+$/, ""))
|
|
33522
33915
|
throw new Error("Invalid base64 attachment");
|
|
@@ -33921,8 +34314,8 @@ async function createOAuthCallback(options) {
|
|
|
33921
34314
|
let accept;
|
|
33922
34315
|
let reject;
|
|
33923
34316
|
let settled = false;
|
|
33924
|
-
const response = new Promise((
|
|
33925
|
-
accept =
|
|
34317
|
+
const response = new Promise((resolve15, fail) => {
|
|
34318
|
+
accept = resolve15;
|
|
33926
34319
|
reject = fail;
|
|
33927
34320
|
});
|
|
33928
34321
|
void response.catch(() => {
|
|
@@ -33952,9 +34345,9 @@ async function createOAuthCallback(options) {
|
|
|
33952
34345
|
reply.writeHead(200, { "Content-Type": "text/plain", "Cache-Control": "no-store" }).end("Authorization received. Return to laohuang.");
|
|
33953
34346
|
accept(url.searchParams);
|
|
33954
34347
|
});
|
|
33955
|
-
await new Promise((
|
|
34348
|
+
await new Promise((resolve15, fail) => {
|
|
33956
34349
|
server.once("error", fail);
|
|
33957
|
-
server.listen(0, "127.0.0.1",
|
|
34350
|
+
server.listen(0, "127.0.0.1", resolve15);
|
|
33958
34351
|
});
|
|
33959
34352
|
const address = server.address();
|
|
33960
34353
|
if (address === null || typeof address === "string")
|
|
@@ -33969,8 +34362,8 @@ async function createOAuthCallback(options) {
|
|
|
33969
34362
|
settled = true;
|
|
33970
34363
|
reject(new Error("OAuth authorization cancelled"));
|
|
33971
34364
|
}
|
|
33972
|
-
closing = new Promise((
|
|
33973
|
-
server.close(() =>
|
|
34365
|
+
closing = new Promise((resolve15) => {
|
|
34366
|
+
server.close(() => resolve15());
|
|
33974
34367
|
server.closeAllConnections();
|
|
33975
34368
|
});
|
|
33976
34369
|
return closing;
|
|
@@ -34493,7 +34886,7 @@ var ConnectionManager = class {
|
|
|
34493
34886
|
// src/create-mcp-runtime.ts
|
|
34494
34887
|
async function createMcpRuntime(options) {
|
|
34495
34888
|
const directory = dirname6(resolve6(options.configPath));
|
|
34496
|
-
const paths = [.../* @__PURE__ */ new Set([
|
|
34889
|
+
const paths = [.../* @__PURE__ */ new Set([join12(directory, "mcp.json"), resolve6(options.projectRoot, ".laohuang/mcp.json")])];
|
|
34497
34890
|
const readSources = async (reload) => {
|
|
34498
34891
|
const sources = [];
|
|
34499
34892
|
let invalid2 = false;
|
|
@@ -34523,8 +34916,8 @@ ${url}`, tone: "info" });
|
|
|
34523
34916
|
projectRoot: options.projectRoot,
|
|
34524
34917
|
version: options.version,
|
|
34525
34918
|
env: options.env,
|
|
34526
|
-
credentials: new McpCredentialFileStore(
|
|
34527
|
-
artifactRoot:
|
|
34919
|
+
credentials: new McpCredentialFileStore(join12(directory, "mcp-credentials")),
|
|
34920
|
+
artifactRoot: join12(directory, "mcp-artifacts", randomUUID13()),
|
|
34528
34921
|
onAuthorization: ({ serverId, url }) => showAuthorization(serverId, url),
|
|
34529
34922
|
onLoginState: (active) => {
|
|
34530
34923
|
if (active) process.on("SIGINT", cancel);
|
|
@@ -34760,10 +35153,10 @@ var ReaddirpStream = class extends Readable {
|
|
|
34760
35153
|
}
|
|
34761
35154
|
async _formatEntry(dirent, path5) {
|
|
34762
35155
|
let entry;
|
|
34763
|
-
const
|
|
35156
|
+
const basename10 = this._isDirent ? dirent.name : dirent;
|
|
34764
35157
|
try {
|
|
34765
|
-
const fullPath = presolve(pjoin(path5,
|
|
34766
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename:
|
|
35158
|
+
const fullPath = presolve(pjoin(path5, basename10));
|
|
35159
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename10 };
|
|
34767
35160
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
34768
35161
|
} catch (err) {
|
|
34769
35162
|
this._onError(err);
|
|
@@ -34834,7 +35227,7 @@ function readdirp(root, options = {}) {
|
|
|
34834
35227
|
|
|
34835
35228
|
// ../../packages/fs/file-watcher/node_modules/chokidar/esm/handler.js
|
|
34836
35229
|
import { watchFile, unwatchFile, watch as fs_watch } from "fs";
|
|
34837
|
-
import { open as
|
|
35230
|
+
import { open as open3, stat as stat3, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
|
|
34838
35231
|
import * as sysPath from "path";
|
|
34839
35232
|
import { type as osType } from "os";
|
|
34840
35233
|
var STR_DATA = "data";
|
|
@@ -35218,7 +35611,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
35218
35611
|
cont.watcherUnusable = true;
|
|
35219
35612
|
if (isWindows && error.code === "EPERM") {
|
|
35220
35613
|
try {
|
|
35221
|
-
const fd = await
|
|
35614
|
+
const fd = await open3(path5, "r");
|
|
35222
35615
|
await fd.close();
|
|
35223
35616
|
broadcastErr(error);
|
|
35224
35617
|
} catch (err) {
|
|
@@ -35302,9 +35695,9 @@ var NodeFsHandler = class {
|
|
|
35302
35695
|
_watchWithNodeFs(path5, listener) {
|
|
35303
35696
|
const opts = this.fsw.options;
|
|
35304
35697
|
const directory = sysPath.dirname(path5);
|
|
35305
|
-
const
|
|
35698
|
+
const basename10 = sysPath.basename(path5);
|
|
35306
35699
|
const parent = this.fsw._getWatchedDir(directory);
|
|
35307
|
-
parent.add(
|
|
35700
|
+
parent.add(basename10);
|
|
35308
35701
|
const absolutePath = sysPath.resolve(path5);
|
|
35309
35702
|
const options = {
|
|
35310
35703
|
persistent: opts.persistent
|
|
@@ -35314,7 +35707,7 @@ var NodeFsHandler = class {
|
|
|
35314
35707
|
let closer;
|
|
35315
35708
|
if (opts.usePolling) {
|
|
35316
35709
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
35317
|
-
options.interval = enableBin && isBinaryPath(
|
|
35710
|
+
options.interval = enableBin && isBinaryPath(basename10) ? opts.binaryInterval : opts.interval;
|
|
35318
35711
|
closer = setFsWatchFileListener(path5, absolutePath, options, {
|
|
35319
35712
|
listener,
|
|
35320
35713
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -35337,10 +35730,10 @@ var NodeFsHandler = class {
|
|
|
35337
35730
|
return;
|
|
35338
35731
|
}
|
|
35339
35732
|
const dirname14 = sysPath.dirname(file);
|
|
35340
|
-
const
|
|
35733
|
+
const basename10 = sysPath.basename(file);
|
|
35341
35734
|
const parent = this.fsw._getWatchedDir(dirname14);
|
|
35342
35735
|
let prevStats = stats;
|
|
35343
|
-
if (parent.has(
|
|
35736
|
+
if (parent.has(basename10))
|
|
35344
35737
|
return;
|
|
35345
35738
|
const listener = async (path5, newStats) => {
|
|
35346
35739
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
@@ -35365,9 +35758,9 @@ var NodeFsHandler = class {
|
|
|
35365
35758
|
prevStats = newStats2;
|
|
35366
35759
|
}
|
|
35367
35760
|
} catch (error) {
|
|
35368
|
-
this.fsw._remove(dirname14,
|
|
35761
|
+
this.fsw._remove(dirname14, basename10);
|
|
35369
35762
|
}
|
|
35370
|
-
} else if (parent.has(
|
|
35763
|
+
} else if (parent.has(basename10)) {
|
|
35371
35764
|
const at = newStats.atimeMs;
|
|
35372
35765
|
const mt = newStats.mtimeMs;
|
|
35373
35766
|
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
@@ -35461,7 +35854,7 @@ var NodeFsHandler = class {
|
|
|
35461
35854
|
this._addToNodeFs(path5, initialAdd, wh, depth + 1);
|
|
35462
35855
|
}
|
|
35463
35856
|
}).on(EV.ERROR, this._boundHandleError);
|
|
35464
|
-
return new Promise((
|
|
35857
|
+
return new Promise((resolve15, reject) => {
|
|
35465
35858
|
if (!stream)
|
|
35466
35859
|
return reject();
|
|
35467
35860
|
stream.once(STR_END, () => {
|
|
@@ -35470,7 +35863,7 @@ var NodeFsHandler = class {
|
|
|
35470
35863
|
return;
|
|
35471
35864
|
}
|
|
35472
35865
|
const wasThrottled = throttler ? throttler.clear() : false;
|
|
35473
|
-
|
|
35866
|
+
resolve15(void 0);
|
|
35474
35867
|
previous.getChildren().filter((item) => {
|
|
35475
35868
|
return item !== directory && !current.has(item);
|
|
35476
35869
|
}).forEach((item) => {
|
|
@@ -36550,8 +36943,8 @@ var Handle = class {
|
|
|
36550
36943
|
constructor(task, release) {
|
|
36551
36944
|
this.task = task;
|
|
36552
36945
|
this.release = release;
|
|
36553
|
-
this.ready = new Promise((
|
|
36554
|
-
this.resolveReady =
|
|
36946
|
+
this.ready = new Promise((resolve15, reject) => {
|
|
36947
|
+
this.resolveReady = resolve15;
|
|
36555
36948
|
this.rejectReady = reject;
|
|
36556
36949
|
});
|
|
36557
36950
|
void this.ready.catch(() => {
|
|
@@ -36862,7 +37255,7 @@ var SkillRegistry = class {
|
|
|
36862
37255
|
|
|
36863
37256
|
// ../../packages/skill/skill-filesystem/dist/index.js
|
|
36864
37257
|
import { lstat as lstat3, readFile as readFile3, readlink, readdir as readdir4, realpath as realpath2 } from "node:fs/promises";
|
|
36865
|
-
import { dirname as dirname10, isAbsolute as isAbsolute3, join as
|
|
37258
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, join as join15, parse as parse2, relative as relative4, resolve as resolve11, sep as sep2 } from "node:path";
|
|
36866
37259
|
|
|
36867
37260
|
// ../../packages/skill/skill-filesystem/dist/parser.js
|
|
36868
37261
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -36893,11 +37286,11 @@ function parseSkill(text3) {
|
|
|
36893
37286
|
// ../../packages/skill/skill-filesystem/dist/index.js
|
|
36894
37287
|
function skillRoots(projectRoot, home, extras) {
|
|
36895
37288
|
return [
|
|
36896
|
-
{ path:
|
|
36897
|
-
{ path:
|
|
37289
|
+
{ path: join15(projectRoot, ".laohuang/skills"), priority: 100 },
|
|
37290
|
+
{ path: join15(projectRoot, ".agents/skills"), priority: 200 },
|
|
36898
37291
|
...extras.map((path5, index) => ({ path: path5, priority: 300 + index / (extras.length + 1) })),
|
|
36899
|
-
{ path:
|
|
36900
|
-
{ path:
|
|
37292
|
+
{ path: join15(home, ".laohuang/skills"), priority: 400 },
|
|
37293
|
+
{ path: join15(home, ".agents/skills"), priority: 500 }
|
|
36901
37294
|
];
|
|
36902
37295
|
}
|
|
36903
37296
|
function parseSkillDirs(value, configPath, home) {
|
|
@@ -37068,7 +37461,7 @@ var FileSystemSkillProvider = class {
|
|
|
37068
37461
|
let current = root;
|
|
37069
37462
|
let followed = false;
|
|
37070
37463
|
for (let index = 0; index < parts.length; index++) {
|
|
37071
|
-
current =
|
|
37464
|
+
current = join15(current, parts[index]);
|
|
37072
37465
|
let stat6;
|
|
37073
37466
|
try {
|
|
37074
37467
|
stat6 = await lstat3(current);
|
|
@@ -37140,13 +37533,13 @@ var FileSystemSkillProvider = class {
|
|
|
37140
37533
|
}
|
|
37141
37534
|
children.sort((a, b) => compareSkillPaths(a.name, b.name));
|
|
37142
37535
|
if (children.some((child) => child.name === "SKILL.md")) {
|
|
37143
|
-
await file(
|
|
37536
|
+
await file(join15(path5, "SKILL.md"));
|
|
37144
37537
|
return;
|
|
37145
37538
|
}
|
|
37146
37539
|
for (const child of children) {
|
|
37147
37540
|
if (child.name.startsWith(".") || child.name === "node_modules")
|
|
37148
37541
|
continue;
|
|
37149
|
-
const childPath =
|
|
37542
|
+
const childPath = join15(path5, child.name);
|
|
37150
37543
|
if (child.isDirectory())
|
|
37151
37544
|
await walk(childPath, false);
|
|
37152
37545
|
else if (child.isSymbolicLink()) {
|
|
@@ -37231,7 +37624,7 @@ var FileSystemSkillProvider = class {
|
|
|
37231
37624
|
};
|
|
37232
37625
|
|
|
37233
37626
|
// ../../packages/skill/tool-skill/dist/index.js
|
|
37234
|
-
import { randomUUID as
|
|
37627
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
37235
37628
|
function parseSkillCommand(input) {
|
|
37236
37629
|
const match = /^\s*\/skill:([^\s]+)(?:[ \t]|\r?\n)?([\s\S]*)$/.exec(input);
|
|
37237
37630
|
return match ? { name: match[1], arguments: match[2] } : null;
|
|
@@ -37281,7 +37674,7 @@ var SkillSession = class {
|
|
|
37281
37674
|
const skill = await this.registry.load(args["name"], context.signal);
|
|
37282
37675
|
if (context.isCancelled() || this.closed)
|
|
37283
37676
|
throw new Error("Skill load cancelled");
|
|
37284
|
-
const key =
|
|
37677
|
+
const key = randomUUID14();
|
|
37285
37678
|
this.pending.set(key, { session: this.state, skill });
|
|
37286
37679
|
return { ok: true, content: render(skill), skillReceipt: key, skill: metadata(skill) };
|
|
37287
37680
|
}
|
|
@@ -37304,7 +37697,7 @@ var SkillSession = class {
|
|
|
37304
37697
|
signal?.throwIfAborted();
|
|
37305
37698
|
if (this.closed)
|
|
37306
37699
|
throw new Error("Skill session is closed");
|
|
37307
|
-
const key =
|
|
37700
|
+
const key = randomUUID14();
|
|
37308
37701
|
this.pending.set(key, { session: this.state, skill });
|
|
37309
37702
|
return {
|
|
37310
37703
|
role: "user",
|
|
@@ -37495,6 +37888,500 @@ async function createSkillRuntime(options) {
|
|
|
37495
37888
|
};
|
|
37496
37889
|
}
|
|
37497
37890
|
|
|
37891
|
+
// src/create-attachment-runtime.ts
|
|
37892
|
+
import { mkdirSync as mkdirSync6 } from "node:fs";
|
|
37893
|
+
|
|
37894
|
+
// ../../packages/attachment/attachment-local/dist/local-attachment-store.js
|
|
37895
|
+
import { createHash as createHash10, randomUUID as randomUUID16 } from "node:crypto";
|
|
37896
|
+
import { chmodSync as chmodSync4, existsSync as existsSync8, lstatSync as lstatSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync } from "node:fs";
|
|
37897
|
+
import { mkdir as mkdir5, readFile as readFile5, rename as rename2, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
|
|
37898
|
+
import { join as join17, resolve as resolve13 } from "node:path";
|
|
37899
|
+
import { DatabaseSync } from "node:sqlite";
|
|
37900
|
+
import sharp2 from "sharp";
|
|
37901
|
+
|
|
37902
|
+
// ../../packages/attachment/attachment-local/dist/object-store.js
|
|
37903
|
+
import { createHash as createHash9, randomUUID as randomUUID15 } from "node:crypto";
|
|
37904
|
+
import { constants as constants2 } from "node:fs";
|
|
37905
|
+
import { mkdir as mkdir4, open as open4, link, unlink as unlink3, chmod } from "node:fs/promises";
|
|
37906
|
+
import { join as join16 } from "node:path";
|
|
37907
|
+
async function syncDirectory(path5) {
|
|
37908
|
+
if (process.platform === "win32")
|
|
37909
|
+
return;
|
|
37910
|
+
const handle = await open4(path5, "r");
|
|
37911
|
+
try {
|
|
37912
|
+
await handle.sync();
|
|
37913
|
+
} finally {
|
|
37914
|
+
await handle.close();
|
|
37915
|
+
}
|
|
37916
|
+
}
|
|
37917
|
+
async function writeObject(root, source, name, signal) {
|
|
37918
|
+
const temporary = join16(root, "tmp", randomUUID15());
|
|
37919
|
+
const handle = await open4(temporary, "wx", 384);
|
|
37920
|
+
const hash = createHash9("sha256");
|
|
37921
|
+
let bytes = 0;
|
|
37922
|
+
try {
|
|
37923
|
+
for await (const input of source) {
|
|
37924
|
+
signal?.throwIfAborted();
|
|
37925
|
+
const chunk = Buffer.from(input);
|
|
37926
|
+
bytes += chunk.byteLength;
|
|
37927
|
+
if (!Number.isSafeInteger(bytes))
|
|
37928
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "File is too large");
|
|
37929
|
+
hash.update(chunk);
|
|
37930
|
+
await handle.writeFile(chunk);
|
|
37931
|
+
}
|
|
37932
|
+
signal?.throwIfAborted();
|
|
37933
|
+
await handle.sync();
|
|
37934
|
+
const ref = {
|
|
37935
|
+
id: `sha256:${hash.digest("hex")}`,
|
|
37936
|
+
bytes,
|
|
37937
|
+
name: name.split(/[\\/]/).at(-1).replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 255)
|
|
37938
|
+
};
|
|
37939
|
+
const directory = join16(root, "objects", ref.id.slice(7, 9));
|
|
37940
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
37941
|
+
await syncDirectory(join16(root, "objects"));
|
|
37942
|
+
const target = join16(directory, ref.id.slice(7));
|
|
37943
|
+
try {
|
|
37944
|
+
await link(temporary, target);
|
|
37945
|
+
} catch (error) {
|
|
37946
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST"))
|
|
37947
|
+
throw error;
|
|
37948
|
+
for await (const _chunk of readObject(root, ref, signal)) {
|
|
37949
|
+
}
|
|
37950
|
+
}
|
|
37951
|
+
await chmod(target, 384);
|
|
37952
|
+
await syncDirectory(directory);
|
|
37953
|
+
return ref;
|
|
37954
|
+
} finally {
|
|
37955
|
+
await handle.close();
|
|
37956
|
+
await unlink3(temporary);
|
|
37957
|
+
}
|
|
37958
|
+
}
|
|
37959
|
+
async function* readObject(root, ref, signal) {
|
|
37960
|
+
assertFileRef(ref);
|
|
37961
|
+
signal?.throwIfAborted();
|
|
37962
|
+
let handle;
|
|
37963
|
+
try {
|
|
37964
|
+
handle = await open4(join16(root, "objects", ref.id.slice(7, 9), ref.id.slice(7)), constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
37965
|
+
} catch (error) {
|
|
37966
|
+
throw new AttachmentError(error instanceof Error && "code" in error && error.code === "ENOENT" ? "NOT_FOUND" : "IO", `Cannot read attachment ${ref.id}`, { cause: error });
|
|
37967
|
+
}
|
|
37968
|
+
try {
|
|
37969
|
+
const info = await handle.stat();
|
|
37970
|
+
if (!info.isFile() || info.size !== ref.bytes)
|
|
37971
|
+
throw new AttachmentError("CORRUPT", `Attachment size mismatch: ${ref.id}`);
|
|
37972
|
+
const hash = createHash9("sha256");
|
|
37973
|
+
let bytes = 0;
|
|
37974
|
+
while (true) {
|
|
37975
|
+
signal?.throwIfAborted();
|
|
37976
|
+
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
37977
|
+
const { bytesRead } = await handle.read(chunk);
|
|
37978
|
+
if (bytesRead === 0)
|
|
37979
|
+
break;
|
|
37980
|
+
const part = chunk.subarray(0, bytesRead);
|
|
37981
|
+
bytes += bytesRead;
|
|
37982
|
+
hash.update(part);
|
|
37983
|
+
yield part;
|
|
37984
|
+
}
|
|
37985
|
+
if (bytes !== ref.bytes || `sha256:${hash.digest("hex")}` !== ref.id)
|
|
37986
|
+
throw new AttachmentError("CORRUPT", `Attachment digest mismatch: ${ref.id}`);
|
|
37987
|
+
} finally {
|
|
37988
|
+
await handle.close();
|
|
37989
|
+
}
|
|
37990
|
+
}
|
|
37991
|
+
|
|
37992
|
+
// ../../packages/attachment/attachment-local/dist/image.js
|
|
37993
|
+
import sharp from "sharp";
|
|
37994
|
+
var MIME = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp", gif: "image/gif" };
|
|
37995
|
+
async function inspectImage(input, limits) {
|
|
37996
|
+
if (input.data.byteLength === 0 || input.data.byteLength > limits.maxBytes)
|
|
37997
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Image source byte limit exceeded or empty image");
|
|
37998
|
+
try {
|
|
37999
|
+
const decoder = sharp(input.data, { failOn: "warning", limitInputPixels: limits.maxPixels });
|
|
38000
|
+
const meta = await decoder.metadata();
|
|
38001
|
+
if (!meta.format || !(meta.format in MIME))
|
|
38002
|
+
throw new AttachmentError("INVALID_IMAGE", "Only PNG, JPEG, WebP and GIF are accepted");
|
|
38003
|
+
const mimeType = MIME[meta.format];
|
|
38004
|
+
if (input.mimeType !== void 0 && input.mimeType !== mimeType)
|
|
38005
|
+
throw new AttachmentError("INVALID_IMAGE", "Image MIME does not match its bytes");
|
|
38006
|
+
const rotated = (meta.orientation ?? 1) >= 5;
|
|
38007
|
+
const width = rotated ? meta.height : meta.width;
|
|
38008
|
+
const height = rotated ? meta.width : meta.height;
|
|
38009
|
+
if (width * height > limits.maxPixels || Math.max(width, height) > limits.maxDimension)
|
|
38010
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Image pixel or dimension limit exceeded");
|
|
38011
|
+
await decoder.stats();
|
|
38012
|
+
return { mimeType, width, height, animated: (meta.pages ?? 1) > 1 };
|
|
38013
|
+
} catch (error) {
|
|
38014
|
+
if (error instanceof AttachmentError)
|
|
38015
|
+
throw error;
|
|
38016
|
+
throw new AttachmentError("INVALID_IMAGE", "Image cannot be decoded safely", { cause: error });
|
|
38017
|
+
}
|
|
38018
|
+
}
|
|
38019
|
+
function validateTarget(target) {
|
|
38020
|
+
assertImageView(target);
|
|
38021
|
+
if (![target.maxBytes, target.maxPixels, target.maxDimension].every((n) => Number.isSafeInteger(n) && n > 0) || target.formats.length === 0 || target.formats.some((f) => !["image/png", "image/jpeg", "image/webp"].includes(f))) {
|
|
38022
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Invalid image request limits");
|
|
38023
|
+
}
|
|
38024
|
+
}
|
|
38025
|
+
async function renderImage(data, ref, target, variantId, signal) {
|
|
38026
|
+
validateTarget(target);
|
|
38027
|
+
const region = target.region;
|
|
38028
|
+
if (region && (region.x + region.width > ref.width || region.y + region.height > ref.height))
|
|
38029
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Crop is outside the oriented original image");
|
|
38030
|
+
const sourceWidth = region?.width ?? ref.width;
|
|
38031
|
+
const sourceHeight = region?.height ?? ref.height;
|
|
38032
|
+
let scale = Math.min(1, Math.sqrt(target.maxPixels / (sourceWidth * sourceHeight)), target.maxDimension / Math.max(sourceWidth, sourceHeight));
|
|
38033
|
+
if (target.fullResolution && scale < 1)
|
|
38034
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Native resolution exceeds image pixel limits; use a crop or allow resizing");
|
|
38035
|
+
const base = sharp(data, { failOn: "warning", limitInputPixels: ref.width * ref.height }).rotate().toColourspace("srgb");
|
|
38036
|
+
if (region)
|
|
38037
|
+
base.extract({ left: region.x, top: region.y, width: region.width, height: region.height });
|
|
38038
|
+
const alpha = (await base.metadata()).hasAlpha;
|
|
38039
|
+
const formats2 = target.formats.filter((f) => !alpha || f !== "image/jpeg");
|
|
38040
|
+
if (formats2.length === 0)
|
|
38041
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Request formats cannot preserve image transparency");
|
|
38042
|
+
let previous = "";
|
|
38043
|
+
while (true) {
|
|
38044
|
+
signal?.throwIfAborted();
|
|
38045
|
+
const width = Math.max(1, Math.floor(sourceWidth * scale));
|
|
38046
|
+
const height = Math.max(1, Math.floor(sourceHeight * scale));
|
|
38047
|
+
if (width * height > target.maxPixels) {
|
|
38048
|
+
scale *= 0.75;
|
|
38049
|
+
continue;
|
|
38050
|
+
}
|
|
38051
|
+
const dimensions = `${width}x${height}`;
|
|
38052
|
+
if (dimensions === previous)
|
|
38053
|
+
break;
|
|
38054
|
+
previous = dimensions;
|
|
38055
|
+
for (const mimeType of formats2) {
|
|
38056
|
+
for (const quality of mimeType === "image/png" ? [100] : [85, 75, 60]) {
|
|
38057
|
+
signal?.throwIfAborted();
|
|
38058
|
+
const pipeline = base.clone().resize(width, height, { fit: "fill", withoutEnlargement: true });
|
|
38059
|
+
const encoded = mimeType === "image/png" ? pipeline.png() : mimeType === "image/webp" ? pipeline.webp({ quality }) : pipeline.jpeg({ quality });
|
|
38060
|
+
const { data: output, info } = await encoded.toBuffer({ resolveWithObject: true });
|
|
38061
|
+
if (output.length <= target.maxBytes) {
|
|
38062
|
+
signal?.throwIfAborted();
|
|
38063
|
+
return {
|
|
38064
|
+
data: output,
|
|
38065
|
+
mimeType,
|
|
38066
|
+
width: info.width,
|
|
38067
|
+
height: info.height,
|
|
38068
|
+
variantId,
|
|
38069
|
+
note: `Original oriented size ${ref.width}x${ref.height}; sent ${info.width}x${info.height}.` + (region ? ` Crop origin (${region.x},${region.y}), source region ${region.width}x${region.height}; scale coordinates to that region then add the origin.` : " Scale coordinates to the original size.") + (ref.animated ? " Animation: only the first frame is shown." : "")
|
|
38070
|
+
};
|
|
38071
|
+
}
|
|
38072
|
+
}
|
|
38073
|
+
}
|
|
38074
|
+
if (target.fullResolution || width === 1 && height === 1)
|
|
38075
|
+
break;
|
|
38076
|
+
scale *= 0.75;
|
|
38077
|
+
}
|
|
38078
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Image cannot fit the request byte limit without violating the requested view");
|
|
38079
|
+
}
|
|
38080
|
+
|
|
38081
|
+
// ../../packages/attachment/attachment-local/dist/local-attachment-store.js
|
|
38082
|
+
var LocalAttachmentStore = class extends AttachmentStore {
|
|
38083
|
+
root;
|
|
38084
|
+
imageLimits;
|
|
38085
|
+
db;
|
|
38086
|
+
now;
|
|
38087
|
+
concurrency;
|
|
38088
|
+
active = 0;
|
|
38089
|
+
waiting = [];
|
|
38090
|
+
operations = /* @__PURE__ */ new Set();
|
|
38091
|
+
closed = false;
|
|
38092
|
+
constructor(options) {
|
|
38093
|
+
super();
|
|
38094
|
+
this.root = resolve13(options.root);
|
|
38095
|
+
this.now = options.now ?? Date.now;
|
|
38096
|
+
this.imageLimits = Object.freeze({ ...DEFAULT_IMAGE_LIMITS, ...options.imageLimits });
|
|
38097
|
+
this.concurrency = options.compressionConcurrency ?? 2;
|
|
38098
|
+
if (![...Object.values(this.imageLimits), this.concurrency].every((n) => Number.isSafeInteger(n) && n > 0) || this.concurrency > 8)
|
|
38099
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Invalid attachment configuration");
|
|
38100
|
+
for (const path5 of [this.root, join17(this.root, "objects"), join17(this.root, "tmp"), join17(this.root, "variants")]) {
|
|
38101
|
+
mkdirSync5(path5, { recursive: true, mode: 448 });
|
|
38102
|
+
}
|
|
38103
|
+
this.db = new DatabaseSync(join17(this.root, "metadata.sqlite"));
|
|
38104
|
+
chmodSync4(join17(this.root, "metadata.sqlite"), 384);
|
|
38105
|
+
this.db.exec(`PRAGMA busy_timeout=5000; PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL;
|
|
38106
|
+
CREATE TABLE IF NOT EXISTS objects(id TEXT PRIMARY KEY, created INTEGER NOT NULL, unreferenced INTEGER, referenced INTEGER NOT NULL DEFAULT 0);
|
|
38107
|
+
CREATE TABLE IF NOT EXISTS operations(id TEXT PRIMARY KEY, pid INTEGER NOT NULL);
|
|
38108
|
+
CREATE TABLE IF NOT EXISTS settings(key TEXT PRIMARY KEY, value INTEGER NOT NULL);`);
|
|
38109
|
+
}
|
|
38110
|
+
transaction(action) {
|
|
38111
|
+
if (this.closed)
|
|
38112
|
+
throw new AttachmentError("CLOSED", "Attachment store is closed");
|
|
38113
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
38114
|
+
try {
|
|
38115
|
+
const result = action();
|
|
38116
|
+
this.db.exec("COMMIT");
|
|
38117
|
+
return result;
|
|
38118
|
+
} catch (error) {
|
|
38119
|
+
this.db.exec("ROLLBACK");
|
|
38120
|
+
throw error;
|
|
38121
|
+
}
|
|
38122
|
+
}
|
|
38123
|
+
protect() {
|
|
38124
|
+
const id = randomUUID16();
|
|
38125
|
+
this.transaction(() => this.db.prepare("INSERT INTO operations VALUES (?, ?)").run(id, process.pid));
|
|
38126
|
+
this.operations.add(id);
|
|
38127
|
+
let released = false;
|
|
38128
|
+
return () => {
|
|
38129
|
+
if (released)
|
|
38130
|
+
return;
|
|
38131
|
+
released = true;
|
|
38132
|
+
if (!this.closed)
|
|
38133
|
+
this.db.prepare("DELETE FROM operations WHERE id=?").run(id);
|
|
38134
|
+
this.operations.delete(id);
|
|
38135
|
+
};
|
|
38136
|
+
}
|
|
38137
|
+
async limited(task) {
|
|
38138
|
+
if (this.active >= this.concurrency)
|
|
38139
|
+
await new Promise((resolve15) => this.waiting.push(resolve15));
|
|
38140
|
+
else
|
|
38141
|
+
this.active++;
|
|
38142
|
+
try {
|
|
38143
|
+
return await task();
|
|
38144
|
+
} finally {
|
|
38145
|
+
const next = this.waiting.shift();
|
|
38146
|
+
if (next)
|
|
38147
|
+
next();
|
|
38148
|
+
else
|
|
38149
|
+
this.active--;
|
|
38150
|
+
}
|
|
38151
|
+
}
|
|
38152
|
+
async saveFile(source, name, signal) {
|
|
38153
|
+
const release = this.protect();
|
|
38154
|
+
try {
|
|
38155
|
+
const ref = await writeObject(this.root, source, name, signal);
|
|
38156
|
+
this.transaction(() => this.db.prepare("INSERT OR IGNORE INTO objects VALUES (?, ?, ?, 0)").run(ref.id, this.now(), this.now()));
|
|
38157
|
+
return ref;
|
|
38158
|
+
} finally {
|
|
38159
|
+
release();
|
|
38160
|
+
}
|
|
38161
|
+
}
|
|
38162
|
+
async *readFile(ref, signal) {
|
|
38163
|
+
const release = this.protect();
|
|
38164
|
+
try {
|
|
38165
|
+
yield* readObject(this.root, ref, signal);
|
|
38166
|
+
} finally {
|
|
38167
|
+
release();
|
|
38168
|
+
}
|
|
38169
|
+
}
|
|
38170
|
+
async saveImages(inputs, signal) {
|
|
38171
|
+
const release = this.protect();
|
|
38172
|
+
try {
|
|
38173
|
+
if (inputs.length > this.imageLimits.maxImages || inputs.reduce((sum, input) => sum + input.data.byteLength, 0) > this.imageLimits.maxBatchBytes)
|
|
38174
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Image batch exceeds count or byte limits");
|
|
38175
|
+
const owned = inputs.map((input) => ({ ...input, data: Buffer.from(input.data) }));
|
|
38176
|
+
const inspected = [];
|
|
38177
|
+
for (const input of owned) {
|
|
38178
|
+
signal?.throwIfAborted();
|
|
38179
|
+
inspected.push(await this.limited(() => inspectImage(input, this.imageLimits)));
|
|
38180
|
+
}
|
|
38181
|
+
const refs = [];
|
|
38182
|
+
for (const [index, input] of owned.entries()) {
|
|
38183
|
+
const ref = await this.saveFile((async function* () {
|
|
38184
|
+
yield input.data;
|
|
38185
|
+
})(), input.name, signal);
|
|
38186
|
+
refs.push({ ...ref, ...inspected[index] });
|
|
38187
|
+
}
|
|
38188
|
+
return refs;
|
|
38189
|
+
} finally {
|
|
38190
|
+
release();
|
|
38191
|
+
}
|
|
38192
|
+
}
|
|
38193
|
+
async prepareImage(ref, target, signal) {
|
|
38194
|
+
assertAttachmentContent({ type: "image", ref });
|
|
38195
|
+
validateTarget(target);
|
|
38196
|
+
const release = this.protect();
|
|
38197
|
+
try {
|
|
38198
|
+
if (ref.bytes > this.imageLimits.maxBytes)
|
|
38199
|
+
throw new AttachmentError("LIMIT_EXCEEDED", "Stored image exceeds configured source limit");
|
|
38200
|
+
const chunks = [];
|
|
38201
|
+
for await (const chunk of this.readFile(ref, signal))
|
|
38202
|
+
chunks.push(chunk);
|
|
38203
|
+
const data = Buffer.concat(chunks);
|
|
38204
|
+
return await this.limited(async () => {
|
|
38205
|
+
const actual = await inspectImage({ data, name: ref.name, mimeType: ref.mimeType }, this.imageLimits);
|
|
38206
|
+
if (actual.width !== ref.width || actual.height !== ref.height || actual.animated !== ref.animated)
|
|
38207
|
+
throw new AttachmentError("CORRUPT", "Image reference metadata mismatch");
|
|
38208
|
+
const variantId = createHash10("sha256").update(JSON.stringify({
|
|
38209
|
+
version: 1,
|
|
38210
|
+
id: ref.id,
|
|
38211
|
+
pixels: target.maxPixels,
|
|
38212
|
+
dimension: target.maxDimension,
|
|
38213
|
+
bytes: target.maxBytes,
|
|
38214
|
+
formats: target.formats,
|
|
38215
|
+
region: target.region ?? null,
|
|
38216
|
+
full: target.fullResolution ?? false
|
|
38217
|
+
})).digest("hex");
|
|
38218
|
+
const directory = join17(this.root, "variants", ref.id.slice(7));
|
|
38219
|
+
const path5 = join17(directory, variantId + ".json");
|
|
38220
|
+
try {
|
|
38221
|
+
const raw = JSON.parse(await readFile5(path5, { encoding: "utf8", signal }));
|
|
38222
|
+
const cached = raw;
|
|
38223
|
+
if (cached.variantId === variantId && typeof cached.data === "string" && typeof cached.digest === "string" && typeof cached.note === "string") {
|
|
38224
|
+
const bytes = Buffer.from(cached.data, "base64");
|
|
38225
|
+
if (bytes.length <= target.maxBytes && createHash10("sha256").update(bytes).digest("hex") === cached.digest) {
|
|
38226
|
+
const decoder = sharp2(bytes, { failOn: "warning", limitInputPixels: target.maxPixels });
|
|
38227
|
+
const meta = await decoder.metadata();
|
|
38228
|
+
if (meta.width === cached.width && meta.height === cached.height && Math.max(meta.width, meta.height) <= target.maxDimension && meta.width * meta.height <= target.maxPixels && cached.mimeType === `image/${meta.format}` && target.formats.includes(cached.mimeType)) {
|
|
38229
|
+
await decoder.stats();
|
|
38230
|
+
signal?.throwIfAborted();
|
|
38231
|
+
return { data: bytes, width: meta.width, height: meta.height, mimeType: cached.mimeType, variantId, note: cached.note };
|
|
38232
|
+
}
|
|
38233
|
+
}
|
|
38234
|
+
}
|
|
38235
|
+
} catch (error) {
|
|
38236
|
+
signal?.throwIfAborted();
|
|
38237
|
+
}
|
|
38238
|
+
const result = await renderImage(data, ref, target, variantId, signal);
|
|
38239
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
38240
|
+
const temporary = join17(this.root, "tmp", randomUUID16());
|
|
38241
|
+
try {
|
|
38242
|
+
await writeFile4(temporary, JSON.stringify({ ...result, data: Buffer.from(result.data).toString("base64"), digest: createHash10("sha256").update(result.data).digest("hex") }), { flag: "wx", mode: 384 });
|
|
38243
|
+
await rename2(temporary, path5);
|
|
38244
|
+
} finally {
|
|
38245
|
+
await unlink4(temporary).catch((error) => {
|
|
38246
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
38247
|
+
throw error;
|
|
38248
|
+
});
|
|
38249
|
+
}
|
|
38250
|
+
return result;
|
|
38251
|
+
});
|
|
38252
|
+
} finally {
|
|
38253
|
+
release();
|
|
38254
|
+
}
|
|
38255
|
+
}
|
|
38256
|
+
commit(refs, write) {
|
|
38257
|
+
return this.transaction(() => {
|
|
38258
|
+
for (const ref of refs) {
|
|
38259
|
+
assertFileRef(ref);
|
|
38260
|
+
const path5 = join17(this.root, "objects", ref.id.slice(7, 9), ref.id.slice(7));
|
|
38261
|
+
if (!existsSync8(path5))
|
|
38262
|
+
throw new AttachmentError("NOT_FOUND", `Attachment missing before commit: ${ref.id}`);
|
|
38263
|
+
const info = lstatSync3(path5);
|
|
38264
|
+
if (!info.isFile() || info.size !== ref.bytes)
|
|
38265
|
+
throw new AttachmentError("CORRUPT", "Attachment changed before commit");
|
|
38266
|
+
this.db.prepare("INSERT OR IGNORE INTO objects VALUES (?, ?, NULL, 1)").run(ref.id, this.now());
|
|
38267
|
+
this.db.prepare("UPDATE objects SET referenced=1, unreferenced=NULL WHERE id=?").run(ref.id);
|
|
38268
|
+
}
|
|
38269
|
+
const result = write();
|
|
38270
|
+
if (result instanceof Promise)
|
|
38271
|
+
throw new TypeError("Attachment commit callback must be synchronous");
|
|
38272
|
+
return result;
|
|
38273
|
+
});
|
|
38274
|
+
}
|
|
38275
|
+
collectGarbage(references, force = false) {
|
|
38276
|
+
return this.transaction(() => {
|
|
38277
|
+
const now = this.now();
|
|
38278
|
+
const last = this.db.prepare("SELECT value FROM settings WHERE key='lastCleanup'").get();
|
|
38279
|
+
if (!force && last && now - last.value < CLEANUP_INTERVAL_MS)
|
|
38280
|
+
return { deleted: [], skipped: true };
|
|
38281
|
+
const operations2 = this.db.prepare("SELECT id,pid FROM operations").all();
|
|
38282
|
+
for (const operation of operations2) {
|
|
38283
|
+
let alive = true;
|
|
38284
|
+
try {
|
|
38285
|
+
process.kill(operation.pid, 0);
|
|
38286
|
+
} catch (error) {
|
|
38287
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH")
|
|
38288
|
+
alive = false;
|
|
38289
|
+
}
|
|
38290
|
+
if (alive)
|
|
38291
|
+
return { deleted: [], skipped: true };
|
|
38292
|
+
this.db.prepare("DELETE FROM operations WHERE id=?").run(operation.id);
|
|
38293
|
+
}
|
|
38294
|
+
const retained = references();
|
|
38295
|
+
for (const prefix of readdirSync3(join17(this.root, "objects"))) {
|
|
38296
|
+
if (!/^[a-f0-9]{2}$/.test(prefix))
|
|
38297
|
+
continue;
|
|
38298
|
+
for (const hash of readdirSync3(join17(this.root, "objects", prefix))) {
|
|
38299
|
+
if (!/^[a-f0-9]{64}$/.test(hash) || !hash.startsWith(prefix))
|
|
38300
|
+
continue;
|
|
38301
|
+
const info = lstatSync3(join17(this.root, "objects", prefix, hash));
|
|
38302
|
+
if (!info.isFile())
|
|
38303
|
+
throw new AttachmentError("CORRUPT", "Unexpected attachment object type");
|
|
38304
|
+
this.db.prepare("INSERT OR IGNORE INTO objects VALUES (?, ?, ?, 0)").run(`sha256:${hash}`, info.mtimeMs, info.mtimeMs);
|
|
38305
|
+
}
|
|
38306
|
+
}
|
|
38307
|
+
const rows = this.db.prepare("SELECT * FROM objects").all();
|
|
38308
|
+
const deleted = [];
|
|
38309
|
+
for (const row of rows) {
|
|
38310
|
+
if (retained.has(row.id)) {
|
|
38311
|
+
this.db.prepare("UPDATE objects SET referenced=1, unreferenced=NULL WHERE id=?").run(row.id);
|
|
38312
|
+
continue;
|
|
38313
|
+
}
|
|
38314
|
+
if (row.unreferenced === null) {
|
|
38315
|
+
this.db.prepare("UPDATE objects SET unreferenced=? WHERE id=?").run(now, row.id);
|
|
38316
|
+
continue;
|
|
38317
|
+
}
|
|
38318
|
+
if (now - row.unreferenced < RETENTION_MS)
|
|
38319
|
+
continue;
|
|
38320
|
+
rmSync(join17(this.root, "variants", row.id.slice(7)), { recursive: true, force: true });
|
|
38321
|
+
rmSync(join17(this.root, "objects", row.id.slice(7, 9), row.id.slice(7)), { force: true });
|
|
38322
|
+
this.db.prepare("DELETE FROM objects WHERE id=?").run(row.id);
|
|
38323
|
+
deleted.push(row.id);
|
|
38324
|
+
}
|
|
38325
|
+
for (const file of readdirSync3(join17(this.root, "tmp"))) {
|
|
38326
|
+
if (/^[a-f0-9-]{36}$/.test(file))
|
|
38327
|
+
rmSync(join17(this.root, "tmp", file), { force: true });
|
|
38328
|
+
}
|
|
38329
|
+
this.db.prepare("INSERT OR REPLACE INTO settings VALUES ('lastCleanup', ?)").run(now);
|
|
38330
|
+
return { deleted, skipped: false };
|
|
38331
|
+
});
|
|
38332
|
+
}
|
|
38333
|
+
get nextCollectionAt() {
|
|
38334
|
+
if (this.closed)
|
|
38335
|
+
throw new AttachmentError("CLOSED", "Attachment store is closed");
|
|
38336
|
+
const row = this.db.prepare("SELECT value FROM settings WHERE key='lastCleanup'").get();
|
|
38337
|
+
return row ? row.value + CLEANUP_INTERVAL_MS : 0;
|
|
38338
|
+
}
|
|
38339
|
+
close() {
|
|
38340
|
+
if (this.closed)
|
|
38341
|
+
return;
|
|
38342
|
+
if (this.active || this.waiting.length || this.operations.size)
|
|
38343
|
+
throw new AttachmentError("IO", "Cannot close while attachment operations are active");
|
|
38344
|
+
for (const id of this.operations)
|
|
38345
|
+
this.db.prepare("DELETE FROM operations WHERE id=?").run(id);
|
|
38346
|
+
this.operations.clear();
|
|
38347
|
+
this.db.close();
|
|
38348
|
+
this.closed = true;
|
|
38349
|
+
}
|
|
38350
|
+
};
|
|
38351
|
+
|
|
38352
|
+
// src/create-attachment-runtime.ts
|
|
38353
|
+
function createAttachmentRuntime(options) {
|
|
38354
|
+
mkdirSync6(options.sessionsRoot, { recursive: true, mode: 448 });
|
|
38355
|
+
const store = new LocalAttachmentStore(options);
|
|
38356
|
+
const now = options.now ?? Date.now;
|
|
38357
|
+
let closed = false;
|
|
38358
|
+
let timer;
|
|
38359
|
+
const collect = (force = false) => store.collectGarbage(
|
|
38360
|
+
() => scanAttachmentReferences(options.sessionsRoot),
|
|
38361
|
+
force
|
|
38362
|
+
);
|
|
38363
|
+
const run = () => {
|
|
38364
|
+
if (closed) return;
|
|
38365
|
+
try {
|
|
38366
|
+
collect();
|
|
38367
|
+
} catch (error) {
|
|
38368
|
+
options.onError(error);
|
|
38369
|
+
} finally {
|
|
38370
|
+
if (!closed) timer = setTimeout(run, CLEANUP_INTERVAL_MS).unref();
|
|
38371
|
+
}
|
|
38372
|
+
};
|
|
38373
|
+
timer = setTimeout(run, Math.max(0, store.nextCollectionAt - now())).unref();
|
|
38374
|
+
return {
|
|
38375
|
+
store,
|
|
38376
|
+
collect,
|
|
38377
|
+
close() {
|
|
38378
|
+
clearTimeout(timer);
|
|
38379
|
+
store.close();
|
|
38380
|
+
closed = true;
|
|
38381
|
+
}
|
|
38382
|
+
};
|
|
38383
|
+
}
|
|
38384
|
+
|
|
37498
38385
|
// src/stdin-prompts.ts
|
|
37499
38386
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
37500
38387
|
var StdinPrompts = class {
|
|
@@ -37513,7 +38400,7 @@ var StdinPrompts = class {
|
|
|
37513
38400
|
if (input.readableEnded || input.destroyed) throw new PromptEofError();
|
|
37514
38401
|
this.#active = true;
|
|
37515
38402
|
const raw = Boolean(secret && input.isTTY && input.setRawMode);
|
|
37516
|
-
return new Promise((
|
|
38403
|
+
return new Promise((resolve15, reject) => {
|
|
37517
38404
|
const decoder = new StringDecoder2("utf8");
|
|
37518
38405
|
let answer = "";
|
|
37519
38406
|
let settled = false;
|
|
@@ -37539,7 +38426,7 @@ var StdinPrompts = class {
|
|
|
37539
38426
|
error ??= outputError;
|
|
37540
38427
|
}
|
|
37541
38428
|
if (error !== void 0) reject(error);
|
|
37542
|
-
else
|
|
38429
|
+
else resolve15(answer);
|
|
37543
38430
|
};
|
|
37544
38431
|
const onData = (data) => {
|
|
37545
38432
|
try {
|
|
@@ -37782,8 +38669,8 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
|
|
|
37782
38669
|
const done = (async () => {
|
|
37783
38670
|
for (; ; ) {
|
|
37784
38671
|
if (queue.length === 0) {
|
|
37785
|
-
await new Promise((
|
|
37786
|
-
wake =
|
|
38672
|
+
await new Promise((resolve15) => {
|
|
38673
|
+
wake = resolve15;
|
|
37787
38674
|
});
|
|
37788
38675
|
wake = null;
|
|
37789
38676
|
}
|
|
@@ -37829,14 +38716,14 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
|
|
|
37829
38716
|
};
|
|
37830
38717
|
}
|
|
37831
38718
|
function delay(ms) {
|
|
37832
|
-
return new Promise((
|
|
37833
|
-
setTimeout(
|
|
38719
|
+
return new Promise((resolve15) => {
|
|
38720
|
+
setTimeout(resolve15, ms);
|
|
37834
38721
|
});
|
|
37835
38722
|
}
|
|
37836
38723
|
async function settlesWithin(promise, ms) {
|
|
37837
38724
|
let timer;
|
|
37838
|
-
const timeout = new Promise((
|
|
37839
|
-
timer = setTimeout(() =>
|
|
38725
|
+
const timeout = new Promise((resolve15) => {
|
|
38726
|
+
timer = setTimeout(() => resolve15(false), ms);
|
|
37840
38727
|
});
|
|
37841
38728
|
const settled = promise.then(
|
|
37842
38729
|
() => true,
|
|
@@ -38005,13 +38892,13 @@ async function runTerminalUi(ui, driver, onSubmit) {
|
|
|
38005
38892
|
|
|
38006
38893
|
// src/update-installation.ts
|
|
38007
38894
|
import { spawn as spawn2 } from "node:child_process";
|
|
38008
|
-
import { lstat as lstat4, readFile as
|
|
38009
|
-
import { basename as
|
|
38895
|
+
import { lstat as lstat4, readFile as readFile7, realpath as realpath4 } from "node:fs/promises";
|
|
38896
|
+
import { basename as basename9, delimiter, dirname as dirname12, isAbsolute as isAbsolute4, join as join19, relative as relative5, resolve as resolve14, sep as sep3, win32 as win324 } from "node:path";
|
|
38010
38897
|
|
|
38011
38898
|
// src/update.ts
|
|
38012
|
-
import { randomUUID as
|
|
38013
|
-
import { open as
|
|
38014
|
-
import { join as
|
|
38899
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
38900
|
+
import { open as open5, readFile as readFile6, realpath as realpath3, unlink as unlink5 } from "node:fs/promises";
|
|
38901
|
+
import { join as join18 } from "node:path";
|
|
38015
38902
|
function compareStableVersions(left, right) {
|
|
38016
38903
|
const parse3 = (value) => {
|
|
38017
38904
|
if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) || value.includes("\n")) {
|
|
@@ -38031,13 +38918,13 @@ function updateDiagnostic(value) {
|
|
|
38031
38918
|
return String(value instanceof Error ? value.message : value).replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\x00-\x1f\x7f-\x9f]/g, " ").replace(/https?:\/\/[^\s]+/gi, "[registry URL]").replace(/npm_[a-zA-Z0-9]+/g, "[redacted]").replace(/bearer\s+[^\s"']+/gi, "[redacted]").replace(/["']?(?:_authToken|_auth|password|token|authorization)["']?\s*[=:]\s*["']?[^\s"',;}]+/gi, "[redacted]").slice(0, 600);
|
|
38032
38919
|
}
|
|
38033
38920
|
var defaultLockIO = {
|
|
38034
|
-
acquire: (path5) =>
|
|
38035
|
-
read: (path5) =>
|
|
38036
|
-
remove: (path5) =>
|
|
38921
|
+
acquire: (path5) => open5(path5, "wx", 384),
|
|
38922
|
+
read: (path5) => readFile6(path5, "utf8"),
|
|
38923
|
+
remove: (path5) => unlink5(path5)
|
|
38037
38924
|
};
|
|
38038
38925
|
async function installedVersion(installation) {
|
|
38039
38926
|
if (await realpath3(installation.packageRoot) !== installation.packageRoot) throw new Error("Installation target changed");
|
|
38040
|
-
const value = JSON.parse(await
|
|
38927
|
+
const value = JSON.parse(await readFile6(join18(installation.packageRoot, "package.json"), "utf8"));
|
|
38041
38928
|
if (typeof value !== "object" || value === null || !("name" in value) || value.name !== "laohuang" || !("version" in value) || typeof value.version !== "string") {
|
|
38042
38929
|
throw new Error("Installed laohuang package is missing or invalid");
|
|
38043
38930
|
}
|
|
@@ -38045,7 +38932,7 @@ async function installedVersion(installation) {
|
|
|
38045
38932
|
return value.version;
|
|
38046
38933
|
}
|
|
38047
38934
|
async function runUpdate({ installation, output, signal, lockIO = defaultLockIO }) {
|
|
38048
|
-
const token =
|
|
38935
|
+
const token = randomUUID17();
|
|
38049
38936
|
let ownsLock = false;
|
|
38050
38937
|
let installing = false;
|
|
38051
38938
|
const cancelled = () => {
|
|
@@ -38128,7 +39015,7 @@ function object(value) {
|
|
|
38128
39015
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
38129
39016
|
}
|
|
38130
39017
|
async function json(path5) {
|
|
38131
|
-
return object(JSON.parse(await
|
|
39018
|
+
return object(JSON.parse(await readFile7(path5, "utf8")));
|
|
38132
39019
|
}
|
|
38133
39020
|
async function exists(path5) {
|
|
38134
39021
|
try {
|
|
@@ -38237,17 +39124,17 @@ function createNpmRunner(cliPath, environ, options = {}) {
|
|
|
38237
39124
|
};
|
|
38238
39125
|
}
|
|
38239
39126
|
async function npmCandidates(environ) {
|
|
38240
|
-
const paths = [environ.npm_execpath,
|
|
39127
|
+
const paths = [environ.npm_execpath, join19(dirname12(process.execPath), "node_modules/npm/bin/npm-cli.js"), join19(dirname12(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js")];
|
|
38241
39128
|
for (const directory of (environ.PATH ?? environ.Path ?? "").split(delimiter).filter(Boolean)) {
|
|
38242
|
-
paths.push(
|
|
39129
|
+
paths.push(join19(directory, "npm"), join19(directory, "node_modules/npm/bin/npm-cli.js"));
|
|
38243
39130
|
}
|
|
38244
39131
|
const result = /* @__PURE__ */ new Set();
|
|
38245
39132
|
for (const path5 of paths) {
|
|
38246
39133
|
if (!path5) continue;
|
|
38247
39134
|
try {
|
|
38248
39135
|
const actual = await realpath4(path5);
|
|
38249
|
-
if (
|
|
38250
|
-
const manifest = await json(
|
|
39136
|
+
if (basename9(actual) !== "npm-cli.js") continue;
|
|
39137
|
+
const manifest = await json(join19(dirname12(actual), "../package.json"));
|
|
38251
39138
|
if (manifest.name === "npm") result.add(actual);
|
|
38252
39139
|
} catch {
|
|
38253
39140
|
}
|
|
@@ -38264,8 +39151,8 @@ async function resolveNpmInstallation(entryPath, environ, options = {}) {
|
|
|
38264
39151
|
let packageRoot = dirname12(entry);
|
|
38265
39152
|
let manifest = {};
|
|
38266
39153
|
while (true) {
|
|
38267
|
-
if (await exists(
|
|
38268
|
-
manifest = await json(
|
|
39154
|
+
if (await exists(join19(packageRoot, "package.json"))) {
|
|
39155
|
+
manifest = await json(join19(packageRoot, "package.json"));
|
|
38269
39156
|
break;
|
|
38270
39157
|
}
|
|
38271
39158
|
const parent = dirname12(packageRoot);
|
|
@@ -38275,8 +39162,8 @@ async function resolveNpmInstallation(entryPath, environ, options = {}) {
|
|
|
38275
39162
|
if (manifest.name !== "laohuang" || typeof manifest.version !== "string") throw new Error("Entry does not belong to laohuang");
|
|
38276
39163
|
compareStableVersions(manifest.version, manifest.version);
|
|
38277
39164
|
const bin = typeof manifest.bin === "string" ? manifest.bin : object(manifest.bin).laohuang;
|
|
38278
|
-
if (typeof bin !== "string" || await realpath4(
|
|
38279
|
-
if (
|
|
39165
|
+
if (typeof bin !== "string" || await realpath4(resolve14(packageRoot, bin)) !== entry) throw new Error("Entry does not match the installed laohuang executable");
|
|
39166
|
+
if (basename9(dirname12(packageRoot)) !== "node_modules" || packageRoot.split(sep3).some((part) => ["_npx", ".pnpm", ".yarn"].includes(part))) throw new Error("Update requires a direct npm installation; source, linked, cache and other package-manager installations are unsupported");
|
|
38280
39167
|
const candidates = options.cliPaths ?? await npmCandidates(environ);
|
|
38281
39168
|
const runner = options.createRunner ?? createNpmRunner;
|
|
38282
39169
|
if (candidates.length === 0) throw new Error("Cannot locate npm-cli.js");
|
|
@@ -38294,7 +39181,7 @@ async function resolveNpmInstallation(entryPath, environ, options = {}) {
|
|
|
38294
39181
|
if (checked.code !== 0 || await realpath4(checked.stdout.trim()) !== dirname12(packageRoot)) continue;
|
|
38295
39182
|
globals.push({ packageRoot, installedVersion: manifest.version, npm: {
|
|
38296
39183
|
run: (args, runOptions) => npm.run(args[0] === "view" ? [...args, "--global", "--prefix", prefix] : args, runOptions)
|
|
38297
|
-
}, workingDirectory: prefix, installArgs: ["--global", "--prefix", prefix], lockPath:
|
|
39184
|
+
}, workingDirectory: prefix, installArgs: ["--global", "--prefix", prefix], lockPath: join19(dirname12(packageRoot), ".laohuang-update.lock") });
|
|
38298
39185
|
} catch {
|
|
38299
39186
|
options.signal?.throwIfAborted();
|
|
38300
39187
|
}
|
|
@@ -38306,12 +39193,12 @@ async function resolveNpmInstallation(entryPath, environ, options = {}) {
|
|
|
38306
39193
|
const owners = [];
|
|
38307
39194
|
for (let root = dirname12(dirname12(packageRoot)); ; root = dirname12(root)) {
|
|
38308
39195
|
options.signal?.throwIfAborted();
|
|
38309
|
-
const lockPath =
|
|
39196
|
+
const lockPath = join19(root, "package-lock.json");
|
|
38310
39197
|
if (await exists(lockPath)) {
|
|
38311
39198
|
for (const other of ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb", "npm-shrinkwrap.json"]) {
|
|
38312
|
-
if (await exists(
|
|
39199
|
+
if (await exists(join19(root, other))) throw new Error("Ambiguous or unsupported package manager ownership");
|
|
38313
39200
|
}
|
|
38314
|
-
const rootManifest = await json(
|
|
39201
|
+
const rootManifest = await json(join19(root, "package.json"));
|
|
38315
39202
|
if (typeof rootManifest.packageManager === "string" && !rootManifest.packageManager.startsWith("npm@")) throw new Error("Installation belongs to another package manager");
|
|
38316
39203
|
const lock = await json(lockPath);
|
|
38317
39204
|
const packages = object(lock.packages);
|
|
@@ -38330,27 +39217,27 @@ async function resolveNpmInstallation(entryPath, environ, options = {}) {
|
|
|
38330
39217
|
if (!Array.isArray(workspaces)) throw new Error("npm returned invalid workspace metadata");
|
|
38331
39218
|
for (const value of workspaces) {
|
|
38332
39219
|
const workspace = object(value);
|
|
38333
|
-
if (typeof workspace.location !== "string" || typeof workspace.realpath !== "string" || !inside2(root,
|
|
38334
|
-
if (await realpath4(
|
|
39220
|
+
if (typeof workspace.location !== "string" || typeof workspace.realpath !== "string" || !inside2(root, resolve14(root, workspace.location))) throw new Error("npm returned invalid workspace ownership");
|
|
39221
|
+
if (await realpath4(resolve14(root, workspace.location)) !== workspace.realpath) throw new Error("npm workspace target changed");
|
|
38335
39222
|
workspacePaths.add(workspace.location);
|
|
38336
39223
|
}
|
|
38337
39224
|
}
|
|
38338
39225
|
for (const [key, metadata2] of Object.entries(packages)) {
|
|
38339
|
-
if (key.includes("node_modules") || !inside2(root,
|
|
39226
|
+
if (key.includes("node_modules") || !inside2(root, resolve14(root, key))) continue;
|
|
38340
39227
|
if (key !== "") {
|
|
38341
39228
|
if (!workspacePaths.has(key)) continue;
|
|
38342
39229
|
if (!Object.values(packages).some((value) => object(value).link === true && object(value).resolved === key)) continue;
|
|
38343
39230
|
}
|
|
38344
|
-
const owner = await realpath4(
|
|
39231
|
+
const owner = await realpath4(resolve14(root, key));
|
|
38345
39232
|
if (!inside2(root, owner)) continue;
|
|
38346
|
-
const declaration = await json(
|
|
39233
|
+
const declaration = await json(join19(owner, "package.json"));
|
|
38347
39234
|
for (const bucket of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
38348
39235
|
const range = object(declaration[bucket]).laohuang;
|
|
38349
39236
|
if (typeof range !== "string") continue;
|
|
38350
39237
|
let resolvedPackage;
|
|
38351
39238
|
for (let directory = owner; inside2(root, directory); directory = dirname12(directory)) {
|
|
38352
|
-
if (await exists(
|
|
38353
|
-
resolvedPackage = await realpath4(
|
|
39239
|
+
if (await exists(join19(directory, "node_modules/laohuang"))) {
|
|
39240
|
+
resolvedPackage = await realpath4(join19(directory, "node_modules/laohuang"));
|
|
38354
39241
|
break;
|
|
38355
39242
|
}
|
|
38356
39243
|
if (directory === root) break;
|
|
@@ -38367,7 +39254,7 @@ async function resolveNpmInstallation(entryPath, environ, options = {}) {
|
|
|
38367
39254
|
},
|
|
38368
39255
|
workingDirectory: root,
|
|
38369
39256
|
installArgs: ["--global=false", "--prefix", root, "--package-lock=true", "--save=true", "--save-peer=false", "--save-dev=false", "--save-optional=false", "--save-prod=false", bucket === "devDependencies" ? "--save-dev" : bucket === "optionalDependencies" ? "--save-optional" : "--save-prod", ...bucket === "devDependencies" ? ["--include=dev"] : [], .../^[~^]/.test(range) ? ["--save-exact=false", `--save-prefix=${range[0]}`] : ["--save-exact"], ...key ? ["--workspace", key, "--include-workspace-root=false"] : ["--workspaces=false"]],
|
|
38370
|
-
lockPath:
|
|
39257
|
+
lockPath: join19(root, ".laohuang-update.lock")
|
|
38371
39258
|
});
|
|
38372
39259
|
}
|
|
38373
39260
|
}
|
|
@@ -38397,7 +39284,7 @@ function safeDiagnostic(value) {
|
|
|
38397
39284
|
}
|
|
38398
39285
|
function createClipboardRunner(environ, timeoutMs = 5e3) {
|
|
38399
39286
|
const env = { ...environ };
|
|
38400
|
-
return (command, args, text3) => new Promise((
|
|
39287
|
+
return (command, args, text3) => new Promise((resolve15) => {
|
|
38401
39288
|
let child;
|
|
38402
39289
|
try {
|
|
38403
39290
|
child = spawn3(command, [...args], {
|
|
@@ -38407,7 +39294,7 @@ function createClipboardRunner(environ, timeoutMs = 5e3) {
|
|
|
38407
39294
|
stdio: ["pipe", "ignore", "pipe"]
|
|
38408
39295
|
});
|
|
38409
39296
|
} catch (error) {
|
|
38410
|
-
|
|
39297
|
+
resolve15({ code: 1, stderr: errorMessage12(error) });
|
|
38411
39298
|
return;
|
|
38412
39299
|
}
|
|
38413
39300
|
let stderr = "";
|
|
@@ -38417,7 +39304,7 @@ function createClipboardRunner(environ, timeoutMs = 5e3) {
|
|
|
38417
39304
|
if (settled) return;
|
|
38418
39305
|
settled = true;
|
|
38419
39306
|
if (timer) clearTimeout(timer);
|
|
38420
|
-
|
|
39307
|
+
resolve15({ code, stderr: diagnostic });
|
|
38421
39308
|
};
|
|
38422
39309
|
const stop = (diagnostic) => {
|
|
38423
39310
|
try {
|
|
@@ -38612,17 +39499,19 @@ async function main(argv, options = {}) {
|
|
|
38612
39499
|
const configPath = options.configPath ?? defaultConfigPath(environ);
|
|
38613
39500
|
const manager = new ConfigManager(configPath);
|
|
38614
39501
|
const credentials = new CredentialStore(
|
|
38615
|
-
options.credentialsPath ??
|
|
39502
|
+
options.credentialsPath ?? join20(dirname13(configPath), "credentials.json")
|
|
38616
39503
|
);
|
|
38617
39504
|
const modelCatalogStore = new ModelCatalogStore(
|
|
38618
|
-
options.modelsPath ??
|
|
39505
|
+
options.modelsPath ?? join20(dirname13(configPath), "models.json")
|
|
38619
39506
|
);
|
|
38620
39507
|
const customModels = new CustomModelsStore(
|
|
38621
|
-
options.customModelsPath ??
|
|
39508
|
+
options.customModelsPath ?? join20(dirname13(configPath), "custom-models.json")
|
|
38622
39509
|
);
|
|
38623
39510
|
let modelPlatform;
|
|
39511
|
+
let attachmentRuntime;
|
|
38624
39512
|
try {
|
|
38625
39513
|
modelPlatform = await createPiAiPlatform({
|
|
39514
|
+
attachments: { store: () => attachmentRuntime?.store },
|
|
38626
39515
|
credentials,
|
|
38627
39516
|
modelCatalogStore,
|
|
38628
39517
|
excludedProviderIds: EXCLUDED_PROVIDER_IDS,
|
|
@@ -38751,7 +39640,7 @@ async function main(argv, options = {}) {
|
|
|
38751
39640
|
let shellPath;
|
|
38752
39641
|
try {
|
|
38753
39642
|
shellPath = manager.getShellPath();
|
|
38754
|
-
if (
|
|
39643
|
+
if (existsSync9(configPath)) {
|
|
38755
39644
|
config = manager.resolve({
|
|
38756
39645
|
environ,
|
|
38757
39646
|
profile: args.profile,
|
|
@@ -38796,7 +39685,13 @@ async function main(argv, options = {}) {
|
|
|
38796
39685
|
let terminalUi = null;
|
|
38797
39686
|
let terminalDriver = null;
|
|
38798
39687
|
let selectedModel = modelPlatform.catalog.getModel(config.provider, config.model);
|
|
39688
|
+
attachmentRuntime = createAttachmentRuntime({
|
|
39689
|
+
root: join20(getHomeDirectory({ env: environ }), ".laohuang", "attachments"),
|
|
39690
|
+
sessionsRoot: defaultSessionsRoot(environ),
|
|
39691
|
+
onError: (error) => writeStderr(`Attachment cleanup failed: ${errorMessage11(error)}`)
|
|
39692
|
+
});
|
|
38799
39693
|
const sessionController = new SessionController({
|
|
39694
|
+
attachments: attachmentRuntime.store,
|
|
38800
39695
|
sessionsRoot: defaultSessionsRoot(environ),
|
|
38801
39696
|
projectRoot,
|
|
38802
39697
|
initialCwd: process.cwd(),
|
|
@@ -38849,6 +39744,18 @@ async function main(argv, options = {}) {
|
|
|
38849
39744
|
}
|
|
38850
39745
|
});
|
|
38851
39746
|
const toolRegistry = new ToolRegistry([
|
|
39747
|
+
createReadImageTool({
|
|
39748
|
+
store: attachmentRuntime.store,
|
|
39749
|
+
projectRoot,
|
|
39750
|
+
pathOptions: { env: environ, shellPath: () => resolveBashPath({ shellPath, env: environ }) },
|
|
39751
|
+
resolveReference: (id) => {
|
|
39752
|
+
for (const entry of sessionController.history?.entries() ?? []) {
|
|
39753
|
+
if (!("message" in entry.payload) || !("attachments" in entry.payload.message)) continue;
|
|
39754
|
+
for (const block of entry.payload.message.attachments ?? []) if (block.type === "image" && block.ref.id === id) return block.ref;
|
|
39755
|
+
}
|
|
39756
|
+
return void 0;
|
|
39757
|
+
}
|
|
39758
|
+
}),
|
|
38852
39759
|
skillRuntime.session.tool,
|
|
38853
39760
|
...createFileToolDefinitions({ projectRoot, pathOptions: {
|
|
38854
39761
|
env: environ,
|
|
@@ -38859,6 +39766,7 @@ async function main(argv, options = {}) {
|
|
|
38859
39766
|
let commandDispatcher;
|
|
38860
39767
|
let runtimeInitialized = false;
|
|
38861
39768
|
const composedRuntime = createSessionRuntime({
|
|
39769
|
+
attachments: attachmentRuntime.store,
|
|
38862
39770
|
modelAdapter: modelPlatform.adapter,
|
|
38863
39771
|
catalog: modelPlatform.catalog,
|
|
38864
39772
|
route: { provider: config.provider, model: config.model, baseUrl: config.baseUrl },
|
|
@@ -39087,15 +39995,19 @@ Submit an empty line to retry, or enter a replacement.`);
|
|
|
39087
39995
|
try {
|
|
39088
39996
|
await skillRuntime?.close();
|
|
39089
39997
|
} finally {
|
|
39090
|
-
|
|
39091
|
-
|
|
39998
|
+
try {
|
|
39999
|
+
if (composedForCleanup) await composedForCleanup.close();
|
|
40000
|
+
else await sessionController.close();
|
|
40001
|
+
} finally {
|
|
40002
|
+
attachmentRuntime.close();
|
|
40003
|
+
}
|
|
39092
40004
|
}
|
|
39093
40005
|
}
|
|
39094
40006
|
}
|
|
39095
40007
|
}
|
|
39096
40008
|
}
|
|
39097
40009
|
function defaultSessionsRoot(environ) {
|
|
39098
|
-
return
|
|
40010
|
+
return join20(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
|
|
39099
40011
|
}
|
|
39100
40012
|
async function runInitialModelSelection(options) {
|
|
39101
40013
|
let providerName = options.providerName;
|