@letta-ai/letta-code 0.13.2 → 0.13.3
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
CHANGED
|
@@ -3065,7 +3065,7 @@ var package_default;
|
|
|
3065
3065
|
var init_package = __esm(() => {
|
|
3066
3066
|
package_default = {
|
|
3067
3067
|
name: "@letta-ai/letta-code",
|
|
3068
|
-
version: "0.13.
|
|
3068
|
+
version: "0.13.3",
|
|
3069
3069
|
description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
3070
3070
|
type: "module",
|
|
3071
3071
|
bin: {
|
|
@@ -8619,6 +8619,8 @@ Launch a subagent to perform a task. Parameters:
|
|
|
8619
8619
|
- **prompt**: Detailed, self-contained instructions for the agent (agents cannot ask questions mid-execution)
|
|
8620
8620
|
- **description**: Short 3-5 word summary for tracking
|
|
8621
8621
|
- **model** (optional): Override the model for this agent
|
|
8622
|
+
- **agent_id** (optional): Deploy an existing agent instead of creating a new one
|
|
8623
|
+
- **conversation_id** (optional): Resume from an existing conversation
|
|
8622
8624
|
|
|
8623
8625
|
### Refresh
|
|
8624
8626
|
Re-scan the \`.letta/agents/\` directories to discover new or updated custom subagents:
|
|
@@ -8650,6 +8652,57 @@ Use this after creating or modifying custom subagent definitions.
|
|
|
8650
8652
|
- **Parallel execution**: Launch multiple agents concurrently by calling Task multiple times in a single response
|
|
8651
8653
|
- **Specify return format**: Tell agents exactly what information to include in their report
|
|
8652
8654
|
|
|
8655
|
+
## Deploying an Existing Agent
|
|
8656
|
+
|
|
8657
|
+
Instead of spawning a fresh subagent from a template, you can deploy an existing agent to work in your local codebase.
|
|
8658
|
+
|
|
8659
|
+
### Access Levels (subagent_type)
|
|
8660
|
+
Use subagent_type to control what tools the deployed agent can access:
|
|
8661
|
+
- **explore**: Read-only access (Read, Glob, Grep) - safer for exploration tasks
|
|
8662
|
+
- **general-purpose**: Full read-write access (Bash, Edit, Write, etc.) - for implementation tasks
|
|
8663
|
+
|
|
8664
|
+
If subagent_type is not specified when deploying an existing agent, it defaults to "general-purpose".
|
|
8665
|
+
|
|
8666
|
+
### Parameters
|
|
8667
|
+
|
|
8668
|
+
- **agent_id**: The ID of an existing agent to deploy (e.g., "agent-abc123")
|
|
8669
|
+
- Starts a new conversation with that agent
|
|
8670
|
+
- The agent keeps its own system prompt and memory
|
|
8671
|
+
- Tool access is controlled by subagent_type
|
|
8672
|
+
|
|
8673
|
+
- **conversation_id**: Resume from an existing conversation (e.g., "conv-xyz789")
|
|
8674
|
+
- Does NOT require agent_id (conversation IDs are unique and encode the agent)
|
|
8675
|
+
- Continues from the conversation's existing message history
|
|
8676
|
+
- Use this to continue context from:
|
|
8677
|
+
- A prior Task tool invocation that returned a conversation_id
|
|
8678
|
+
- A message thread started via the messaging-agents skill
|
|
8679
|
+
|
|
8680
|
+
### Examples
|
|
8681
|
+
|
|
8682
|
+
\`\`\`typescript
|
|
8683
|
+
// Deploy agent with read-only access
|
|
8684
|
+
Task({
|
|
8685
|
+
agent_id: "agent-abc123",
|
|
8686
|
+
subagent_type: "explore",
|
|
8687
|
+
description: "Find auth code",
|
|
8688
|
+
prompt: "Find all auth-related code in this codebase"
|
|
8689
|
+
})
|
|
8690
|
+
|
|
8691
|
+
// Deploy agent with full access (default)
|
|
8692
|
+
Task({
|
|
8693
|
+
agent_id: "agent-abc123",
|
|
8694
|
+
description: "Fix auth bug",
|
|
8695
|
+
prompt: "Fix the bug in auth.ts"
|
|
8696
|
+
})
|
|
8697
|
+
|
|
8698
|
+
// Continue an existing conversation
|
|
8699
|
+
Task({
|
|
8700
|
+
conversation_id: "conv-xyz789",
|
|
8701
|
+
description: "Continue implementation",
|
|
8702
|
+
prompt: "Now implement the fix we discussed"
|
|
8703
|
+
})
|
|
8704
|
+
\`\`\`
|
|
8705
|
+
|
|
8653
8706
|
## Examples:
|
|
8654
8707
|
|
|
8655
8708
|
\`\`\`typescript
|
|
@@ -19848,6 +19901,9 @@ function handleInitEvent(event, state, baseURL, subagentId) {
|
|
|
19848
19901
|
const agentURL = `${baseURL}/agents/${event.agent_id}`;
|
|
19849
19902
|
updateSubagent(subagentId, { agentURL });
|
|
19850
19903
|
}
|
|
19904
|
+
if (event.conversation_id) {
|
|
19905
|
+
state.conversationId = event.conversation_id;
|
|
19906
|
+
}
|
|
19851
19907
|
}
|
|
19852
19908
|
function handleApprovalRequestEvent(event, state) {
|
|
19853
19909
|
const toolCalls = Array.isArray(event.tool_calls) ? event.tool_calls : event.tool_call ? [event.tool_call] : [];
|
|
@@ -19947,18 +20003,24 @@ function parseResultFromStdout(stdout, agentId) {
|
|
|
19947
20003
|
};
|
|
19948
20004
|
}
|
|
19949
20005
|
}
|
|
19950
|
-
function buildSubagentArgs(type, config, model, userPrompt, preloadedSkillsContent) {
|
|
19951
|
-
const args = [
|
|
19952
|
-
|
|
19953
|
-
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
"--
|
|
19960
|
-
|
|
19961
|
-
|
|
20006
|
+
function buildSubagentArgs(type, config, model, userPrompt, existingAgentId, existingConversationId, preloadedSkillsContent) {
|
|
20007
|
+
const args = [];
|
|
20008
|
+
const isDeployingExisting = Boolean(existingAgentId || existingConversationId);
|
|
20009
|
+
if (isDeployingExisting) {
|
|
20010
|
+
if (existingConversationId) {
|
|
20011
|
+
args.push("--conv", existingConversationId);
|
|
20012
|
+
} else if (existingAgentId) {
|
|
20013
|
+
args.push("--agent", existingAgentId);
|
|
20014
|
+
}
|
|
20015
|
+
args.push("--no-skills");
|
|
20016
|
+
} else {
|
|
20017
|
+
args.push("--new-agent", "--system", type);
|
|
20018
|
+
if (model) {
|
|
20019
|
+
args.push("--model", model);
|
|
20020
|
+
}
|
|
20021
|
+
}
|
|
20022
|
+
args.push("-p", userPrompt);
|
|
20023
|
+
args.push("--output-format", "stream-json");
|
|
19962
20024
|
const subagentMode = config.permissionMode;
|
|
19963
20025
|
const parentMode = permissionMode.getMode();
|
|
19964
20026
|
const modeToUse = subagentMode || parentMode;
|
|
@@ -19967,8 +20029,9 @@ function buildSubagentArgs(type, config, model, userPrompt, preloadedSkillsConte
|
|
|
19967
20029
|
}
|
|
19968
20030
|
const parentAllowedTools = cliPermissions.getAllowedTools();
|
|
19969
20031
|
const sessionAllowRules = sessionPermissions.getRules().allow || [];
|
|
20032
|
+
const subagentTools = config.allowedTools !== "all" && Array.isArray(config.allowedTools) ? config.allowedTools : [];
|
|
19970
20033
|
const combinedAllowedTools = [
|
|
19971
|
-
...new Set([...parentAllowedTools, ...sessionAllowRules])
|
|
20034
|
+
...new Set([...parentAllowedTools, ...sessionAllowRules, ...subagentTools])
|
|
19972
20035
|
];
|
|
19973
20036
|
if (combinedAllowedTools.length > 0) {
|
|
19974
20037
|
args.push("--allowedTools", combinedAllowedTools.join(","));
|
|
@@ -19977,20 +20040,22 @@ function buildSubagentArgs(type, config, model, userPrompt, preloadedSkillsConte
|
|
|
19977
20040
|
if (parentDisallowedTools.length > 0) {
|
|
19978
20041
|
args.push("--disallowedTools", parentDisallowedTools.join(","));
|
|
19979
20042
|
}
|
|
19980
|
-
if (
|
|
19981
|
-
|
|
19982
|
-
|
|
19983
|
-
|
|
20043
|
+
if (!isDeployingExisting) {
|
|
20044
|
+
if (config.memoryBlocks === "none") {
|
|
20045
|
+
args.push("--init-blocks", "none");
|
|
20046
|
+
} else if (Array.isArray(config.memoryBlocks) && config.memoryBlocks.length > 0) {
|
|
20047
|
+
args.push("--init-blocks", config.memoryBlocks.join(","));
|
|
20048
|
+
}
|
|
19984
20049
|
}
|
|
19985
20050
|
if (config.allowedTools !== "all" && Array.isArray(config.allowedTools) && config.allowedTools.length > 0) {
|
|
19986
20051
|
args.push("--tools", config.allowedTools.join(","));
|
|
19987
20052
|
}
|
|
19988
|
-
if (preloadedSkillsContent) {
|
|
20053
|
+
if (!isDeployingExisting && preloadedSkillsContent) {
|
|
19989
20054
|
args.push("--block-value", `loaded_skills=${preloadedSkillsContent}`);
|
|
19990
20055
|
}
|
|
19991
20056
|
return args;
|
|
19992
20057
|
}
|
|
19993
|
-
async function executeSubagent(type, config, model, userPrompt, baseURL, subagentId, isRetry = false, signal) {
|
|
20058
|
+
async function executeSubagent(type, config, model, userPrompt, baseURL, subagentId, isRetry = false, signal, existingAgentId, existingConversationId) {
|
|
19994
20059
|
if (signal?.aborted) {
|
|
19995
20060
|
return {
|
|
19996
20061
|
agentId: "",
|
|
@@ -19999,14 +20064,17 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
|
|
|
19999
20064
|
error: INTERRUPTED_BY_USER
|
|
20000
20065
|
};
|
|
20001
20066
|
}
|
|
20002
|
-
|
|
20067
|
+
if (model) {
|
|
20068
|
+
updateSubagent(subagentId, { model });
|
|
20069
|
+
}
|
|
20003
20070
|
try {
|
|
20004
20071
|
let preloadedSkillsContent;
|
|
20005
20072
|
if (config.skills && config.skills.length > 0) {
|
|
20006
20073
|
preloadedSkillsContent = await preloadSkillsContent(config.skills, SKILLS_DIR);
|
|
20007
20074
|
}
|
|
20008
|
-
const cliArgs = buildSubagentArgs(type, config, model, userPrompt, preloadedSkillsContent);
|
|
20009
|
-
const
|
|
20075
|
+
const cliArgs = buildSubagentArgs(type, config, model, userPrompt, existingAgentId, existingConversationId, preloadedSkillsContent);
|
|
20076
|
+
const currentScript = process.argv[1] || "";
|
|
20077
|
+
const lettaCmd = process.env.LETTA_CODE_BIN || (currentScript.endsWith(".js") ? currentScript : null) || (currentScript.includes("src/index.ts") ? "./letta.js" : null) || "letta";
|
|
20010
20078
|
let parentAgentId;
|
|
20011
20079
|
try {
|
|
20012
20080
|
parentAgentId = getCurrentAgentId();
|
|
@@ -20028,7 +20096,8 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
|
|
|
20028
20096
|
const stdoutChunks = [];
|
|
20029
20097
|
const stderrChunks = [];
|
|
20030
20098
|
const state = {
|
|
20031
|
-
agentId: null,
|
|
20099
|
+
agentId: existingAgentId || null,
|
|
20100
|
+
conversationId: existingConversationId || null,
|
|
20032
20101
|
finalResult: null,
|
|
20033
20102
|
finalError: null,
|
|
20034
20103
|
resultStats: null,
|
|
@@ -20055,6 +20124,7 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
|
|
|
20055
20124
|
if (wasAborted) {
|
|
20056
20125
|
return {
|
|
20057
20126
|
agentId: state.agentId || "",
|
|
20127
|
+
conversationId: state.conversationId || undefined,
|
|
20058
20128
|
report: "",
|
|
20059
20129
|
success: false,
|
|
20060
20130
|
error: INTERRUPTED_BY_USER
|
|
@@ -20070,6 +20140,7 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
|
|
|
20070
20140
|
}
|
|
20071
20141
|
return {
|
|
20072
20142
|
agentId: state.agentId || "",
|
|
20143
|
+
conversationId: state.conversationId || undefined,
|
|
20073
20144
|
report: "",
|
|
20074
20145
|
success: false,
|
|
20075
20146
|
error: stderr || `Subagent exited with code ${exitCode}`
|
|
@@ -20078,6 +20149,7 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
|
|
|
20078
20149
|
if (state.finalResult !== null) {
|
|
20079
20150
|
return {
|
|
20080
20151
|
agentId: state.agentId || "",
|
|
20152
|
+
conversationId: state.conversationId || undefined,
|
|
20081
20153
|
report: state.finalResult,
|
|
20082
20154
|
success: !state.finalError,
|
|
20083
20155
|
error: state.finalError || undefined,
|
|
@@ -20087,6 +20159,7 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
|
|
|
20087
20159
|
if (state.finalError) {
|
|
20088
20160
|
return {
|
|
20089
20161
|
agentId: state.agentId || "",
|
|
20162
|
+
conversationId: state.conversationId || undefined,
|
|
20090
20163
|
report: "",
|
|
20091
20164
|
success: false,
|
|
20092
20165
|
error: state.finalError,
|
|
@@ -20112,7 +20185,17 @@ function getBaseURL() {
|
|
|
20112
20185
|
}
|
|
20113
20186
|
return baseURL;
|
|
20114
20187
|
}
|
|
20115
|
-
|
|
20188
|
+
function buildDeploySystemReminder(senderAgentName, senderAgentId, subagentType) {
|
|
20189
|
+
const toolDescription = subagentType === "explore" ? "read-only tools (Read, Glob, Grep)" : "local tools (Bash, Read, Write, Edit, etc.)";
|
|
20190
|
+
return `<system-reminder>
|
|
20191
|
+
This task is from "${senderAgentName}" (agent ID: ${senderAgentId}), which deployed you as a subagent inside the Letta Code CLI (docs.letta.com/letta-code).
|
|
20192
|
+
You have access to ${toolDescription} in their codebase.
|
|
20193
|
+
Your final message will be returned to the caller.
|
|
20194
|
+
</system-reminder>
|
|
20195
|
+
|
|
20196
|
+
`;
|
|
20197
|
+
}
|
|
20198
|
+
async function spawnSubagent(type, prompt, userModel, subagentId, signal, existingAgentId, existingConversationId) {
|
|
20116
20199
|
const allConfigs = await getAllSubagentConfigs();
|
|
20117
20200
|
const config = allConfigs[type];
|
|
20118
20201
|
if (!config) {
|
|
@@ -20123,9 +20206,20 @@ async function spawnSubagent(type, prompt, userModel, subagentId, signal) {
|
|
|
20123
20206
|
error: `Unknown subagent type: ${type}`
|
|
20124
20207
|
};
|
|
20125
20208
|
}
|
|
20126
|
-
const
|
|
20209
|
+
const isDeployingExisting = Boolean(existingAgentId || existingConversationId);
|
|
20210
|
+
const model = isDeployingExisting ? null : userModel || config.recommendedModel;
|
|
20127
20211
|
const baseURL = getBaseURL();
|
|
20128
|
-
|
|
20212
|
+
let finalPrompt = prompt;
|
|
20213
|
+
if (isDeployingExisting) {
|
|
20214
|
+
try {
|
|
20215
|
+
const parentAgentId = getCurrentAgentId();
|
|
20216
|
+
const client = await getClient2();
|
|
20217
|
+
const parentAgent = await client.agents.retrieve(parentAgentId);
|
|
20218
|
+
const systemReminder = buildDeploySystemReminder(parentAgent.name, parentAgentId, type);
|
|
20219
|
+
finalPrompt = systemReminder + prompt;
|
|
20220
|
+
} catch {}
|
|
20221
|
+
}
|
|
20222
|
+
const result = await executeSubagent(type, config, model, finalPrompt, baseURL, subagentId, false, signal, existingAgentId, existingConversationId);
|
|
20129
20223
|
return result;
|
|
20130
20224
|
}
|
|
20131
20225
|
var init_manager2 = __esm(async () => {
|
|
@@ -20162,19 +20256,27 @@ async function task(args) {
|
|
|
20162
20256
|
const errorSuffix = errors.length > 0 ? `, ${errors.length} error(s)` : "";
|
|
20163
20257
|
return `Refreshed subagents list: found ${totalCount} total (${customCount} custom)${errorSuffix}`;
|
|
20164
20258
|
}
|
|
20165
|
-
|
|
20166
|
-
|
|
20259
|
+
const isDeployingExisting = Boolean(args.agent_id || args.conversation_id);
|
|
20260
|
+
if (isDeployingExisting) {
|
|
20261
|
+
validateRequiredParams(args, ["prompt", "description"], "Task");
|
|
20262
|
+
} else {
|
|
20263
|
+
validateRequiredParams(args, ["subagent_type", "prompt", "description"], "Task");
|
|
20264
|
+
}
|
|
20167
20265
|
const prompt = args.prompt;
|
|
20168
20266
|
const description = args.description;
|
|
20267
|
+
const subagent_type = isDeployingExisting ? args.subagent_type || "general-purpose" : args.subagent_type;
|
|
20169
20268
|
const allConfigs = await getAllSubagentConfigs();
|
|
20170
20269
|
if (!(subagent_type in allConfigs)) {
|
|
20171
20270
|
const available = Object.keys(allConfigs).join(", ");
|
|
20172
20271
|
return `Error: Invalid subagent type "${subagent_type}". Available types: ${available}`;
|
|
20173
20272
|
}
|
|
20273
|
+
if (isDeployingExisting && !VALID_DEPLOY_TYPES.has(subagent_type)) {
|
|
20274
|
+
return `Error: When deploying an existing agent, subagent_type must be "explore" (read-only) or "general-purpose" (read-write). Got: "${subagent_type}"`;
|
|
20275
|
+
}
|
|
20174
20276
|
const subagentId = generateSubagentId();
|
|
20175
20277
|
registerSubagent(subagentId, subagent_type, description, toolCallId);
|
|
20176
20278
|
try {
|
|
20177
|
-
const result = await spawnSubagent(subagent_type, prompt, model, subagentId, signal);
|
|
20279
|
+
const result = await spawnSubagent(subagent_type, prompt, model, subagentId, signal, args.agent_id, args.conversation_id);
|
|
20178
20280
|
completeSubagent(subagentId, {
|
|
20179
20281
|
success: result.success,
|
|
20180
20282
|
error: result.error,
|
|
@@ -20185,7 +20287,8 @@ async function task(args) {
|
|
|
20185
20287
|
}
|
|
20186
20288
|
const header = [
|
|
20187
20289
|
`subagent_type=${subagent_type}`,
|
|
20188
|
-
result.agentId ? `agent_id=${result.agentId}` : undefined
|
|
20290
|
+
result.agentId ? `agent_id=${result.agentId}` : undefined,
|
|
20291
|
+
result.conversationId ? `conversation_id=${result.conversationId}` : undefined
|
|
20189
20292
|
].filter(Boolean).join(" ");
|
|
20190
20293
|
const fullOutput = `${header}
|
|
20191
20294
|
|
|
@@ -20199,11 +20302,13 @@ ${result.report}`;
|
|
|
20199
20302
|
return `Error: ${errorMessage}`;
|
|
20200
20303
|
}
|
|
20201
20304
|
}
|
|
20305
|
+
var VALID_DEPLOY_TYPES;
|
|
20202
20306
|
var init_Task2 = __esm(async () => {
|
|
20203
20307
|
init_subagents();
|
|
20204
20308
|
init_subagentState();
|
|
20205
20309
|
init_truncation();
|
|
20206
20310
|
await init_manager2();
|
|
20311
|
+
VALID_DEPLOY_TYPES = new Set(["explore", "general-purpose"]);
|
|
20207
20312
|
});
|
|
20208
20313
|
|
|
20209
20314
|
// src/tools/impl/TodoWrite.ts
|
|
@@ -53417,7 +53522,8 @@ async function handleHeadlessCommand(argv, model, skillsDirectory) {
|
|
|
53417
53522
|
sleeptime: { type: "boolean" },
|
|
53418
53523
|
"init-blocks": { type: "string" },
|
|
53419
53524
|
"base-tools": { type: "string" },
|
|
53420
|
-
"from-af": { type: "string" }
|
|
53525
|
+
"from-af": { type: "string" },
|
|
53526
|
+
"no-skills": { type: "boolean" }
|
|
53421
53527
|
},
|
|
53422
53528
|
strict: false,
|
|
53423
53529
|
allowPositionals: true
|
|
@@ -53732,7 +53838,18 @@ Usage: letta -p "..." --new-agent`);
|
|
|
53732
53838
|
}
|
|
53733
53839
|
}
|
|
53734
53840
|
let conversationId;
|
|
53735
|
-
const
|
|
53841
|
+
const noSkillsFlag = values["no-skills"];
|
|
53842
|
+
const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
|
|
53843
|
+
if (!noSkillsFlag && !isSubagent) {
|
|
53844
|
+
const createdBlocks = await ensureSkillsBlocks(agent.id);
|
|
53845
|
+
if (createdBlocks.length > 0) {
|
|
53846
|
+
console.log("Created missing skills blocks for agent compatibility");
|
|
53847
|
+
}
|
|
53848
|
+
}
|
|
53849
|
+
let isolatedBlockLabels = [];
|
|
53850
|
+
if (!noSkillsFlag) {
|
|
53851
|
+
isolatedBlockLabels = initBlocks === undefined ? [...ISOLATED_BLOCK_LABELS] : ISOLATED_BLOCK_LABELS.filter((label) => initBlocks.includes(label));
|
|
53852
|
+
}
|
|
53736
53853
|
if (specifiedConversationId) {
|
|
53737
53854
|
if (specifiedConversationId === "default") {
|
|
53738
53855
|
conversationId = "default";
|
|
@@ -53778,7 +53895,6 @@ Usage: letta -p "..." --new-agent`);
|
|
|
53778
53895
|
conversationId = conversation.id;
|
|
53779
53896
|
}
|
|
53780
53897
|
markMilestone("HEADLESS_CONVERSATION_READY");
|
|
53781
|
-
const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
|
|
53782
53898
|
if (!isSubagent) {
|
|
53783
53899
|
await settingsManager.loadLocalProjectSettings();
|
|
53784
53900
|
settingsManager.setLocalLastSession({ agentId: agent.id, conversationId }, process.cwd());
|
|
@@ -53787,24 +53903,22 @@ Usage: letta -p "..." --new-agent`);
|
|
|
53787
53903
|
conversationId
|
|
53788
53904
|
});
|
|
53789
53905
|
}
|
|
53790
|
-
const createdBlocks = await ensureSkillsBlocks(agent.id);
|
|
53791
|
-
if (createdBlocks.length > 0) {
|
|
53792
|
-
console.log("Created missing skills blocks for agent compatibility");
|
|
53793
|
-
}
|
|
53794
53906
|
setAgentContext2(agent.id, skillsDirectory);
|
|
53795
|
-
|
|
53796
|
-
|
|
53797
|
-
|
|
53798
|
-
|
|
53799
|
-
|
|
53800
|
-
|
|
53801
|
-
|
|
53802
|
-
|
|
53803
|
-
|
|
53804
|
-
|
|
53805
|
-
|
|
53806
|
-
|
|
53807
|
-
|
|
53907
|
+
if (!noSkillsFlag && !isSubagent) {
|
|
53908
|
+
initializeLoadedSkillsFlag2().catch(() => {});
|
|
53909
|
+
(async () => {
|
|
53910
|
+
try {
|
|
53911
|
+
const { syncSkillsToAgent: syncSkillsToAgent3, SKILLS_DIR: SKILLS_DIR3 } = await Promise.resolve().then(() => (init_skills2(), exports_skills));
|
|
53912
|
+
const { join: join17 } = await import("node:path");
|
|
53913
|
+
const resolvedSkillsDirectory = skillsDirectory || join17(process.cwd(), SKILLS_DIR3);
|
|
53914
|
+
await syncSkillsToAgent3(client, agent.id, resolvedSkillsDirectory, {
|
|
53915
|
+
skipIfUnchanged: true
|
|
53916
|
+
});
|
|
53917
|
+
} catch (error) {
|
|
53918
|
+
console.warn(`[skills] Background sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
53919
|
+
}
|
|
53920
|
+
})();
|
|
53921
|
+
}
|
|
53808
53922
|
const outputFormat = values["output-format"] || "text";
|
|
53809
53923
|
const includePartialMessages = Boolean(values["include-partial-messages"]);
|
|
53810
53924
|
if (!["text", "json", "stream-json"].includes(outputFormat)) {
|
|
@@ -75139,6 +75253,7 @@ function App2({
|
|
|
75139
75253
|
const [autoDeniedApprovals, setAutoDeniedApprovals] = import_react84.useState([]);
|
|
75140
75254
|
const executingToolCallIdsRef = import_react84.useRef([]);
|
|
75141
75255
|
const interruptQueuedRef = import_react84.useRef(false);
|
|
75256
|
+
const toolResultsInFlightRef = import_react84.useRef(false);
|
|
75142
75257
|
const autoAllowedExecutionRef = import_react84.useRef(null);
|
|
75143
75258
|
const queuedApprovalMetadataRef = import_react84.useRef(null);
|
|
75144
75259
|
const queueApprovalResults = import_react84.useCallback((results, metadata) => {
|
|
@@ -76147,12 +76262,14 @@ ${newState.originalPrompt}`
|
|
|
76147
76262
|
}
|
|
76148
76263
|
setThinkingMessage(getRandomThinkingVerb());
|
|
76149
76264
|
refreshDerived();
|
|
76265
|
+
toolResultsInFlightRef.current = true;
|
|
76150
76266
|
await processConversation([
|
|
76151
76267
|
{
|
|
76152
76268
|
type: "approval",
|
|
76153
76269
|
approvals: allResults
|
|
76154
76270
|
}
|
|
76155
76271
|
], { allowReentry: true });
|
|
76272
|
+
toolResultsInFlightRef.current = false;
|
|
76156
76273
|
return;
|
|
76157
76274
|
}
|
|
76158
76275
|
if (waitingForQueueCancelRef.current) {
|
|
@@ -76198,6 +76315,7 @@ ${newState.originalPrompt}`
|
|
|
76198
76315
|
toolAbortControllerRef.current = null;
|
|
76199
76316
|
executingToolCallIdsRef.current = [];
|
|
76200
76317
|
autoAllowedExecutionRef.current = null;
|
|
76318
|
+
toolResultsInFlightRef.current = false;
|
|
76201
76319
|
}
|
|
76202
76320
|
}
|
|
76203
76321
|
if (userCancelledRef.current || abortControllerRef.current?.signal.aborted) {
|
|
@@ -76423,7 +76541,8 @@ ${newState.originalPrompt}`
|
|
|
76423
76541
|
setMessageQueue([]);
|
|
76424
76542
|
}, []);
|
|
76425
76543
|
const handleInterrupt = import_react84.useCallback(async () => {
|
|
76426
|
-
|
|
76544
|
+
const hasTrackedTools = executingToolCallIdsRef.current.length > 0 || autoAllowedExecutionRef.current?.results;
|
|
76545
|
+
if (isExecutingTool && toolAbortControllerRef.current && hasTrackedTools && !toolResultsInFlightRef.current) {
|
|
76427
76546
|
toolAbortControllerRef.current.abort();
|
|
76428
76547
|
conversationGenerationRef.current += 1;
|
|
76429
76548
|
processingConversationRef.current = 0;
|
|
@@ -76460,6 +76579,7 @@ ${newState.originalPrompt}`
|
|
|
76460
76579
|
userCancelledRef.current = true;
|
|
76461
76580
|
setStreaming(false);
|
|
76462
76581
|
setIsExecutingTool(false);
|
|
76582
|
+
toolResultsInFlightRef.current = false;
|
|
76463
76583
|
refreshDerived();
|
|
76464
76584
|
setTimeout(() => {
|
|
76465
76585
|
userCancelledRef.current = false;
|
|
@@ -76485,6 +76605,7 @@ ${newState.originalPrompt}`
|
|
|
76485
76605
|
conversationGenerationRef.current += 1;
|
|
76486
76606
|
processingConversationRef.current = 0;
|
|
76487
76607
|
setStreaming(false);
|
|
76608
|
+
toolResultsInFlightRef.current = false;
|
|
76488
76609
|
if (!toolsCancelled) {
|
|
76489
76610
|
appendError(INTERRUPT_MESSAGE, true);
|
|
76490
76611
|
}
|
|
@@ -76900,9 +77021,11 @@ ${newState.originalPrompt}`
|
|
|
76900
77021
|
return { blocked: false };
|
|
76901
77022
|
}
|
|
76902
77023
|
if (allResults.length > 0) {
|
|
77024
|
+
toolResultsInFlightRef.current = true;
|
|
76903
77025
|
await processConversation([
|
|
76904
77026
|
{ type: "approval", approvals: allResults }
|
|
76905
77027
|
]);
|
|
77028
|
+
toolResultsInFlightRef.current = false;
|
|
76906
77029
|
}
|
|
76907
77030
|
} finally {
|
|
76908
77031
|
if (shouldTrackAutoAllowed) {
|
|
@@ -76910,6 +77033,7 @@ ${newState.originalPrompt}`
|
|
|
76910
77033
|
toolAbortControllerRef.current = null;
|
|
76911
77034
|
executingToolCallIdsRef.current = [];
|
|
76912
77035
|
autoAllowedExecutionRef.current = null;
|
|
77036
|
+
toolResultsInFlightRef.current = false;
|
|
76913
77037
|
}
|
|
76914
77038
|
}
|
|
76915
77039
|
return { blocked: false };
|
|
@@ -78564,7 +78688,9 @@ DO NOT respond to these messages or otherwise consider them in your response unl
|
|
|
78564
78688
|
content: messageContent
|
|
78565
78689
|
}
|
|
78566
78690
|
];
|
|
78691
|
+
toolResultsInFlightRef.current = true;
|
|
78567
78692
|
await processConversation(initialInput2);
|
|
78693
|
+
toolResultsInFlightRef.current = false;
|
|
78568
78694
|
clearPlaceholdersInText(msg);
|
|
78569
78695
|
return { submitted: true };
|
|
78570
78696
|
} finally {
|
|
@@ -78573,6 +78699,7 @@ DO NOT respond to these messages or otherwise consider them in your response unl
|
|
|
78573
78699
|
toolAbortControllerRef.current = null;
|
|
78574
78700
|
executingToolCallIdsRef.current = [];
|
|
78575
78701
|
autoAllowedExecutionRef.current = null;
|
|
78702
|
+
toolResultsInFlightRef.current = false;
|
|
78576
78703
|
}
|
|
78577
78704
|
}
|
|
78578
78705
|
} else {
|
|
@@ -78879,18 +79006,21 @@ DO NOT respond to these messages or otherwise consider them in your response unl
|
|
|
78879
79006
|
waitingForQueueCancelRef.current = false;
|
|
78880
79007
|
queueSnapshotRef.current = [];
|
|
78881
79008
|
} else {
|
|
79009
|
+
toolResultsInFlightRef.current = true;
|
|
78882
79010
|
await processConversation([
|
|
78883
79011
|
{
|
|
78884
79012
|
type: "approval",
|
|
78885
79013
|
approvals: allResults
|
|
78886
79014
|
}
|
|
78887
79015
|
]);
|
|
79016
|
+
toolResultsInFlightRef.current = false;
|
|
78888
79017
|
}
|
|
78889
79018
|
} finally {
|
|
78890
79019
|
setIsExecutingTool(false);
|
|
78891
79020
|
toolAbortControllerRef.current = null;
|
|
78892
79021
|
executingToolCallIdsRef.current = [];
|
|
78893
79022
|
interruptQueuedRef.current = false;
|
|
79023
|
+
toolResultsInFlightRef.current = false;
|
|
78894
79024
|
}
|
|
78895
79025
|
}, [
|
|
78896
79026
|
approvalResults,
|
|
@@ -83936,7 +84066,8 @@ async function main() {
|
|
|
83936
84066
|
"include-partial-messages": { type: "boolean" },
|
|
83937
84067
|
skills: { type: "string" },
|
|
83938
84068
|
sleeptime: { type: "boolean" },
|
|
83939
|
-
"from-af": { type: "string" }
|
|
84069
|
+
"from-af": { type: "string" },
|
|
84070
|
+
"no-skills": { type: "boolean" }
|
|
83940
84071
|
},
|
|
83941
84072
|
strict: true,
|
|
83942
84073
|
allowPositionals: true
|
|
@@ -84879,4 +85010,4 @@ Error during initialization: ${message}`);
|
|
|
84879
85010
|
}
|
|
84880
85011
|
main();
|
|
84881
85012
|
|
|
84882
|
-
//# debugId=
|
|
85013
|
+
//# debugId=A2D04DEC6336411264756E2164756E21
|
package/package.json
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: messaging-agents
|
|
3
|
+
description: Send messages to other agents on your server. Use when you need to communicate with, query, or delegate tasks to another agent.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Messaging Agents
|
|
7
|
+
|
|
8
|
+
This skill enables you to send messages to other agents on the same Letta server using the thread-safe conversations API.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- You need to ask another agent a question
|
|
13
|
+
- You want to query an agent that has specialized knowledge
|
|
14
|
+
- You need information that another agent has in their memory
|
|
15
|
+
- You want to coordinate with another agent on a task
|
|
16
|
+
|
|
17
|
+
## What the Target Agent Can and Cannot Do
|
|
18
|
+
|
|
19
|
+
**The target agent CANNOT:**
|
|
20
|
+
- Access your local environment (read/write files in your codebase)
|
|
21
|
+
- Execute shell commands on your machine
|
|
22
|
+
- Use your tools (Bash, Read, Write, Edit, etc.)
|
|
23
|
+
|
|
24
|
+
**The target agent CAN:**
|
|
25
|
+
- Use their own tools (whatever they have configured)
|
|
26
|
+
- Access their own memory blocks
|
|
27
|
+
- Make API calls if they have web/API tools
|
|
28
|
+
- Search the web if they have web search tools
|
|
29
|
+
- Respond with information from their knowledge/memory
|
|
30
|
+
|
|
31
|
+
**Important:** This skill is for *communication* with other agents, not *delegation* of local work. The target agent runs in their own environment and cannot interact with your codebase.
|
|
32
|
+
|
|
33
|
+
**Need local access?** If you need the target agent to access your local environment (read/write files, run commands), use the Task tool instead to deploy them as a subagent:
|
|
34
|
+
```typescript
|
|
35
|
+
Task({
|
|
36
|
+
agent_id: "agent-xxx", // Deploy this existing agent
|
|
37
|
+
subagent_type: "explore", // "explore" = read-only, "general-purpose" = read-write
|
|
38
|
+
prompt: "Look at the code in src/ and tell me about the architecture"
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
This gives the agent access to your codebase while running as a subagent.
|
|
42
|
+
|
|
43
|
+
## Finding an Agent to Message
|
|
44
|
+
|
|
45
|
+
If you don't have a specific agent ID, use these skills to find one:
|
|
46
|
+
|
|
47
|
+
### By Name or Tags
|
|
48
|
+
Load the `finding-agents` skill to search for agents:
|
|
49
|
+
```bash
|
|
50
|
+
npx tsx <FINDING_AGENTS_SKILL_DIR>/scripts/find-agents.ts --query "agent-name"
|
|
51
|
+
npx tsx <FINDING_AGENTS_SKILL_DIR>/scripts/find-agents.ts --tags "origin:letta-code"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### By Topic They Discussed
|
|
55
|
+
Load the `searching-messages` skill to find which agent worked on something:
|
|
56
|
+
```bash
|
|
57
|
+
npx tsx <SEARCHING_MESSAGES_SKILL_DIR>/scripts/search-messages.ts --query "topic" --all-agents
|
|
58
|
+
```
|
|
59
|
+
Results include `agent_id` for each matching message.
|
|
60
|
+
|
|
61
|
+
## Script Usage
|
|
62
|
+
|
|
63
|
+
### Starting a New Conversation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx tsx <SKILL_DIR>/scripts/start-conversation.ts --agent-id <id> --message "<text>"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Arguments:**
|
|
70
|
+
| Arg | Required | Description |
|
|
71
|
+
|-----|----------|-------------|
|
|
72
|
+
| `--agent-id <id>` | Yes | Target agent ID to message |
|
|
73
|
+
| `--message <text>` | Yes | Message to send |
|
|
74
|
+
| `--timeout <ms>` | No | Max wait time in ms (default: 120000) |
|
|
75
|
+
|
|
76
|
+
**Example:**
|
|
77
|
+
```bash
|
|
78
|
+
npx tsx <SKILL_DIR>/scripts/start-conversation.ts \
|
|
79
|
+
--agent-id agent-abc123 \
|
|
80
|
+
--message "What do you know about the authentication system?"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Response:**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"conversation_id": "conversation-xyz789",
|
|
87
|
+
"response": "The authentication system uses JWT tokens...",
|
|
88
|
+
"agent_id": "agent-abc123",
|
|
89
|
+
"agent_name": "BackendExpert"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Continuing a Conversation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx tsx <SKILL_DIR>/scripts/continue-conversation.ts --conversation-id <id> --message "<text>"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Arguments:**
|
|
100
|
+
| Arg | Required | Description |
|
|
101
|
+
|-----|----------|-------------|
|
|
102
|
+
| `--conversation-id <id>` | Yes | Existing conversation ID |
|
|
103
|
+
| `--message <text>` | Yes | Follow-up message to send |
|
|
104
|
+
| `--timeout <ms>` | No | Max wait time in ms (default: 120000) |
|
|
105
|
+
|
|
106
|
+
**Example:**
|
|
107
|
+
```bash
|
|
108
|
+
npx tsx <SKILL_DIR>/scripts/continue-conversation.ts \
|
|
109
|
+
--conversation-id conversation-xyz789 \
|
|
110
|
+
--message "Can you explain more about the token refresh flow?"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Understanding the Response
|
|
114
|
+
|
|
115
|
+
- Scripts return only the **final assistant message** (not tool calls or reasoning)
|
|
116
|
+
- The target agent may use tools, think, and reason - but you only see their final response
|
|
117
|
+
- To see the full conversation transcript (including tool calls), use the `searching-messages` skill with `--agent-id` targeting the other agent
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
When you send a message, the target agent receives it with a system reminder:
|
|
122
|
+
```
|
|
123
|
+
<system-reminder>
|
|
124
|
+
This message is from "YourAgentName" (agent ID: agent-xxx), an agent currently running inside the Letta Code CLI (docs.letta.com/letta-code).
|
|
125
|
+
The sender will only see the final message you generate (not tool calls or reasoning).
|
|
126
|
+
If you need to share detailed information, include it in your response text.
|
|
127
|
+
</system-reminder>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This helps the target agent understand the context and format their response appropriately.
|
|
131
|
+
|
|
132
|
+
## Related Skills
|
|
133
|
+
|
|
134
|
+
- **finding-agents**: Find agents by name, tags, or fuzzy search
|
|
135
|
+
- **searching-messages**: Search past messages across agents, or view full conversation transcripts
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Continue Conversation - Send a follow-up message to an existing conversation
|
|
4
|
+
*
|
|
5
|
+
* This script is standalone and can be run outside the CLI process.
|
|
6
|
+
* It reads auth from LETTA_API_KEY env var or ~/.letta/settings.json.
|
|
7
|
+
* It reads sender agent ID from LETTA_AGENT_ID env var.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx continue-conversation.ts --conversation-id <id> --message "<text>"
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --conversation-id <id> Existing conversation ID (required)
|
|
14
|
+
* --message <text> Message to send (required)
|
|
15
|
+
* --timeout <ms> Max wait time in ms (default: 120000)
|
|
16
|
+
*
|
|
17
|
+
* Output:
|
|
18
|
+
* JSON with conversation_id, response, agent_id, agent_name
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
|
|
26
|
+
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
|
|
27
|
+
// (ES module imports don't respect NODE_PATH, but require does)
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const Letta = require("@letta-ai/letta-client")
|
|
30
|
+
.default as typeof import("@letta-ai/letta-client").default;
|
|
31
|
+
type LettaClient = InstanceType<typeof Letta>;
|
|
32
|
+
|
|
33
|
+
interface ContinueConversationOptions {
|
|
34
|
+
conversationId: string;
|
|
35
|
+
message: string;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ContinueConversationResult {
|
|
40
|
+
conversation_id: string;
|
|
41
|
+
response: string;
|
|
42
|
+
agent_id: string;
|
|
43
|
+
agent_name: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get API key from env var or settings file
|
|
48
|
+
*/
|
|
49
|
+
function getApiKey(): string {
|
|
50
|
+
if (process.env.LETTA_API_KEY) {
|
|
51
|
+
return process.env.LETTA_API_KEY;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const settingsPath = join(homedir(), ".letta", "settings.json");
|
|
55
|
+
try {
|
|
56
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
57
|
+
if (settings.env?.LETTA_API_KEY) {
|
|
58
|
+
return settings.env.LETTA_API_KEY;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Settings file doesn't exist or is invalid
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error(
|
|
65
|
+
"No LETTA_API_KEY found. Set the env var or run the Letta CLI to authenticate.",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the sender agent ID from env var
|
|
71
|
+
*/
|
|
72
|
+
function getSenderAgentId(): string {
|
|
73
|
+
if (process.env.LETTA_AGENT_ID) {
|
|
74
|
+
return process.env.LETTA_AGENT_ID;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(
|
|
77
|
+
"No LETTA_AGENT_ID found. This script should be run from within a Letta Code session.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a Letta client with auth from env/settings
|
|
83
|
+
*/
|
|
84
|
+
function createClient(): LettaClient {
|
|
85
|
+
return new Letta({ apiKey: getApiKey() });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build the system reminder prefix for the message
|
|
90
|
+
*/
|
|
91
|
+
function buildSystemReminder(
|
|
92
|
+
senderAgentName: string,
|
|
93
|
+
senderAgentId: string,
|
|
94
|
+
): string {
|
|
95
|
+
return `<system-reminder>
|
|
96
|
+
This message is from "${senderAgentName}" (agent ID: ${senderAgentId}), an agent currently running inside the Letta Code CLI (docs.letta.com/letta-code).
|
|
97
|
+
The sender will only see the final message you generate (not tool calls or reasoning).
|
|
98
|
+
If you need to share detailed information, include it in your response text.
|
|
99
|
+
</system-reminder>
|
|
100
|
+
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Continue an existing conversation by sending a follow-up message
|
|
106
|
+
* @param client - Letta client instance
|
|
107
|
+
* @param options - Options including conversation ID and message
|
|
108
|
+
* @returns Conversation result with response and metadata
|
|
109
|
+
*/
|
|
110
|
+
export async function continueConversation(
|
|
111
|
+
client: LettaClient,
|
|
112
|
+
options: ContinueConversationOptions,
|
|
113
|
+
): Promise<ContinueConversationResult> {
|
|
114
|
+
const { conversationId, message } = options;
|
|
115
|
+
|
|
116
|
+
// 1. Fetch conversation to get agent_id and validate it exists
|
|
117
|
+
const conversation = await client.conversations.retrieve(conversationId);
|
|
118
|
+
|
|
119
|
+
// 2. Fetch target agent to get name
|
|
120
|
+
const targetAgent = await client.agents.retrieve(conversation.agent_id);
|
|
121
|
+
|
|
122
|
+
// 3. Fetch sender agent to get name for system reminder
|
|
123
|
+
const senderAgentId = getSenderAgentId();
|
|
124
|
+
const senderAgent = await client.agents.retrieve(senderAgentId);
|
|
125
|
+
|
|
126
|
+
// 4. Build message with system reminder prefix
|
|
127
|
+
const systemReminder = buildSystemReminder(senderAgent.name, senderAgentId);
|
|
128
|
+
const fullMessage = systemReminder + message;
|
|
129
|
+
|
|
130
|
+
// 5. Send message and consume the stream
|
|
131
|
+
// Note: conversations.messages.create always returns a Stream
|
|
132
|
+
const stream = await client.conversations.messages.create(conversationId, {
|
|
133
|
+
input: fullMessage,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// 6. Consume stream and extract final assistant message
|
|
137
|
+
let finalResponse = "";
|
|
138
|
+
for await (const chunk of stream) {
|
|
139
|
+
if (chunk.message_type === "assistant_message") {
|
|
140
|
+
// Content can be string or array of content parts
|
|
141
|
+
const content = chunk.content;
|
|
142
|
+
if (typeof content === "string") {
|
|
143
|
+
finalResponse += content;
|
|
144
|
+
} else if (Array.isArray(content)) {
|
|
145
|
+
for (const part of content) {
|
|
146
|
+
if (
|
|
147
|
+
typeof part === "object" &&
|
|
148
|
+
part !== null &&
|
|
149
|
+
"type" in part &&
|
|
150
|
+
part.type === "text" &&
|
|
151
|
+
"text" in part
|
|
152
|
+
) {
|
|
153
|
+
finalResponse += (part as { text: string }).text;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
conversation_id: conversationId,
|
|
162
|
+
response: finalResponse,
|
|
163
|
+
agent_id: targetAgent.id,
|
|
164
|
+
agent_name: targetAgent.name,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function parseArgs(args: string[]): ContinueConversationOptions {
|
|
169
|
+
const conversationIdIndex = args.indexOf("--conversation-id");
|
|
170
|
+
if (conversationIdIndex === -1 || conversationIdIndex + 1 >= args.length) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Missing required argument: --conversation-id <conversation-id>",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const conversationId = args[conversationIdIndex + 1] as string;
|
|
176
|
+
|
|
177
|
+
const messageIndex = args.indexOf("--message");
|
|
178
|
+
if (messageIndex === -1 || messageIndex + 1 >= args.length) {
|
|
179
|
+
throw new Error("Missing required argument: --message <text>");
|
|
180
|
+
}
|
|
181
|
+
const message = args[messageIndex + 1] as string;
|
|
182
|
+
|
|
183
|
+
const options: ContinueConversationOptions = { conversationId, message };
|
|
184
|
+
|
|
185
|
+
const timeoutIndex = args.indexOf("--timeout");
|
|
186
|
+
if (timeoutIndex !== -1 && timeoutIndex + 1 < args.length) {
|
|
187
|
+
const timeout = Number.parseInt(args[timeoutIndex + 1] as string, 10);
|
|
188
|
+
if (!Number.isNaN(timeout)) {
|
|
189
|
+
options.timeout = timeout;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return options;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// CLI entry point - check if this file is being run directly
|
|
197
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
198
|
+
if (isMainModule) {
|
|
199
|
+
(async () => {
|
|
200
|
+
try {
|
|
201
|
+
const options = parseArgs(process.argv.slice(2));
|
|
202
|
+
const client = createClient();
|
|
203
|
+
const result = await continueConversation(client, options);
|
|
204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
|
+
process.exit(0);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error(
|
|
208
|
+
"Error:",
|
|
209
|
+
error instanceof Error ? error.message : String(error),
|
|
210
|
+
);
|
|
211
|
+
console.error(`
|
|
212
|
+
Usage: npx tsx continue-conversation.ts --conversation-id <id> --message "<text>"
|
|
213
|
+
|
|
214
|
+
Options:
|
|
215
|
+
--conversation-id <id> Existing conversation ID (required)
|
|
216
|
+
--message <text> Message to send (required)
|
|
217
|
+
--timeout <ms> Max wait time in ms (default: 120000)
|
|
218
|
+
`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Start Conversation - Start a new conversation with an agent and send a message
|
|
4
|
+
*
|
|
5
|
+
* This script is standalone and can be run outside the CLI process.
|
|
6
|
+
* It reads auth from LETTA_API_KEY env var or ~/.letta/settings.json.
|
|
7
|
+
* It reads sender agent ID from LETTA_AGENT_ID env var.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx start-conversation.ts --agent-id <id> --message "<text>"
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --agent-id <id> Target agent ID to message (required)
|
|
14
|
+
* --message <text> Message to send (required)
|
|
15
|
+
* --timeout <ms> Max wait time in ms (default: 120000)
|
|
16
|
+
*
|
|
17
|
+
* Output:
|
|
18
|
+
* JSON with conversation_id, response, agent_id, agent_name
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
|
|
26
|
+
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
|
|
27
|
+
// (ES module imports don't respect NODE_PATH, but require does)
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const Letta = require("@letta-ai/letta-client")
|
|
30
|
+
.default as typeof import("@letta-ai/letta-client").default;
|
|
31
|
+
type LettaClient = InstanceType<typeof Letta>;
|
|
32
|
+
|
|
33
|
+
interface StartConversationOptions {
|
|
34
|
+
agentId: string;
|
|
35
|
+
message: string;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface StartConversationResult {
|
|
40
|
+
conversation_id: string;
|
|
41
|
+
response: string;
|
|
42
|
+
agent_id: string;
|
|
43
|
+
agent_name: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get API key from env var or settings file
|
|
48
|
+
*/
|
|
49
|
+
function getApiKey(): string {
|
|
50
|
+
if (process.env.LETTA_API_KEY) {
|
|
51
|
+
return process.env.LETTA_API_KEY;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const settingsPath = join(homedir(), ".letta", "settings.json");
|
|
55
|
+
try {
|
|
56
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
57
|
+
if (settings.env?.LETTA_API_KEY) {
|
|
58
|
+
return settings.env.LETTA_API_KEY;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Settings file doesn't exist or is invalid
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error(
|
|
65
|
+
"No LETTA_API_KEY found. Set the env var or run the Letta CLI to authenticate.",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the sender agent ID from env var
|
|
71
|
+
*/
|
|
72
|
+
function getSenderAgentId(): string {
|
|
73
|
+
if (process.env.LETTA_AGENT_ID) {
|
|
74
|
+
return process.env.LETTA_AGENT_ID;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(
|
|
77
|
+
"No LETTA_AGENT_ID found. This script should be run from within a Letta Code session.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a Letta client with auth from env/settings
|
|
83
|
+
*/
|
|
84
|
+
function createClient(): LettaClient {
|
|
85
|
+
return new Letta({ apiKey: getApiKey() });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build the system reminder prefix for the message
|
|
90
|
+
*/
|
|
91
|
+
function buildSystemReminder(
|
|
92
|
+
senderAgentName: string,
|
|
93
|
+
senderAgentId: string,
|
|
94
|
+
): string {
|
|
95
|
+
return `<system-reminder>
|
|
96
|
+
This message is from "${senderAgentName}" (agent ID: ${senderAgentId}), an agent currently running inside the Letta Code CLI (docs.letta.com/letta-code).
|
|
97
|
+
The sender will only see the final message you generate (not tool calls or reasoning).
|
|
98
|
+
If you need to share detailed information, include it in your response text.
|
|
99
|
+
</system-reminder>
|
|
100
|
+
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Start a new conversation with an agent and send a message
|
|
106
|
+
* @param client - Letta client instance
|
|
107
|
+
* @param options - Options including target agent ID and message
|
|
108
|
+
* @returns Conversation result with response and metadata
|
|
109
|
+
*/
|
|
110
|
+
export async function startConversation(
|
|
111
|
+
client: LettaClient,
|
|
112
|
+
options: StartConversationOptions,
|
|
113
|
+
): Promise<StartConversationResult> {
|
|
114
|
+
const { agentId, message } = options;
|
|
115
|
+
|
|
116
|
+
// 1. Fetch target agent to validate existence and get name
|
|
117
|
+
const targetAgent = await client.agents.retrieve(agentId);
|
|
118
|
+
|
|
119
|
+
// 2. Fetch sender agent to get name for system reminder
|
|
120
|
+
const senderAgentId = getSenderAgentId();
|
|
121
|
+
const senderAgent = await client.agents.retrieve(senderAgentId);
|
|
122
|
+
|
|
123
|
+
// 3. Create new conversation
|
|
124
|
+
const conversation = await client.conversations.create({
|
|
125
|
+
agent_id: agentId,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 4. Build message with system reminder prefix
|
|
129
|
+
const systemReminder = buildSystemReminder(senderAgent.name, senderAgentId);
|
|
130
|
+
const fullMessage = systemReminder + message;
|
|
131
|
+
|
|
132
|
+
// 5. Send message and consume the stream
|
|
133
|
+
// Note: conversations.messages.create always returns a Stream
|
|
134
|
+
const stream = await client.conversations.messages.create(conversation.id, {
|
|
135
|
+
input: fullMessage,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 6. Consume stream and extract final assistant message
|
|
139
|
+
let finalResponse = "";
|
|
140
|
+
for await (const chunk of stream) {
|
|
141
|
+
if (process.env.DEBUG) {
|
|
142
|
+
console.error("Chunk:", JSON.stringify(chunk, null, 2));
|
|
143
|
+
}
|
|
144
|
+
if (chunk.message_type === "assistant_message") {
|
|
145
|
+
// Content can be string or array of content parts
|
|
146
|
+
const content = chunk.content;
|
|
147
|
+
if (typeof content === "string") {
|
|
148
|
+
finalResponse += content;
|
|
149
|
+
} else if (Array.isArray(content)) {
|
|
150
|
+
for (const part of content) {
|
|
151
|
+
if (
|
|
152
|
+
typeof part === "object" &&
|
|
153
|
+
part !== null &&
|
|
154
|
+
"type" in part &&
|
|
155
|
+
part.type === "text" &&
|
|
156
|
+
"text" in part
|
|
157
|
+
) {
|
|
158
|
+
finalResponse += (part as { text: string }).text;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
conversation_id: conversation.id,
|
|
167
|
+
response: finalResponse,
|
|
168
|
+
agent_id: targetAgent.id,
|
|
169
|
+
agent_name: targetAgent.name,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseArgs(args: string[]): StartConversationOptions {
|
|
174
|
+
const agentIdIndex = args.indexOf("--agent-id");
|
|
175
|
+
if (agentIdIndex === -1 || agentIdIndex + 1 >= args.length) {
|
|
176
|
+
throw new Error("Missing required argument: --agent-id <agent-id>");
|
|
177
|
+
}
|
|
178
|
+
const agentId = args[agentIdIndex + 1] as string;
|
|
179
|
+
|
|
180
|
+
const messageIndex = args.indexOf("--message");
|
|
181
|
+
if (messageIndex === -1 || messageIndex + 1 >= args.length) {
|
|
182
|
+
throw new Error("Missing required argument: --message <text>");
|
|
183
|
+
}
|
|
184
|
+
const message = args[messageIndex + 1] as string;
|
|
185
|
+
|
|
186
|
+
const options: StartConversationOptions = { agentId, message };
|
|
187
|
+
|
|
188
|
+
const timeoutIndex = args.indexOf("--timeout");
|
|
189
|
+
if (timeoutIndex !== -1 && timeoutIndex + 1 < args.length) {
|
|
190
|
+
const timeout = Number.parseInt(args[timeoutIndex + 1] as string, 10);
|
|
191
|
+
if (!Number.isNaN(timeout)) {
|
|
192
|
+
options.timeout = timeout;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return options;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// CLI entry point - check if this file is being run directly
|
|
200
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
201
|
+
if (isMainModule) {
|
|
202
|
+
(async () => {
|
|
203
|
+
try {
|
|
204
|
+
const options = parseArgs(process.argv.slice(2));
|
|
205
|
+
const client = createClient();
|
|
206
|
+
const result = await startConversation(client, options);
|
|
207
|
+
console.log(JSON.stringify(result, null, 2));
|
|
208
|
+
process.exit(0);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error(
|
|
211
|
+
"Error:",
|
|
212
|
+
error instanceof Error ? error.message : String(error),
|
|
213
|
+
);
|
|
214
|
+
console.error(`
|
|
215
|
+
Usage: npx tsx start-conversation.ts --agent-id <id> --message "<text>"
|
|
216
|
+
|
|
217
|
+
Options:
|
|
218
|
+
--agent-id <id> Target agent ID to message (required)
|
|
219
|
+
--message <text> Message to send (required)
|
|
220
|
+
--timeout <ms> Max wait time in ms (default: 120000)
|
|
221
|
+
`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
}
|