@letta-ai/letta-code 0.23.6 → 0.23.7
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/letta.js +636 -34
- package/package.json +1 -1
package/letta.js
CHANGED
|
@@ -3269,7 +3269,7 @@ var package_default;
|
|
|
3269
3269
|
var init_package = __esm(() => {
|
|
3270
3270
|
package_default = {
|
|
3271
3271
|
name: "@letta-ai/letta-code",
|
|
3272
|
-
version: "0.23.
|
|
3272
|
+
version: "0.23.7",
|
|
3273
3273
|
description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
3274
3274
|
type: "module",
|
|
3275
3275
|
bin: {
|
|
@@ -40138,6 +40138,357 @@ var init_types = __esm(() => {
|
|
|
40138
40138
|
SUPPORTED_CHANNEL_IDS = ["telegram", "slack"];
|
|
40139
40139
|
});
|
|
40140
40140
|
|
|
40141
|
+
// src/channels/interactive.ts
|
|
40142
|
+
function normalizeWhitespace(text) {
|
|
40143
|
+
return text.replace(/\s+/g, " ").trim();
|
|
40144
|
+
}
|
|
40145
|
+
function isAffirmativeResponse(text) {
|
|
40146
|
+
const normalized = normalizeWhitespace(text).toLowerCase();
|
|
40147
|
+
return [
|
|
40148
|
+
"approve",
|
|
40149
|
+
"approved",
|
|
40150
|
+
"allow",
|
|
40151
|
+
"yes",
|
|
40152
|
+
"y",
|
|
40153
|
+
"ok",
|
|
40154
|
+
"okay",
|
|
40155
|
+
"continue",
|
|
40156
|
+
"go ahead",
|
|
40157
|
+
"looks good",
|
|
40158
|
+
"lgtm",
|
|
40159
|
+
"sgtm",
|
|
40160
|
+
"ship it"
|
|
40161
|
+
].includes(normalized);
|
|
40162
|
+
}
|
|
40163
|
+
function isNegativeResponse(text) {
|
|
40164
|
+
const normalized = normalizeWhitespace(text).toLowerCase();
|
|
40165
|
+
return [
|
|
40166
|
+
"deny",
|
|
40167
|
+
"denied",
|
|
40168
|
+
"reject",
|
|
40169
|
+
"rejected",
|
|
40170
|
+
"no",
|
|
40171
|
+
"n",
|
|
40172
|
+
"cancel",
|
|
40173
|
+
"skip",
|
|
40174
|
+
"keep planning"
|
|
40175
|
+
].includes(normalized);
|
|
40176
|
+
}
|
|
40177
|
+
function stripApprovalPrefix(text) {
|
|
40178
|
+
return normalizeWhitespace(text.replace(/^(approve|allow|yes|y|ok|okay|deny|reject|no|n)\s*[:-]?\s*/i, ""));
|
|
40179
|
+
}
|
|
40180
|
+
function summarizeControlRequestInput(input) {
|
|
40181
|
+
const serialized = JSON.stringify(input, null, 2);
|
|
40182
|
+
if (!serialized || serialized === "{}") {
|
|
40183
|
+
return null;
|
|
40184
|
+
}
|
|
40185
|
+
if (serialized.length <= 1200) {
|
|
40186
|
+
return serialized;
|
|
40187
|
+
}
|
|
40188
|
+
return `${serialized.slice(0, 1197).trimEnd()}...`;
|
|
40189
|
+
}
|
|
40190
|
+
function summarizePlanPreview(planContent) {
|
|
40191
|
+
const normalized = planContent.trim();
|
|
40192
|
+
if (!normalized) {
|
|
40193
|
+
return "";
|
|
40194
|
+
}
|
|
40195
|
+
const maxLength = 1800;
|
|
40196
|
+
if (normalized.length <= maxLength) {
|
|
40197
|
+
return normalized;
|
|
40198
|
+
}
|
|
40199
|
+
return `${normalized.slice(0, maxLength).trimEnd()}
|
|
40200
|
+
|
|
40201
|
+
[Plan preview truncated for channel delivery.]`;
|
|
40202
|
+
}
|
|
40203
|
+
function buildQuestionPrompt(question, index) {
|
|
40204
|
+
const lines = [
|
|
40205
|
+
`${index + 1}. ${question.question ?? `Question ${index + 1}`}`
|
|
40206
|
+
];
|
|
40207
|
+
const options = question.options ?? [];
|
|
40208
|
+
options.forEach((option, optionIndex) => {
|
|
40209
|
+
const label = option.label?.trim() || `Option ${optionIndex + 1}`;
|
|
40210
|
+
const description = option.description?.trim();
|
|
40211
|
+
lines.push(description ? ` ${optionIndex + 1}) ${label} — ${description}` : ` ${optionIndex + 1}) ${label}`);
|
|
40212
|
+
});
|
|
40213
|
+
if (question.multiSelect) {
|
|
40214
|
+
lines.push(" Choose one or more options. Separate multiple answers with commas.");
|
|
40215
|
+
}
|
|
40216
|
+
return lines;
|
|
40217
|
+
}
|
|
40218
|
+
function matchQuestionOption(question, text) {
|
|
40219
|
+
const trimmed = normalizeWhitespace(text);
|
|
40220
|
+
const options = question.options ?? [];
|
|
40221
|
+
if (!trimmed || options.length === 0) {
|
|
40222
|
+
return trimmed;
|
|
40223
|
+
}
|
|
40224
|
+
const numberMatch = trimmed.match(/^(\d+)$/);
|
|
40225
|
+
if (numberMatch?.[1]) {
|
|
40226
|
+
const option = options[Number(numberMatch[1]) - 1];
|
|
40227
|
+
if (option?.label?.trim()) {
|
|
40228
|
+
return option.label.trim();
|
|
40229
|
+
}
|
|
40230
|
+
}
|
|
40231
|
+
const exactLabel = options.find((option) => option.label && normalizeWhitespace(option.label).toLowerCase() === trimmed.toLowerCase());
|
|
40232
|
+
if (exactLabel?.label?.trim()) {
|
|
40233
|
+
return exactLabel.label.trim();
|
|
40234
|
+
}
|
|
40235
|
+
return trimmed;
|
|
40236
|
+
}
|
|
40237
|
+
function matchQuestionAnswer(question, text) {
|
|
40238
|
+
if (!question.multiSelect) {
|
|
40239
|
+
return matchQuestionOption(question, text);
|
|
40240
|
+
}
|
|
40241
|
+
const normalized = normalizeWhitespace(text);
|
|
40242
|
+
if (!normalized) {
|
|
40243
|
+
return normalized;
|
|
40244
|
+
}
|
|
40245
|
+
const selections = normalized.replace(/\band\b/gi, ",").split(/\s*(?:,|\/|;)\s*/).map((entry) => normalizeWhitespace(entry)).filter(Boolean);
|
|
40246
|
+
if (selections.length <= 1) {
|
|
40247
|
+
return matchQuestionOption(question, normalized);
|
|
40248
|
+
}
|
|
40249
|
+
const matchedSelections = Array.from(new Set(selections.map((selection) => matchQuestionOption(question, selection)).filter(Boolean)));
|
|
40250
|
+
return matchedSelections.length > 0 ? matchedSelections.join(", ") : normalized;
|
|
40251
|
+
}
|
|
40252
|
+
function parseNumberedAnswers(rawText, questions) {
|
|
40253
|
+
const matches = Array.from(rawText.matchAll(/(?:^|\n)\s*(\d+)[).:-]\s*(.+?)(?=(?:\n\s*\d+[).:-]\s*)|$)/gs));
|
|
40254
|
+
if (matches.length === 0) {
|
|
40255
|
+
return null;
|
|
40256
|
+
}
|
|
40257
|
+
const answers = {};
|
|
40258
|
+
for (const match of matches) {
|
|
40259
|
+
const questionIndex = Number(match[1]) - 1;
|
|
40260
|
+
const question = questions[questionIndex];
|
|
40261
|
+
const answerText = match[2]?.trim();
|
|
40262
|
+
if (!question?.question || !answerText) {
|
|
40263
|
+
continue;
|
|
40264
|
+
}
|
|
40265
|
+
answers[question.question] = matchQuestionAnswer(question, answerText);
|
|
40266
|
+
}
|
|
40267
|
+
return Object.keys(answers).length > 0 ? answers : null;
|
|
40268
|
+
}
|
|
40269
|
+
function buildAllowResponse(requestId, decision) {
|
|
40270
|
+
return {
|
|
40271
|
+
request_id: requestId,
|
|
40272
|
+
decision
|
|
40273
|
+
};
|
|
40274
|
+
}
|
|
40275
|
+
function buildDenyResponse(requestId, message) {
|
|
40276
|
+
return {
|
|
40277
|
+
request_id: requestId,
|
|
40278
|
+
decision: {
|
|
40279
|
+
behavior: "deny",
|
|
40280
|
+
message
|
|
40281
|
+
}
|
|
40282
|
+
};
|
|
40283
|
+
}
|
|
40284
|
+
function getAskUserQuestionInput(input) {
|
|
40285
|
+
return input;
|
|
40286
|
+
}
|
|
40287
|
+
function formatAskUserQuestionPrompt(event) {
|
|
40288
|
+
const input = getAskUserQuestionInput(event.input);
|
|
40289
|
+
const questions = (input.questions ?? []).filter((question) => normalizeWhitespace(question.question ?? ""));
|
|
40290
|
+
const lines = [
|
|
40291
|
+
"The agent needs an answer before it can continue.",
|
|
40292
|
+
"",
|
|
40293
|
+
...questions.flatMap((question, index) => buildQuestionPrompt(question, index)),
|
|
40294
|
+
""
|
|
40295
|
+
];
|
|
40296
|
+
if (questions.length <= 1) {
|
|
40297
|
+
const singleQuestion = questions[0];
|
|
40298
|
+
lines.push(singleQuestion?.multiSelect ? "Reply with one or more option numbers/labels separated by commas, or just send a freeform answer in your next message." : "Reply with an option number/label, or just send a freeform answer in your next message.");
|
|
40299
|
+
} else {
|
|
40300
|
+
lines.push("Reply with numbered lines, for example:", "1: your answer", "2: your answer", "", "You can also use option numbers or option labels. For multi-select questions, separate multiple answers with commas.");
|
|
40301
|
+
}
|
|
40302
|
+
return lines.join(`
|
|
40303
|
+
`);
|
|
40304
|
+
}
|
|
40305
|
+
function formatEnterPlanModePrompt() {
|
|
40306
|
+
return [
|
|
40307
|
+
"The agent wants to enter plan mode before making changes.",
|
|
40308
|
+
"",
|
|
40309
|
+
"Reply `approve` to let it plan first, or reply `deny` to skip planning and continue normally."
|
|
40310
|
+
].join(`
|
|
40311
|
+
`);
|
|
40312
|
+
}
|
|
40313
|
+
function formatExitPlanModePrompt(event) {
|
|
40314
|
+
const lines = [
|
|
40315
|
+
"The agent is ready to leave plan mode and start implementing."
|
|
40316
|
+
];
|
|
40317
|
+
if (event.planContent?.trim()) {
|
|
40318
|
+
lines.push("", "Proposed plan:", summarizePlanPreview(event.planContent));
|
|
40319
|
+
if (event.planFilePath?.trim()) {
|
|
40320
|
+
lines.push("", `Plan file: ${event.planFilePath.trim()}`);
|
|
40321
|
+
}
|
|
40322
|
+
}
|
|
40323
|
+
lines.push("", "Reply `approve` to accept the plan and start coding.", "Reply with feedback instead if you want the agent to keep planning.");
|
|
40324
|
+
return lines.join(`
|
|
40325
|
+
`);
|
|
40326
|
+
}
|
|
40327
|
+
function formatGenericToolApprovalPrompt(event) {
|
|
40328
|
+
const inputSummary = summarizeControlRequestInput(event.input);
|
|
40329
|
+
const lines = [`The agent wants approval to run \`${event.toolName}\`.`];
|
|
40330
|
+
if (inputSummary) {
|
|
40331
|
+
lines.push("", "Tool input:", inputSummary);
|
|
40332
|
+
}
|
|
40333
|
+
lines.push("", "Reply `approve` to allow it.", "Reply with feedback instead if you want to deny it.");
|
|
40334
|
+
return lines.join(`
|
|
40335
|
+
`);
|
|
40336
|
+
}
|
|
40337
|
+
function formatChannelControlRequestPrompt(event) {
|
|
40338
|
+
switch (event.kind) {
|
|
40339
|
+
case "ask_user_question":
|
|
40340
|
+
return formatAskUserQuestionPrompt(event);
|
|
40341
|
+
case "enter_plan_mode":
|
|
40342
|
+
return formatEnterPlanModePrompt();
|
|
40343
|
+
case "exit_plan_mode":
|
|
40344
|
+
return formatExitPlanModePrompt(event);
|
|
40345
|
+
case "generic_tool_approval":
|
|
40346
|
+
return formatGenericToolApprovalPrompt(event);
|
|
40347
|
+
default: {
|
|
40348
|
+
const exhaustiveCheck = event.kind;
|
|
40349
|
+
return exhaustiveCheck;
|
|
40350
|
+
}
|
|
40351
|
+
}
|
|
40352
|
+
}
|
|
40353
|
+
function parseAskUserQuestionResponse(event, rawText) {
|
|
40354
|
+
const input = getAskUserQuestionInput(event.input);
|
|
40355
|
+
const questions = (input.questions ?? []).filter((question) => normalizeWhitespace(question.question ?? ""));
|
|
40356
|
+
if (questions.length === 0) {
|
|
40357
|
+
return {
|
|
40358
|
+
type: "reprompt",
|
|
40359
|
+
message: "I couldn't find the original question payload. Please ask the agent to try again."
|
|
40360
|
+
};
|
|
40361
|
+
}
|
|
40362
|
+
if (questions.length === 1) {
|
|
40363
|
+
const [question] = questions;
|
|
40364
|
+
if (!question?.question) {
|
|
40365
|
+
return {
|
|
40366
|
+
type: "reprompt",
|
|
40367
|
+
message: "I couldn't find the original question text. Please ask the agent to try again."
|
|
40368
|
+
};
|
|
40369
|
+
}
|
|
40370
|
+
const answer = matchQuestionAnswer(question, rawText);
|
|
40371
|
+
return {
|
|
40372
|
+
type: "response",
|
|
40373
|
+
response: buildAllowResponse(event.requestId, {
|
|
40374
|
+
behavior: "allow",
|
|
40375
|
+
updated_input: {
|
|
40376
|
+
...event.input,
|
|
40377
|
+
answers: {
|
|
40378
|
+
...input.answers ?? {},
|
|
40379
|
+
[question.question]: answer
|
|
40380
|
+
}
|
|
40381
|
+
}
|
|
40382
|
+
})
|
|
40383
|
+
};
|
|
40384
|
+
}
|
|
40385
|
+
const numberedAnswers = parseNumberedAnswers(rawText, questions);
|
|
40386
|
+
if (!numberedAnswers) {
|
|
40387
|
+
return {
|
|
40388
|
+
type: "reprompt",
|
|
40389
|
+
message: `Please answer with numbered lines so I can map each reply to the right question.
|
|
40390
|
+
Example:
|
|
40391
|
+
1: your answer
|
|
40392
|
+
2: your answer`
|
|
40393
|
+
};
|
|
40394
|
+
}
|
|
40395
|
+
const missingQuestions = questions.filter((question) => question.question && !Object.hasOwn(numberedAnswers, question.question));
|
|
40396
|
+
if (missingQuestions.length > 0) {
|
|
40397
|
+
return {
|
|
40398
|
+
type: "reprompt",
|
|
40399
|
+
message: `I still need answers for: ${missingQuestions.map((question) => question.question).join(", ")}`
|
|
40400
|
+
};
|
|
40401
|
+
}
|
|
40402
|
+
return {
|
|
40403
|
+
type: "response",
|
|
40404
|
+
response: buildAllowResponse(event.requestId, {
|
|
40405
|
+
behavior: "allow",
|
|
40406
|
+
updated_input: {
|
|
40407
|
+
...event.input,
|
|
40408
|
+
answers: {
|
|
40409
|
+
...input.answers ?? {},
|
|
40410
|
+
...numberedAnswers
|
|
40411
|
+
}
|
|
40412
|
+
}
|
|
40413
|
+
})
|
|
40414
|
+
};
|
|
40415
|
+
}
|
|
40416
|
+
function parseEnterPlanModeResponse(event, rawText) {
|
|
40417
|
+
if (isAffirmativeResponse(rawText)) {
|
|
40418
|
+
return {
|
|
40419
|
+
type: "response",
|
|
40420
|
+
response: buildAllowResponse(event.requestId, {
|
|
40421
|
+
behavior: "allow"
|
|
40422
|
+
})
|
|
40423
|
+
};
|
|
40424
|
+
}
|
|
40425
|
+
if (isNegativeResponse(rawText)) {
|
|
40426
|
+
return {
|
|
40427
|
+
type: "response",
|
|
40428
|
+
response: buildDenyResponse(event.requestId, "User chose to skip plan mode and continue implementing directly.")
|
|
40429
|
+
};
|
|
40430
|
+
}
|
|
40431
|
+
return {
|
|
40432
|
+
type: "reprompt",
|
|
40433
|
+
message: "Reply `approve` to let the agent enter plan mode, or `deny` to skip planning."
|
|
40434
|
+
};
|
|
40435
|
+
}
|
|
40436
|
+
function parseExitPlanModeResponse(event, rawText) {
|
|
40437
|
+
if (isAffirmativeResponse(rawText)) {
|
|
40438
|
+
return {
|
|
40439
|
+
type: "response",
|
|
40440
|
+
response: buildAllowResponse(event.requestId, {
|
|
40441
|
+
behavior: "allow"
|
|
40442
|
+
})
|
|
40443
|
+
};
|
|
40444
|
+
}
|
|
40445
|
+
const feedback = stripApprovalPrefix(rawText);
|
|
40446
|
+
return {
|
|
40447
|
+
type: "response",
|
|
40448
|
+
response: buildDenyResponse(event.requestId, feedback || "Please keep planning and revise the proposal.")
|
|
40449
|
+
};
|
|
40450
|
+
}
|
|
40451
|
+
function parseGenericToolApprovalResponse(event, rawText) {
|
|
40452
|
+
if (isAffirmativeResponse(rawText)) {
|
|
40453
|
+
const message = stripApprovalPrefix(rawText);
|
|
40454
|
+
return {
|
|
40455
|
+
type: "response",
|
|
40456
|
+
response: buildAllowResponse(event.requestId, {
|
|
40457
|
+
behavior: "allow",
|
|
40458
|
+
...message ? { message } : {}
|
|
40459
|
+
})
|
|
40460
|
+
};
|
|
40461
|
+
}
|
|
40462
|
+
const feedback = stripApprovalPrefix(rawText);
|
|
40463
|
+
return {
|
|
40464
|
+
type: "response",
|
|
40465
|
+
response: buildDenyResponse(event.requestId, feedback || "Denied by channel user.")
|
|
40466
|
+
};
|
|
40467
|
+
}
|
|
40468
|
+
function parseChannelControlRequestResponse(event, rawText) {
|
|
40469
|
+
const trimmed = rawText.trim();
|
|
40470
|
+
if (!trimmed) {
|
|
40471
|
+
return {
|
|
40472
|
+
type: "reprompt",
|
|
40473
|
+
message: formatChannelControlRequestPrompt(event)
|
|
40474
|
+
};
|
|
40475
|
+
}
|
|
40476
|
+
switch (event.kind) {
|
|
40477
|
+
case "ask_user_question":
|
|
40478
|
+
return parseAskUserQuestionResponse(event, trimmed);
|
|
40479
|
+
case "enter_plan_mode":
|
|
40480
|
+
return parseEnterPlanModeResponse(event, trimmed);
|
|
40481
|
+
case "exit_plan_mode":
|
|
40482
|
+
return parseExitPlanModeResponse(event, trimmed);
|
|
40483
|
+
case "generic_tool_approval":
|
|
40484
|
+
return parseGenericToolApprovalResponse(event, trimmed);
|
|
40485
|
+
default: {
|
|
40486
|
+
const exhaustiveCheck = event.kind;
|
|
40487
|
+
return exhaustiveCheck;
|
|
40488
|
+
}
|
|
40489
|
+
}
|
|
40490
|
+
}
|
|
40491
|
+
|
|
40141
40492
|
// src/channels/telegram/media.ts
|
|
40142
40493
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
40143
40494
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -41121,6 +41472,13 @@ function createTelegramAdapter(config) {
|
|
|
41121
41472
|
} : undefined;
|
|
41122
41473
|
await telegramBot.api.sendMessage(chatId, text, reply_parameters ? { reply_parameters } : {});
|
|
41123
41474
|
},
|
|
41475
|
+
async handleControlRequestEvent(event) {
|
|
41476
|
+
const telegramBot = await ensureBot();
|
|
41477
|
+
const reply_parameters = event.source.messageId || event.source.threadId ? {
|
|
41478
|
+
message_id: Number(event.source.threadId ?? event.source.messageId)
|
|
41479
|
+
} : undefined;
|
|
41480
|
+
await telegramBot.api.sendMessage(event.source.chatId, formatChannelControlRequestPrompt(event), reply_parameters ? { reply_parameters } : {});
|
|
41481
|
+
},
|
|
41124
41482
|
onMessage: undefined
|
|
41125
41483
|
};
|
|
41126
41484
|
return adapter;
|
|
@@ -42280,6 +42638,16 @@ function createSlackAdapter(config) {
|
|
|
42280
42638
|
});
|
|
42281
42639
|
rememberMessageThread(response.ts, options?.replyToMessageId ?? response.ts ?? null);
|
|
42282
42640
|
},
|
|
42641
|
+
async handleControlRequestEvent(event) {
|
|
42642
|
+
await ensureApp();
|
|
42643
|
+
const slackClient = await ensureWriteClient();
|
|
42644
|
+
const response = await slackClient.chat.postMessage({
|
|
42645
|
+
channel: event.source.chatId,
|
|
42646
|
+
text: formatChannelControlRequestPrompt(event),
|
|
42647
|
+
...event.source.threadId ?? event.source.messageId ? { thread_ts: event.source.threadId ?? event.source.messageId } : {}
|
|
42648
|
+
});
|
|
42649
|
+
rememberMessageThread(response.ts, event.source.threadId ?? event.source.messageId ?? response.ts ?? null);
|
|
42650
|
+
},
|
|
42283
42651
|
async prepareInboundMessage(msg, options) {
|
|
42284
42652
|
if (!options?.isFirstRouteTurn || msg.channel !== "slack" || msg.chatType !== "channel" || !isNonEmptyString2(msg.threadId) || !isNonEmptyString2(msg.messageId)) {
|
|
42285
42653
|
return msg;
|
|
@@ -43347,6 +43715,14 @@ function buildChannelTurnSource(route, msg) {
|
|
|
43347
43715
|
conversationId: route.conversationId
|
|
43348
43716
|
};
|
|
43349
43717
|
}
|
|
43718
|
+
function getChannelApprovalScopeKey(params) {
|
|
43719
|
+
return [
|
|
43720
|
+
params.channel,
|
|
43721
|
+
params.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID,
|
|
43722
|
+
params.chatId,
|
|
43723
|
+
params.threadId ?? ""
|
|
43724
|
+
].join(":");
|
|
43725
|
+
}
|
|
43350
43726
|
function getChannelRegistry() {
|
|
43351
43727
|
return instance;
|
|
43352
43728
|
}
|
|
@@ -43364,7 +43740,10 @@ class ChannelRegistry {
|
|
|
43364
43740
|
ready = false;
|
|
43365
43741
|
messageHandler = null;
|
|
43366
43742
|
eventHandler = null;
|
|
43743
|
+
approvalResponseHandler = null;
|
|
43367
43744
|
buffer = [];
|
|
43745
|
+
pendingControlRequestsById = new Map;
|
|
43746
|
+
pendingControlRequestIdByScope = new Map;
|
|
43368
43747
|
constructor() {
|
|
43369
43748
|
if (instance) {
|
|
43370
43749
|
throw new Error("ChannelRegistry is a singleton — use getChannelRegistry()");
|
|
@@ -43434,9 +43813,57 @@ class ChannelRegistry {
|
|
|
43434
43813
|
setMessageHandler(handler) {
|
|
43435
43814
|
this.messageHandler = handler;
|
|
43436
43815
|
}
|
|
43816
|
+
setApprovalResponseHandler(handler) {
|
|
43817
|
+
this.approvalResponseHandler = handler;
|
|
43818
|
+
}
|
|
43437
43819
|
setEventHandler(handler) {
|
|
43438
43820
|
this.eventHandler = handler;
|
|
43439
43821
|
}
|
|
43822
|
+
async registerPendingControlRequest(event) {
|
|
43823
|
+
const scopeKey = getChannelApprovalScopeKey({
|
|
43824
|
+
channel: event.source.channel,
|
|
43825
|
+
accountId: event.source.accountId,
|
|
43826
|
+
chatId: event.source.chatId,
|
|
43827
|
+
threadId: event.source.threadId
|
|
43828
|
+
});
|
|
43829
|
+
const existingRequestId = this.pendingControlRequestIdByScope.get(scopeKey);
|
|
43830
|
+
if (existingRequestId) {
|
|
43831
|
+
this.clearPendingControlRequest(existingRequestId);
|
|
43832
|
+
}
|
|
43833
|
+
this.pendingControlRequestsById.set(event.requestId, event);
|
|
43834
|
+
this.pendingControlRequestIdByScope.set(scopeKey, event.requestId);
|
|
43835
|
+
const adapter = this.getAdapter(event.source.channel, event.source.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID);
|
|
43836
|
+
if (!adapter) {
|
|
43837
|
+
return;
|
|
43838
|
+
}
|
|
43839
|
+
try {
|
|
43840
|
+
if (adapter.handleControlRequestEvent) {
|
|
43841
|
+
await adapter.handleControlRequestEvent(event);
|
|
43842
|
+
return;
|
|
43843
|
+
}
|
|
43844
|
+
await adapter.sendDirectReply(event.source.chatId, formatChannelControlRequestPrompt(event), {
|
|
43845
|
+
replyToMessageId: event.source.threadId ?? event.source.messageId
|
|
43846
|
+
});
|
|
43847
|
+
} catch (error) {
|
|
43848
|
+
console.error(`[Channels] Failed to deliver control request prompt for ${event.source.channel}/${event.source.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID}:`, error instanceof Error ? error.message : error);
|
|
43849
|
+
}
|
|
43850
|
+
}
|
|
43851
|
+
clearPendingControlRequest(requestId) {
|
|
43852
|
+
const pending = this.pendingControlRequestsById.get(requestId);
|
|
43853
|
+
if (!pending) {
|
|
43854
|
+
return;
|
|
43855
|
+
}
|
|
43856
|
+
this.pendingControlRequestsById.delete(requestId);
|
|
43857
|
+
const scopeKey = getChannelApprovalScopeKey({
|
|
43858
|
+
channel: pending.source.channel,
|
|
43859
|
+
accountId: pending.source.accountId,
|
|
43860
|
+
chatId: pending.source.chatId,
|
|
43861
|
+
threadId: pending.source.threadId
|
|
43862
|
+
});
|
|
43863
|
+
if (this.pendingControlRequestIdByScope.get(scopeKey) === requestId) {
|
|
43864
|
+
this.pendingControlRequestIdByScope.delete(scopeKey);
|
|
43865
|
+
}
|
|
43866
|
+
}
|
|
43440
43867
|
setReady() {
|
|
43441
43868
|
this.ready = true;
|
|
43442
43869
|
this.flushBuffer();
|
|
@@ -43528,6 +43955,7 @@ class ChannelRegistry {
|
|
|
43528
43955
|
this.ready = false;
|
|
43529
43956
|
this.messageHandler = null;
|
|
43530
43957
|
this.eventHandler = null;
|
|
43958
|
+
this.approvalResponseHandler = null;
|
|
43531
43959
|
}
|
|
43532
43960
|
async stopAll() {
|
|
43533
43961
|
for (const adapter of Array.from(this.adapters.values())) {
|
|
@@ -43538,13 +43966,63 @@ class ChannelRegistry {
|
|
|
43538
43966
|
this.ready = false;
|
|
43539
43967
|
this.messageHandler = null;
|
|
43540
43968
|
this.eventHandler = null;
|
|
43969
|
+
this.approvalResponseHandler = null;
|
|
43970
|
+
this.pendingControlRequestsById.clear();
|
|
43971
|
+
this.pendingControlRequestIdByScope.clear();
|
|
43541
43972
|
instance = null;
|
|
43542
43973
|
}
|
|
43974
|
+
async tryHandlePendingControlRequest(adapter, msg) {
|
|
43975
|
+
const scopeKey = getChannelApprovalScopeKey({
|
|
43976
|
+
channel: msg.channel,
|
|
43977
|
+
accountId: msg.accountId,
|
|
43978
|
+
chatId: msg.chatId,
|
|
43979
|
+
threadId: msg.threadId
|
|
43980
|
+
});
|
|
43981
|
+
const requestId = this.pendingControlRequestIdByScope.get(scopeKey);
|
|
43982
|
+
if (!requestId) {
|
|
43983
|
+
return false;
|
|
43984
|
+
}
|
|
43985
|
+
const pending = this.pendingControlRequestsById.get(requestId);
|
|
43986
|
+
if (!pending) {
|
|
43987
|
+
this.pendingControlRequestIdByScope.delete(scopeKey);
|
|
43988
|
+
return false;
|
|
43989
|
+
}
|
|
43990
|
+
const parsed = parseChannelControlRequestResponse(pending, msg.text);
|
|
43991
|
+
if (parsed.type === "reprompt") {
|
|
43992
|
+
await adapter.sendDirectReply(msg.chatId, parsed.message, {
|
|
43993
|
+
replyToMessageId: msg.threadId ?? msg.messageId
|
|
43994
|
+
});
|
|
43995
|
+
return true;
|
|
43996
|
+
}
|
|
43997
|
+
if (!this.approvalResponseHandler) {
|
|
43998
|
+
await adapter.sendDirectReply(msg.chatId, "I’m reconnecting to Letta Code right now, so I couldn’t use that reply yet. Please send it again in a moment.", {
|
|
43999
|
+
replyToMessageId: msg.threadId ?? msg.messageId
|
|
44000
|
+
});
|
|
44001
|
+
return true;
|
|
44002
|
+
}
|
|
44003
|
+
const handled = await this.approvalResponseHandler({
|
|
44004
|
+
runtime: {
|
|
44005
|
+
agent_id: pending.source.agentId,
|
|
44006
|
+
conversation_id: pending.source.conversationId
|
|
44007
|
+
},
|
|
44008
|
+
response: parsed.response
|
|
44009
|
+
});
|
|
44010
|
+
this.clearPendingControlRequest(requestId);
|
|
44011
|
+
if (!handled) {
|
|
44012
|
+
await adapter.sendDirectReply(msg.chatId, "That approval prompt expired before I could use your reply. Please ask the agent to try again.", {
|
|
44013
|
+
replyToMessageId: msg.threadId ?? msg.messageId
|
|
44014
|
+
});
|
|
44015
|
+
}
|
|
44016
|
+
return true;
|
|
44017
|
+
}
|
|
43543
44018
|
async handleInboundMessage(msg) {
|
|
43544
44019
|
const accountId = msg.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
43545
44020
|
const adapter = this.getAdapter(msg.channel, accountId);
|
|
43546
44021
|
if (!adapter)
|
|
43547
44022
|
return;
|
|
44023
|
+
if (await this.tryHandlePendingControlRequest(adapter, msg)) {
|
|
44024
|
+
return;
|
|
44025
|
+
}
|
|
43548
44026
|
const config = getChannelAccount(msg.channel, accountId);
|
|
43549
44027
|
if (!config)
|
|
43550
44028
|
return;
|
|
@@ -52599,6 +53077,7 @@ function createConversationRuntime(listener, agentId, conversationId) {
|
|
|
52599
53077
|
key: runtimeKey,
|
|
52600
53078
|
agentId: normalizedAgentId,
|
|
52601
53079
|
conversationId: normalizedConversationId,
|
|
53080
|
+
activeChannelTurnSources: null,
|
|
52602
53081
|
messageQueue: Promise.resolve(),
|
|
52603
53082
|
pendingApprovalResolvers: new Map,
|
|
52604
53083
|
recoveredApprovalState: null,
|
|
@@ -77282,13 +77761,58 @@ function writeCronFile(data) {
|
|
|
77282
77761
|
writeFileSync14(tmp, JSON.stringify(data, null, 2), { flush: true });
|
|
77283
77762
|
renameSync2(tmp, path20);
|
|
77284
77763
|
}
|
|
77285
|
-
function
|
|
77764
|
+
function readLinuxProcessIdentity(pid) {
|
|
77765
|
+
try {
|
|
77766
|
+
const stat5 = readFileSync15(`/proc/${pid}/stat`, "utf8");
|
|
77767
|
+
const endCommand = stat5.lastIndexOf(")");
|
|
77768
|
+
if (endCommand === -1) {
|
|
77769
|
+
return null;
|
|
77770
|
+
}
|
|
77771
|
+
const fields = stat5.slice(endCommand + 2).trim().split(/\s+/);
|
|
77772
|
+
const startTicks = fields[19] ?? null;
|
|
77773
|
+
if (!startTicks) {
|
|
77774
|
+
return null;
|
|
77775
|
+
}
|
|
77776
|
+
let bootId = null;
|
|
77777
|
+
try {
|
|
77778
|
+
bootId = readFileSync15("/proc/sys/kernel/random/boot_id", "utf8").trim() || null;
|
|
77779
|
+
} catch {}
|
|
77780
|
+
return { startTicks, bootId };
|
|
77781
|
+
} catch {
|
|
77782
|
+
return null;
|
|
77783
|
+
}
|
|
77784
|
+
}
|
|
77785
|
+
function readProcessIdentity(pid) {
|
|
77786
|
+
if (readProcessIdentityOverride) {
|
|
77787
|
+
return readProcessIdentityOverride(pid);
|
|
77788
|
+
}
|
|
77789
|
+
return readLinuxProcessIdentity(pid);
|
|
77790
|
+
}
|
|
77791
|
+
function captureProcessIdentity(pid) {
|
|
77792
|
+
const identity = readProcessIdentity(pid);
|
|
77793
|
+
return {
|
|
77794
|
+
process_start_ticks: identity?.startTicks ?? null,
|
|
77795
|
+
boot_id: identity?.bootId ?? null
|
|
77796
|
+
};
|
|
77797
|
+
}
|
|
77798
|
+
function isProcessAlive(pid, owner) {
|
|
77286
77799
|
try {
|
|
77287
77800
|
process.kill(pid, 0);
|
|
77288
|
-
return true;
|
|
77289
77801
|
} catch {
|
|
77290
77802
|
return false;
|
|
77291
77803
|
}
|
|
77804
|
+
if (owner) {
|
|
77805
|
+
const identity = readProcessIdentity(pid);
|
|
77806
|
+
if (identity) {
|
|
77807
|
+
if (owner.boot_id && identity.bootId && owner.boot_id !== identity.bootId) {
|
|
77808
|
+
return false;
|
|
77809
|
+
}
|
|
77810
|
+
if (owner.process_start_ticks && identity.startTicks && owner.process_start_ticks !== identity.startTicks) {
|
|
77811
|
+
return false;
|
|
77812
|
+
}
|
|
77813
|
+
}
|
|
77814
|
+
}
|
|
77815
|
+
return true;
|
|
77292
77816
|
}
|
|
77293
77817
|
function readLockOwner(lockDir) {
|
|
77294
77818
|
try {
|
|
@@ -77311,7 +77835,7 @@ function isLockStale(lockDir) {
|
|
|
77311
77835
|
return true;
|
|
77312
77836
|
}
|
|
77313
77837
|
}
|
|
77314
|
-
const pidDead = !isProcessAlive(owner.pid);
|
|
77838
|
+
const pidDead = !isProcessAlive(owner.pid, owner);
|
|
77315
77839
|
const isOld = Date.now() - owner.acquired_at > LOCK_STALE_AGE_MS;
|
|
77316
77840
|
return pidDead && isOld;
|
|
77317
77841
|
}
|
|
@@ -77330,7 +77854,8 @@ function acquireLock() {
|
|
|
77330
77854
|
const owner = {
|
|
77331
77855
|
pid: process.pid,
|
|
77332
77856
|
token,
|
|
77333
|
-
acquired_at: Date.now()
|
|
77857
|
+
acquired_at: Date.now(),
|
|
77858
|
+
...captureProcessIdentity(process.pid)
|
|
77334
77859
|
};
|
|
77335
77860
|
writeLockOwner(lockDir, owner);
|
|
77336
77861
|
return {
|
|
@@ -77435,7 +77960,7 @@ function addTask(input) {
|
|
|
77435
77960
|
data.tasks.push(task2);
|
|
77436
77961
|
writeCronFile(data);
|
|
77437
77962
|
let warning;
|
|
77438
|
-
if (!data.scheduler_owner || !isProcessAlive(data.scheduler_owner.pid)) {
|
|
77963
|
+
if (!data.scheduler_owner || !isProcessAlive(data.scheduler_owner.pid, data.scheduler_owner)) {
|
|
77439
77964
|
warning = "No letta server is currently running. This task will only execute when a WS listener is active.";
|
|
77440
77965
|
}
|
|
77441
77966
|
return { task: task2, warning };
|
|
@@ -77483,15 +78008,17 @@ function claimSchedulerLease() {
|
|
|
77483
78008
|
const data = readCronFile();
|
|
77484
78009
|
const token = randomBytes(4).toString("hex");
|
|
77485
78010
|
if (data.scheduler_owner) {
|
|
77486
|
-
const
|
|
77487
|
-
|
|
78011
|
+
const existingOwner = data.scheduler_owner;
|
|
78012
|
+
const { pid, token: existingToken } = existingOwner;
|
|
78013
|
+
if (isProcessAlive(pid, existingOwner)) {
|
|
77488
78014
|
throw new Error(`Scheduler lease held by PID ${pid} (token ${existingToken}). Cannot claim.`);
|
|
77489
78015
|
}
|
|
77490
78016
|
}
|
|
77491
78017
|
data.scheduler_owner = {
|
|
77492
78018
|
pid: process.pid,
|
|
77493
78019
|
token,
|
|
77494
|
-
started_at: new Date().toISOString()
|
|
78020
|
+
started_at: new Date().toISOString(),
|
|
78021
|
+
...captureProcessIdentity(process.pid)
|
|
77495
78022
|
};
|
|
77496
78023
|
writeCronFile(data);
|
|
77497
78024
|
return token;
|
|
@@ -77551,7 +78078,7 @@ function getCronFileMtime() {
|
|
|
77551
78078
|
return 0;
|
|
77552
78079
|
}
|
|
77553
78080
|
}
|
|
77554
|
-
var CRON_FILE_NAME = "crons.json", LOCK_DIR_NAME = "crons.lock", LOCK_TOKEN_FILE = "owner.json", LOCK_TIMEOUT_MS = 5000, LOCK_RETRY_MS = 50, LOCK_STALE_AGE_MS = 30000, MAX_ACTIVE_TASKS_PER_AGENT = 50, TASK_ID_BYTES = 4, GC_AGE_MS;
|
|
78081
|
+
var CRON_FILE_NAME = "crons.json", LOCK_DIR_NAME = "crons.lock", LOCK_TOKEN_FILE = "owner.json", LOCK_TIMEOUT_MS = 5000, LOCK_RETRY_MS = 50, LOCK_STALE_AGE_MS = 30000, MAX_ACTIVE_TASKS_PER_AGENT = 50, TASK_ID_BYTES = 4, GC_AGE_MS, readProcessIdentityOverride = null;
|
|
77555
78082
|
var init_cronFile = __esm(() => {
|
|
77556
78083
|
init_parseInterval();
|
|
77557
78084
|
GC_AGE_MS = 24 * 60 * 60 * 1000;
|
|
@@ -81837,6 +82364,18 @@ var MIN_SPLIT_LENGTH = 1500;
|
|
|
81837
82364
|
function isInteractiveApprovalTool(toolName) {
|
|
81838
82365
|
return INTERACTIVE_APPROVAL_TOOLS.has(toolName);
|
|
81839
82366
|
}
|
|
82367
|
+
function getInteractiveApprovalKind(toolName) {
|
|
82368
|
+
switch (toolName) {
|
|
82369
|
+
case "AskUserQuestion":
|
|
82370
|
+
return "ask_user_question";
|
|
82371
|
+
case "EnterPlanMode":
|
|
82372
|
+
return "enter_plan_mode";
|
|
82373
|
+
case "ExitPlanMode":
|
|
82374
|
+
return "exit_plan_mode";
|
|
82375
|
+
default:
|
|
82376
|
+
return null;
|
|
82377
|
+
}
|
|
82378
|
+
}
|
|
81840
82379
|
function isHeadlessAutoAllowTool(toolName) {
|
|
81841
82380
|
return HEADLESS_AUTO_ALLOW_TOOLS.has(toolName);
|
|
81842
82381
|
}
|
|
@@ -88493,7 +89032,46 @@ var init_send = __esm(async () => {
|
|
|
88493
89032
|
});
|
|
88494
89033
|
|
|
88495
89034
|
// src/websocket/listener/turn-approval.ts
|
|
89035
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
88496
89036
|
import WebSocket2 from "ws";
|
|
89037
|
+
function getChannelApprovalSourceScopeKey(source) {
|
|
89038
|
+
return [
|
|
89039
|
+
source.channel,
|
|
89040
|
+
source.accountId ?? "",
|
|
89041
|
+
source.chatId,
|
|
89042
|
+
source.threadId ?? ""
|
|
89043
|
+
].join(":");
|
|
89044
|
+
}
|
|
89045
|
+
function resolveChannelApprovalSource(runtime) {
|
|
89046
|
+
const sources = runtime.activeChannelTurnSources ?? [];
|
|
89047
|
+
if (sources.length === 0) {
|
|
89048
|
+
return null;
|
|
89049
|
+
}
|
|
89050
|
+
const sourcesByScope = new Map;
|
|
89051
|
+
for (const source of sources) {
|
|
89052
|
+
sourcesByScope.set(getChannelApprovalSourceScopeKey(source), source);
|
|
89053
|
+
}
|
|
89054
|
+
if (sourcesByScope.size !== 1) {
|
|
89055
|
+
return null;
|
|
89056
|
+
}
|
|
89057
|
+
return [...sourcesByScope.values()].at(-1) ?? null;
|
|
89058
|
+
}
|
|
89059
|
+
async function maybeReadPlanPreview(toolName, turnPermissionModeState) {
|
|
89060
|
+
if (toolName !== "ExitPlanMode" || !turnPermissionModeState.planFilePath) {
|
|
89061
|
+
return {};
|
|
89062
|
+
}
|
|
89063
|
+
try {
|
|
89064
|
+
const planContent = await readFile11(turnPermissionModeState.planFilePath, "utf8");
|
|
89065
|
+
return {
|
|
89066
|
+
planFilePath: turnPermissionModeState.planFilePath,
|
|
89067
|
+
planContent
|
|
89068
|
+
};
|
|
89069
|
+
} catch {
|
|
89070
|
+
return {
|
|
89071
|
+
planFilePath: turnPermissionModeState.planFilePath
|
|
89072
|
+
};
|
|
89073
|
+
}
|
|
89074
|
+
}
|
|
88497
89075
|
async function handleApprovalStop(params) {
|
|
88498
89076
|
const {
|
|
88499
89077
|
approvals,
|
|
@@ -88631,6 +89209,18 @@ async function handleApprovalStop(params) {
|
|
|
88631
89209
|
agent_id: agentId,
|
|
88632
89210
|
conversation_id: conversationId
|
|
88633
89211
|
};
|
|
89212
|
+
const registry = getChannelRegistry();
|
|
89213
|
+
const channelSource = resolveChannelApprovalSource(runtime);
|
|
89214
|
+
if (registry && channelSource) {
|
|
89215
|
+
await registry.registerPendingControlRequest({
|
|
89216
|
+
requestId,
|
|
89217
|
+
kind: getInteractiveApprovalKind(ac.approval.toolName) ?? "generic_tool_approval",
|
|
89218
|
+
source: channelSource,
|
|
89219
|
+
toolName: ac.approval.toolName,
|
|
89220
|
+
input: ac.parsedArgs,
|
|
89221
|
+
...await maybeReadPlanPreview(ac.approval.toolName, turnPermissionModeState)
|
|
89222
|
+
});
|
|
89223
|
+
}
|
|
88634
89224
|
let responseBody;
|
|
88635
89225
|
try {
|
|
88636
89226
|
responseBody = await requestApprovalOverWS(runtime, socket, requestId, controlRequest);
|
|
@@ -88639,6 +89229,8 @@ async function handleApprovalStop(params) {
|
|
|
88639
89229
|
return interruptTermination();
|
|
88640
89230
|
}
|
|
88641
89231
|
throw error;
|
|
89232
|
+
} finally {
|
|
89233
|
+
registry?.clearPendingControlRequest(requestId);
|
|
88642
89234
|
}
|
|
88643
89235
|
if (shouldInterrupt()) {
|
|
88644
89236
|
return interruptTermination();
|
|
@@ -88841,6 +89433,7 @@ async function handleApprovalStop(params) {
|
|
|
88841
89433
|
};
|
|
88842
89434
|
}
|
|
88843
89435
|
var init_turn_approval = __esm(async () => {
|
|
89436
|
+
init_registry();
|
|
88844
89437
|
init_diffPreview();
|
|
88845
89438
|
init_interactivePolicy();
|
|
88846
89439
|
init_skill_injection();
|
|
@@ -90684,6 +91277,7 @@ async function drainQueuedMessages(runtime, socket, opts, processQueuedTurn) {
|
|
|
90684
91277
|
}
|
|
90685
91278
|
let turnError;
|
|
90686
91279
|
let didThrow = false;
|
|
91280
|
+
runtime.activeChannelTurnSources = channelTurnSources;
|
|
90687
91281
|
try {
|
|
90688
91282
|
await processQueuedTurn(queuedTurn, dequeuedBatch);
|
|
90689
91283
|
} catch (error) {
|
|
@@ -90691,6 +91285,7 @@ async function drainQueuedMessages(runtime, socket, opts, processQueuedTurn) {
|
|
|
90691
91285
|
turnError = error instanceof Error ? error.message : String(error);
|
|
90692
91286
|
throw error;
|
|
90693
91287
|
} finally {
|
|
91288
|
+
runtime.activeChannelTurnSources = null;
|
|
90694
91289
|
if (channelTurnSources.length > 0) {
|
|
90695
91290
|
await dispatchChannelTurnLifecycleEvent({
|
|
90696
91291
|
type: "finished",
|
|
@@ -93471,6 +94066,13 @@ function wireChannelIngress(listener, socket, opts, processQueuedTurn) {
|
|
|
93471
94066
|
}
|
|
93472
94067
|
scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
|
|
93473
94068
|
});
|
|
94069
|
+
registry.setApprovalResponseHandler(async ({ runtime, response }) => handleApprovalResponseInput(listener, {
|
|
94070
|
+
runtime,
|
|
94071
|
+
response,
|
|
94072
|
+
socket,
|
|
94073
|
+
opts,
|
|
94074
|
+
processQueuedTurn
|
|
94075
|
+
}));
|
|
93474
94076
|
registry.setEventHandler((event) => {
|
|
93475
94077
|
handleChannelRegistryEvent(event, socket, listener);
|
|
93476
94078
|
});
|
|
@@ -94380,8 +94982,8 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
|
|
|
94380
94982
|
console.log(`[Listen] Received read_file command: path=${parsed.path}, request_id=${parsed.request_id}`);
|
|
94381
94983
|
runDetachedListenerTask("read_file", async () => {
|
|
94382
94984
|
try {
|
|
94383
|
-
const { readFile:
|
|
94384
|
-
const content = await
|
|
94985
|
+
const { readFile: readFile12 } = await import("node:fs/promises");
|
|
94986
|
+
const content = await readFile12(parsed.path, "utf-8");
|
|
94385
94987
|
console.log(`[Listen] read_file success: ${parsed.path} (${content.length} bytes)`);
|
|
94386
94988
|
safeSocketSend(socket, {
|
|
94387
94989
|
type: "read_file_response",
|
|
@@ -94411,10 +95013,10 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
|
|
|
94411
95013
|
try {
|
|
94412
95014
|
const { edit: edit2 } = await Promise.resolve().then(() => (init_Edit2(), exports_Edit));
|
|
94413
95015
|
const { write: write2 } = await Promise.resolve().then(() => (init_Write2(), exports_Write));
|
|
94414
|
-
const { readFile:
|
|
95016
|
+
const { readFile: readFile12 } = await import("node:fs/promises");
|
|
94415
95017
|
let currentContent = null;
|
|
94416
95018
|
try {
|
|
94417
|
-
currentContent = await
|
|
95019
|
+
currentContent = await readFile12(parsed.path, "utf-8");
|
|
94418
95020
|
} catch (readErr) {
|
|
94419
95021
|
const e = readErr;
|
|
94420
95022
|
if (e.code !== "ENOENT")
|
|
@@ -94522,7 +95124,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
|
|
|
94522
95124
|
console.log(`[Listen] Received edit_file command: file_path=${parsed.file_path}, request_id=${parsed.request_id}`);
|
|
94523
95125
|
runDetachedListenerTask("edit_file", async () => {
|
|
94524
95126
|
try {
|
|
94525
|
-
const { readFile:
|
|
95127
|
+
const { readFile: readFile12 } = await import("node:fs/promises");
|
|
94526
95128
|
const { edit: edit2 } = await Promise.resolve().then(() => (init_Edit2(), exports_Edit));
|
|
94527
95129
|
console.log(`[Listen] Executing edit: old_string="${parsed.old_string.slice(0, 50)}${parsed.old_string.length > 50 ? "..." : ""}"`);
|
|
94528
95130
|
const result = await edit2({
|
|
@@ -94538,7 +95140,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
|
|
|
94538
95140
|
}
|
|
94539
95141
|
if (result.replacements > 0) {
|
|
94540
95142
|
try {
|
|
94541
|
-
const contentAfter = await
|
|
95143
|
+
const contentAfter = await readFile12(parsed.file_path, "utf-8");
|
|
94542
95144
|
safeSocketSend(socket, {
|
|
94543
95145
|
type: "file_ops",
|
|
94544
95146
|
path: parsed.file_path,
|
|
@@ -95453,7 +96055,7 @@ __export(exports_skills2, {
|
|
|
95453
96055
|
GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR2
|
|
95454
96056
|
});
|
|
95455
96057
|
import { existsSync as existsSync28 } from "node:fs";
|
|
95456
|
-
import { readdir as readdir8, readFile as
|
|
96058
|
+
import { readdir as readdir8, readFile as readFile12, realpath as realpath4, stat as stat7 } from "node:fs/promises";
|
|
95457
96059
|
import { dirname as dirname13, join as join35 } from "node:path";
|
|
95458
96060
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
95459
96061
|
function getBundledSkillsPath2() {
|
|
@@ -95584,7 +96186,7 @@ async function findSkillFiles2(currentPath, rootPath, skills, errors, source, vi
|
|
|
95584
96186
|
}
|
|
95585
96187
|
}
|
|
95586
96188
|
async function parseSkillFile2(filePath, rootPath, source) {
|
|
95587
|
-
const content = await
|
|
96189
|
+
const content = await readFile12(filePath, "utf-8");
|
|
95588
96190
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
95589
96191
|
const normalizedRoot = rootPath.endsWith("/") ? rootPath.slice(0, -1) : rootPath;
|
|
95590
96192
|
const relativePath = filePath.slice(normalizedRoot.length + 1);
|
|
@@ -95643,7 +96245,7 @@ __export(exports_fs, {
|
|
|
95643
96245
|
writeJsonFile: () => writeJsonFile,
|
|
95644
96246
|
writeFile: () => writeFile10,
|
|
95645
96247
|
readJsonFile: () => readJsonFile,
|
|
95646
|
-
readFile: () =>
|
|
96248
|
+
readFile: () => readFile13,
|
|
95647
96249
|
mkdir: () => mkdir9,
|
|
95648
96250
|
exists: () => exists2
|
|
95649
96251
|
});
|
|
@@ -95654,7 +96256,7 @@ import {
|
|
|
95654
96256
|
mkdirSync as mkdirSync21
|
|
95655
96257
|
} from "node:fs";
|
|
95656
96258
|
import { dirname as dirname14 } from "node:path";
|
|
95657
|
-
async function
|
|
96259
|
+
async function readFile13(path24) {
|
|
95658
96260
|
return fsReadFileSync2(path24, { encoding: "utf-8" });
|
|
95659
96261
|
}
|
|
95660
96262
|
async function writeFile10(path24, content) {
|
|
@@ -95671,7 +96273,7 @@ async function mkdir9(path24, options) {
|
|
|
95671
96273
|
mkdirSync21(path24, options);
|
|
95672
96274
|
}
|
|
95673
96275
|
async function readJsonFile(path24) {
|
|
95674
|
-
const text = await
|
|
96276
|
+
const text = await readFile13(path24);
|
|
95675
96277
|
return JSON.parse(text);
|
|
95676
96278
|
}
|
|
95677
96279
|
async function writeJsonFile(path24, data, options) {
|
|
@@ -97510,7 +98112,7 @@ __export(exports_import, {
|
|
|
97510
98112
|
extractSkillsFromAf: () => extractSkillsFromAf
|
|
97511
98113
|
});
|
|
97512
98114
|
import { createReadStream } from "node:fs";
|
|
97513
|
-
import { chmod, mkdir as mkdir10, readFile as
|
|
98115
|
+
import { chmod, mkdir as mkdir10, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
97514
98116
|
import { dirname as dirname15, resolve as resolve28 } from "node:path";
|
|
97515
98117
|
async function importAgentFromFile(options) {
|
|
97516
98118
|
const client = await getClient();
|
|
@@ -97543,7 +98145,7 @@ async function importAgentFromFile(options) {
|
|
|
97543
98145
|
}
|
|
97544
98146
|
async function extractSkillsFromAf(afPath, destDir) {
|
|
97545
98147
|
const extracted = [];
|
|
97546
|
-
const content = await
|
|
98148
|
+
const content = await readFile14(afPath, "utf-8");
|
|
97547
98149
|
const afData = JSON.parse(content);
|
|
97548
98150
|
if (!afData.skills || !Array.isArray(afData.skills)) {
|
|
97549
98151
|
return [];
|
|
@@ -124440,7 +125042,7 @@ __export(exports_custom, {
|
|
|
124440
125042
|
COMMANDS_DIR: () => COMMANDS_DIR
|
|
124441
125043
|
});
|
|
124442
125044
|
import { existsSync as existsSync36 } from "node:fs";
|
|
124443
|
-
import { readdir as readdir10, readFile as
|
|
125045
|
+
import { readdir as readdir10, readFile as readFile15 } from "node:fs/promises";
|
|
124444
125046
|
import { basename as basename11, dirname as dirname17, join as join44 } from "node:path";
|
|
124445
125047
|
async function getCustomCommands() {
|
|
124446
125048
|
if (cachedCommands !== null) {
|
|
@@ -124499,7 +125101,7 @@ async function findCommandFiles(currentPath, rootPath, commands2, source2) {
|
|
|
124499
125101
|
} catch (_error) {}
|
|
124500
125102
|
}
|
|
124501
125103
|
async function parseCommandFile(filePath, rootPath, source2) {
|
|
124502
|
-
const content = await
|
|
125104
|
+
const content = await readFile15(filePath, "utf-8");
|
|
124503
125105
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
124504
125106
|
const id = basename11(filePath, ".md");
|
|
124505
125107
|
const relativePath = dirname17(filePath).slice(rootPath.length);
|
|
@@ -137231,7 +137833,7 @@ var init_PersonalitySelector = __esm(async () => {
|
|
|
137231
137833
|
});
|
|
137232
137834
|
|
|
137233
137835
|
// src/utils/aws-credentials.ts
|
|
137234
|
-
import { readFile as
|
|
137836
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
137235
137837
|
import { homedir as homedir36 } from "node:os";
|
|
137236
137838
|
import { join as join48 } from "node:path";
|
|
137237
137839
|
async function parseAwsCredentials() {
|
|
@@ -137239,11 +137841,11 @@ async function parseAwsCredentials() {
|
|
|
137239
137841
|
const configPath = join48(homedir36(), ".aws", "config");
|
|
137240
137842
|
const profiles = new Map;
|
|
137241
137843
|
try {
|
|
137242
|
-
const content = await
|
|
137844
|
+
const content = await readFile16(credentialsPath, "utf-8");
|
|
137243
137845
|
parseIniFile(content, profiles, false);
|
|
137244
137846
|
} catch {}
|
|
137245
137847
|
try {
|
|
137246
|
-
const content = await
|
|
137848
|
+
const content = await readFile16(configPath, "utf-8");
|
|
137247
137849
|
parseIniFile(content, profiles, true);
|
|
137248
137850
|
} catch {}
|
|
137249
137851
|
return Array.from(profiles.values());
|
|
@@ -143908,7 +144510,7 @@ var exports_export = {};
|
|
|
143908
144510
|
__export(exports_export, {
|
|
143909
144511
|
packageSkills: () => packageSkills
|
|
143910
144512
|
});
|
|
143911
|
-
import { readdir as readdir11, readFile as
|
|
144513
|
+
import { readdir as readdir11, readFile as readFile17 } from "node:fs/promises";
|
|
143912
144514
|
import { relative as relative17, resolve as resolve32 } from "node:path";
|
|
143913
144515
|
async function packageSkills(agentId, skillsDir) {
|
|
143914
144516
|
const skills = [];
|
|
@@ -143929,7 +144531,7 @@ async function packageSkills(agentId, skillsDir) {
|
|
|
143929
144531
|
const skillDir = resolve32(baseDir, entry.name);
|
|
143930
144532
|
const skillMdPath = resolve32(skillDir, "SKILL.md");
|
|
143931
144533
|
try {
|
|
143932
|
-
await
|
|
144534
|
+
await readFile17(skillMdPath, "utf-8");
|
|
143933
144535
|
} catch {
|
|
143934
144536
|
console.warn(`Skipping invalid skill ${entry.name}: missing SKILL.md`);
|
|
143935
144537
|
continue;
|
|
@@ -143961,7 +144563,7 @@ async function readSkillFiles(skillDir) {
|
|
|
143961
144563
|
if (entry.isDirectory()) {
|
|
143962
144564
|
await walk(fullPath);
|
|
143963
144565
|
} else {
|
|
143964
|
-
const content = await
|
|
144566
|
+
const content = await readFile17(fullPath, "utf-8");
|
|
143965
144567
|
const relativePath = relative17(skillDir, fullPath).replace(/\\/g, "/");
|
|
143966
144568
|
files[relativePath] = content;
|
|
143967
144569
|
}
|
|
@@ -154739,7 +155341,7 @@ __export(exports_import2, {
|
|
|
154739
155341
|
extractSkillsFromAf: () => extractSkillsFromAf2
|
|
154740
155342
|
});
|
|
154741
155343
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
154742
|
-
import { chmod as chmod2, mkdir as mkdir11, readFile as
|
|
155344
|
+
import { chmod as chmod2, mkdir as mkdir11, readFile as readFile18, writeFile as writeFile12 } from "node:fs/promises";
|
|
154743
155345
|
import { dirname as dirname20, resolve as resolve33 } from "node:path";
|
|
154744
155346
|
async function importAgentFromFile2(options) {
|
|
154745
155347
|
const client = await getClient();
|
|
@@ -154772,7 +155374,7 @@ async function importAgentFromFile2(options) {
|
|
|
154772
155374
|
}
|
|
154773
155375
|
async function extractSkillsFromAf2(afPath, destDir) {
|
|
154774
155376
|
const extracted = [];
|
|
154775
|
-
const content = await
|
|
155377
|
+
const content = await readFile18(afPath, "utf-8");
|
|
154776
155378
|
const afData = JSON.parse(content);
|
|
154777
155379
|
if (!afData.skills || !Array.isArray(afData.skills)) {
|
|
154778
155380
|
return [];
|
|
@@ -164230,4 +164832,4 @@ Error during initialization: ${message}`);
|
|
|
164230
164832
|
}
|
|
164231
164833
|
main();
|
|
164232
164834
|
|
|
164233
|
-
//# debugId=
|
|
164835
|
+
//# debugId=13707EE7EA6B933364756E2164756E21
|