assistme 0.8.12 → 0.9.0
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/PLAN.md +1 -1
- package/dist/{chunk-MP4E522X.js → chunk-E27P57PM.js} +188 -32
- package/dist/{chunk-5ML5YMCM.js → chunk-RRMI6RDG.js} +85 -8
- package/dist/index.js +30 -6
- package/dist/{job-runner-NA3KJCKA.js → job-runner-4KTCVIQP.js} +1 -1
- package/dist/workers/entry.js +2 -2
- package/package.json +1 -1
- package/src/agent/execution/processor.ts +14 -0
- package/src/agent/memory/manager.ts +92 -27
- package/src/agent/scheduling/index.ts +1 -1
- package/src/agent/scheduling/job-runner.ts +72 -7
- package/src/db/api-client.ts +1 -1
- package/src/mcp/tools/job-tools.ts +109 -5
- package/src/orchestrator.ts +18 -1
- package/src/utils/schemas.ts +11 -0
- package/tests/agent/processor.test.ts +2 -0
package/PLAN.md
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readAuthStore,
|
|
9
9
|
safeParse,
|
|
10
10
|
writeAuthStore
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-RRMI6RDG.js";
|
|
12
12
|
import {
|
|
13
13
|
log,
|
|
14
14
|
newCorrelationId,
|
|
@@ -2833,19 +2833,51 @@ var TaskPoller = class {
|
|
|
2833
2833
|
// src/agent/memory/manager.ts
|
|
2834
2834
|
var MemoryManager = class {
|
|
2835
2835
|
/**
|
|
2836
|
-
*
|
|
2837
|
-
*
|
|
2836
|
+
* When set, all memory operations use this agent's users.id as the
|
|
2837
|
+
* effective user, giving the agent its own memory space.
|
|
2838
|
+
*/
|
|
2839
|
+
agentUserId = null;
|
|
2840
|
+
/** The human owner's user ID — used to load owner memories as fallback. */
|
|
2841
|
+
ownerUserId = null;
|
|
2842
|
+
/**
|
|
2843
|
+
* Configure agent-scoped memory.
|
|
2844
|
+
* @param agentUserId The agent's users.id — memories are stored/read here
|
|
2845
|
+
* @param ownerUserId The human owner's users.id — owner memories loaded as fallback
|
|
2846
|
+
*/
|
|
2847
|
+
setAgentContext(agentUserId, ownerUserId) {
|
|
2848
|
+
this.agentUserId = agentUserId;
|
|
2849
|
+
this.ownerUserId = ownerUserId;
|
|
2850
|
+
}
|
|
2851
|
+
/** Clear agent context (revert to normal user-scoped mode). */
|
|
2852
|
+
clearAgentContext() {
|
|
2853
|
+
this.agentUserId = null;
|
|
2854
|
+
this.ownerUserId = null;
|
|
2855
|
+
}
|
|
2856
|
+
/** Inject effective_user_id + owner_user_id into params. */
|
|
2857
|
+
agentParams(extra = {}) {
|
|
2858
|
+
const params = { ...extra };
|
|
2859
|
+
if (this.agentUserId) {
|
|
2860
|
+
params.effective_user_id = this.agentUserId;
|
|
2861
|
+
if (this.ownerUserId) {
|
|
2862
|
+
params.owner_user_id = this.ownerUserId;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
return params;
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Store a new memory. Called by the agent after completing tasks.
|
|
2869
|
+
* When agent context is set, stores under the agent's user_id.
|
|
2838
2870
|
*/
|
|
2839
2871
|
async remember(content, category = "general", options) {
|
|
2840
2872
|
const expiresAt = options?.expiresInDays ? new Date(Date.now() + options.expiresInDays * 864e5).toISOString() : null;
|
|
2841
|
-
const data = await callMcpHandler("memory.store", {
|
|
2873
|
+
const data = await callMcpHandler("memory.store", this.agentParams({
|
|
2842
2874
|
category,
|
|
2843
2875
|
content,
|
|
2844
2876
|
importance: options?.importance ?? 5,
|
|
2845
2877
|
tags: options?.tags ?? [],
|
|
2846
2878
|
source_message_id: options?.sourceMessageId ?? null,
|
|
2847
2879
|
expires_at: expiresAt
|
|
2848
|
-
});
|
|
2880
|
+
}));
|
|
2849
2881
|
log.debug(`Memory stored: [${category}] ${content.slice(0, 80)}...`);
|
|
2850
2882
|
return data;
|
|
2851
2883
|
}
|
|
@@ -2854,10 +2886,10 @@ var MemoryManager = class {
|
|
|
2854
2886
|
*/
|
|
2855
2887
|
async search(query9, limit = 10) {
|
|
2856
2888
|
try {
|
|
2857
|
-
return await callMcpHandler("memory.search", {
|
|
2889
|
+
return await callMcpHandler("memory.search", this.agentParams({
|
|
2858
2890
|
query: query9,
|
|
2859
2891
|
limit
|
|
2860
|
-
});
|
|
2892
|
+
}));
|
|
2861
2893
|
} catch (err) {
|
|
2862
2894
|
log.warn(`Memory search failed: ${err instanceof Error ? err.message : err}`);
|
|
2863
2895
|
return [];
|
|
@@ -2865,13 +2897,13 @@ var MemoryManager = class {
|
|
|
2865
2897
|
}
|
|
2866
2898
|
/**
|
|
2867
2899
|
* Get the most important/recent memories to include in context.
|
|
2868
|
-
*
|
|
2869
|
-
*
|
|
2900
|
+
* When agent context is set, loads agent's memories first,
|
|
2901
|
+
* then supplements with the owner's memories as fallback.
|
|
2870
2902
|
*/
|
|
2871
2903
|
async getContext(maxItems = 20) {
|
|
2872
|
-
const all = await callMcpHandler("memory.get_context", {
|
|
2904
|
+
const all = await callMcpHandler("memory.get_context", this.agentParams({
|
|
2873
2905
|
max_items: maxItems
|
|
2874
|
-
});
|
|
2906
|
+
}));
|
|
2875
2907
|
const seen = /* @__PURE__ */ new Set();
|
|
2876
2908
|
return (all || []).filter((m) => {
|
|
2877
2909
|
if (seen.has(m.id)) return false;
|
|
@@ -2885,48 +2917,63 @@ var MemoryManager = class {
|
|
|
2885
2917
|
async buildMemoryPrompt() {
|
|
2886
2918
|
const memories = await this.getContext();
|
|
2887
2919
|
if (memories.length === 0) return "";
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
if (!sections[key]) sections[key] = [];
|
|
2892
|
-
sections[key].push(`- ${m.content}`);
|
|
2893
|
-
}
|
|
2920
|
+
const hasOwnerFlag = memories.some((m) => m._from_owner);
|
|
2921
|
+
const agentMemories = hasOwnerFlag ? memories.filter((m) => !m._from_owner) : memories;
|
|
2922
|
+
const ownerMemories = hasOwnerFlag ? memories.filter((m) => m._from_owner) : [];
|
|
2894
2923
|
const categoryLabels = {
|
|
2895
2924
|
instruction: "Standing Instructions",
|
|
2896
|
-
preference: "
|
|
2925
|
+
preference: "Preferences",
|
|
2897
2926
|
general: "Known Facts",
|
|
2898
2927
|
context: "Context",
|
|
2899
2928
|
skill_learned: "Learned Skills",
|
|
2900
2929
|
fact: "Facts"
|
|
2901
2930
|
};
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2931
|
+
function formatSection(mems) {
|
|
2932
|
+
const sections = {};
|
|
2933
|
+
for (const m of mems) {
|
|
2934
|
+
const key = m.category;
|
|
2935
|
+
if (!sections[key]) sections[key] = [];
|
|
2936
|
+
sections[key].push(`- ${m.content}`);
|
|
2937
|
+
}
|
|
2938
|
+
let out = "";
|
|
2939
|
+
for (const [cat, items] of Object.entries(sections)) {
|
|
2940
|
+
out += `
|
|
2905
2941
|
### ${categoryLabels[cat] || cat}
|
|
2906
2942
|
`;
|
|
2907
|
-
|
|
2943
|
+
out += `${items.join("\n")}
|
|
2908
2944
|
`;
|
|
2945
|
+
}
|
|
2946
|
+
return out;
|
|
2947
|
+
}
|
|
2948
|
+
let prompt = "";
|
|
2949
|
+
if (agentMemories.length > 0) {
|
|
2950
|
+
prompt += this.agentUserId ? "\n\n## Your Memories\n" : "\n\n## What You Know About The User\n";
|
|
2951
|
+
prompt += formatSection(agentMemories);
|
|
2952
|
+
}
|
|
2953
|
+
if (ownerMemories.length > 0) {
|
|
2954
|
+
prompt += "\n\n## Owner's Context\n";
|
|
2955
|
+
prompt += formatSection(ownerMemories);
|
|
2909
2956
|
}
|
|
2910
2957
|
return prompt;
|
|
2911
2958
|
}
|
|
2912
2959
|
// ── CRUD for CLI ────────────────────────────────────────────
|
|
2913
2960
|
async list(category, limit = 20) {
|
|
2914
|
-
const data = await callMcpHandler("memory.list", {
|
|
2961
|
+
const data = await callMcpHandler("memory.list", this.agentParams({
|
|
2915
2962
|
category: category || null,
|
|
2916
2963
|
limit
|
|
2917
|
-
});
|
|
2964
|
+
}));
|
|
2918
2965
|
return data || [];
|
|
2919
2966
|
}
|
|
2920
2967
|
async add(content, category = "general", importance = 5, tags = []) {
|
|
2921
2968
|
return this.remember(content, category, { importance, tags });
|
|
2922
2969
|
}
|
|
2923
2970
|
async remove(memoryId) {
|
|
2924
|
-
await callMcpHandler("memory.remove", { memory_id: memoryId });
|
|
2971
|
+
await callMcpHandler("memory.remove", this.agentParams({ memory_id: memoryId }));
|
|
2925
2972
|
}
|
|
2926
2973
|
async clear(category) {
|
|
2927
|
-
const result = await callMcpHandler("memory.clear", {
|
|
2974
|
+
const result = await callMcpHandler("memory.clear", this.agentParams({
|
|
2928
2975
|
category: category || null
|
|
2929
|
-
});
|
|
2976
|
+
}));
|
|
2930
2977
|
return result.count;
|
|
2931
2978
|
}
|
|
2932
2979
|
};
|
|
@@ -5528,7 +5575,7 @@ function createJobTools(sessionId, taskId) {
|
|
|
5528
5575
|
const job = await runner.loadJob(args.job_name);
|
|
5529
5576
|
if (!job) {
|
|
5530
5577
|
const jobs = await runner.listJobs();
|
|
5531
|
-
const available = jobs.length > 0 ? `Available jobs: ${jobs.map((j) => `"${j.name}" (${j.skillCount} skills)`).join(", ")}` : "No jobs defined yet. Use skill_generate to create a job from a job description.";
|
|
5578
|
+
const available = jobs.length > 0 ? `Available jobs: ${jobs.map((j) => `"${j.name}" (${j.skillCount} skills, ${j.agentCount} agents)`).join(", ")}` : "No jobs defined yet. Use skill_generate to create a job from a job description.";
|
|
5532
5579
|
return textResponse(`Job "${args.job_name}" not found. ${available}`);
|
|
5533
5580
|
}
|
|
5534
5581
|
if (job.skills.length === 0) {
|
|
@@ -5569,11 +5616,38 @@ function createJobTools(sessionId, taskId) {
|
|
|
5569
5616
|
}
|
|
5570
5617
|
}
|
|
5571
5618
|
}
|
|
5572
|
-
const
|
|
5619
|
+
const effectiveRunId = runId || "untracked";
|
|
5620
|
+
const prompt = runner.buildJobPrompt(job, effectiveRunId);
|
|
5621
|
+
const primaryAgentForFilter = job.agents.length > 0 ? job.agents.find((p) => p.role === "primary") || job.agents[0] : null;
|
|
5622
|
+
const parallelAgents = job.agents.filter(
|
|
5623
|
+
(a) => a.role === "parallel" && a.agentId !== primaryAgentForFilter?.agentId
|
|
5624
|
+
);
|
|
5625
|
+
let fullResponse = prompt;
|
|
5626
|
+
if (parallelAgents.length > 0) {
|
|
5627
|
+
fullResponse += `
|
|
5628
|
+
|
|
5629
|
+
---
|
|
5630
|
+
|
|
5631
|
+
## Parallel Agent Dispatch
|
|
5632
|
+
`;
|
|
5633
|
+
fullResponse += `The following parallel agents are assigned to this job. `;
|
|
5634
|
+
fullResponse += `After you complete your primary tasks, review their responsibilities and execute any that haven't been covered:
|
|
5635
|
+
|
|
5636
|
+
`;
|
|
5637
|
+
for (const pa of parallelAgents) {
|
|
5638
|
+
fullResponse += `### ${pa.agentName} (@${pa.agentUsername})
|
|
5639
|
+
`;
|
|
5640
|
+
if (pa.agentBio) fullResponse += `*${pa.agentBio}*
|
|
5641
|
+
`;
|
|
5642
|
+
fullResponse += `**Instructions:** ${pa.agentPrompt}
|
|
5643
|
+
|
|
5644
|
+
`;
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5573
5647
|
log.info(
|
|
5574
|
-
`Job "${args.job_name}" started with ${job.skills.length} skills (run: ${
|
|
5648
|
+
`Job "${args.job_name}" started with ${job.skills.length} skills, ${job.agents.length} agents (run: ${effectiveRunId.slice(0, 8)})`
|
|
5575
5649
|
);
|
|
5576
|
-
return textResponse(
|
|
5650
|
+
return textResponse(fullResponse);
|
|
5577
5651
|
})
|
|
5578
5652
|
),
|
|
5579
5653
|
tool4(
|
|
@@ -5639,6 +5713,80 @@ function createJobTools(sessionId, taskId) {
|
|
|
5639
5713
|
}
|
|
5640
5714
|
)
|
|
5641
5715
|
),
|
|
5716
|
+
tool4(
|
|
5717
|
+
"job_assign_agent",
|
|
5718
|
+
"Create or update an agent for a job. The agent is a user with a specific prompt and identity that will execute this job.",
|
|
5719
|
+
{
|
|
5720
|
+
job_name: z4.string().describe("Name of the job to assign the agent to"),
|
|
5721
|
+
agent_name: z4.string().describe("Display name for the agent (e.g. 'Daily Reporter')"),
|
|
5722
|
+
agent_prompt: z4.string().describe("The agent's system prompt that defines its behavior and personality"),
|
|
5723
|
+
agent_bio: z4.string().optional().describe("Short bio describing the agent's role"),
|
|
5724
|
+
role: z4.enum(["primary", "parallel"]).optional().describe("Agent role: 'primary' (default) or 'parallel' for concurrent execution")
|
|
5725
|
+
},
|
|
5726
|
+
typedHandler(
|
|
5727
|
+
async (args) => {
|
|
5728
|
+
try {
|
|
5729
|
+
const result = await callMcpHandler("job.save_agent", {
|
|
5730
|
+
job_name: args.job_name,
|
|
5731
|
+
agent_name: args.agent_name,
|
|
5732
|
+
agent_prompt: args.agent_prompt,
|
|
5733
|
+
agent_bio: args.agent_bio || null,
|
|
5734
|
+
role: args.role || "primary"
|
|
5735
|
+
});
|
|
5736
|
+
if (!result) {
|
|
5737
|
+
return textResponse(`Failed to create agent for job "${args.job_name}".`);
|
|
5738
|
+
}
|
|
5739
|
+
log.info(
|
|
5740
|
+
`Agent "${result.username}" assigned to job "${args.job_name}" (${args.role || "primary"})`
|
|
5741
|
+
);
|
|
5742
|
+
let response = `## Agent Assigned to Job: ${args.job_name}
|
|
5743
|
+
|
|
5744
|
+
`;
|
|
5745
|
+
response += `- **Agent:** ${args.agent_name} (@${result.username})
|
|
5746
|
+
`;
|
|
5747
|
+
response += `- **Role:** ${args.role || "primary"}
|
|
5748
|
+
`;
|
|
5749
|
+
if (args.agent_bio) {
|
|
5750
|
+
response += `- **Bio:** ${args.agent_bio}
|
|
5751
|
+
`;
|
|
5752
|
+
}
|
|
5753
|
+
response += `
|
|
5754
|
+
The agent is now ready to execute this job.`;
|
|
5755
|
+
return textResponse(response);
|
|
5756
|
+
} catch (err) {
|
|
5757
|
+
return textResponse(
|
|
5758
|
+
`Failed to assign agent: ${err instanceof Error ? err.message : err}`
|
|
5759
|
+
);
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
)
|
|
5763
|
+
),
|
|
5764
|
+
tool4(
|
|
5765
|
+
"job_remove_agent",
|
|
5766
|
+
"Remove (deactivate) an agent from a job.",
|
|
5767
|
+
{
|
|
5768
|
+
job_name: z4.string().describe("Name of the job"),
|
|
5769
|
+
agent_name: z4.string().describe("Display name of the agent to remove")
|
|
5770
|
+
},
|
|
5771
|
+
typedHandler(
|
|
5772
|
+
async (args) => {
|
|
5773
|
+
try {
|
|
5774
|
+
await callMcpHandler("job.remove_agent", {
|
|
5775
|
+
job_name: args.job_name,
|
|
5776
|
+
agent_name: args.agent_name
|
|
5777
|
+
});
|
|
5778
|
+
log.info(`Agent "${args.agent_name}" removed from job "${args.job_name}"`);
|
|
5779
|
+
return textResponse(
|
|
5780
|
+
`Agent "${args.agent_name}" has been removed from job "${args.job_name}".`
|
|
5781
|
+
);
|
|
5782
|
+
} catch (err) {
|
|
5783
|
+
return textResponse(
|
|
5784
|
+
`Failed to remove agent: ${err instanceof Error ? err.message : err}`
|
|
5785
|
+
);
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
)
|
|
5789
|
+
),
|
|
5642
5790
|
tool4(
|
|
5643
5791
|
"job_status",
|
|
5644
5792
|
"Check the status and run history of a job.",
|
|
@@ -5657,7 +5805,7 @@ function createJobTools(sessionId, taskId) {
|
|
|
5657
5805
|
}
|
|
5658
5806
|
let response2 = "## Your Jobs\n\n";
|
|
5659
5807
|
for (const job of jobs) {
|
|
5660
|
-
response2 += `- **${job.name}** (${job.skillCount} skills): ${job.description.slice(0, 100)}
|
|
5808
|
+
response2 += `- **${job.name}** (${job.skillCount} skills, ${job.agentCount} agents): ${job.description.slice(0, 100)}
|
|
5661
5809
|
`;
|
|
5662
5810
|
}
|
|
5663
5811
|
response2 += "\nUse `job_run` to execute a job, or `job_schedule` to automate it.";
|
|
@@ -8420,6 +8568,11 @@ var TaskProcessor = class {
|
|
|
8420
8568
|
resetEventSequence();
|
|
8421
8569
|
newCorrelationId();
|
|
8422
8570
|
log.info(`Processing task ${task.id.slice(0, 8)}...`);
|
|
8571
|
+
const taskAgentId = task.metadata?.agent_id || (task.sender_id !== this.userId ? task.sender_id : null);
|
|
8572
|
+
if (taskAgentId && this.memoryManager && this.userId) {
|
|
8573
|
+
this.memoryManager.setAgentContext(taskAgentId, this.userId);
|
|
8574
|
+
log.debug(`Memory context set to agent ${taskAgentId.slice(0, 8)}`);
|
|
8575
|
+
}
|
|
8423
8576
|
const toolCallRecords = [];
|
|
8424
8577
|
const toolFailures = [];
|
|
8425
8578
|
try {
|
|
@@ -8479,6 +8632,9 @@ var TaskProcessor = class {
|
|
|
8479
8632
|
await emitEvent(task.id, "status_change", { status: "failed" });
|
|
8480
8633
|
} finally {
|
|
8481
8634
|
setCorrelationId(null);
|
|
8635
|
+
if (taskAgentId && this.memoryManager) {
|
|
8636
|
+
this.memoryManager.clearAgentContext();
|
|
8637
|
+
}
|
|
8482
8638
|
try {
|
|
8483
8639
|
const browser = getBrowser();
|
|
8484
8640
|
if (browser.isConnected()) {
|
|
@@ -43,7 +43,7 @@ function getRawToken() {
|
|
|
43
43
|
async function callMcpHandler(action, params = {}, overrideToken) {
|
|
44
44
|
const config = getConfig();
|
|
45
45
|
const token = overrideToken || getRawToken();
|
|
46
|
-
const url = `${config.supabaseUrl}/functions/v1/mcp-handler`;
|
|
46
|
+
const url = `${config.supabaseUrl}/functions/v1/main-mcp-handler`;
|
|
47
47
|
const response = await fetch(url, {
|
|
48
48
|
method: "POST",
|
|
49
49
|
headers: {
|
|
@@ -91,6 +91,14 @@ var SkillRowSchema = z.object({
|
|
|
91
91
|
source_skill_id: z.string().optional().nullable(),
|
|
92
92
|
invocation_count: z.number().optional().default(0)
|
|
93
93
|
});
|
|
94
|
+
var JobAgentSchema = z.object({
|
|
95
|
+
agent_id: z.string(),
|
|
96
|
+
agent_username: z.string(),
|
|
97
|
+
agent_name: z.string().optional().default(""),
|
|
98
|
+
agent_bio: z.string().optional().nullable(),
|
|
99
|
+
agent_prompt: z.string(),
|
|
100
|
+
role: z.string().optional().default("primary")
|
|
101
|
+
});
|
|
94
102
|
var JobRowSchema = z.object({
|
|
95
103
|
job_id: z.string(),
|
|
96
104
|
job_name: z.string(),
|
|
@@ -100,14 +108,16 @@ var JobRowSchema = z.object({
|
|
|
100
108
|
skill_name: z.string().optional().nullable(),
|
|
101
109
|
skill_description: z.string().optional().default(""),
|
|
102
110
|
skill_emoji: z.string().optional().default(""),
|
|
103
|
-
skill_content: z.string().optional().default("")
|
|
111
|
+
skill_content: z.string().optional().default(""),
|
|
112
|
+
agents: z.array(JobAgentSchema).optional().default([])
|
|
104
113
|
});
|
|
105
114
|
var JobListRowSchema = z.object({
|
|
106
115
|
id: z.string(),
|
|
107
116
|
name: z.string(),
|
|
108
117
|
description: z.string().optional().default(""),
|
|
109
118
|
prompt: z.string().optional().nullable(),
|
|
110
|
-
skill_count: z.number().optional().default(0)
|
|
119
|
+
skill_count: z.number().optional().default(0),
|
|
120
|
+
agent_count: z.number().optional().default(0)
|
|
111
121
|
});
|
|
112
122
|
var JobRunRowSchema = z.object({
|
|
113
123
|
run_id: z.string(),
|
|
@@ -193,6 +203,15 @@ var JobRunner = class {
|
|
|
193
203
|
const rows = data.map((row) => safeParse(JobRowSchema, row)).filter((r) => r != null);
|
|
194
204
|
if (rows.length === 0) return null;
|
|
195
205
|
const first = rows[0];
|
|
206
|
+
const rawAgents = first.agents || [];
|
|
207
|
+
const agents = rawAgents.map((a) => safeParse(JobAgentSchema, a)).filter((a) => a != null).map((a) => ({
|
|
208
|
+
agentId: a.agent_id,
|
|
209
|
+
agentUsername: a.agent_username,
|
|
210
|
+
agentName: a.agent_name || "",
|
|
211
|
+
agentBio: a.agent_bio ?? null,
|
|
212
|
+
agentPrompt: a.agent_prompt,
|
|
213
|
+
role: a.role
|
|
214
|
+
}));
|
|
196
215
|
return {
|
|
197
216
|
jobId: first.job_id,
|
|
198
217
|
jobName: first.job_name,
|
|
@@ -204,7 +223,8 @@ var JobRunner = class {
|
|
|
204
223
|
skillDescription: row.skill_description,
|
|
205
224
|
skillEmoji: row.skill_emoji,
|
|
206
225
|
skillContent: row.skill_content
|
|
207
|
-
}))
|
|
226
|
+
})),
|
|
227
|
+
agents
|
|
208
228
|
};
|
|
209
229
|
} catch (err) {
|
|
210
230
|
log.debug(`Failed to load job "${jobName}": ${errorMessage(err)}`);
|
|
@@ -221,7 +241,8 @@ var JobRunner = class {
|
|
|
221
241
|
id: row.id,
|
|
222
242
|
name: row.name,
|
|
223
243
|
description: row.description,
|
|
224
|
-
skillCount: row.skill_count
|
|
244
|
+
skillCount: row.skill_count,
|
|
245
|
+
agentCount: row.agent_count
|
|
225
246
|
}));
|
|
226
247
|
} catch (err) {
|
|
227
248
|
log.debug(`Failed to list jobs: ${errorMessage(err)}`);
|
|
@@ -313,6 +334,9 @@ var JobRunner = class {
|
|
|
313
334
|
`;
|
|
314
335
|
}
|
|
315
336
|
prompt += `## Instructions
|
|
337
|
+
|
|
338
|
+
`;
|
|
339
|
+
prompt += `### Step 1: Analyze and set up skills
|
|
316
340
|
`;
|
|
317
341
|
prompt += `For each capability the job needs:
|
|
318
342
|
`;
|
|
@@ -326,8 +350,24 @@ var JobRunner = class {
|
|
|
326
350
|
|
|
327
351
|
`;
|
|
328
352
|
prompt += `Be practical \u2014 only create skills that would genuinely help automate this job.
|
|
353
|
+
|
|
354
|
+
`;
|
|
355
|
+
prompt += `### Step 2: Create the job's agent
|
|
356
|
+
`;
|
|
357
|
+
prompt += `After setting up skills, create an agent for this job using \`job_assign_agent\`.
|
|
358
|
+
`;
|
|
359
|
+
prompt += `The agent is the identity that will execute this job. Design it with:
|
|
329
360
|
`;
|
|
330
|
-
prompt +=
|
|
361
|
+
prompt += `- **agent_name**: A concise, role-based name (e.g. "Daily Reporter", "Code Reviewer")
|
|
362
|
+
`;
|
|
363
|
+
prompt += `- **agent_prompt**: A system prompt that defines the agent's behavior, expertise, and personality. `;
|
|
364
|
+
prompt += `Include the job's goals, preferred approach, and any domain knowledge the agent should have. `;
|
|
365
|
+
prompt += `Reference the linked skills as capabilities the agent can use.
|
|
366
|
+
`;
|
|
367
|
+
prompt += `- **agent_bio**: A one-line description of what this agent does.
|
|
368
|
+
|
|
369
|
+
`;
|
|
370
|
+
prompt += `When done, summarize what skills were created/updated and the agent that was assigned.
|
|
331
371
|
`;
|
|
332
372
|
return prompt;
|
|
333
373
|
}
|
|
@@ -340,6 +380,10 @@ var JobRunner = class {
|
|
|
340
380
|
* chain them based on what it discovers at runtime.
|
|
341
381
|
*/
|
|
342
382
|
buildJobPrompt(job, runId) {
|
|
383
|
+
const primaryAgent = job.agents.length > 0 ? job.agents.find((a) => a.role === "primary") || job.agents[0] : null;
|
|
384
|
+
const parallelAgents = job.agents.filter(
|
|
385
|
+
(a) => a.role === "parallel" && a.agentId !== primaryAgent?.agentId
|
|
386
|
+
);
|
|
343
387
|
const effectiveDescription = job.jobPrompt || job.jobDescription;
|
|
344
388
|
let prompt = `## Job: ${job.jobName}
|
|
345
389
|
`;
|
|
@@ -349,10 +393,43 @@ var JobRunner = class {
|
|
|
349
393
|
prompt += `**Run ID:** ${runId}
|
|
350
394
|
|
|
351
395
|
`;
|
|
352
|
-
|
|
353
|
-
|
|
396
|
+
if (primaryAgent) {
|
|
397
|
+
prompt += `## Agent Identity
|
|
398
|
+
`;
|
|
399
|
+
prompt += `You are **${primaryAgent.agentName}** (@${primaryAgent.agentUsername}).
|
|
400
|
+
`;
|
|
401
|
+
if (primaryAgent.agentBio) {
|
|
402
|
+
prompt += `*${primaryAgent.agentBio}*
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
prompt += `
|
|
406
|
+
### Agent Instructions
|
|
407
|
+
`;
|
|
408
|
+
prompt += `${primaryAgent.agentPrompt}
|
|
409
|
+
|
|
410
|
+
`;
|
|
411
|
+
} else {
|
|
412
|
+
prompt += `You are now acting as "${job.jobName}". `;
|
|
413
|
+
prompt += `Your goal is to accomplish the objectives described above using the skills and tools available to you.
|
|
414
|
+
|
|
415
|
+
`;
|
|
416
|
+
}
|
|
417
|
+
if (parallelAgents.length > 0) {
|
|
418
|
+
prompt += `### Parallel Agents
|
|
419
|
+
`;
|
|
420
|
+
prompt += `The following agents are running concurrently on this job. `;
|
|
421
|
+
prompt += `They handle their own tasks independently \u2014 coordinate to avoid duplicate work.
|
|
354
422
|
|
|
355
423
|
`;
|
|
424
|
+
for (const pa of parallelAgents) {
|
|
425
|
+
prompt += `- **${pa.agentName}** (@${pa.agentUsername})`;
|
|
426
|
+
if (pa.agentBio) prompt += ` \u2014 ${pa.agentBio}`;
|
|
427
|
+
prompt += `
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
prompt += `
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
356
433
|
prompt += `### Available Skills
|
|
357
434
|
`;
|
|
358
435
|
prompt += `These skills are your capabilities for this job. `;
|
package/dist/index.js
CHANGED
|
@@ -34,11 +34,11 @@ import {
|
|
|
34
34
|
setSessionBusy,
|
|
35
35
|
toggleScheduledTask,
|
|
36
36
|
updateHeartbeat
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-E27P57PM.js";
|
|
38
38
|
import {
|
|
39
39
|
JobRunner,
|
|
40
40
|
callMcpHandler
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-RRMI6RDG.js";
|
|
42
42
|
import {
|
|
43
43
|
log,
|
|
44
44
|
setLogConversationId,
|
|
@@ -382,7 +382,7 @@ function registerJobCommands(program2) {
|
|
|
382
382
|
jobCmd.command("list").description("List your defined jobs").action(async () => {
|
|
383
383
|
try {
|
|
384
384
|
await getCurrentUserId();
|
|
385
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
385
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-4KTCVIQP.js");
|
|
386
386
|
const runner = new JobRunner2();
|
|
387
387
|
const jobs = await runner.listJobs();
|
|
388
388
|
if (jobs.length === 0) {
|
|
@@ -406,7 +406,7 @@ function registerJobCommands(program2) {
|
|
|
406
406
|
jobCmd.command("status [name]").description("Show run history for a job (or all jobs)").option("-l, --limit <number>", "Max runs to show (default: 5)").action(async (name, opts) => {
|
|
407
407
|
try {
|
|
408
408
|
await getCurrentUserId();
|
|
409
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
409
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-4KTCVIQP.js");
|
|
410
410
|
const runner = new JobRunner2();
|
|
411
411
|
const runs = await runner.getRunHistory(name, parseInt(opts.limit || "5"));
|
|
412
412
|
if (runs.length === 0) {
|
|
@@ -454,7 +454,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`));
|
|
|
454
454
|
process.exit(1);
|
|
455
455
|
}
|
|
456
456
|
await getCurrentUserId();
|
|
457
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
457
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-4KTCVIQP.js");
|
|
458
458
|
const runner = new JobRunner2();
|
|
459
459
|
const job = await runner.loadJob(name);
|
|
460
460
|
if (!job) {
|
|
@@ -2002,7 +2002,31 @@ var Orchestrator = class {
|
|
|
2002
2002
|
} catch (linkErr) {
|
|
2003
2003
|
log.debug(`Failed to link session to job run: ${linkErr}`);
|
|
2004
2004
|
}
|
|
2005
|
-
|
|
2005
|
+
let prompt = runner.buildJobPrompt(job, jobRun.id);
|
|
2006
|
+
const primaryAgent = job.agents.length > 0 ? job.agents.find((a) => a.role === "primary") || job.agents[0] : null;
|
|
2007
|
+
const parallelAgents = job.agents.filter(
|
|
2008
|
+
(a) => a.role === "parallel" && a.agentId !== primaryAgent?.agentId
|
|
2009
|
+
);
|
|
2010
|
+
if (parallelAgents.length > 0) {
|
|
2011
|
+
prompt += `
|
|
2012
|
+
|
|
2013
|
+
---
|
|
2014
|
+
|
|
2015
|
+
## Parallel Agent Responsibilities
|
|
2016
|
+
`;
|
|
2017
|
+
prompt += `The following agents are also assigned. Execute their responsibilities as part of this run:
|
|
2018
|
+
|
|
2019
|
+
`;
|
|
2020
|
+
for (const pa of parallelAgents) {
|
|
2021
|
+
prompt += `### ${pa.agentName}
|
|
2022
|
+
`;
|
|
2023
|
+
if (pa.agentBio) prompt += `*${pa.agentBio}*
|
|
2024
|
+
`;
|
|
2025
|
+
prompt += `**Instructions:** ${pa.agentPrompt}
|
|
2026
|
+
|
|
2027
|
+
`;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2006
2030
|
try {
|
|
2007
2031
|
await this.dispatchAndWait(`[JobRun: ${jobRun.job_name}] ${prompt}`);
|
|
2008
2032
|
await runner.completeRun(jobRun.id, "completed", "Job executed via web trigger");
|
package/dist/workers/entry.js
CHANGED
package/package.json
CHANGED
|
@@ -194,6 +194,15 @@ export class TaskProcessor {
|
|
|
194
194
|
newCorrelationId();
|
|
195
195
|
log.info(`Processing task ${task.id.slice(0, 8)}...`);
|
|
196
196
|
|
|
197
|
+
// Set agent memory context if this task is from a job agent
|
|
198
|
+
const taskAgentId =
|
|
199
|
+
(task.metadata?.agent_id as string) ||
|
|
200
|
+
(task.sender_id !== this.userId ? task.sender_id : null);
|
|
201
|
+
if (taskAgentId && this.memoryManager && this.userId) {
|
|
202
|
+
this.memoryManager.setAgentContext(taskAgentId, this.userId);
|
|
203
|
+
log.debug(`Memory context set to agent ${taskAgentId.slice(0, 8)}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
197
206
|
const toolCallRecords: ToolCallRecord[] = [];
|
|
198
207
|
const toolFailures: ToolFailureRecord[] = [];
|
|
199
208
|
|
|
@@ -266,6 +275,11 @@ export class TaskProcessor {
|
|
|
266
275
|
} finally {
|
|
267
276
|
setCorrelationId(null);
|
|
268
277
|
|
|
278
|
+
// Clear agent memory context so next task uses default user scope
|
|
279
|
+
if (taskAgentId && this.memoryManager) {
|
|
280
|
+
this.memoryManager.clearAgentContext();
|
|
281
|
+
}
|
|
282
|
+
|
|
269
283
|
try {
|
|
270
284
|
const browser = getBrowser();
|
|
271
285
|
if (browser.isConnected()) {
|
|
@@ -21,14 +21,53 @@ export interface Memory {
|
|
|
21
21
|
last_accessed_at: string | null;
|
|
22
22
|
expires_at: string | null;
|
|
23
23
|
created_at: string;
|
|
24
|
+
/** Set by the backend when this memory comes from the owner (human user)
|
|
25
|
+
* rather than the agent itself. Only present in agent-scoped get_context. */
|
|
26
|
+
_from_owner?: boolean;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// ── Memory Manager ──────────────────────────────────────────────────
|
|
27
30
|
|
|
28
31
|
export class MemoryManager {
|
|
29
32
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
33
|
+
* When set, all memory operations use this agent's users.id as the
|
|
34
|
+
* effective user, giving the agent its own memory space.
|
|
35
|
+
*/
|
|
36
|
+
private agentUserId: string | null = null;
|
|
37
|
+
/** The human owner's user ID — used to load owner memories as fallback. */
|
|
38
|
+
private ownerUserId: string | null = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configure agent-scoped memory.
|
|
42
|
+
* @param agentUserId The agent's users.id — memories are stored/read here
|
|
43
|
+
* @param ownerUserId The human owner's users.id — owner memories loaded as fallback
|
|
44
|
+
*/
|
|
45
|
+
setAgentContext(agentUserId: string, ownerUserId: string): void {
|
|
46
|
+
this.agentUserId = agentUserId;
|
|
47
|
+
this.ownerUserId = ownerUserId;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Clear agent context (revert to normal user-scoped mode). */
|
|
51
|
+
clearAgentContext(): void {
|
|
52
|
+
this.agentUserId = null;
|
|
53
|
+
this.ownerUserId = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Inject effective_user_id + owner_user_id into params. */
|
|
57
|
+
private agentParams(extra: Record<string, unknown> = {}): Record<string, unknown> {
|
|
58
|
+
const params: Record<string, unknown> = { ...extra };
|
|
59
|
+
if (this.agentUserId) {
|
|
60
|
+
params.effective_user_id = this.agentUserId;
|
|
61
|
+
if (this.ownerUserId) {
|
|
62
|
+
params.owner_user_id = this.ownerUserId;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return params;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Store a new memory. Called by the agent after completing tasks.
|
|
70
|
+
* When agent context is set, stores under the agent's user_id.
|
|
32
71
|
*/
|
|
33
72
|
async remember(
|
|
34
73
|
content: string,
|
|
@@ -44,14 +83,14 @@ export class MemoryManager {
|
|
|
44
83
|
? new Date(Date.now() + options.expiresInDays * 86400_000).toISOString()
|
|
45
84
|
: null;
|
|
46
85
|
|
|
47
|
-
const data = await callMcpHandler<Memory>("memory.store", {
|
|
86
|
+
const data = await callMcpHandler<Memory>("memory.store", this.agentParams({
|
|
48
87
|
category,
|
|
49
88
|
content,
|
|
50
89
|
importance: options?.importance ?? 5,
|
|
51
90
|
tags: options?.tags ?? [],
|
|
52
91
|
source_message_id: options?.sourceMessageId ?? null,
|
|
53
92
|
expires_at: expiresAt,
|
|
54
|
-
});
|
|
93
|
+
}));
|
|
55
94
|
|
|
56
95
|
log.debug(`Memory stored: [${category}] ${content.slice(0, 80)}...`);
|
|
57
96
|
return data;
|
|
@@ -62,10 +101,10 @@ export class MemoryManager {
|
|
|
62
101
|
*/
|
|
63
102
|
async search(query: string, limit = 10): Promise<Memory[]> {
|
|
64
103
|
try {
|
|
65
|
-
return await callMcpHandler<Memory[]>("memory.search", {
|
|
104
|
+
return await callMcpHandler<Memory[]>("memory.search", this.agentParams({
|
|
66
105
|
query,
|
|
67
106
|
limit,
|
|
68
|
-
});
|
|
107
|
+
}));
|
|
69
108
|
} catch (err) {
|
|
70
109
|
log.warn(`Memory search failed: ${err instanceof Error ? err.message : err}`);
|
|
71
110
|
return [];
|
|
@@ -74,13 +113,13 @@ export class MemoryManager {
|
|
|
74
113
|
|
|
75
114
|
/**
|
|
76
115
|
* Get the most important/recent memories to include in context.
|
|
77
|
-
*
|
|
78
|
-
*
|
|
116
|
+
* When agent context is set, loads agent's memories first,
|
|
117
|
+
* then supplements with the owner's memories as fallback.
|
|
79
118
|
*/
|
|
80
119
|
async getContext(maxItems = 20): Promise<Memory[]> {
|
|
81
|
-
const all = await callMcpHandler<Memory[]>("memory.get_context", {
|
|
120
|
+
const all = await callMcpHandler<Memory[]>("memory.get_context", this.agentParams({
|
|
82
121
|
max_items: maxItems,
|
|
83
|
-
});
|
|
122
|
+
}));
|
|
84
123
|
|
|
85
124
|
// Deduplicate by id
|
|
86
125
|
const seen = new Set<string>();
|
|
@@ -98,39 +137,65 @@ export class MemoryManager {
|
|
|
98
137
|
const memories = await this.getContext();
|
|
99
138
|
if (memories.length === 0) return "";
|
|
100
139
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
// Separate agent's own memories from owner's (only relevant in agent mode)
|
|
141
|
+
const hasOwnerFlag = memories.some((m) => m._from_owner);
|
|
142
|
+
const agentMemories = hasOwnerFlag
|
|
143
|
+
? memories.filter((m) => !m._from_owner)
|
|
144
|
+
: memories;
|
|
145
|
+
const ownerMemories = hasOwnerFlag
|
|
146
|
+
? memories.filter((m) => m._from_owner)
|
|
147
|
+
: [];
|
|
108
148
|
|
|
109
149
|
const categoryLabels: Record<string, string> = {
|
|
110
150
|
instruction: "Standing Instructions",
|
|
111
|
-
preference: "
|
|
151
|
+
preference: "Preferences",
|
|
112
152
|
general: "Known Facts",
|
|
113
153
|
context: "Context",
|
|
114
154
|
skill_learned: "Learned Skills",
|
|
115
155
|
fact: "Facts",
|
|
116
156
|
};
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
158
|
+
function formatSection(mems: Memory[]): string {
|
|
159
|
+
const sections: Record<string, string[]> = {};
|
|
160
|
+
for (const m of mems) {
|
|
161
|
+
const key = m.category;
|
|
162
|
+
if (!sections[key]) sections[key] = [];
|
|
163
|
+
sections[key].push(`- ${m.content}`);
|
|
164
|
+
}
|
|
165
|
+
let out = "";
|
|
166
|
+
for (const [cat, items] of Object.entries(sections)) {
|
|
167
|
+
out += `\n### ${categoryLabels[cat] || cat}\n`;
|
|
168
|
+
out += `${items.join("\n")}\n`;
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let prompt = "";
|
|
174
|
+
|
|
175
|
+
if (agentMemories.length > 0) {
|
|
176
|
+
prompt += this.agentUserId
|
|
177
|
+
? "\n\n## Your Memories\n"
|
|
178
|
+
: "\n\n## What You Know About The User\n";
|
|
179
|
+
prompt += formatSection(agentMemories);
|
|
122
180
|
}
|
|
123
181
|
|
|
182
|
+
if (ownerMemories.length > 0) {
|
|
183
|
+
prompt += "\n\n## Owner's Context\n";
|
|
184
|
+
prompt += formatSection(ownerMemories);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
124
189
|
return prompt;
|
|
125
190
|
}
|
|
126
191
|
|
|
127
192
|
// ── CRUD for CLI ────────────────────────────────────────────
|
|
128
193
|
|
|
129
194
|
async list(category?: MemoryCategory, limit = 20): Promise<Memory[]> {
|
|
130
|
-
const data = await callMcpHandler<Memory[]>("memory.list", {
|
|
195
|
+
const data = await callMcpHandler<Memory[]>("memory.list", this.agentParams({
|
|
131
196
|
category: category || null,
|
|
132
197
|
limit,
|
|
133
|
-
});
|
|
198
|
+
}));
|
|
134
199
|
return data || [];
|
|
135
200
|
}
|
|
136
201
|
|
|
@@ -144,13 +209,13 @@ export class MemoryManager {
|
|
|
144
209
|
}
|
|
145
210
|
|
|
146
211
|
async remove(memoryId: string): Promise<void> {
|
|
147
|
-
await callMcpHandler("memory.remove", { memory_id: memoryId });
|
|
212
|
+
await callMcpHandler("memory.remove", this.agentParams({ memory_id: memoryId }));
|
|
148
213
|
}
|
|
149
214
|
|
|
150
215
|
async clear(category?: MemoryCategory): Promise<number> {
|
|
151
|
-
const result = await callMcpHandler<{ count: number }>("memory.clear", {
|
|
216
|
+
const result = await callMcpHandler<{ count: number }>("memory.clear", this.agentParams({
|
|
152
217
|
category: category || null,
|
|
153
|
-
});
|
|
218
|
+
}));
|
|
154
219
|
return result.count;
|
|
155
220
|
}
|
|
156
221
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Scheduling — orchestration, job lifecycle, task polling, and cron scheduling.
|
|
3
3
|
*/
|
|
4
4
|
export { JobAnalysisPoller } from "./job-analysis-poller.js";
|
|
5
|
-
export { type JobInfo, type JobRunInfo,JobRunner, type JobSkillInfo } from "./job-runner.js";
|
|
5
|
+
export { type JobAgentInfo, type JobInfo, type JobRunInfo, JobRunner, type JobSkillInfo } from "./job-runner.js";
|
|
6
6
|
export {
|
|
7
7
|
createScheduledTask,
|
|
8
8
|
deleteScheduledTask,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { callMcpHandler } from "../../db/api-client.js";
|
|
2
2
|
import { errorMessage } from "../../utils/errors.js";
|
|
3
3
|
import { log } from "../../utils/logger.js";
|
|
4
|
-
import { JobListRowSchema, JobRowSchema, JobRunRowSchema, safeParse } from "../../utils/schemas.js";
|
|
4
|
+
import { JobAgentSchema, JobListRowSchema, JobRowSchema, JobRunRowSchema, safeParse } from "../../utils/schemas.js";
|
|
5
5
|
|
|
6
6
|
// ── Interfaces ─────────────────────────────────────────────────────
|
|
7
7
|
|
|
@@ -13,12 +13,22 @@ export interface JobSkillInfo {
|
|
|
13
13
|
skillContent: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface JobAgentInfo {
|
|
17
|
+
agentId: string;
|
|
18
|
+
agentUsername: string;
|
|
19
|
+
agentName: string;
|
|
20
|
+
agentBio: string | null;
|
|
21
|
+
agentPrompt: string;
|
|
22
|
+
role: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export interface JobInfo {
|
|
17
26
|
jobId: string;
|
|
18
27
|
jobName: string;
|
|
19
28
|
jobDescription: string;
|
|
20
29
|
jobPrompt: string | null;
|
|
21
30
|
skills: JobSkillInfo[];
|
|
31
|
+
agents: JobAgentInfo[];
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
export interface JobRunInfo {
|
|
@@ -55,6 +65,20 @@ export class JobRunner {
|
|
|
55
65
|
|
|
56
66
|
const first = rows[0];
|
|
57
67
|
|
|
68
|
+
// Agents are attached to the first row only
|
|
69
|
+
const rawAgents = (first as Record<string, unknown>).agents as unknown[] || [];
|
|
70
|
+
const agents: JobAgentInfo[] = rawAgents
|
|
71
|
+
.map((a) => safeParse(JobAgentSchema, a))
|
|
72
|
+
.filter((a): a is NonNullable<typeof a> => a != null)
|
|
73
|
+
.map((a) => ({
|
|
74
|
+
agentId: a.agent_id,
|
|
75
|
+
agentUsername: a.agent_username,
|
|
76
|
+
agentName: a.agent_name || "",
|
|
77
|
+
agentBio: a.agent_bio ?? null,
|
|
78
|
+
agentPrompt: a.agent_prompt,
|
|
79
|
+
role: a.role,
|
|
80
|
+
}));
|
|
81
|
+
|
|
58
82
|
return {
|
|
59
83
|
jobId: first.job_id,
|
|
60
84
|
jobName: first.job_name,
|
|
@@ -69,6 +93,7 @@ export class JobRunner {
|
|
|
69
93
|
skillEmoji: row.skill_emoji,
|
|
70
94
|
skillContent: row.skill_content,
|
|
71
95
|
})),
|
|
96
|
+
agents,
|
|
72
97
|
};
|
|
73
98
|
} catch (err) {
|
|
74
99
|
log.debug(`Failed to load job "${jobName}": ${errorMessage(err)}`);
|
|
@@ -80,7 +105,7 @@ export class JobRunner {
|
|
|
80
105
|
* List all jobs for the user.
|
|
81
106
|
*/
|
|
82
107
|
async listJobs(): Promise<
|
|
83
|
-
Array<{ id: string; name: string; description: string; skillCount: number }>
|
|
108
|
+
Array<{ id: string; name: string; description: string; skillCount: number; agentCount: number }>
|
|
84
109
|
> {
|
|
85
110
|
try {
|
|
86
111
|
const data = await callMcpHandler<unknown[]>("job.list");
|
|
@@ -93,6 +118,7 @@ export class JobRunner {
|
|
|
93
118
|
name: row.name,
|
|
94
119
|
description: row.description,
|
|
95
120
|
skillCount: row.skill_count,
|
|
121
|
+
agentCount: row.agent_count,
|
|
96
122
|
}));
|
|
97
123
|
} catch (err) {
|
|
98
124
|
log.debug(`Failed to list jobs: ${errorMessage(err)}`);
|
|
@@ -190,14 +216,25 @@ export class JobRunner {
|
|
|
190
216
|
prompt += `\n`;
|
|
191
217
|
}
|
|
192
218
|
|
|
193
|
-
prompt += `## Instructions\n`;
|
|
219
|
+
prompt += `## Instructions\n\n`;
|
|
220
|
+
prompt += `### Step 1: Analyze and set up skills\n`;
|
|
194
221
|
prompt += `For each capability the job needs:\n`;
|
|
195
222
|
prompt += `1. Check if an existing skill covers it (use \`skill_search\` to check)\n`;
|
|
196
223
|
prompt += `2. If no existing skill covers it, create a new one using \`skill_create\`\n`;
|
|
197
224
|
prompt += `3. If an existing skill needs improvement, update it using \`skill_improve\`\n`;
|
|
198
225
|
prompt += `4. Link all relevant skills to the job using \`skill_link_job\`\n\n`;
|
|
199
|
-
prompt += `Be practical — only create skills that would genuinely help automate this job.\n`;
|
|
200
|
-
|
|
226
|
+
prompt += `Be practical — only create skills that would genuinely help automate this job.\n\n`;
|
|
227
|
+
|
|
228
|
+
prompt += `### Step 2: Create the job's agent\n`;
|
|
229
|
+
prompt += `After setting up skills, create an agent for this job using \`job_assign_agent\`.\n`;
|
|
230
|
+
prompt += `The agent is the identity that will execute this job. Design it with:\n`;
|
|
231
|
+
prompt += `- **agent_name**: A concise, role-based name (e.g. "Daily Reporter", "Code Reviewer")\n`;
|
|
232
|
+
prompt += `- **agent_prompt**: A system prompt that defines the agent's behavior, expertise, and personality. `;
|
|
233
|
+
prompt += `Include the job's goals, preferred approach, and any domain knowledge the agent should have. `;
|
|
234
|
+
prompt += `Reference the linked skills as capabilities the agent can use.\n`;
|
|
235
|
+
prompt += `- **agent_bio**: A one-line description of what this agent does.\n\n`;
|
|
236
|
+
|
|
237
|
+
prompt += `When done, summarize what skills were created/updated and the agent that was assigned.\n`;
|
|
201
238
|
|
|
202
239
|
return prompt;
|
|
203
240
|
}
|
|
@@ -211,13 +248,41 @@ export class JobRunner {
|
|
|
211
248
|
* chain them based on what it discovers at runtime.
|
|
212
249
|
*/
|
|
213
250
|
buildJobPrompt(job: JobInfo, runId: string): string {
|
|
251
|
+
const primaryAgent = job.agents.length > 0
|
|
252
|
+
? (job.agents.find((a) => a.role === "primary") || job.agents[0])
|
|
253
|
+
: null;
|
|
254
|
+
const parallelAgents = job.agents.filter(
|
|
255
|
+
(a) => a.role === "parallel" && a.agentId !== primaryAgent?.agentId,
|
|
256
|
+
);
|
|
214
257
|
const effectiveDescription = job.jobPrompt || job.jobDescription;
|
|
215
258
|
let prompt = `## Job: ${job.jobName}\n`;
|
|
216
259
|
prompt += `*${effectiveDescription}*\n\n`;
|
|
217
260
|
prompt += `**Run ID:** ${runId}\n\n`;
|
|
218
261
|
|
|
219
|
-
|
|
220
|
-
|
|
262
|
+
if (primaryAgent) {
|
|
263
|
+
prompt += `## Agent Identity\n`;
|
|
264
|
+
prompt += `You are **${primaryAgent.agentName}** (@${primaryAgent.agentUsername}).\n`;
|
|
265
|
+
if (primaryAgent.agentBio) {
|
|
266
|
+
prompt += `*${primaryAgent.agentBio}*\n`;
|
|
267
|
+
}
|
|
268
|
+
prompt += `\n### Agent Instructions\n`;
|
|
269
|
+
prompt += `${primaryAgent.agentPrompt}\n\n`;
|
|
270
|
+
} else {
|
|
271
|
+
prompt += `You are now acting as "${job.jobName}". `;
|
|
272
|
+
prompt += `Your goal is to accomplish the objectives described above using the skills and tools available to you.\n\n`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (parallelAgents.length > 0) {
|
|
276
|
+
prompt += `### Parallel Agents\n`;
|
|
277
|
+
prompt += `The following agents are running concurrently on this job. `;
|
|
278
|
+
prompt += `They handle their own tasks independently — coordinate to avoid duplicate work.\n\n`;
|
|
279
|
+
for (const pa of parallelAgents) {
|
|
280
|
+
prompt += `- **${pa.agentName}** (@${pa.agentUsername})`;
|
|
281
|
+
if (pa.agentBio) prompt += ` — ${pa.agentBio}`;
|
|
282
|
+
prompt += `\n`;
|
|
283
|
+
}
|
|
284
|
+
prompt += `\n`;
|
|
285
|
+
}
|
|
221
286
|
|
|
222
287
|
prompt += `### Available Skills\n`;
|
|
223
288
|
prompt += `These skills are your capabilities for this job. `;
|
package/src/db/api-client.ts
CHANGED
|
@@ -17,7 +17,7 @@ export async function callMcpHandler<T = unknown>(
|
|
|
17
17
|
): Promise<T> {
|
|
18
18
|
const config = getConfig();
|
|
19
19
|
const token = overrideToken || getRawToken();
|
|
20
|
-
const url = `${config.supabaseUrl}/functions/v1/mcp-handler`;
|
|
20
|
+
const url = `${config.supabaseUrl}/functions/v1/main-mcp-handler`;
|
|
21
21
|
|
|
22
22
|
const response = await fetch(url, {
|
|
23
23
|
method: "POST",
|
|
@@ -26,7 +26,7 @@ export function createJobTools(sessionId?: string, taskId?: string) {
|
|
|
26
26
|
const jobs = await runner.listJobs();
|
|
27
27
|
const available =
|
|
28
28
|
jobs.length > 0
|
|
29
|
-
? `Available jobs: ${jobs.map((j) => `"${j.name}" (${j.skillCount} skills)`).join(", ")}`
|
|
29
|
+
? `Available jobs: ${jobs.map((j) => `"${j.name}" (${j.skillCount} skills, ${j.agentCount} agents)`).join(", ")}`
|
|
30
30
|
: "No jobs defined yet. Use skill_generate to create a job from a job description.";
|
|
31
31
|
return textResponse(`Job "${args.job_name}" not found. ${available}`);
|
|
32
32
|
}
|
|
@@ -72,13 +72,34 @@ export function createJobTools(sessionId?: string, taskId?: string) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const effectiveRunId = runId || "untracked";
|
|
76
|
+
const prompt = runner.buildJobPrompt(job, effectiveRunId);
|
|
77
|
+
|
|
78
|
+
// Build parallel agent prompts if any
|
|
79
|
+
const primaryAgentForFilter = job.agents.length > 0
|
|
80
|
+
? (job.agents.find((p) => p.role === "primary") || job.agents[0])
|
|
81
|
+
: null;
|
|
82
|
+
const parallelAgents = job.agents.filter(
|
|
83
|
+
(a) => a.role === "parallel" && a.agentId !== primaryAgentForFilter?.agentId,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
let fullResponse = prompt;
|
|
87
|
+
if (parallelAgents.length > 0) {
|
|
88
|
+
fullResponse += `\n\n---\n\n## Parallel Agent Dispatch\n`;
|
|
89
|
+
fullResponse += `The following parallel agents are assigned to this job. `;
|
|
90
|
+
fullResponse += `After you complete your primary tasks, review their responsibilities and execute any that haven't been covered:\n\n`;
|
|
91
|
+
for (const pa of parallelAgents) {
|
|
92
|
+
fullResponse += `### ${pa.agentName} (@${pa.agentUsername})\n`;
|
|
93
|
+
if (pa.agentBio) fullResponse += `*${pa.agentBio}*\n`;
|
|
94
|
+
fullResponse += `**Instructions:** ${pa.agentPrompt}\n\n`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
76
97
|
|
|
77
98
|
log.info(
|
|
78
|
-
`Job "${args.job_name}" started with ${job.skills.length} skills (run: ${
|
|
99
|
+
`Job "${args.job_name}" started with ${job.skills.length} skills, ${job.agents.length} agents (run: ${effectiveRunId.slice(0, 8)})`
|
|
79
100
|
);
|
|
80
101
|
|
|
81
|
-
return textResponse(
|
|
102
|
+
return textResponse(fullResponse);
|
|
82
103
|
})
|
|
83
104
|
),
|
|
84
105
|
tool(
|
|
@@ -151,6 +172,89 @@ export function createJobTools(sessionId?: string, taskId?: string) {
|
|
|
151
172
|
}
|
|
152
173
|
)
|
|
153
174
|
),
|
|
175
|
+
tool(
|
|
176
|
+
"job_assign_agent",
|
|
177
|
+
"Create or update an agent for a job. The agent is a user with a specific prompt and identity that will execute this job.",
|
|
178
|
+
{
|
|
179
|
+
job_name: z.string().describe("Name of the job to assign the agent to"),
|
|
180
|
+
agent_name: z.string().describe("Display name for the agent (e.g. 'Daily Reporter')"),
|
|
181
|
+
agent_prompt: z.string().describe("The agent's system prompt that defines its behavior and personality"),
|
|
182
|
+
agent_bio: z.string().optional().describe("Short bio describing the agent's role"),
|
|
183
|
+
role: z.enum(["primary", "parallel"]).optional().describe("Agent role: 'primary' (default) or 'parallel' for concurrent execution"),
|
|
184
|
+
},
|
|
185
|
+
typedHandler(
|
|
186
|
+
async (args: {
|
|
187
|
+
job_name: string;
|
|
188
|
+
agent_name: string;
|
|
189
|
+
agent_prompt: string;
|
|
190
|
+
agent_bio?: string;
|
|
191
|
+
role?: string;
|
|
192
|
+
}) => {
|
|
193
|
+
try {
|
|
194
|
+
const result = await callMcpHandler<{
|
|
195
|
+
agent_id: string;
|
|
196
|
+
username: string;
|
|
197
|
+
job_id: string;
|
|
198
|
+
}>("job.save_agent", {
|
|
199
|
+
job_name: args.job_name,
|
|
200
|
+
agent_name: args.agent_name,
|
|
201
|
+
agent_prompt: args.agent_prompt,
|
|
202
|
+
agent_bio: args.agent_bio || null,
|
|
203
|
+
role: args.role || "primary",
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!result) {
|
|
207
|
+
return textResponse(`Failed to create agent for job "${args.job_name}".`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
log.info(
|
|
211
|
+
`Agent "${result.username}" assigned to job "${args.job_name}" (${args.role || "primary"})`
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
let response = `## Agent Assigned to Job: ${args.job_name}\n\n`;
|
|
215
|
+
response += `- **Agent:** ${args.agent_name} (@${result.username})\n`;
|
|
216
|
+
response += `- **Role:** ${args.role || "primary"}\n`;
|
|
217
|
+
if (args.agent_bio) {
|
|
218
|
+
response += `- **Bio:** ${args.agent_bio}\n`;
|
|
219
|
+
}
|
|
220
|
+
response += `\nThe agent is now ready to execute this job.`;
|
|
221
|
+
|
|
222
|
+
return textResponse(response);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
return textResponse(
|
|
225
|
+
`Failed to assign agent: ${err instanceof Error ? err.message : err}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
),
|
|
231
|
+
tool(
|
|
232
|
+
"job_remove_agent",
|
|
233
|
+
"Remove (deactivate) an agent from a job.",
|
|
234
|
+
{
|
|
235
|
+
job_name: z.string().describe("Name of the job"),
|
|
236
|
+
agent_name: z.string().describe("Display name of the agent to remove"),
|
|
237
|
+
},
|
|
238
|
+
typedHandler(
|
|
239
|
+
async (args: { job_name: string; agent_name: string }) => {
|
|
240
|
+
try {
|
|
241
|
+
await callMcpHandler("job.remove_agent", {
|
|
242
|
+
job_name: args.job_name,
|
|
243
|
+
agent_name: args.agent_name,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
log.info(`Agent "${args.agent_name}" removed from job "${args.job_name}"`);
|
|
247
|
+
return textResponse(
|
|
248
|
+
`Agent "${args.agent_name}" has been removed from job "${args.job_name}".`
|
|
249
|
+
);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
return textResponse(
|
|
252
|
+
`Failed to remove agent: ${err instanceof Error ? err.message : err}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
),
|
|
154
258
|
tool(
|
|
155
259
|
"job_status",
|
|
156
260
|
"Check the status and run history of a job.",
|
|
@@ -171,7 +275,7 @@ export function createJobTools(sessionId?: string, taskId?: string) {
|
|
|
171
275
|
|
|
172
276
|
let response = "## Your Jobs\n\n";
|
|
173
277
|
for (const job of jobs) {
|
|
174
|
-
response += `- **${job.name}** (${job.skillCount} skills): ${job.description.slice(0, 100)}\n`;
|
|
278
|
+
response += `- **${job.name}** (${job.skillCount} skills, ${job.agentCount} agents): ${job.description.slice(0, 100)}\n`;
|
|
175
279
|
}
|
|
176
280
|
response += "\nUse `job_run` to execute a job, or `job_schedule` to automate it.";
|
|
177
281
|
return textResponse(response);
|
package/src/orchestrator.ts
CHANGED
|
@@ -219,7 +219,24 @@ export class Orchestrator {
|
|
|
219
219
|
log.debug(`Failed to link session to job run: ${linkErr}`);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
let prompt = runner.buildJobPrompt(job, jobRun.id);
|
|
223
|
+
|
|
224
|
+
// Append parallel agent instructions so the primary agent covers all responsibilities
|
|
225
|
+
const primaryAgent = job.agents.length > 0
|
|
226
|
+
? (job.agents.find((a) => a.role === "primary") || job.agents[0])
|
|
227
|
+
: null;
|
|
228
|
+
const parallelAgents = job.agents.filter(
|
|
229
|
+
(a) => a.role === "parallel" && a.agentId !== primaryAgent?.agentId,
|
|
230
|
+
);
|
|
231
|
+
if (parallelAgents.length > 0) {
|
|
232
|
+
prompt += `\n\n---\n\n## Parallel Agent Responsibilities\n`;
|
|
233
|
+
prompt += `The following agents are also assigned. Execute their responsibilities as part of this run:\n\n`;
|
|
234
|
+
for (const pa of parallelAgents) {
|
|
235
|
+
prompt += `### ${pa.agentName}\n`;
|
|
236
|
+
if (pa.agentBio) prompt += `*${pa.agentBio}*\n`;
|
|
237
|
+
prompt += `**Instructions:** ${pa.agentPrompt}\n\n`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
223
240
|
|
|
224
241
|
try {
|
|
225
242
|
await this.dispatchAndWait(`[JobRun: ${jobRun.job_name}] ${prompt}`);
|
package/src/utils/schemas.ts
CHANGED
|
@@ -49,6 +49,15 @@ export const SkillRowSchema = z.object({
|
|
|
49
49
|
|
|
50
50
|
export type SkillRow = z.infer<typeof SkillRowSchema>;
|
|
51
51
|
|
|
52
|
+
export const JobAgentSchema = z.object({
|
|
53
|
+
agent_id: z.string(),
|
|
54
|
+
agent_username: z.string(),
|
|
55
|
+
agent_name: z.string().optional().default(""),
|
|
56
|
+
agent_bio: z.string().optional().nullable(),
|
|
57
|
+
agent_prompt: z.string(),
|
|
58
|
+
role: z.string().optional().default("primary"),
|
|
59
|
+
});
|
|
60
|
+
|
|
52
61
|
export const JobRowSchema = z.object({
|
|
53
62
|
job_id: z.string(),
|
|
54
63
|
job_name: z.string(),
|
|
@@ -59,6 +68,7 @@ export const JobRowSchema = z.object({
|
|
|
59
68
|
skill_description: z.string().optional().default(""),
|
|
60
69
|
skill_emoji: z.string().optional().default(""),
|
|
61
70
|
skill_content: z.string().optional().default(""),
|
|
71
|
+
agents: z.array(JobAgentSchema).optional().default([]),
|
|
62
72
|
});
|
|
63
73
|
|
|
64
74
|
export const JobListRowSchema = z.object({
|
|
@@ -67,6 +77,7 @@ export const JobListRowSchema = z.object({
|
|
|
67
77
|
description: z.string().optional().default(""),
|
|
68
78
|
prompt: z.string().optional().nullable(),
|
|
69
79
|
skill_count: z.number().optional().default(0),
|
|
80
|
+
agent_count: z.number().optional().default(0),
|
|
70
81
|
});
|
|
71
82
|
|
|
72
83
|
export const JobRunRowSchema = z.object({
|