botholomew 0.8.9 → 0.9.4

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.
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatDriveRef } from "../../context/drives.ts";
2
3
  import {
3
4
  contextPathExists,
4
5
  copyContextItem,
@@ -7,14 +8,16 @@ import {
7
8
  import type { ToolDefinition } from "../tool.ts";
8
9
 
9
10
  const inputSchema = z.object({
10
- src: z.string().describe("Source file path"),
11
- dst: z.string().describe("Destination file path"),
11
+ src_drive: z.string().describe("Source drive"),
12
+ src_path: z.string().describe("Source path within the drive"),
13
+ dst_drive: z.string().describe("Destination drive"),
14
+ dst_path: z.string().describe("Destination path within the drive"),
12
15
  overwrite: z.boolean().optional().describe("Overwrite if destination exists"),
13
16
  });
14
17
 
15
18
  const outputSchema = z.object({
16
19
  id: z.string(),
17
- path: z.string(),
20
+ ref: z.string(),
18
21
  is_error: z.boolean(),
19
22
  });
20
23
 
@@ -25,15 +28,18 @@ export const contextCopyTool = {
25
28
  inputSchema,
26
29
  outputSchema,
27
30
  execute: async (input, ctx) => {
28
- const dstExists = await contextPathExists(ctx.conn, input.dst);
31
+ const src = { drive: input.src_drive, path: input.src_path };
32
+ const dst = { drive: input.dst_drive, path: input.dst_path };
33
+
34
+ const dstExists = await contextPathExists(ctx.conn, dst);
29
35
  if (dstExists && !input.overwrite) {
30
- throw new Error(`Destination already exists: ${input.dst}`);
36
+ throw new Error(`Destination already exists: ${formatDriveRef(dst)}`);
31
37
  }
32
38
  if (dstExists) {
33
- await deleteContextItemByPath(ctx.conn, input.dst);
39
+ await deleteContextItemByPath(ctx.conn, dst);
34
40
  }
35
41
 
36
- const item = await copyContextItem(ctx.conn, input.src, input.dst);
37
- return { id: item.id, path: item.context_path, is_error: false };
42
+ const item = await copyContextItem(ctx.conn, src, dst);
43
+ return { id: item.id, ref: formatDriveRef(item), is_error: false };
38
44
  },
39
45
  } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
@@ -1,9 +1,11 @@
1
1
  import { z } from "zod";
2
+ import { formatDriveRef } from "../../context/drives.ts";
2
3
  import { resolveContextItemOrThrow } from "../../db/context.ts";
3
4
  import type { ToolDefinition } from "../tool.ts";
4
5
 
5
6
  const inputSchema = z.object({
6
- path: z.string().describe("File path or context item ID"),
7
+ drive: z.string().describe("Drive name (e.g. 'disk', 'agent')"),
8
+ path: z.string().describe("Path within the drive"),
7
9
  });
8
10
 
9
11
  const outputSchema = z.object({
@@ -19,8 +21,9 @@ export const contextCountLinesTool = {
19
21
  inputSchema,
20
22
  outputSchema,
21
23
  execute: async (input, ctx) => {
22
- const item = await resolveContextItemOrThrow(ctx.conn, input.path);
23
- if (item.content == null) throw new Error(`No text content: ${input.path}`);
24
+ const ref = formatDriveRef({ drive: input.drive, path: input.path });
25
+ const item = await resolveContextItemOrThrow(ctx.conn, ref);
26
+ if (item.content == null) throw new Error(`No text content: ${ref}`);
24
27
 
25
28
  return { lines: item.content.split("\n").length, is_error: false };
26
29
  },
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatDriveRef } from "../../context/drives.ts";
2
3
  import {
3
4
  deleteContextItemByPath,
4
5
  deleteContextItemsByPrefix,
@@ -6,7 +7,8 @@ import {
6
7
  import type { ToolDefinition } from "../tool.ts";
7
8
 
8
9
  const inputSchema = z.object({
9
- path: z.string().describe("Path to delete"),
10
+ drive: z.string().describe("Drive name"),
11
+ path: z.string().describe("Path to delete within the drive"),
10
12
  recursive: z
11
13
  .boolean()
12
14
  .optional()
@@ -30,15 +32,20 @@ export const contextDeleteTool = {
30
32
  inputSchema,
31
33
  outputSchema,
32
34
  execute: async (input, ctx) => {
35
+ const target = { drive: input.drive, path: input.path };
33
36
  if (input.recursive) {
34
- const count = await deleteContextItemsByPrefix(ctx.conn, input.path);
35
- const exact = await deleteContextItemByPath(ctx.conn, input.path);
37
+ const count = await deleteContextItemsByPrefix(
38
+ ctx.conn,
39
+ target.drive,
40
+ target.path,
41
+ );
42
+ const exact = await deleteContextItemByPath(ctx.conn, target);
36
43
  return { deleted: count + (exact ? 1 : 0), is_error: false };
37
44
  }
38
45
 
39
- const deleted = await deleteContextItemByPath(ctx.conn, input.path);
46
+ const deleted = await deleteContextItemByPath(ctx.conn, target);
40
47
  if (!deleted && !input.force) {
41
- throw new Error(`Not found: ${input.path}`);
48
+ throw new Error(`Not found: ${formatDriveRef(target)}`);
42
49
  }
43
50
  return { deleted: deleted ? 1 : 0, is_error: false };
44
51
  },
@@ -14,7 +14,8 @@ const PatchSchema = z.object({
14
14
  });
15
15
 
16
16
  const inputSchema = z.object({
17
- path: z.string().describe("File path to edit"),
17
+ drive: z.string().describe("Drive name (e.g. 'agent', 'disk')"),
18
+ path: z.string().describe("Path within the drive (starts with /)"),
18
19
  patches: z.array(PatchSchema).describe("Patches to apply"),
19
20
  });
20
21
 
@@ -32,13 +33,14 @@ export const contextEditTool = {
32
33
  inputSchema,
33
34
  outputSchema,
34
35
  execute: async (input, ctx) => {
36
+ const target = { drive: input.drive, path: input.path };
35
37
  const { item, applied } = await applyPatchesToContextItem(
36
38
  ctx.conn,
37
- input.path,
39
+ target,
38
40
  input.patches,
39
41
  );
40
42
 
41
- await ingestByPath(ctx.conn, input.path, ctx.config);
43
+ await ingestByPath(ctx.conn, target, ctx.config);
42
44
  return { applied, content: item.content ?? "", is_error: false };
43
45
  },
44
46
  } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
@@ -1,9 +1,17 @@
1
1
  import { z } from "zod";
2
- import { resolveContextItem } from "../../db/context.ts";
2
+ import { parseDriveRef } from "../../context/drives.ts";
3
+ import { getContextItem, getContextItemById } from "../../db/context.ts";
4
+ import { isUuid } from "../../db/uuid.ts";
3
5
  import type { ToolDefinition } from "../tool.ts";
4
6
 
5
7
  const inputSchema = z.object({
6
- path: z.string().describe("File path or context item ID"),
8
+ drive: z
9
+ .string()
10
+ .optional()
11
+ .describe("Drive name. Optional when `path` is a UUID or 'drive:/path'."),
12
+ path: z
13
+ .string()
14
+ .describe("Path within the drive (or UUID / drive:/path ref)"),
7
15
  });
8
16
 
9
17
  const outputSchema = z.object({
@@ -19,7 +27,21 @@ export const contextExistsTool = {
19
27
  inputSchema,
20
28
  outputSchema,
21
29
  execute: async (input, ctx) => {
22
- const item = await resolveContextItem(ctx.conn, input.path);
30
+ if (isUuid(input.path)) {
31
+ const item = await getContextItemById(ctx.conn, input.path);
32
+ return { exists: item !== null, is_error: false };
33
+ }
34
+ const parsed = parseDriveRef(input.path);
35
+ if (parsed) {
36
+ const item = await getContextItem(ctx.conn, parsed);
37
+ return { exists: item !== null, is_error: false };
38
+ }
39
+ if (!input.drive) return { exists: false, is_error: false };
40
+ const path = input.path.startsWith("/") ? input.path : `/${input.path}`;
41
+ const item = await getContextItem(ctx.conn, {
42
+ drive: input.drive,
43
+ path,
44
+ });
23
45
  return { exists: item !== null, is_error: false };
24
46
  },
25
47
  } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
@@ -1,12 +1,28 @@
1
1
  import { z } from "zod";
2
- import { resolveContextItemOrThrow } from "../../db/context.ts";
2
+ import { formatDriveRef, parseDriveRef } from "../../context/drives.ts";
3
+ import {
4
+ findNearbyContextPaths,
5
+ getContextItem,
6
+ getContextItemById,
7
+ } from "../../db/context.ts";
8
+ import { isUuid } from "../../db/uuid.ts";
3
9
  import type { ToolDefinition } from "../tool.ts";
4
10
 
5
11
  const inputSchema = z.object({
6
- path: z.string().describe("File path or context item ID"),
12
+ drive: z
13
+ .string()
14
+ .optional()
15
+ .describe(
16
+ "Drive name (e.g. 'disk', 'url', 'agent'). Optional when `path` is a UUID or already in `drive:/...` form.",
17
+ ),
18
+ path: z
19
+ .string()
20
+ .describe(
21
+ "Path within the drive (starts with /), or a bare UUID / 'drive:/path' ref.",
22
+ ),
7
23
  });
8
24
 
9
- const outputSchema = z.object({
25
+ const fileSchema = z.object({
10
26
  id: z.string(),
11
27
  title: z.string(),
12
28
  description: z.string(),
@@ -14,12 +30,20 @@ const outputSchema = z.object({
14
30
  is_textual: z.boolean(),
15
31
  size: z.number(),
16
32
  lines: z.number(),
17
- source_path: z.string().nullable(),
18
- context_path: z.string(),
33
+ drive: z.string(),
34
+ path: z.string(),
35
+ ref: z.string(),
19
36
  indexed_at: z.string().nullable(),
20
37
  created_at: z.string(),
21
38
  updated_at: z.string(),
39
+ });
40
+
41
+ const outputSchema = z.object({
42
+ file: fileSchema.optional(),
22
43
  is_error: z.boolean(),
44
+ error_type: z.string().optional(),
45
+ message: z.string().optional(),
46
+ next_action_hint: z.string().optional(),
23
47
  });
24
48
 
25
49
  export const contextInfoTool = {
@@ -30,22 +54,70 @@ export const contextInfoTool = {
30
54
  inputSchema,
31
55
  outputSchema,
32
56
  execute: async (input, ctx) => {
33
- const item = await resolveContextItemOrThrow(ctx.conn, input.path);
57
+ let drive = input.drive;
58
+ let path = input.path;
59
+ if (isUuid(input.path)) {
60
+ const byId = await getContextItemById(ctx.conn, input.path);
61
+ if (byId) {
62
+ drive = byId.drive;
63
+ path = byId.path;
64
+ }
65
+ } else {
66
+ const parsed = parseDriveRef(input.path);
67
+ if (parsed) {
68
+ drive = parsed.drive;
69
+ path = parsed.path;
70
+ }
71
+ }
72
+
73
+ if (!drive) {
74
+ return {
75
+ is_error: true,
76
+ error_type: "missing_drive",
77
+ message: `Cannot resolve context item: no drive provided and \`${input.path}\` is not a UUID or \`drive:/path\` ref.`,
78
+ next_action_hint:
79
+ "Pass `drive` explicitly, or use a `drive:/path` ref. Call context_list_drives to see which drives exist.",
80
+ };
81
+ }
82
+
83
+ if (!path.startsWith("/")) path = `/${path}`;
84
+
85
+ const item = await getContextItem(ctx.conn, { drive, path });
86
+ if (!item) {
87
+ const { parent, siblings, walkedUp } = await findNearbyContextPaths(
88
+ ctx.conn,
89
+ drive,
90
+ path,
91
+ );
92
+ const hint =
93
+ siblings.length > 0
94
+ ? `${walkedUp ? `Parent ${parent} has no direct entries; ` : ""}Nearby items under ${parent}: ${siblings.join(", ")}. Call context_tree({drive:"${drive}",path:"${parent.replace(/^[^:]*:/, "")}"}) to see more.`
95
+ : `No items found under ${parent}. Call context_list_drives to see which drives exist.`;
96
+ return {
97
+ is_error: true,
98
+ error_type: "not_found",
99
+ message: `No context item at ${formatDriveRef({ drive, path })}`,
100
+ next_action_hint: hint,
101
+ };
102
+ }
34
103
 
35
104
  const content = item.content ?? "";
36
105
  return {
37
- id: item.id,
38
- title: item.title,
39
- description: item.description,
40
- mime_type: item.mime_type,
41
- is_textual: item.is_textual,
42
- size: content.length,
43
- lines: content ? content.split("\n").length : 0,
44
- source_path: item.source_path,
45
- context_path: item.context_path,
46
- indexed_at: item.indexed_at?.toISOString() ?? null,
47
- created_at: item.created_at.toISOString(),
48
- updated_at: item.updated_at.toISOString(),
106
+ file: {
107
+ id: item.id,
108
+ title: item.title,
109
+ description: item.description,
110
+ mime_type: item.mime_type,
111
+ is_textual: item.is_textual,
112
+ size: content.length,
113
+ lines: content ? content.split("\n").length : 0,
114
+ drive: item.drive,
115
+ path: item.path,
116
+ ref: formatDriveRef(item),
117
+ indexed_at: item.indexed_at?.toISOString() ?? null,
118
+ created_at: item.created_at.toISOString(),
119
+ updated_at: item.updated_at.toISOString(),
120
+ },
49
121
  is_error: false,
50
122
  };
51
123
  },
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { formatDriveRef } from "../../context/drives.ts";
2
3
  import {
3
4
  contextPathExists,
4
5
  deleteContextItemByPath,
@@ -7,41 +8,39 @@ import {
7
8
  import type { ToolDefinition } from "../tool.ts";
8
9
 
9
10
  const inputSchema = z.object({
10
- src: z.string().describe("Source file path"),
11
- dst: z.string().describe("Destination file path"),
11
+ src_drive: z.string().describe("Source drive"),
12
+ src_path: z.string().describe("Source path within the drive"),
13
+ dst_drive: z.string().describe("Destination drive"),
14
+ dst_path: z.string().describe("Destination path within the drive"),
12
15
  overwrite: z.boolean().optional().describe("Overwrite if destination exists"),
13
16
  });
14
17
 
15
18
  const outputSchema = z.object({
16
- path: z.string(),
19
+ ref: z.string(),
17
20
  is_error: z.boolean(),
18
21
  });
19
22
 
20
23
  export const contextMoveTool = {
21
24
  name: "context_move",
22
25
  description:
23
- "[[ bash equivalent command: mv ]] Move or rename a context item.",
26
+ "[[ bash equivalent command: mv ]] Move or rename a context item (can also relocate between drives).",
24
27
  group: "context",
25
28
  inputSchema,
26
29
  outputSchema,
27
30
  execute: async (input, ctx) => {
28
- const dstExists = await contextPathExists(ctx.conn, input.dst);
31
+ const src = { drive: input.src_drive, path: input.src_path };
32
+ const dst = { drive: input.dst_drive, path: input.dst_path };
33
+
34
+ const dstExists = await contextPathExists(ctx.conn, dst);
29
35
  if (dstExists && !input.overwrite) {
30
- throw new Error(`Destination already exists: ${input.dst}`);
36
+ throw new Error(`Destination already exists: ${formatDriveRef(dst)}`);
31
37
  }
32
38
  if (dstExists) {
33
- await deleteContextItemByPath(ctx.conn, input.dst);
39
+ await deleteContextItemByPath(ctx.conn, dst);
34
40
  }
35
41
 
36
- await moveContextItem(ctx.conn, input.src, input.dst);
37
-
38
- // Update embedding source_paths to match new location
39
- await ctx.conn.queryRun(
40
- "UPDATE embeddings SET source_path = ?1 WHERE source_path = ?2",
41
- input.dst,
42
- input.src,
43
- );
42
+ await moveContextItem(ctx.conn, src, dst);
44
43
 
45
- return { path: input.dst, is_error: false };
44
+ return { ref: formatDriveRef(dst), is_error: false };
46
45
  },
47
46
  } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
@@ -1,9 +1,25 @@
1
1
  import { z } from "zod";
2
- import { resolveContextItemOrThrow } from "../../db/context.ts";
2
+ import { formatDriveRef, parseDriveRef } from "../../context/drives.ts";
3
+ import {
4
+ findNearbyContextPaths,
5
+ getContextItem,
6
+ getContextItemById,
7
+ } from "../../db/context.ts";
8
+ import { isUuid } from "../../db/uuid.ts";
3
9
  import type { ToolDefinition } from "../tool.ts";
4
10
 
5
11
  const inputSchema = z.object({
6
- path: z.string().describe("File path or context item ID"),
12
+ drive: z
13
+ .string()
14
+ .optional()
15
+ .describe(
16
+ "Drive name (e.g. 'disk', 'url', 'agent', 'google-docs', 'github'). Use context_list_drives to see what's available. Optional when `path` is a UUID or already in `drive:/...` form.",
17
+ ),
18
+ path: z
19
+ .string()
20
+ .describe(
21
+ "Path within the drive (starts with /), or a bare UUID / 'drive:/path' ref (in which case `drive` is ignored).",
22
+ ),
7
23
  offset: z
8
24
  .number()
9
25
  .optional()
@@ -12,8 +28,11 @@ const inputSchema = z.object({
12
28
  });
13
29
 
14
30
  const outputSchema = z.object({
15
- content: z.string(),
31
+ content: z.string().optional(),
16
32
  is_error: z.boolean(),
33
+ error_type: z.string().optional(),
34
+ message: z.string().optional(),
35
+ next_action_hint: z.string().optional(),
17
36
  });
18
37
 
19
38
  export const contextReadTool = {
@@ -24,8 +43,63 @@ export const contextReadTool = {
24
43
  inputSchema,
25
44
  outputSchema,
26
45
  execute: async (input, ctx) => {
27
- const item = await resolveContextItemOrThrow(ctx.conn, input.path);
28
- if (item.content == null) throw new Error(`No text content: ${input.path}`);
46
+ // Accept either (drive, path) or a bare UUID / drive:/path ref in `path`.
47
+ let drive = input.drive;
48
+ let path = input.path;
49
+ if (isUuid(input.path)) {
50
+ const byId = await getContextItemById(ctx.conn, input.path);
51
+ if (byId) {
52
+ drive = byId.drive;
53
+ path = byId.path;
54
+ }
55
+ } else {
56
+ const parsed = parseDriveRef(input.path);
57
+ if (parsed) {
58
+ drive = parsed.drive;
59
+ path = parsed.path;
60
+ }
61
+ }
62
+
63
+ if (!drive) {
64
+ return {
65
+ is_error: true,
66
+ error_type: "missing_drive",
67
+ message: `Cannot resolve context item: no drive provided and \`${input.path}\` is not a UUID or \`drive:/path\` ref.`,
68
+ next_action_hint:
69
+ "Pass `drive` explicitly, or use a `drive:/path` ref. Call context_list_drives to see which drives exist.",
70
+ };
71
+ }
72
+
73
+ if (!path.startsWith("/")) path = `/${path}`;
74
+
75
+ const item = await getContextItem(ctx.conn, { drive, path });
76
+ if (!item) {
77
+ const { parent, siblings, walkedUp } = await findNearbyContextPaths(
78
+ ctx.conn,
79
+ drive,
80
+ path,
81
+ );
82
+ const hint =
83
+ siblings.length > 0
84
+ ? `${walkedUp ? `Parent ${parent} has no direct entries; ` : ""}Nearby items under ${parent}: ${siblings.join(", ")}. Call context_tree({drive:"${drive}",path:"${parent.replace(/^[^:]*:/, "")}"}) to see more.`
85
+ : `No items found under ${parent}. Call context_list_drives to see which drives exist.`;
86
+ return {
87
+ is_error: true,
88
+ error_type: "not_found",
89
+ message: `No context item at ${formatDriveRef({ drive, path })}`,
90
+ next_action_hint: hint,
91
+ };
92
+ }
93
+
94
+ if (item.content == null) {
95
+ return {
96
+ is_error: true,
97
+ error_type: "no_text_content",
98
+ message: `Context item ${formatDriveRef(item)} has no text content (mime: ${item.mime_type})`,
99
+ next_action_hint:
100
+ "Binary items can't be read as text. Call context_info to inspect metadata, or pick a textual sibling.",
101
+ };
102
+ }
29
103
 
30
104
  let content = item.content;
31
105
 
@@ -1,5 +1,6 @@
1
1
  import { isText } from "istextorbinary";
2
2
  import { z } from "zod";
3
+ import { formatDriveRef } from "../../context/drives.ts";
3
4
  import { ingestByPath } from "../../context/ingest.ts";
4
5
  import {
5
6
  createContextItemStrict,
@@ -17,12 +18,17 @@ function mimeFromPath(path: string): string {
17
18
  function isTextualPath(path: string): boolean {
18
19
  const filename = path.split("/").pop() ?? path;
19
20
  const result = isText(filename);
20
- // isText returns null if it can't determine from filename alone — default to true
21
21
  return result !== false;
22
22
  }
23
23
 
24
24
  const inputSchema = z.object({
25
- path: z.string().describe("File path to write"),
25
+ drive: z
26
+ .string()
27
+ .default("agent")
28
+ .describe(
29
+ "Drive to write to (defaults to 'agent', which is the agent's scratch drive). Only 'agent' and drives mirroring an external system you can write back to make sense here.",
30
+ ),
31
+ path: z.string().describe("Path within the drive (starts with /)"),
26
32
  content: z.string().describe("Text content to write"),
27
33
  content_base64: z
28
34
  .string()
@@ -39,13 +45,15 @@ const inputSchema = z.object({
39
45
  .enum(["error", "overwrite"])
40
46
  .optional()
41
47
  .describe(
42
- "What to do if a file already exists at this path. Defaults to 'error'. Pass 'overwrite' to replace.",
48
+ "What to do if a file already exists at this (drive, path). Defaults to 'error'. Pass 'overwrite' to replace.",
43
49
  ),
44
50
  });
45
51
 
46
52
  const outputSchema = z.object({
47
53
  id: z.string().nullable(),
54
+ drive: z.string(),
48
55
  path: z.string(),
56
+ ref: z.string(),
49
57
  is_error: z.boolean(),
50
58
  error_type: z.string().optional(),
51
59
  message: z.string().optional(),
@@ -54,14 +62,14 @@ const outputSchema = z.object({
54
62
  .string()
55
63
  .optional()
56
64
  .describe(
57
- "Snapshot of the context filesystem after the write so you can see the surrounding files.",
65
+ "Snapshot of the drive's tree after the write so you can see the surrounding files.",
58
66
  ),
59
67
  });
60
68
 
61
69
  export const contextWriteTool = {
62
70
  name: "context_write",
63
71
  description:
64
- "[[ bash equivalent command: tee ]] Write content to a context item. By default, fails if the path already exists — pass on_conflict='overwrite' to replace.",
72
+ "[[ bash equivalent command: tee ]] Write content to a context item. By default writes to drive='agent' (the agent's scratch drive). Fails if the (drive, path) already exists — pass on_conflict='overwrite' to replace.",
65
73
  group: "context",
66
74
  inputSchema,
67
75
  outputSchema,
@@ -71,6 +79,7 @@ export const contextWriteTool = {
71
79
  const title =
72
80
  input.title ?? input.path.split("/").filter(Boolean).pop() ?? input.path;
73
81
  const onConflict = input.on_conflict ?? "error";
82
+ const target = { drive: input.drive, path: input.path };
74
83
 
75
84
  try {
76
85
  const item =
@@ -79,7 +88,8 @@ export const contextWriteTool = {
79
88
  title,
80
89
  description: input.description,
81
90
  content: input.content_base64 ?? input.content,
82
- contextPath: input.path,
91
+ drive: target.drive,
92
+ path: target.path,
83
93
  mimeType,
84
94
  isTextual,
85
95
  })
@@ -87,16 +97,21 @@ export const contextWriteTool = {
87
97
  title,
88
98
  description: input.description,
89
99
  content: input.content_base64 ?? input.content,
90
- contextPath: input.path,
100
+ drive: target.drive,
101
+ path: target.path,
91
102
  mimeType,
92
103
  isTextual,
93
104
  });
94
105
 
95
- await ingestByPath(ctx.conn, input.path, ctx.config);
96
- const { tree } = await buildContextTree(ctx.conn);
106
+ await ingestByPath(ctx.conn, target, ctx.config);
107
+ const { tree } = await buildContextTree(ctx.conn, {
108
+ drive: target.drive,
109
+ });
97
110
  return {
98
111
  id: item.id,
99
- path: item.context_path,
112
+ drive: item.drive,
113
+ path: item.path,
114
+ ref: formatDriveRef(item),
100
115
  is_error: false,
101
116
  tree,
102
117
  };
@@ -104,10 +119,12 @@ export const contextWriteTool = {
104
119
  if (err instanceof PathConflictError) {
105
120
  return {
106
121
  id: null,
107
- path: input.path,
122
+ drive: err.drive,
123
+ path: err.path,
124
+ ref: formatDriveRef({ drive: err.drive, path: err.path }),
108
125
  is_error: true,
109
126
  error_type: "path_conflict",
110
- message: `A file already exists at ${input.path} (id: ${err.existingId}).`,
127
+ message: `A file already exists at ${formatDriveRef({ drive: err.drive, path: err.path })} (id: ${err.existingId}).`,
111
128
  next_action_hint:
112
129
  "Call context_read to inspect the existing file, or retry with on_conflict='overwrite' to replace it.",
113
130
  };
@@ -1,6 +1,7 @@
1
1
  // Capabilities tools
2
2
  import { capabilitiesRefreshTool } from "./capabilities/refresh.ts";
3
3
  // Context tools
4
+ import { contextListDrivesTool } from "./context/list-drives.ts";
4
5
  import { readLargeResultTool } from "./context/read-large-result.ts";
5
6
  import { contextRefreshTool } from "./context/refresh.ts";
6
7
  import { contextSearchTool } from "./context/search.ts";
@@ -8,7 +9,6 @@ import { updateBeliefsTool } from "./context/update-beliefs.ts";
8
9
  import { updateGoalsTool } from "./context/update-goals.ts";
9
10
  // Context — directory operations
10
11
  import { contextCreateDirTool } from "./dir/create.ts";
11
- import { contextListDirTool } from "./dir/list.ts";
12
12
  import { contextDirSizeTool } from "./dir/size.ts";
13
13
  import { contextTreeTool } from "./dir/tree.ts";
14
14
  // Context — file operations
@@ -58,8 +58,8 @@ export function registerAllTools(): void {
58
58
  registerTool(viewTaskTool);
59
59
 
60
60
  // Context
61
+ registerTool(contextListDrivesTool);
61
62
  registerTool(contextCreateDirTool);
62
- registerTool(contextListDirTool);
63
63
  registerTool(contextTreeTool);
64
64
  registerTool(contextDirSizeTool);
65
65
  registerTool(contextReadTool);