@chloejs/core 0.2.4 → 0.3.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.
@@ -0,0 +1,46 @@
1
+ // The tools over services/ownFilesService.ts: an agent reading and changing
2
+ // its own folder, as far as its definition allows. `selfImprovement` with a
3
+ // `selfImprovement` in the definition turns them on.
4
+ import { z } from "zod";
5
+
6
+ import type { Home, OwnFileRules } from "#chloe/load/load.ts";
7
+ import { listOwn, readOwn, writeOwn } from "#chloe/services/ownFilesService.ts";
8
+ import { tool, type Tools } from "../tool.ts";
9
+
10
+ /** list_own_files, read_own_file and write_own_file, for the files `rules` lets it change. */
11
+ export function ownFiles(rules: OwnFileRules): (agent: Home) => Tools {
12
+ const endings = rules.files.map((one) => `.${one.replace(/^\./, "")}`).join(", ");
13
+ const kept = rules.except?.length ? ` Never ${rules.except.join(", ")}.` : "";
14
+ return (agent) => ({
15
+ list_own_files: tool({
16
+ id: "list_own_files",
17
+ description:
18
+ "List the files in your own folder (your instructions, skills and jobs), and which of them you can " +
19
+ "change. Your memory is not in it: that is list_notes.",
20
+ inputSchema: z.object({}),
21
+ execute: () => listOwn(agent, rules),
22
+ }),
23
+ read_own_file: tool({
24
+ id: "read_own_file",
25
+ description:
26
+ "Read one file in your own folder, like instructions.md or jobs/morning-run.md. Read a file before you change it.",
27
+ inputSchema: z.object({ path: z.string().describe("A path inside your folder, from list_own_files.") }),
28
+ execute: ({ path }) => readOwn(agent, rules, path),
29
+ }),
30
+ write_own_file: tool({
31
+ id: "write_own_file",
32
+ description:
33
+ `Change one file in your own folder, ending in ${endings}.${kept} This replaces the whole file, so read it ` +
34
+ "first and include everything you want kept. A markdown file directly in skills/ is a skill, with name and " +
35
+ "description at the top. One directly in jobs/ is a job, with cron, description, timezone and model at the " +
36
+ "top, and a job you write runs at most once an hour. Code, your evals and your memory are not yours to " +
37
+ "write here. Every change is committed under your name with your message, so it can be seen and undone.",
38
+ inputSchema: z.object({
39
+ path: z.string().describe("A path inside your folder, like skills/deploys.md."),
40
+ content: z.string().min(1),
41
+ message: z.string().min(10).describe("What changed and why, as a commit message."),
42
+ }),
43
+ execute: ({ path, content, message }) => writeOwn(agent, rules, path, content, message),
44
+ }),
45
+ });
46
+ }
@@ -5,25 +5,52 @@
5
5
  import { z } from "zod";
6
6
 
7
7
  import { type EmailSender, sendEmail } from "#chloe/services/emailService.ts";
8
- import { tool } from "#chloe/model/tool.ts";
8
+ import { writeFiles } from "#chloe/services/filesService.ts";
9
+ import { tool, type Tools } from "#chloe/model/tool.ts";
9
10
 
10
11
  interface Options extends EmailSender {
11
12
  /** Who it reaches and when to use it, in the agent's own words. Shown to the model. */
12
13
  when: string;
14
+ /**
15
+ * A folder in the agent's memory, like "outbox". Every email sent is copied
16
+ * there as `2026-09-23-0715-<subject>.md`, so the agent can see what it
17
+ * already said before saying it again. Unsaid, nothing is kept.
18
+ */
19
+ keep?: string;
13
20
  }
14
21
 
15
22
  /**
16
23
  * A tool that sends mail from the address the agent was given, to the address
17
24
  * it was given.
18
25
  */
19
- export function send_email({ when, ...sender }: Options) {
20
- return tool({
21
- id: "send_email",
22
- description: `Send an email. ${when}`,
23
- inputSchema: z.object({
24
- subject: z.string().min(5).max(120),
25
- body: z.string().min(20).describe("Plain text. Lead with what happened and what you did."),
26
+ export function send_email({ when, keep, ...sender }: Options) {
27
+ return (agent: { name: string; memory: { folder: string; commit?: boolean | "each run" } }): Tools => ({
28
+ send_email: tool({
29
+ id: "send_email",
30
+ description: `Send an email. ${when}`,
31
+ inputSchema: z.object({
32
+ subject: z.string().min(5).max(120),
33
+ body: z
34
+ .string()
35
+ .min(20)
36
+ .describe(`${sender.markdown ? "Markdown" : "Plain text"}. Lead with what happened and what you did.`),
37
+ }),
38
+ execute: async ({ subject, body }) => {
39
+ const sent = await sendEmail(sender, subject, body);
40
+ if (!keep) return sent;
41
+ const at = new Date().toISOString();
42
+ const slug = subject.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
43
+ const copy = `---\nto: ${sender.to.join(", ")}\nsubject: ${sent.subject}\nsent: ${at}\n---\n\n${body}\n`;
44
+ const { folder, commit } = agent.memory;
45
+ // With "each run", the end of the run commits the copy with everything else it wrote.
46
+ const kept = await writeFiles(folder, `${keep}/${at.slice(0, 16).replace("T", "-").replace(":", "")}-${slug}.md`, copy, {
47
+ commit: commit === true,
48
+ message: `Sent: ${sent.subject}`,
49
+ author: agent.name,
50
+ in: "memory",
51
+ });
52
+ return { ...sent, copy: kept.path };
53
+ },
26
54
  }),
27
- execute: ({ subject, body }) => sendEmail(sender, subject, body),
28
55
  });
29
56
  }