@clinebot/core 0.0.11 → 0.0.12

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.
Files changed (53) hide show
  1. package/dist/agents/agent-config-loader.d.ts +1 -1
  2. package/dist/agents/agent-config-parser.d.ts +5 -2
  3. package/dist/agents/index.d.ts +1 -1
  4. package/dist/agents/plugin-config-loader.d.ts +4 -0
  5. package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
  6. package/dist/agents/plugin-sandbox.d.ts +4 -0
  7. package/dist/index.node.d.ts +1 -0
  8. package/dist/index.node.js +658 -407
  9. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
  10. package/dist/session/default-session-manager.d.ts +5 -0
  11. package/dist/session/session-config-builder.d.ts +4 -1
  12. package/dist/session/session-manager.d.ts +1 -0
  13. package/dist/session/unified-session-persistence-service.d.ts +6 -0
  14. package/dist/session/utils/types.d.ts +9 -0
  15. package/dist/tools/definitions.d.ts +2 -2
  16. package/dist/tools/presets.d.ts +3 -3
  17. package/dist/tools/schemas.d.ts +14 -14
  18. package/dist/types/config.d.ts +5 -0
  19. package/dist/types/events.d.ts +22 -0
  20. package/package.json +5 -4
  21. package/src/agents/agent-config-loader.test.ts +2 -0
  22. package/src/agents/agent-config-loader.ts +1 -0
  23. package/src/agents/agent-config-parser.ts +12 -5
  24. package/src/agents/index.ts +1 -0
  25. package/src/agents/plugin-config-loader.test.ts +49 -0
  26. package/src/agents/plugin-config-loader.ts +10 -73
  27. package/src/agents/plugin-loader.test.ts +128 -2
  28. package/src/agents/plugin-loader.ts +70 -5
  29. package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
  30. package/src/agents/plugin-sandbox.test.ts +198 -1
  31. package/src/agents/plugin-sandbox.ts +223 -353
  32. package/src/index.node.ts +4 -0
  33. package/src/runtime/hook-file-hooks.test.ts +1 -1
  34. package/src/runtime/hook-file-hooks.ts +16 -6
  35. package/src/runtime/runtime-builder.test.ts +67 -0
  36. package/src/runtime/runtime-builder.ts +70 -16
  37. package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
  38. package/src/session/default-session-manager.e2e.test.ts +20 -1
  39. package/src/session/default-session-manager.test.ts +453 -1
  40. package/src/session/default-session-manager.ts +200 -0
  41. package/src/session/session-config-builder.ts +2 -0
  42. package/src/session/session-manager.ts +1 -0
  43. package/src/session/session-team-coordination.ts +30 -0
  44. package/src/session/unified-session-persistence-service.ts +45 -0
  45. package/src/session/utils/types.ts +10 -0
  46. package/src/storage/sqlite-team-store.ts +16 -5
  47. package/src/tools/definitions.test.ts +87 -8
  48. package/src/tools/definitions.ts +89 -70
  49. package/src/tools/presets.test.ts +2 -3
  50. package/src/tools/presets.ts +3 -3
  51. package/src/tools/schemas.ts +23 -22
  52. package/src/types/config.ts +5 -0
  53. package/src/types/events.ts +23 -0
@@ -38,7 +38,7 @@ import type {
38
38
  EditorExecutor,
39
39
  FileReadExecutor,
40
40
  SearchExecutor,
41
- SkillsExecutor,
41
+ SkillsExecutorWithMetadata,
42
42
  ToolOperationResult,
43
43
  WebFetchExecutor,
44
44
  } from "./types.js";
@@ -105,7 +105,7 @@ function normalizeReadFileRequests(input: unknown): ReadFileRequest[] {
105
105
 
106
106
  function formatReadFileQuery(request: ReadFileRequest): string {
107
107
  const { path, start_line, end_line } = request;
108
- if (start_line === undefined && end_line === undefined) {
108
+ if (start_line == null && end_line == null) {
109
109
  return path;
110
110
  }
111
111
  const start = start_line ?? 1;
@@ -113,63 +113,6 @@ function formatReadFileQuery(request: ReadFileRequest): string {
113
113
  return `${path}:${start}-${end}`;
114
114
  }
115
115
 
116
- const APPLY_PATCH_TOOL_DESC = `This is a custom utility that makes it more convenient to add, remove, move, or edit code in a single file. \`apply_patch\` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. To use the \`apply_patch\` command, you should pass a message of the following structure as "input":
117
-
118
- %%bash
119
- apply_patch <<"EOF"
120
- *** Begin Patch
121
- [YOUR_PATCH]
122
- *** End Patch
123
- EOF
124
-
125
- Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format.
126
-
127
- *** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete.
128
-
129
- In a Add File section, every line of the new file (including blank/empty lines) MUST start with a \`+\` prefix. Do not include any unprefixed lines inside an Add section
130
- In a Update/Delete section, repeat the following for each snippet of code that needs to be changed:
131
- [context_before] -> See below for further instructions on context.
132
- - [old_code] -> Precede the old code with a minus sign.
133
- + [new_code] -> Precede the new, replacement code with a plus sign.
134
- [context_after] -> See below for further instructions on context.
135
-
136
- For instructions on [context_before] and [context_after]:
137
- - By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change’s [context_after] lines in the second change’s [context_before] lines.
138
- - If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:
139
- @@ class BaseClass
140
- [3 lines of pre-context]
141
- - [old_code]
142
- + [new_code]
143
- [3 lines of post-context]
144
-
145
- - If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple \`@@\` statements to jump to the right context. For instance:
146
-
147
- @@ class BaseClass
148
- @@ def method():
149
- [3 lines of pre-context]
150
- - [old_code]
151
- + [new_code]
152
- [3 lines of post-context]
153
-
154
- Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below.
155
-
156
- %%bash
157
- apply_patch <<"EOF"
158
- *** Begin Patch
159
- *** Update File: pygorithm/searching/binary_search.py
160
- @@ class BaseClass
161
- @@ def search():
162
- - pass
163
- + raise NotImplementedError()
164
-
165
- @@ class Subclass
166
- @@ def search():
167
- - pass
168
- + raise NotImplementedError()
169
-
170
- *** End Patch
171
- EOF`;
172
-
173
116
  // =============================================================================
174
117
  // Tool Factory Functions
175
118
  // =============================================================================
@@ -404,6 +347,63 @@ export function createWebFetchTool(
404
347
  });
405
348
  }
406
349
 
350
+ const APPLY_PATCH_TOOL_DESC = `This is a custom utility that makes it more convenient to add, remove, move, or edit code in a single file. \`apply_patch\` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. To use the \`apply_patch\` command, you should pass a message of the following structure as "input":
351
+
352
+ %%bash
353
+ apply_patch <<"EOF"
354
+ *** Begin Patch
355
+ [YOUR_PATCH]
356
+ *** End Patch
357
+ EOF
358
+
359
+ Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format.
360
+
361
+ *** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete.
362
+
363
+ In a Add File section, every line of the new file (including blank/empty lines) MUST start with a \`+\` prefix. Do not include any unprefixed lines inside an Add section
364
+ In a Update/Delete section, repeat the following for each snippet of code that needs to be changed:
365
+ [context_before] -> See below for further instructions on context.
366
+ - [old_code] -> Precede the old code with a minus sign.
367
+ + [new_code] -> Precede the new, replacement code with a plus sign.
368
+ [context_after] -> See below for further instructions on context.
369
+
370
+ For instructions on [context_before] and [context_after]:
371
+ - By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change’s [context_after] lines in the second change’s [context_before] lines.
372
+ - If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:
373
+ @@ class BaseClass
374
+ [3 lines of pre-context]
375
+ - [old_code]
376
+ + [new_code]
377
+ [3 lines of post-context]
378
+
379
+ - If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple \`@@\` statements to jump to the right context. For instance:
380
+
381
+ @@ class BaseClass
382
+ @@ def method():
383
+ [3 lines of pre-context]
384
+ - [old_code]
385
+ + [new_code]
386
+ [3 lines of post-context]
387
+
388
+ Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below.
389
+
390
+ %%bash
391
+ apply_patch <<"EOF"
392
+ *** Begin Patch
393
+ *** Update File: pygorithm/searching/binary_search.py
394
+ @@ class BaseClass
395
+ @@ def search():
396
+ - pass
397
+ + raise NotImplementedError()
398
+
399
+ @@ class Subclass
400
+ @@ def search():
401
+ - pass
402
+ + raise NotImplementedError()
403
+
404
+ *** End Patch
405
+ EOF`;
406
+
407
407
  /**
408
408
  * Create the apply_patch tool
409
409
  *
@@ -470,7 +470,9 @@ export function createEditorTool(
470
470
  description:
471
471
  "An editor for controlled filesystem edits on the text file at the provided path. " +
472
472
  "Provide `insert_line` to insert `new_text` at a specific line number. " +
473
- "Otherwise, the tool replaces `old_text` with `new_text`, or creates the file with `new_text` if it does not exist.",
473
+ "Otherwise, the tool replaces `old_text` with `new_text`, or creates the file with `new_text` if file does not exist. " +
474
+ "Use this tools for making small, precise edits to existing files or creating new files over shell commands.",
475
+
474
476
  inputSchema: zodToJsonSchema(EditFileInputSchema),
475
477
  timeoutMs,
476
478
  retryable: false, // Editing operations are stateful and should not auto-retry
@@ -510,21 +512,22 @@ export function createEditorTool(
510
512
  * Invokes a configured skill by name and optional arguments.
511
513
  */
512
514
  export function createSkillsTool(
513
- executor: SkillsExecutor,
515
+ executor: SkillsExecutorWithMetadata,
514
516
  config: Pick<DefaultToolsConfig, "skillsTimeoutMs"> = {},
515
517
  ): Tool<SkillsInput, string> {
516
518
  const timeoutMs = config.skillsTimeoutMs ?? 15000;
517
519
 
518
- return createTool<SkillsInput, string>({
520
+ const baseDescription =
521
+ "Execute a skill within the main conversation. " +
522
+ "When users ask you to perform tasks, check if any available skills match. " +
523
+ "When users reference a slash command, invoke it with this tool. " +
524
+ 'Input: `skill` (required) and optional `args`. Example: `skill: "pdf"`, `skill: "commit", args: "-m \\"Fix bug\\""`, `skill: "review-pr", args: "123"`, `skill: "ms-office-suite:pdf"`. ' +
525
+ "When a skill matches the user's request, invoking this tool is a blocking requirement before any other response. " +
526
+ "Never mention a skill without invoking this tool.";
527
+
528
+ const tool = createTool<SkillsInput, string>({
519
529
  name: "skills",
520
- description:
521
- "Execute a skill within the main conversation. " +
522
- "When users ask you to perform tasks, check if any available skills match. " +
523
- 'When users reference a slash command (for example "/commit" or "/review-pr"), invoke this tool. ' +
524
- 'Input: `skill` (required) and optional `args`. Example: `skill: "pdf"`, `skill: "commit", args: "-m \\"Fix bug\\""`, `skill: "review-pr", args: "123"`, `skill: "ms-office-suite:pdf"`. ' +
525
- "Available skills are listed in system-reminder messages in the conversation. " +
526
- "When a skill matches the user's request, invoking this tool is a blocking requirement before any other response. " +
527
- "Never mention a skill without invoking this tool.",
530
+ description: baseDescription,
528
531
  inputSchema: zodToJsonSchema(SkillsInputSchema),
529
532
  timeoutMs,
530
533
  retryable: false,
@@ -542,6 +545,22 @@ export function createSkillsTool(
542
545
  );
543
546
  },
544
547
  });
548
+
549
+ Object.defineProperty(tool, "description", {
550
+ get() {
551
+ const skills = executor.configuredSkills
552
+ ?.filter((s) => !s.disabled)
553
+ .map((s) => s.name);
554
+ if (skills && skills.length > 0) {
555
+ return `${baseDescription} Available skills: ${skills.join(", ")}.`;
556
+ }
557
+ return baseDescription;
558
+ },
559
+ enumerable: true,
560
+ configurable: true,
561
+ });
562
+
563
+ return tool;
545
564
  }
546
565
 
547
566
  /**
@@ -11,10 +11,10 @@ describe("default tool presets", () => {
11
11
  expect(ToolPresets.development.enableAskQuestion).toBe(true);
12
12
  expect(ToolPresets.readonly.enableAskQuestion).toBe(true);
13
13
  expect(ToolPresets.minimal.enableAskQuestion).toBe(true);
14
- expect(ToolPresets.yolo.enableAskQuestion).toBe(true);
14
+ expect(ToolPresets.yolo.enableAskQuestion).toBe(false);
15
15
  });
16
16
 
17
- it("yolo preset enables all default tools when executors exist", () => {
17
+ it("yolo preset excludes ask_question even when its executor exists", () => {
18
18
  const tools = createDefaultToolsWithPreset("yolo", {
19
19
  executors: {
20
20
  readFile: async () => "ok",
@@ -35,7 +35,6 @@ describe("default tool presets", () => {
35
35
  "fetch_web_content",
36
36
  "editor",
37
37
  "skills",
38
- "ask_question",
39
38
  ]);
40
39
  });
41
40
  });
@@ -88,7 +88,7 @@ export const ToolPresets = {
88
88
  },
89
89
 
90
90
  /**
91
- * YOLO mode (everything enabled + no approval required)
91
+ * YOLO mode (automation-focused tools + no approval required)
92
92
  * Good for trusted local automation workflows.
93
93
  */
94
94
  yolo: {
@@ -99,7 +99,7 @@ export const ToolPresets = {
99
99
  enableApplyPatch: false,
100
100
  enableEditor: true,
101
101
  enableSkills: true,
102
- enableAskQuestion: true,
102
+ enableAskQuestion: false,
103
103
  },
104
104
  } as const satisfies Record<string, DefaultToolsConfig>;
105
105
 
@@ -115,7 +115,7 @@ export type ToolPolicyPresetName = "default" | "yolo";
115
115
 
116
116
  /**
117
117
  * Build tool policies for a preset.
118
- * `yolo` guarantees all tools are enabled and auto-approved.
118
+ * `yolo` guarantees tool policies are enabled and auto-approved.
119
119
  */
120
120
  export function createToolPoliciesWithPreset(
121
121
  presetName: ToolPolicyPresetName,
@@ -20,20 +20,24 @@ export const ReadFileLineRangeSchema = z
20
20
  .number()
21
21
  .int()
22
22
  .positive()
23
+ .nullable()
23
24
  .optional()
24
- .describe("Optional one-based starting line number to read from"),
25
+ .describe(
26
+ "Optional one-based starting line number to read from; use null or omit for the start of the file",
27
+ ),
25
28
  end_line: z
26
29
  .number()
27
30
  .int()
28
31
  .positive()
32
+ .nullable()
29
33
  .optional()
30
- .describe("Optional one-based ending line number to read through"),
34
+ .describe(
35
+ "Optional one-based ending line number to read through; use null or omit for the end of the file",
36
+ ),
31
37
  })
32
38
  .refine(
33
39
  ({ start_line, end_line }) =>
34
- start_line === undefined ||
35
- end_line === undefined ||
36
- start_line <= end_line,
40
+ start_line == null || end_line == null || start_line <= end_line,
37
41
  {
38
42
  message: "start_line must be less than or equal to end_line",
39
43
  path: ["end_line"],
@@ -48,9 +52,7 @@ export const ReadFileRequestSchema = z
48
52
  })
49
53
  .refine(
50
54
  ({ start_line, end_line }) =>
51
- start_line === undefined ||
52
- end_line === undefined ||
53
- start_line <= end_line,
55
+ start_line == null || end_line == null || start_line <= end_line,
54
56
  {
55
57
  message: "start_line must be less than or equal to end_line",
56
58
  path: ["end_line"],
@@ -64,7 +66,7 @@ export const ReadFilesInputSchema = z.object({
64
66
  files: z
65
67
  .array(ReadFileRequestSchema)
66
68
  .describe(
67
- "Array of file read requests. Omit start_line and end_line to return the full file content; provide them to return only that inclusive one-based line range. Prefer this tool over running terminal command to get file content for better performance and reliability.",
69
+ "Array of file read requests. Omit start_line/end_line or set them to null to return the full file content boundaries; provide integers to return only that inclusive one-based line range. Prefer this tool over running terminal command to get file content for better performance and reliability.",
68
70
  ),
69
71
  });
70
72
 
@@ -176,18 +178,22 @@ export const EditFileInputSchema = z
176
178
  ),
177
179
  })
178
180
  .describe(
179
- "Edit a text file by replacing old_text with new_text, create the file with new_text if it does not exist, or insert new_text at insert_line when insert_line is provided. IMPORTANT: large edits can time out, so use small chunks and multiple calls when possible.",
181
+ "Edit a text file by replacing old_text with new_text, create the file with new_text if it does not exist, or insert new_text at insert_line when insert_line is provided. Prefer using this tool for file edits over shell commands. IMPORTANT: large edits can time out, so use small chunks and multiple calls when possible.",
180
182
  );
181
183
 
182
184
  /**
183
185
  * Schema for apply_patch tool input
184
186
  */
185
- export const ApplyPatchInputSchema = z.object({
186
- input: z
187
- .string()
188
- .min(1)
189
- .describe("The apply_patch text payload, including patch instructions"),
190
- });
187
+ export const ApplyPatchInputSchema = z
188
+ .object({
189
+ input: z
190
+ .string()
191
+ .min(1)
192
+ .describe("The apply_patch text payload, including patch instructions"),
193
+ })
194
+ .describe(
195
+ "Modify or create a text file by applying patches. Prefer using this tool for file edits over shell commands. IMPORTANT: large patches can time out, so use small chunks and multiple calls when possible.",
196
+ );
191
197
  export const ApplyPatchInputUnionSchema = z.union([
192
198
  ApplyPatchInputSchema,
193
199
  z.string(),
@@ -197,12 +203,7 @@ export const ApplyPatchInputUnionSchema = z.union([
197
203
  * Schema for skills tool input
198
204
  */
199
205
  export const SkillsInputSchema = z.object({
200
- skill: z
201
- .string()
202
- .min(1)
203
- .describe(
204
- 'The skill name. E.g., "commit", "review-pr", "pdf", or "ms-office-suite:pdf"',
205
- ),
206
+ skill: z.string().min(1).describe("Name of the skill to execute."),
206
207
  args: z
207
208
  .string()
208
209
  .nullable()
@@ -76,4 +76,9 @@ export interface CoreSessionConfig
76
76
  | Promise<ConsecutiveMistakeLimitDecision>
77
77
  | ConsecutiveMistakeLimitDecision;
78
78
  toolRoutingRules?: ToolRoutingRule[];
79
+ /**
80
+ * Optional skill allowlist for the `skills` tool. When provided, only these
81
+ * skills are surfaced in tool metadata and invocable by name.
82
+ */
83
+ skills?: string[];
79
84
  }
@@ -36,6 +36,24 @@ export interface SessionTeamProgressEvent {
36
36
  summary: import("@clinebot/shared").TeamProgressSummary;
37
37
  }
38
38
 
39
+ export interface SessionPendingPromptsEvent {
40
+ sessionId: string;
41
+ prompts: Array<{
42
+ id: string;
43
+ prompt: string;
44
+ delivery: "queue" | "steer";
45
+ attachmentCount: number;
46
+ }>;
47
+ }
48
+
49
+ export interface SessionPendingPromptSubmittedEvent {
50
+ sessionId: string;
51
+ id: string;
52
+ prompt: string;
53
+ delivery: "queue" | "steer";
54
+ attachmentCount: number;
55
+ }
56
+
39
57
  export type CoreSessionEvent =
40
58
  | { type: "chunk"; payload: SessionChunkEvent }
41
59
  | {
@@ -46,6 +64,11 @@ export type CoreSessionEvent =
46
64
  };
47
65
  }
48
66
  | { type: "team_progress"; payload: SessionTeamProgressEvent }
67
+ | { type: "pending_prompts"; payload: SessionPendingPromptsEvent }
68
+ | {
69
+ type: "pending_prompt_submitted";
70
+ payload: SessionPendingPromptSubmittedEvent;
71
+ }
49
72
  | { type: "ended"; payload: SessionEndedEvent }
50
73
  | { type: "hook"; payload: SessionToolEvent }
51
74
  | { type: "status"; payload: { sessionId: string; status: string } };