pulse-coder-engine 0.0.1-alpha.3 → 0.0.1-alpha.6

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.
@@ -30,9 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/built-in/index.ts
31
31
  var built_in_exports = {};
32
32
  __export(built_in_exports, {
33
+ BuiltInPlanModeService: () => BuiltInPlanModeService,
33
34
  BuiltInSkillRegistry: () => BuiltInSkillRegistry,
34
35
  SubAgentPlugin: () => SubAgentPlugin,
35
36
  builtInMCPPlugin: () => builtInMCPPlugin,
37
+ builtInPlanModePlugin: () => builtInPlanModePlugin,
36
38
  builtInPlugins: () => builtInPlugins,
37
39
  builtInSkillsPlugin: () => builtInSkillsPlugin,
38
40
  default: () => built_in_default
@@ -70,7 +72,7 @@ function createHTTPTransport(config) {
70
72
  };
71
73
  }
72
74
  var builtInMCPPlugin = {
73
- name: "@pulse-coder/engine/built-in-mcp",
75
+ name: "pulse-coder-engine/built-in-mcp",
74
76
  version: "1.0.0",
75
77
  async initialize(context) {
76
78
  const config = await loadMCPConfig(process.cwd());
@@ -257,7 +259,7 @@ function generateSkillTool(skills) {
257
259
  };
258
260
  }
259
261
  var builtInSkillsPlugin = {
260
- name: "@pulse-coder/engine/built-in-skills",
262
+ name: "pulse-coder-engine/built-in-skills",
261
263
  version: "1.0.0",
262
264
  async initialize(context) {
263
265
  const registry = new BuiltInSkillRegistry();
@@ -274,10 +276,410 @@ var builtInSkillsPlugin = {
274
276
  }
275
277
  };
276
278
 
279
+ // src/built-in/plan-mode-plugin/index.ts
280
+ var PLANNING_POLICY = {
281
+ mode: "planning",
282
+ allowedCategories: ["read", "search", "other"],
283
+ disallowedCategories: ["write", "execute"],
284
+ notes: "Planning mode is prompt-constrained only. Disallowed tool attempts are observed and logged, not hard-blocked."
285
+ };
286
+ var EXECUTING_POLICY = {
287
+ mode: "executing",
288
+ allowedCategories: ["read", "search", "write", "execute", "other"],
289
+ disallowedCategories: []
290
+ };
291
+ var EXECUTE_PATTERNS = [
292
+ /开始执行/i,
293
+ /按这个计划做/i,
294
+ /可以改代码了/i,
295
+ /直接实现/i,
296
+ /go\s+ahead/i,
297
+ /proceed/i,
298
+ /implement\s+it/i,
299
+ /start\s+implement/i,
300
+ /start\s+coding/i
301
+ ];
302
+ var NEGATIVE_PATTERNS = [
303
+ /先不(要)?执行/i,
304
+ /先别执行/i,
305
+ /不要执行/i,
306
+ /暂时不要执行/i,
307
+ /先别改代码/i,
308
+ /先不要改代码/i,
309
+ /先不要实现/i,
310
+ /not\s+now/i,
311
+ /hold\s+off/i,
312
+ /do\s+not\s+(start|execute|implement|proceed)/i,
313
+ /don't\s+(start|execute|implement|proceed)/i
314
+ ];
315
+ var PLAN_PATTERNS = [/先计划/i, /先分析/i, /先出方案/i, /plan\s+first/i, /analysis\s+first/i];
316
+ function appendSystemPrompt(base, append) {
317
+ if (!append.trim()) {
318
+ return base ?? { append: "" };
319
+ }
320
+ if (!base) {
321
+ return { append };
322
+ }
323
+ if (typeof base === "string") {
324
+ return `${base}
325
+
326
+ ${append}`;
327
+ }
328
+ if (typeof base === "function") {
329
+ return () => `${base()}
330
+
331
+ ${append}`;
332
+ }
333
+ const currentAppend = base.append.trim();
334
+ return {
335
+ append: currentAppend ? `${currentAppend}
336
+
337
+ ${append}` : append
338
+ };
339
+ }
340
+ var KNOWN_TOOL_META = {
341
+ read: {
342
+ category: "read",
343
+ risk: "low",
344
+ description: "Read file contents from the workspace."
345
+ },
346
+ ls: {
347
+ category: "read",
348
+ risk: "low",
349
+ description: "List files and directories."
350
+ },
351
+ grep: {
352
+ category: "search",
353
+ risk: "low",
354
+ description: "Search text content across files."
355
+ },
356
+ tavily: {
357
+ category: "search",
358
+ risk: "low",
359
+ description: "Search web results from external sources."
360
+ },
361
+ skill: {
362
+ category: "search",
363
+ risk: "low",
364
+ description: "Load procedural guidance from installed skills."
365
+ },
366
+ write: {
367
+ category: "write",
368
+ risk: "high",
369
+ description: "Create or overwrite file content."
370
+ },
371
+ edit: {
372
+ category: "write",
373
+ risk: "high",
374
+ description: "Modify existing files using exact replacements."
375
+ },
376
+ bash: {
377
+ category: "execute",
378
+ risk: "high",
379
+ description: "Execute shell commands."
380
+ },
381
+ clarify: {
382
+ category: "other",
383
+ risk: "low",
384
+ description: "Ask the user a targeted clarification question."
385
+ }
386
+ };
387
+ var BuiltInPlanModeService = class {
388
+ constructor(logger, eventEmitter, initialMode = "executing") {
389
+ this.logger = logger;
390
+ this.eventEmitter = eventEmitter;
391
+ this.mode = initialMode;
392
+ this.emitEvent("mode_entered", { reason: "initialize" });
393
+ }
394
+ mode;
395
+ events = [];
396
+ getMode() {
397
+ return this.mode;
398
+ }
399
+ setMode(mode, reason = "manual") {
400
+ if (this.mode === mode) {
401
+ return;
402
+ }
403
+ this.mode = mode;
404
+ this.emitEvent("mode_entered", { reason });
405
+ }
406
+ detectIntent(input) {
407
+ const text = input.trim();
408
+ if (!text) {
409
+ return "UNCLEAR";
410
+ }
411
+ if (NEGATIVE_PATTERNS.some((pattern) => pattern.test(text))) {
412
+ return "PLAN_ONLY";
413
+ }
414
+ if (EXECUTE_PATTERNS.some((pattern) => pattern.test(text))) {
415
+ return "EXECUTE_NOW";
416
+ }
417
+ if (PLAN_PATTERNS.some((pattern) => pattern.test(text))) {
418
+ return "PLAN_ONLY";
419
+ }
420
+ return "UNCLEAR";
421
+ }
422
+ processContextMessages(messages) {
423
+ const userInput = this.getLatestUserText(messages);
424
+ const modeBefore = this.mode;
425
+ if (!userInput) {
426
+ return {
427
+ modeBefore,
428
+ modeAfter: this.mode,
429
+ switched: false,
430
+ intent: "UNCLEAR",
431
+ userInput: ""
432
+ };
433
+ }
434
+ const intent = this.detectIntent(userInput);
435
+ if (intent === "EXECUTE_NOW") {
436
+ this.emitEvent("execution_intent_detected", { intent, userInput });
437
+ if (this.mode === "planning") {
438
+ this.mode = "executing";
439
+ this.emitEvent("mode_switched_by_intent", {
440
+ from: "planning",
441
+ to: "executing",
442
+ userInput
443
+ });
444
+ this.emitEvent("mode_entered", {
445
+ reason: "intent",
446
+ from: "planning"
447
+ });
448
+ }
449
+ }
450
+ return {
451
+ modeBefore,
452
+ modeAfter: this.mode,
453
+ switched: modeBefore !== this.mode,
454
+ intent,
455
+ userInput
456
+ };
457
+ }
458
+ getModePolicy(mode = this.mode) {
459
+ return mode === "planning" ? PLANNING_POLICY : EXECUTING_POLICY;
460
+ }
461
+ getToolMetadata(toolNames) {
462
+ return Array.from(new Set(toolNames)).sort().map((name) => this.inferToolMeta(name));
463
+ }
464
+ buildPromptAppend(toolNames, transition) {
465
+ const mode = this.mode;
466
+ const policy = this.getModePolicy(mode);
467
+ const toolMeta = this.getToolMetadata(toolNames);
468
+ const shownTools = toolMeta.slice(0, 40);
469
+ const omittedCount = toolMeta.length - shownTools.length;
470
+ const lines = [
471
+ "## Plan Mode Policy (Built-in Plugin)",
472
+ `Current mode: ${mode.toUpperCase()}`,
473
+ `Allowed tool categories: ${policy.allowedCategories.join(", ")}`,
474
+ `Disallowed tool categories: ${policy.disallowedCategories.join(", ") || "none"}`,
475
+ policy.notes ? `Policy notes: ${policy.notes}` : ""
476
+ ].filter(Boolean);
477
+ if (mode === "planning") {
478
+ lines.push(
479
+ "Planning objective: prioritize reading, analysis, and plan generation.",
480
+ "In planning mode, do not intentionally perform write/edit/execute actions.",
481
+ "If implementation is requested but intent is not explicit, ask for execution authorization.",
482
+ "Before each tool call in planning mode, self-check category compliance.",
483
+ "Plan format must include: goals, assumptions, steps, risks, validation approach."
484
+ );
485
+ } else {
486
+ lines.push(
487
+ "Executing objective: follow the agreed plan before broad exploration.",
488
+ "Make targeted edits and keep changes scoped.",
489
+ "Report what changed and how it was validated."
490
+ );
491
+ }
492
+ if (transition?.switched && transition.modeAfter === "executing") {
493
+ lines.push(
494
+ "Mode transition: the latest user message explicitly authorized execution.",
495
+ "In your next reply, acknowledge switching to EXECUTING mode before implementation details."
496
+ );
497
+ }
498
+ lines.push("Tool metadata (prompt-level policy reference):");
499
+ for (const meta of shownTools) {
500
+ lines.push(`- ${meta.name}: category=${meta.category}, risk=${meta.risk}, ${meta.description}`);
501
+ }
502
+ if (omittedCount > 0) {
503
+ lines.push(`- ... ${omittedCount} additional tool(s) omitted for brevity.`);
504
+ }
505
+ return lines.join("\n");
506
+ }
507
+ applyHooks(baseHooks) {
508
+ return {
509
+ onBeforeToolCall: async (name, input) => {
510
+ this.observePotentialPolicyViolation(name, input);
511
+ if (baseHooks?.onBeforeToolCall) {
512
+ const nextInput = await baseHooks.onBeforeToolCall(name, input);
513
+ return nextInput ?? input;
514
+ }
515
+ return input;
516
+ },
517
+ onAfterToolCall: async (name, input, output) => {
518
+ if (baseHooks?.onAfterToolCall) {
519
+ const nextOutput = await baseHooks.onAfterToolCall(name, input, output);
520
+ return nextOutput ?? output;
521
+ }
522
+ return output;
523
+ }
524
+ };
525
+ }
526
+ getEvents(limit = 50) {
527
+ return this.events.slice(-Math.max(0, limit));
528
+ }
529
+ observePotentialPolicyViolation(toolName, input) {
530
+ if (this.mode !== "planning") {
531
+ return;
532
+ }
533
+ const meta = this.inferToolMeta(toolName);
534
+ const policy = this.getModePolicy("planning");
535
+ if (!policy.disallowedCategories.includes(meta.category)) {
536
+ return;
537
+ }
538
+ this.emitEvent("disallowed_tool_attempt_in_planning", {
539
+ toolName,
540
+ category: meta.category,
541
+ risk: meta.risk,
542
+ input
543
+ });
544
+ }
545
+ inferToolMeta(name) {
546
+ const knownMeta = KNOWN_TOOL_META[name];
547
+ if (knownMeta) {
548
+ return {
549
+ name,
550
+ ...knownMeta
551
+ };
552
+ }
553
+ if (name.startsWith("mcp_")) {
554
+ return {
555
+ name,
556
+ category: "execute",
557
+ risk: "medium",
558
+ description: "MCP external tool invocation."
559
+ };
560
+ }
561
+ if (name.endsWith("_agent")) {
562
+ return {
563
+ name,
564
+ category: "execute",
565
+ risk: "high",
566
+ description: "Sub-agent task execution tool."
567
+ };
568
+ }
569
+ if (/read|list|cat/i.test(name)) {
570
+ return {
571
+ name,
572
+ category: "read",
573
+ risk: "low",
574
+ description: "Likely a read-only inspection tool inferred by name."
575
+ };
576
+ }
577
+ if (/search|find|query|grep/i.test(name)) {
578
+ return {
579
+ name,
580
+ category: "search",
581
+ risk: "low",
582
+ description: "Likely a search tool inferred by name."
583
+ };
584
+ }
585
+ if (/write|edit|patch|update|create|delete|remove/i.test(name)) {
586
+ return {
587
+ name,
588
+ category: "write",
589
+ risk: "high",
590
+ description: "Likely a file-modifying tool inferred by name."
591
+ };
592
+ }
593
+ if (/bash|exec|run|shell|deploy|build|test/i.test(name)) {
594
+ return {
595
+ name,
596
+ category: "execute",
597
+ risk: "high",
598
+ description: "Likely a command execution tool inferred by name."
599
+ };
600
+ }
601
+ return {
602
+ name,
603
+ category: "other",
604
+ risk: "low",
605
+ description: "Tool category could not be inferred with high confidence."
606
+ };
607
+ }
608
+ getLatestUserText(messages) {
609
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
610
+ const message = messages[i];
611
+ if (message.role !== "user") {
612
+ continue;
613
+ }
614
+ return this.messageContentToText(message.content);
615
+ }
616
+ return "";
617
+ }
618
+ messageContentToText(content) {
619
+ if (typeof content === "string") {
620
+ return content;
621
+ }
622
+ if (!Array.isArray(content)) {
623
+ return "";
624
+ }
625
+ const textParts = [];
626
+ for (const part of content) {
627
+ if (typeof part === "string") {
628
+ textParts.push(part);
629
+ continue;
630
+ }
631
+ if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
632
+ textParts.push(part.text);
633
+ }
634
+ }
635
+ return textParts.join("\n");
636
+ }
637
+ emitEvent(name, payload) {
638
+ const event = {
639
+ name,
640
+ mode: this.mode,
641
+ timestamp: Date.now(),
642
+ payload
643
+ };
644
+ this.events.push(event);
645
+ if (this.events.length > 500) {
646
+ this.events.shift();
647
+ }
648
+ this.eventEmitter.emit(name, event);
649
+ this.eventEmitter.emit("plan_mode_event", event);
650
+ if (name === "disallowed_tool_attempt_in_planning") {
651
+ this.logger.warn("[PlanMode] Soft violation detected in planning mode", payload);
652
+ return;
653
+ }
654
+ this.logger.info(`[PlanMode] ${name}`, payload);
655
+ }
656
+ };
657
+ var builtInPlanModePlugin = {
658
+ name: "pulse-coder-engine/built-in-plan-mode",
659
+ version: "1.0.0",
660
+ async initialize(context) {
661
+ const service = new BuiltInPlanModeService(context.logger, context.events, "executing");
662
+ context.registerRunHook("plan-mode", ({ context: runContext, tools, systemPrompt, hooks }) => {
663
+ const transition = service.processContextMessages(runContext.messages);
664
+ const append = service.buildPromptAppend(Object.keys(tools), transition);
665
+ const finalSystemPrompt = appendSystemPrompt(systemPrompt, append);
666
+ return {
667
+ systemPrompt: finalSystemPrompt,
668
+ hooks: service.applyHooks(hooks)
669
+ };
670
+ });
671
+ context.registerService("planMode", service);
672
+ context.registerService("planModeService", service);
673
+ context.logger.info("[PlanMode] Built-in plan mode plugin initialized", {
674
+ mode: service.getMode()
675
+ });
676
+ }
677
+ };
678
+
277
679
  // src/built-in/sub-agent-plugin/index.ts
278
680
  var import_zod10 = require("zod");
279
- var import_fs8 = require("fs");
280
- var import_path2 = __toESM(require("path"), 1);
681
+ var import_fs9 = require("fs");
682
+ var import_path3 = __toESM(require("path"), 1);
281
683
 
282
684
  // src/ai/index.ts
283
685
  var import_ai = require("ai");
@@ -293,7 +695,7 @@ var CoderAI = process.env.USE_ANTHROPIC ? (0, import_anthropic.createAnthropic)(
293
695
  }) : (0, import_openai.createOpenAI)({
294
696
  apiKey: process.env.OPENAI_API_KEY || "",
295
697
  baseURL: process.env.OPENAI_API_URL || "https://api.openai.com/v1"
296
- }).chat;
698
+ }).responses;
297
699
  var DEFAULT_MODEL = process.env.ANTHROPIC_MODEL || process.env.OPENAI_MODEL || "novita/deepseek/deepseek_v3";
298
700
  var MAX_ERROR_COUNT = 3;
299
701
  var MAX_STEPS = 100;
@@ -309,8 +711,9 @@ var CLARIFICATION_TIMEOUT = Number(process.env.CLARIFICATION_TIMEOUT ?? 3e5);
309
711
  var CLARIFICATION_ENABLED = process.env.CLARIFICATION_ENABLED !== "false";
310
712
 
311
713
  // src/prompt/system.ts
312
- var generateSystemPrompt = () => {
313
- const basePrompt = `
714
+ var import_fs3 = __toESM(require("fs"), 1);
715
+ var import_path = __toESM(require("path"), 1);
716
+ var DEFAULT_PROMPT = `
314
717
  You are Pulse Coder, the best coding agent on the planet.
315
718
 
316
719
  You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
@@ -390,7 +793,7 @@ Use the 'clarify' tool when you genuinely need information from the user to proc
390
793
  - Explain briefly what would change based on the answer
391
794
 
392
795
  Example usage: Call clarify with a question, optional context, and optional default answer. The tool will pause and wait for the user's response.
393
- - For substantial work, summarize clearly; follow final\u2011answer formatting.
796
+ - For substantial work, summarize clearly; follow final-answer formatting.
394
797
  - Skip heavy formatting for simple confirmations.
395
798
  - Don't dump large files you've written; reference paths only.
396
799
  - No "save/copy this file" - User is on the same machine.
@@ -404,19 +807,19 @@ Example usage: Call clarify with a question, optional context, and optional defa
404
807
  ## Final answer structure and style guidelines
405
808
 
406
809
  - Plain text; CLI handles styling. Use structure only when it helps scanability.
407
- - Headers: optional; short Title Case (1-3 words) wrapped in **\u2026**; no blank line before the first bullet; add only if they truly help.
408
- - Bullets: use - ; merge related points; keep to one line when possible; 4\u20136 per list ordered by importance; keep phrasing consistent.
810
+ - Headers: optional; short Title Case (1-3 words) wrapped in **...**; no blank line before the first bullet; add only if they truly help.
811
+ - Bullets: use - ; merge related points; keep to one line when possible; 4-6 per list ordered by importance; keep phrasing consistent.
409
812
  - Monospace: backticks for commands/paths/env vars/code ids and inline examples; use for literal keyword bullets; never combine with **.
410
813
  - Code samples or multi-line snippets should be wrapped in fenced code blocks; include an info string as often as possible.
411
- - Structure: group related bullets; order sections general \u2192 specific \u2192 supporting; for subsections, start with a bolded keyword bullet, then items; match complexity to the task.
412
- - Tone: collaborative, concise, factual; present tense, active voice; self\u2011contained; no "above/below"; parallel wording.
413
- - Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short\u2014wrap/reformat if long; avoid naming formatting styles in answers.
414
- - Adaptation: code explanations \u2192 precise, structured with code refs; simple tasks \u2192 lead with outcome; big changes \u2192 logical walkthrough + rationale + next actions; casual one-offs \u2192 plain sentences, no headers/bullets.
814
+ - Structure: group related bullets; order sections general -> specific -> supporting; for subsections, start with a bolded keyword bullet, then items; match complexity to the task.
815
+ - Tone: collaborative, concise, factual; present tense, active voice; self-contained; no "above/below"; parallel wording.
816
+ - Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short-wrap/reformat if long; avoid naming formatting styles in answers.
817
+ - Adaptation: code explanations -> precise, structured with code refs; simple tasks -> lead with outcome; big changes -> logical walkthrough + rationale + next actions; casual one-offs -> plain sentences, no headers/bullets.
415
818
  - File References: When referencing files in your response follow the below rules:
416
819
  * Use inline code to make file paths clickable.
417
820
  * Each reference should have a stand alone path. Even if it's the same file.
418
- * Accepted: absolute, workspace\u2011relative, a/ or b/ diff prefixes, or bare filename/suffix.
419
- * Optionally include line/column (1\u2011based): :line[:column] or #Lline[Ccolumn] (column defaults to 1).
821
+ * Accepted: absolute, workspace-relative, a/ or b/ diff prefixes, or bare filename/suffix.
822
+ * Optionally include line/column (1-based): :line[:column] or #Lline[Ccolumn] (column defaults to 1).
420
823
  * Do not use URIs like file://, vscode://, or https://.
421
824
  * Do not provide range of lines
422
825
  * Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\repoprojectmain.rs:12:5
@@ -430,11 +833,37 @@ Here is some useful information about the environment you are running in:
430
833
  <files>
431
834
 
432
835
  </files>`;
433
- return basePrompt;
836
+ var AGENTS_FILE_REGEX = /^agents\.md$/i;
837
+ var loadAgentsPrompt = () => {
838
+ try {
839
+ const cwd = process.cwd();
840
+ const entries = import_fs3.default.readdirSync(cwd, { withFileTypes: true });
841
+ const target = entries.find((entry) => entry.isFile() && AGENTS_FILE_REGEX.test(entry.name));
842
+ if (!target) {
843
+ return null;
844
+ }
845
+ const filePath = import_path.default.join(cwd, target.name);
846
+ const content = import_fs3.default.readFileSync(filePath, "utf8").trim();
847
+ return content.length > 0 ? content : null;
848
+ } catch {
849
+ return null;
850
+ }
851
+ };
852
+ var generateSystemPrompt = () => {
853
+ return loadAgentsPrompt() ?? DEFAULT_PROMPT;
434
854
  };
435
855
 
436
856
  // src/ai/index.ts
437
- var providerOptions = OPENAI_REASONING_EFFORT ? { openai: { reasoningEffort: OPENAI_REASONING_EFFORT } } : void 0;
857
+ var providerOptions = { openai: { store: false, reasoningEffort: OPENAI_REASONING_EFFORT } };
858
+ var resolveSystemPrompt = (option) => {
859
+ const base = generateSystemPrompt();
860
+ if (!option) return base;
861
+ if (typeof option === "string") return option;
862
+ if (typeof option === "function") return option();
863
+ return `${base}
864
+
865
+ ${option.append}`;
866
+ };
438
867
  var wrapToolsWithContext = (tools, context) => {
439
868
  const wrappedTools = {};
440
869
  for (const [name, tool2] of Object.entries(tools)) {
@@ -448,17 +877,13 @@ var wrapToolsWithContext = (tools, context) => {
448
877
  return wrappedTools;
449
878
  };
450
879
  var streamTextAI = (messages, tools, options) => {
451
- const finalMessages = [
452
- {
453
- role: "system",
454
- content: generateSystemPrompt()
455
- },
456
- ...messages
457
- ];
880
+ const provider = options?.provider ?? CoderAI;
881
+ const model = options?.model ?? DEFAULT_MODEL;
458
882
  const wrappedTools = options?.toolExecutionContext ? wrapToolsWithContext(tools, options.toolExecutionContext) : tools;
459
883
  return (0, import_ai.streamText)({
460
- model: CoderAI(DEFAULT_MODEL),
461
- messages: finalMessages,
884
+ model: provider(model),
885
+ system: resolveSystemPrompt(options?.systemPrompt),
886
+ messages,
462
887
  tools: wrappedTools,
463
888
  providerOptions,
464
889
  abortSignal: options?.abortSignal,
@@ -467,6 +892,8 @@ var streamTextAI = (messages, tools, options) => {
467
892
  });
468
893
  };
469
894
  var summarizeMessages = async (messages, options) => {
895
+ const provider = options?.provider ?? CoderAI;
896
+ const model = options?.model ?? DEFAULT_MODEL;
470
897
  const SUMMARY_SYSTEM_PROMPT = "\u4F60\u662F\u8D1F\u8D23\u538B\u7F29\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u7684\u52A9\u624B\u3002\u8BF7\u57FA\u4E8E\u7ED9\u5B9A\u5386\u53F2\u6D88\u606F\uFF0C\u63D0\u70BC\u5173\u952E\u4E8B\u5B9E\u4E0E\u51B3\u7B56\uFF0C\u907F\u514D\u81C6\u6D4B\u6216\u6269\u5C55\u3002";
471
898
  const SUMMARY_USER_PROMPT = [
472
899
  "\u8BF7\u5C06\u4EE5\u4E0A\u5BF9\u8BDD\u538B\u7F29\u4E3A\u4EE5\u4E0B\u683C\u5F0F\uFF0C\u4F7F\u7528\u4E2D\u6587\uFF1A",
@@ -481,9 +908,9 @@ var summarizeMessages = async (messages, options) => {
481
908
  "\u8981\u6C42\uFF1A\u5185\u5BB9\u7B80\u6D01\u51C6\u786E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002"
482
909
  ].join("\n");
483
910
  const result = await (0, import_ai.generateText)({
484
- model: CoderAI(DEFAULT_MODEL),
911
+ model: provider(model),
912
+ system: SUMMARY_SYSTEM_PROMPT,
485
913
  messages: [
486
- { role: "system", content: SUMMARY_SYSTEM_PROMPT },
487
914
  ...messages,
488
915
  { role: "user", content: SUMMARY_USER_PROMPT }
489
916
  ],
@@ -571,7 +998,10 @@ var maybeCompactContext = async (context, options) => {
571
998
  return { didCompact: false };
572
999
  }
573
1000
  try {
574
- const summary = await summarizeMessages(oldMessages);
1001
+ const summary = await summarizeMessages(oldMessages, {
1002
+ provider: options?.provider,
1003
+ model: options?.model
1004
+ });
575
1005
  const summaryText = ensureSummaryPrefix(summary);
576
1006
  if (!summaryText) {
577
1007
  throw new Error("Empty summary result");
@@ -598,6 +1028,20 @@ var maybeCompactContext = async (context, options) => {
598
1028
  };
599
1029
 
600
1030
  // src/core/loop.ts
1031
+ function applyToolHooks(tools, hooks) {
1032
+ const wrapped = {};
1033
+ for (const [name, t] of Object.entries(tools)) {
1034
+ wrapped[name] = {
1035
+ ...t,
1036
+ execute: async (input, ctx) => {
1037
+ const finalInput = hooks.onBeforeToolCall ? await hooks.onBeforeToolCall(name, input) ?? input : input;
1038
+ const output = await t.execute(finalInput, ctx);
1039
+ return hooks.onAfterToolCall ? await hooks.onAfterToolCall(name, finalInput, output) ?? output : output;
1040
+ }
1041
+ };
1042
+ }
1043
+ return wrapped;
1044
+ }
601
1045
  async function loop(context, options) {
602
1046
  let errorCount = 0;
603
1047
  let totalSteps = 0;
@@ -605,7 +1049,10 @@ async function loop(context, options) {
605
1049
  while (true) {
606
1050
  try {
607
1051
  if (compactionAttempts < MAX_COMPACTION_ATTEMPTS) {
608
- const { didCompact, newMessages } = await maybeCompactContext(context);
1052
+ const { didCompact, newMessages } = await maybeCompactContext(context, {
1053
+ provider: options?.provider,
1054
+ model: options?.model
1055
+ });
609
1056
  if (didCompact) {
610
1057
  compactionAttempts++;
611
1058
  if (newMessages) {
@@ -614,7 +1061,10 @@ async function loop(context, options) {
614
1061
  continue;
615
1062
  }
616
1063
  }
617
- const tools = options?.tools || {};
1064
+ let tools = options?.tools || {};
1065
+ if (options?.hooks) {
1066
+ tools = applyToolHooks(tools, options.hooks);
1067
+ }
618
1068
  const toolExecutionContext = {
619
1069
  onClarificationRequest: options?.onClarificationRequest,
620
1070
  abortSignal: options?.abortSignal
@@ -622,6 +1072,9 @@ async function loop(context, options) {
622
1072
  const result = streamTextAI(context.messages, tools, {
623
1073
  abortSignal: options?.abortSignal,
624
1074
  toolExecutionContext,
1075
+ provider: options?.provider,
1076
+ model: options?.model,
1077
+ systemPrompt: options?.systemPrompt,
625
1078
  onStepFinish: (step) => {
626
1079
  options?.onStepFinish?.(step);
627
1080
  },
@@ -654,7 +1107,11 @@ async function loop(context, options) {
654
1107
  }
655
1108
  if (finishReason === "length") {
656
1109
  if (compactionAttempts < MAX_COMPACTION_ATTEMPTS) {
657
- const { didCompact, newMessages } = await maybeCompactContext(context, { force: true });
1110
+ const { didCompact, newMessages } = await maybeCompactContext(context, {
1111
+ force: true,
1112
+ provider: options?.provider,
1113
+ model: options?.model
1114
+ });
658
1115
  if (didCompact) {
659
1116
  compactionAttempts++;
660
1117
  if (newMessages) {
@@ -705,7 +1162,7 @@ function sleep(ms) {
705
1162
 
706
1163
  // src/tools/read.ts
707
1164
  var import_zod2 = __toESM(require("zod"), 1);
708
- var import_fs3 = require("fs");
1165
+ var import_fs4 = require("fs");
709
1166
 
710
1167
  // src/tools/utils.ts
711
1168
  var truncateOutput = (output) => {
@@ -731,14 +1188,14 @@ var ReadTool = {
731
1188
  limit: import_zod2.default.number().optional().describe("The number of lines to read. Only provide if the file is too large to read at once.")
732
1189
  }),
733
1190
  execute: async ({ filePath, offset, limit }) => {
734
- if (!(0, import_fs3.existsSync)(filePath)) {
1191
+ if (!(0, import_fs4.existsSync)(filePath)) {
735
1192
  throw new Error(`File does not exist: ${filePath}`);
736
1193
  }
737
- const stats = (0, import_fs3.statSync)(filePath);
1194
+ const stats = (0, import_fs4.statSync)(filePath);
738
1195
  if (stats.isDirectory()) {
739
1196
  throw new Error(`Cannot read directory: ${filePath}. Use 'ls' tool to list directory contents.`);
740
1197
  }
741
- const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
1198
+ const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
742
1199
  const lines = content.split("\n");
743
1200
  const totalLines = lines.length;
744
1201
  if (offset === void 0 && limit === void 0) {
@@ -766,8 +1223,8 @@ var ReadTool = {
766
1223
 
767
1224
  // src/tools/write.ts
768
1225
  var import_zod3 = __toESM(require("zod"), 1);
769
- var import_fs4 = require("fs");
770
- var import_path = require("path");
1226
+ var import_fs5 = require("fs");
1227
+ var import_path2 = require("path");
771
1228
  var WriteTool = {
772
1229
  name: "write",
773
1230
  description: "Write contents to a file. Automatically creates parent directories if they do not exist. Will overwrite existing files.",
@@ -776,12 +1233,12 @@ var WriteTool = {
776
1233
  content: import_zod3.default.string().describe("The content to write to the file")
777
1234
  }),
778
1235
  execute: async ({ filePath, content }) => {
779
- const fileExists = (0, import_fs4.existsSync)(filePath);
780
- const dir = (0, import_path.dirname)(filePath);
781
- if (!(0, import_fs4.existsSync)(dir)) {
782
- (0, import_fs4.mkdirSync)(dir, { recursive: true });
1236
+ const fileExists = (0, import_fs5.existsSync)(filePath);
1237
+ const dir = (0, import_path2.dirname)(filePath);
1238
+ if (!(0, import_fs5.existsSync)(dir)) {
1239
+ (0, import_fs5.mkdirSync)(dir, { recursive: true });
783
1240
  }
784
- (0, import_fs4.writeFileSync)(filePath, content, "utf-8");
1241
+ (0, import_fs5.writeFileSync)(filePath, content, "utf-8");
785
1242
  const bytes = Buffer.byteLength(content, "utf-8");
786
1243
  return {
787
1244
  success: true,
@@ -793,7 +1250,7 @@ var WriteTool = {
793
1250
 
794
1251
  // src/tools/edit.ts
795
1252
  var import_zod4 = __toESM(require("zod"), 1);
796
- var import_fs5 = require("fs");
1253
+ var import_fs6 = require("fs");
797
1254
  var EditTool = {
798
1255
  name: "edit",
799
1256
  description: "Performs exact string replacements in files. Use this to edit existing files by replacing old_string with new_string.",
@@ -807,7 +1264,7 @@ var EditTool = {
807
1264
  if (oldString === newString) {
808
1265
  throw new Error("old_string and new_string must be different");
809
1266
  }
810
- const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
1267
+ const content = (0, import_fs6.readFileSync)(filePath, "utf-8");
811
1268
  if (!content.includes(oldString)) {
812
1269
  throw new Error(`old_string not found in file: ${filePath}`);
813
1270
  }
@@ -831,7 +1288,7 @@ var EditTool = {
831
1288
  newContent = content.slice(0, index) + newString + content.slice(index + oldString.length);
832
1289
  replacements = 1;
833
1290
  }
834
- (0, import_fs5.writeFileSync)(filePath, newContent, "utf-8");
1291
+ (0, import_fs6.writeFileSync)(filePath, newContent, "utf-8");
835
1292
  const changedIndex = newContent.indexOf(newString);
836
1293
  const contextLength = 200;
837
1294
  const start = Math.max(0, changedIndex - contextLength);
@@ -850,7 +1307,7 @@ var EditTool = {
850
1307
  // src/tools/grep.ts
851
1308
  var import_zod5 = __toESM(require("zod"), 1);
852
1309
  var import_child_process = require("child_process");
853
- var import_fs6 = require("fs");
1310
+ var import_fs7 = require("fs");
854
1311
  var GrepTool = {
855
1312
  name: "grep",
856
1313
  description: "A powerful search tool built on ripgrep. Supports regex patterns, file filtering, and multiple output modes.",
@@ -868,7 +1325,7 @@ var GrepTool = {
868
1325
  }),
869
1326
  execute: async ({
870
1327
  pattern,
871
- path: path3 = ".",
1328
+ path: path4 = ".",
872
1329
  glob,
873
1330
  type,
874
1331
  outputMode = "files_with_matches",
@@ -903,11 +1360,11 @@ var GrepTool = {
903
1360
  if (type) {
904
1361
  args.push("--type", type);
905
1362
  }
906
- if (path3 && path3 !== ".") {
907
- if (!(0, import_fs6.existsSync)(path3)) {
908
- throw new Error(`Path does not exist: ${path3}`);
1363
+ if (path4 && path4 !== ".") {
1364
+ if (!(0, import_fs7.existsSync)(path4)) {
1365
+ throw new Error(`Path does not exist: ${path4}`);
909
1366
  }
910
- args.push(path3);
1367
+ args.push(path4);
911
1368
  }
912
1369
  let command = args.map((arg) => {
913
1370
  if (arg.includes(" ") || arg.includes("$") || arg.includes("*")) {
@@ -955,15 +1412,15 @@ Command: ${command}`
955
1412
 
956
1413
  // src/tools/ls.ts
957
1414
  var import_zod6 = __toESM(require("zod"), 1);
958
- var import_fs7 = require("fs");
1415
+ var import_fs8 = require("fs");
959
1416
  var LsTool = {
960
1417
  name: "ls",
961
1418
  description: "List files and directories in a given path",
962
1419
  inputSchema: import_zod6.default.object({
963
1420
  path: import_zod6.default.string().optional().describe("The path to list files from (defaults to current directory)")
964
1421
  }),
965
- execute: async ({ path: path3 = "." }) => {
966
- const files = (0, import_fs7.readdirSync)(path3);
1422
+ execute: async ({ path: path4 = "." }) => {
1423
+ const files = (0, import_fs8.readdirSync)(path4);
967
1424
  return { files };
968
1425
  }
969
1426
  };
@@ -1134,8 +1591,8 @@ var ConfigLoader = class {
1134
1591
  const fileInfos = [];
1135
1592
  for (let configDir of configDirs) {
1136
1593
  try {
1137
- await import_fs8.promises.access(configDir);
1138
- const files = await import_fs8.promises.readdir(configDir);
1594
+ await import_fs9.promises.access(configDir);
1595
+ const files = await import_fs9.promises.readdir(configDir);
1139
1596
  fileInfos.push({ files, configDir });
1140
1597
  } catch {
1141
1598
  continue;
@@ -1152,7 +1609,7 @@ var ConfigLoader = class {
1152
1609
  const files = fileInfo.files;
1153
1610
  for (const file of files) {
1154
1611
  if (file.endsWith(".md")) {
1155
- const config = await this.parseConfig(import_path2.default.join(fileInfo.configDir, file));
1612
+ const config = await this.parseConfig(import_path3.default.join(fileInfo.configDir, file));
1156
1613
  if (config) configs.push(config);
1157
1614
  }
1158
1615
  }
@@ -1164,7 +1621,7 @@ var ConfigLoader = class {
1164
1621
  }
1165
1622
  async parseConfig(filePath) {
1166
1623
  try {
1167
- const content = await import_fs8.promises.readFile(filePath, "utf-8");
1624
+ const content = await import_fs9.promises.readFile(filePath, "utf-8");
1168
1625
  const lines = content.split("\n");
1169
1626
  let name = "";
1170
1627
  let description = "";
@@ -1193,7 +1650,7 @@ var ConfigLoader = class {
1193
1650
  }
1194
1651
  }
1195
1652
  if (!name) {
1196
- name = import_path2.default.basename(filePath, ".md");
1653
+ name = import_path3.default.basename(filePath, ".md");
1197
1654
  }
1198
1655
  return {
1199
1656
  name: name.trim(),
@@ -1211,7 +1668,6 @@ var AgentRunner = class {
1211
1668
  async runAgent(config, task, context, tools) {
1212
1669
  const subContext = {
1213
1670
  messages: [
1214
- { role: "system", content: config.systemPrompt },
1215
1671
  { role: "user", content: task }
1216
1672
  ]
1217
1673
  };
@@ -1222,7 +1678,7 @@ var AgentRunner = class {
1222
1678
  ${JSON.stringify(context, null, 2)}`
1223
1679
  });
1224
1680
  }
1225
- return await loop(subContext, { tools });
1681
+ return await loop(subContext, { tools, systemPrompt: config.systemPrompt });
1226
1682
  }
1227
1683
  };
1228
1684
  var SubAgentPlugin = class {
@@ -1277,14 +1733,17 @@ var SubAgentPlugin = class {
1277
1733
  var builtInPlugins = [
1278
1734
  builtInMCPPlugin,
1279
1735
  builtInSkillsPlugin,
1736
+ builtInPlanModePlugin,
1280
1737
  new SubAgentPlugin()
1281
1738
  ];
1282
1739
  var built_in_default = builtInPlugins;
1283
1740
  // Annotate the CommonJS export names for ESM import in node:
1284
1741
  0 && (module.exports = {
1742
+ BuiltInPlanModeService,
1285
1743
  BuiltInSkillRegistry,
1286
1744
  SubAgentPlugin,
1287
1745
  builtInMCPPlugin,
1746
+ builtInPlanModePlugin,
1288
1747
  builtInPlugins,
1289
1748
  builtInSkillsPlugin
1290
1749
  });