@intx/tools-mail 0.2.2 → 0.4.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/README.md CHANGED
@@ -14,7 +14,11 @@ its definitions alongside other bundles.
14
14
  import { createMailTools } from "@intx/tools-mail";
15
15
  import { defineMailTools } from "@intx/harness";
16
16
 
17
- const mailFactory = defineMailTools(() => createMailTools({ capabilities }));
17
+ const mailTools = createMailTools({ capabilities });
18
+ const mailFactory = defineMailTools(
19
+ () => mailTools,
20
+ mailTools.definitions.map((def) => ({ name: def.name })),
21
+ );
18
22
  const def = defineAgent({ ..., tools: [mailFactory, posixFactory] });
19
23
  ```
20
24
 
@@ -1,3 +1,3 @@
1
1
  import type { ToolDefinition } from "@intx/types/runtime";
2
- export type MailToolName = "mail_send" | "mail_reply" | "mail_search" | "mail_read" | "mail_wait";
2
+ export type MailToolName = "mail_send" | "mail_reply" | "mail_search" | "mail_read" | "mail_wait" | "mail_flag" | "mail_expunge";
3
3
  export declare const TOOL_DEFINITIONS: ToolDefinition[];
@@ -1,4 +1,4 @@
1
- // Static definitions for the five mail tools. The catalog generator and the
1
+ // Static definitions for the mail tools. The catalog generator and the
2
2
  // inference director both consume these as inert data — no factory calls,
3
3
  // no runtime side effects.
4
4
  //
@@ -134,4 +134,42 @@ export const TOOL_DEFINITIONS = [
134
134
  required: ["query"],
135
135
  },
136
136
  },
137
+ {
138
+ name: "mail_flag",
139
+ description: "Set or clear IMAP flags on a message (system flags like \\Seen or \\Deleted, or custom keywords). Provide EITHER 'set' to add flags OR 'clear' to remove them -- one direction per call. To consume a message, flag it \\Deleted then call mail_expunge. An error result means the mailbox was NOT changed.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ ref: {
144
+ type: "object",
145
+ description: "Mail reference { uid, mailbox }",
146
+ properties: {
147
+ uid: { type: "number" },
148
+ mailbox: { type: "string" },
149
+ },
150
+ required: ["uid", "mailbox"],
151
+ },
152
+ set: {
153
+ type: "array",
154
+ items: { type: "string" },
155
+ description: 'Flags to add (e.g. ["\\\\Deleted"])',
156
+ },
157
+ clear: {
158
+ type: "array",
159
+ items: { type: "string" },
160
+ description: "Flags to remove",
161
+ },
162
+ },
163
+ required: ["ref"],
164
+ },
165
+ },
166
+ {
167
+ name: "mail_expunge",
168
+ description: "Permanently remove every message flagged \\Deleted from the INBOX and return the uids removed. Flag a message \\Deleted with mail_flag first. An error result means nothing was removed.",
169
+ inputSchema: {
170
+ type: "object",
171
+ properties: {},
172
+ required: [],
173
+ },
174
+ },
137
175
  ];
@@ -5,3 +5,5 @@ export declare function makeMailReplyHandler(transport: MessageTransport): ToolH
5
5
  export declare function makeMailSearchHandler(transport: MessageTransport): ToolHandler;
6
6
  export declare function makeMailReadHandler(transport: MessageTransport): ToolHandler;
7
7
  export declare function makeMailWaitHandler(transport: MessageTransport): ToolHandler;
8
+ export declare function makeMailFlagHandler(transport: MessageTransport): ToolHandler;
9
+ export declare function makeMailExpungeHandler(transport: MessageTransport): ToolHandler;
package/dist/handlers.js CHANGED
@@ -1,4 +1,4 @@
1
- // Per-tool handler factories for the five mail tools. Each factory takes
1
+ // Per-tool handler factories for the mail tools. Each factory takes
2
2
  // the bound MessageTransport and returns a closed-over ToolHandler.
3
3
  //
4
4
  // Keeping the factories at this granularity (one per tool, pure
@@ -42,6 +42,12 @@ const WaitArgs = type({
42
42
  "timeout?": "number",
43
43
  "mailbox?": "string",
44
44
  });
45
+ const FlagArgs = type({
46
+ ref: { uid: "number", mailbox: "string" },
47
+ "set?": "string[]",
48
+ "clear?": "string[]",
49
+ });
50
+ const ExpungeArgs = type({});
45
51
  // ---------------------------------------------------------------------------
46
52
  // Individual tool handlers
47
53
  // ---------------------------------------------------------------------------
@@ -104,6 +110,15 @@ export function makeMailReplyHandler(transport) {
104
110
  to: parentHeaders.from,
105
111
  type: args.type ?? "conversation.message",
106
112
  inReplyTo: parentHeaders.messageId,
113
+ // The full RFC 5322 References chain for a reply is the parent's own
114
+ // References plus the parent's Message-Id. The parent is in hand here
115
+ // (fetched above for its threading headers), so build the complete
116
+ // ancestry rather than leaving the transport to derive a single-element
117
+ // chain from inReplyTo alone.
118
+ references: [
119
+ ...(parentHeaders.references ?? []),
120
+ parentHeaders.messageId,
121
+ ],
107
122
  };
108
123
  // Carry forward the subject if available.
109
124
  if (parentHeaders.subject !== undefined) {
@@ -115,9 +130,6 @@ export function makeMailReplyHandler(transport) {
115
130
  if (payload !== undefined) {
116
131
  outbound.payload = payload;
117
132
  }
118
- // OutboundMessage does not carry a References field; the transport
119
- // builds the threading chain from inReplyTo when delivering the
120
- // reply.
121
133
  let receipt;
122
134
  try {
123
135
  receipt = await transport.send(outbound, signal);
@@ -317,6 +329,65 @@ export function makeMailWaitHandler(transport) {
317
329
  });
318
330
  };
319
331
  }
332
+ export function makeMailFlagHandler(transport) {
333
+ return async (call, signal) => {
334
+ const args = FlagArgs(call.arguments);
335
+ if (args instanceof type.errors) {
336
+ return errorResult(call.id, args.summary);
337
+ }
338
+ const set = args.set ?? [];
339
+ const clear = args.clear ?? [];
340
+ // Reject an empty mutation at the boundary: a call with neither direction
341
+ // does nothing, and firing an empty flag write would still round-trip to
342
+ // the supervisor as a pointless commit.
343
+ if (set.length === 0 && clear.length === 0) {
344
+ return errorResult(call.id, "provide flags in 'set' or 'clear'");
345
+ }
346
+ // One direction per call. Adding and removing flags in one call would be
347
+ // two separate supervisor round-trips; if the first landed and the second
348
+ // failed, the error's "mailbox unchanged" contract would be a lie. Keeping
349
+ // each call a single mutation makes that contract unconditionally true.
350
+ if (set.length > 0 && clear.length > 0) {
351
+ return errorResult(call.id, "provide 'set' or 'clear', not both -- call mail_flag once per direction");
352
+ }
353
+ try {
354
+ if (set.length > 0) {
355
+ await transport.setFlags(args.ref, set, signal);
356
+ }
357
+ else {
358
+ await transport.clearFlags(args.ref, clear, signal);
359
+ }
360
+ }
361
+ catch (cause) {
362
+ // A rejection means the supervisor did not apply the mutation, so the
363
+ // mailbox is unchanged. Surface that so the model does not assume the
364
+ // flag stuck (and then expunge expecting the message gone).
365
+ return errorResult(call.id, `flag not applied: ${cause instanceof Error ? cause.message : String(cause)}`, "flag_failed");
366
+ }
367
+ return { callId: call.id, content: { ok: true } };
368
+ };
369
+ }
370
+ export function makeMailExpungeHandler(transport) {
371
+ return async (call, signal) => {
372
+ const args = ExpungeArgs(call.arguments);
373
+ if (args instanceof type.errors) {
374
+ return errorResult(call.id, args.summary);
375
+ }
376
+ // The warm agent owns exactly one mailbox; expunge sweeps its INBOX.
377
+ let outcome;
378
+ try {
379
+ outcome = await transport.expunge("INBOX", signal);
380
+ }
381
+ catch (cause) {
382
+ // A rejection means nothing was removed; the mailbox is unchanged.
383
+ return errorResult(call.id, `expunge not applied: ${cause instanceof Error ? cause.message : String(cause)}`, "expunge_failed");
384
+ }
385
+ return {
386
+ callId: call.id,
387
+ content: { ok: true, expungedUids: outcome.expungedUids },
388
+ };
389
+ };
390
+ }
320
391
  // ---------------------------------------------------------------------------
321
392
  // Helper
322
393
  // ---------------------------------------------------------------------------
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // Public surface for @intx/tools-mail.
2
2
  //
3
3
  // createMailTools resolves the bound agent's MessageTransport from the
4
- // supplied RuntimeCapabilities once at handler-init and wires the five
5
- // mail handlers around it. The returned MailTools satisfies the
4
+ // supplied RuntimeCapabilities once at handler-init and wires the mail
5
+ // handlers around it. The returned MailTools satisfies the
6
6
  // ToolRunner contract the harness consumes.
7
7
  import { TOOL_DEFINITIONS } from "./definitions.js";
8
- import { makeMailReadHandler, makeMailReplyHandler, makeMailSearchHandler, makeMailSendHandler, makeMailWaitHandler, } from "./handlers.js";
8
+ import { makeMailExpungeHandler, makeMailFlagHandler, makeMailReadHandler, makeMailReplyHandler, makeMailSearchHandler, makeMailSendHandler, makeMailWaitHandler, } from "./handlers.js";
9
9
  export { TOOL_DEFINITIONS } from "./definitions.js";
10
10
  export function createMailTools(opts) {
11
11
  // Resolve the transport once at handler-init. The lifecycle contract is
@@ -19,6 +19,8 @@ export function createMailTools(opts) {
19
19
  ["mail_search", makeMailSearchHandler(transport)],
20
20
  ["mail_read", makeMailReadHandler(transport)],
21
21
  ["mail_wait", makeMailWaitHandler(transport)],
22
+ ["mail_flag", makeMailFlagHandler(transport)],
23
+ ["mail_expunge", makeMailExpungeHandler(transport)],
22
24
  ]);
23
25
  let disposed = false;
24
26
  return {
@@ -1,13 +1,12 @@
1
1
  import { type BaseEnv } from "@intx/agent";
2
- import type { MessageTransport } from "@intx/types/runtime";
2
+ import type { RuntimeCapabilities } from "@intx/types/runtime-capabilities";
3
3
  /**
4
4
  * Env contract for the mail tool bundle. Extends `BaseEnv` with the
5
- * harness-level fields the mail tools depend on at handler-init time.
6
- * Compatible by structure with `@intx/harness`'s `MailEnv` so a host
7
- * that already provides that env can pass it straight through.
5
+ * host-assembled `capabilities` -- from which the mail tools resolve
6
+ * `mail.transport` -- and the agent `address`.
8
7
  */
9
8
  export interface MailToolEnv extends BaseEnv {
10
- transport: MessageTransport;
9
+ capabilities: RuntimeCapabilities;
11
10
  address: string;
12
11
  }
13
12
  /**
@@ -1,13 +1,15 @@
1
1
  // Sidecar-bundle entry for `@intx/tools-mail` — the convention-compliant
2
2
  // factory the tool-package loader invokes.
3
3
  //
4
- // The factory declares the env keys it touches (`transport`, `address`)
5
- // via `defineTool`'s `requires`. The host populates those slots in the
6
- // per-instance env; the factory wraps the existing `createMailTools`
7
- // implementation.
4
+ // The bundle consumes the host-assembled runtime capabilities rather than
5
+ // building its own. The host (the sidecar's step-env builder) owns the
6
+ // `RuntimeCapabilities` and puts it on `env.capabilities`; this factory
7
+ // resolves `mail.transport` from it through `createMailTools` instead of
8
+ // re-wrapping a raw transport it was handed separately. The env keys it
9
+ // touches (`capabilities`, `address`) are declared in `requires`.
8
10
  import { defineTool } from "@intx/agent";
9
- import { createRuntimeCapabilities } from "@intx/types/runtime-capabilities";
10
11
  import { createMailTools } from "./index.js";
12
+ import { TOOL_DEFINITIONS } from "./definitions.js";
11
13
  /**
12
14
  * Named export the loader picks up. The id is package-namespaced per
13
15
  * the convention; the model-facing tool names are synthesized by the
@@ -15,12 +17,10 @@ import { createMailTools } from "./index.js";
15
17
  */
16
18
  export const mail = defineTool({
17
19
  id: "@intx/tools-mail/sidecar-bundle",
18
- requires: ["transport", "address"],
20
+ requires: ["capabilities", "address"],
21
+ definitions: TOOL_DEFINITIONS.map((def) => ({ name: def.name })),
19
22
  factory: (env) => {
20
- const capabilities = createRuntimeCapabilities({
21
- "mail.transport": env.transport,
22
- });
23
- const tools = createMailTools({ capabilities });
23
+ const tools = createMailTools({ capabilities: env.capabilities });
24
24
  return {
25
25
  definitions: tools.definitions,
26
26
  run: (call, signal) => tools.run(call, signal),
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@intx/tools-mail",
3
- "version": "0.2.2",
3
+ "description": "Mail tool runner giving agents mail_send, mail_reply, mail_search, mail_read, and mail_wait",
4
+ "version": "0.4.0",
4
5
  "license": "LGPL-2.1-only",
5
6
  "type": "module",
6
7
  "interchange": {
@@ -19,10 +20,13 @@
19
20
  }
20
21
  },
21
22
  "dependencies": {
22
- "@intx/agent": "0.2.2",
23
- "@intx/types": "0.2.2",
23
+ "@intx/agent": "0.4.0",
24
+ "@intx/types": "0.4.0",
24
25
  "arktype": "^2.1.29"
25
26
  },
27
+ "devDependencies": {
28
+ "@intx/storage-isogit": "0.4.0"
29
+ },
26
30
  "files": [
27
31
  "dist",
28
32
  "README.md",