pulse-coder-engine 0.0.1-alpha.10 → 0.0.1-alpha.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.
package/dist/index.cjs CHANGED
@@ -43,12 +43,14 @@ __export(src_exports, {
43
43
  PluginManager: () => PluginManager,
44
44
  PulseAgent: () => Engine,
45
45
  ReadTool: () => ReadTool,
46
+ TaskListService: () => TaskListService,
46
47
  TavilyTool: () => TavilyTool,
47
48
  WriteTool: () => WriteTool,
48
49
  builtInMCPPlugin: () => builtInMCPPlugin,
49
50
  builtInPlanModePlugin: () => builtInPlanModePlugin,
50
51
  builtInPlugins: () => builtInPlugins,
51
52
  builtInSkillsPlugin: () => builtInSkillsPlugin,
53
+ builtInTaskTrackingPlugin: () => builtInTaskTrackingPlugin,
52
54
  getFinalToolsMap: () => getFinalToolsMap,
53
55
  loop: () => loop,
54
56
  maybeCompactContext: () => maybeCompactContext,
@@ -415,15 +417,28 @@ var maybeCompactContext = async (context, options) => {
415
417
  };
416
418
 
417
419
  // src/core/loop.ts
418
- function applyToolHooks(tools, hooks) {
420
+ function wrapToolsWithHooks(tools, beforeHooks, afterHooks) {
419
421
  const wrapped = {};
420
422
  for (const [name, t] of Object.entries(tools)) {
421
423
  wrapped[name] = {
422
424
  ...t,
423
425
  execute: async (input, ctx) => {
424
- const finalInput = hooks.onBeforeToolCall ? await hooks.onBeforeToolCall(name, input) ?? input : input;
426
+ let finalInput = input;
427
+ for (const hook of beforeHooks) {
428
+ const result = await hook({ name, input: finalInput });
429
+ if (result && "input" in result) {
430
+ finalInput = result.input;
431
+ }
432
+ }
425
433
  const output = await t.execute(finalInput, ctx);
426
- return hooks.onAfterToolCall ? await hooks.onAfterToolCall(name, finalInput, output) ?? output : output;
434
+ let finalOutput = output;
435
+ for (const hook of afterHooks) {
436
+ const result = await hook({ name, input: finalInput, output: finalOutput });
437
+ if (result && "output" in result) {
438
+ finalOutput = result.output;
439
+ }
440
+ }
441
+ return finalOutput;
427
442
  }
428
443
  };
429
444
  }
@@ -433,6 +448,7 @@ async function loop(context, options) {
433
448
  let errorCount = 0;
434
449
  let totalSteps = 0;
435
450
  let compactionAttempts = 0;
451
+ const loopHooks = options?.hooks ?? {};
436
452
  while (true) {
437
453
  try {
438
454
  if (compactionAttempts < MAX_COMPACTION_ATTEMPTS) {
@@ -449,8 +465,24 @@ async function loop(context, options) {
449
465
  }
450
466
  }
451
467
  let tools = options?.tools || {};
452
- if (options?.hooks) {
453
- tools = applyToolHooks(tools, options.hooks);
468
+ let systemPrompt = options?.systemPrompt;
469
+ if (loopHooks.beforeLLMCall?.length) {
470
+ for (const hook of loopHooks.beforeLLMCall) {
471
+ const result2 = await hook({ context, systemPrompt, tools });
472
+ if (result2) {
473
+ if ("systemPrompt" in result2 && result2.systemPrompt !== void 0) {
474
+ systemPrompt = result2.systemPrompt;
475
+ }
476
+ if ("tools" in result2 && result2.tools !== void 0) {
477
+ tools = result2.tools;
478
+ }
479
+ }
480
+ }
481
+ }
482
+ const beforeToolHooks = loopHooks.beforeToolCall ?? [];
483
+ const afterToolHooks = loopHooks.afterToolCall ?? [];
484
+ if (beforeToolHooks.length || afterToolHooks.length) {
485
+ tools = wrapToolsWithHooks(tools, beforeToolHooks, afterToolHooks);
454
486
  }
455
487
  const toolExecutionContext = {
456
488
  onClarificationRequest: options?.onClarificationRequest,
@@ -461,7 +493,7 @@ async function loop(context, options) {
461
493
  toolExecutionContext,
462
494
  provider: options?.provider,
463
495
  model: options?.model,
464
- systemPrompt: options?.systemPrompt,
496
+ systemPrompt,
465
497
  onStepFinish: (step) => {
466
498
  options?.onStepFinish?.(step);
467
499
  },
@@ -489,6 +521,11 @@ async function loop(context, options) {
489
521
  options?.onResponse?.(messages);
490
522
  }
491
523
  }
524
+ if (loopHooks.afterLLMCall?.length) {
525
+ for (const hook of loopHooks.afterLLMCall) {
526
+ await hook({ context, finishReason, text });
527
+ }
528
+ }
492
529
  if (finishReason === "stop") {
493
530
  return text || "Task completed.";
494
531
  }
@@ -712,7 +749,7 @@ var GrepTool = {
712
749
  }),
713
750
  execute: async ({
714
751
  pattern,
715
- path: path5 = ".",
752
+ path: path6 = ".",
716
753
  glob: glob2,
717
754
  type,
718
755
  outputMode = "files_with_matches",
@@ -747,11 +784,11 @@ var GrepTool = {
747
784
  if (type) {
748
785
  args.push("--type", type);
749
786
  }
750
- if (path5 && path5 !== ".") {
751
- if (!(0, import_fs5.existsSync)(path5)) {
752
- throw new Error(`Path does not exist: ${path5}`);
787
+ if (path6 && path6 !== ".") {
788
+ if (!(0, import_fs5.existsSync)(path6)) {
789
+ throw new Error(`Path does not exist: ${path6}`);
753
790
  }
754
- args.push(path5);
791
+ args.push(path6);
755
792
  }
756
793
  let command = args.map((arg) => {
757
794
  if (arg.includes(" ") || arg.includes("$") || arg.includes("*")) {
@@ -806,8 +843,8 @@ var LsTool = {
806
843
  inputSchema: import_zod5.default.object({
807
844
  path: import_zod5.default.string().optional().describe("The path to list files from (defaults to current directory)")
808
845
  }),
809
- execute: async ({ path: path5 = "." }) => {
810
- const files = (0, import_fs6.readdirSync)(path5);
846
+ execute: async ({ path: path6 = "." }) => {
847
+ const files = (0, import_fs6.readdirSync)(path6);
811
848
  return { files };
812
849
  }
813
850
  };
@@ -1066,9 +1103,15 @@ var PluginManager = class {
1066
1103
  enginePlugins = /* @__PURE__ */ new Map();
1067
1104
  userConfigPlugins = [];
1068
1105
  tools = /* @__PURE__ */ new Map();
1069
- runHooks = /* @__PURE__ */ new Map();
1106
+ hooks = {
1107
+ beforeRun: [],
1108
+ afterRun: [],
1109
+ beforeLLMCall: [],
1110
+ afterLLMCall: [],
1111
+ beforeToolCall: [],
1112
+ afterToolCall: []
1113
+ };
1070
1114
  services = /* @__PURE__ */ new Map();
1071
- protocols = /* @__PURE__ */ new Map();
1072
1115
  config = /* @__PURE__ */ new Map();
1073
1116
  events = new import_events.EventEmitter();
1074
1117
  logger;
@@ -1177,14 +1220,10 @@ var PluginManager = class {
1177
1220
  },
1178
1221
  getTool: (name) => this.tools.get(name),
1179
1222
  getTools: () => Object.fromEntries(this.tools),
1180
- registerRunHook: (name, hook) => {
1181
- this.runHooks.set(name, hook);
1182
- },
1183
- getRunHook: (name) => this.runHooks.get(name),
1184
- registerProtocol: (name, handler) => {
1185
- this.protocols.set(name, handler);
1223
+ registerHook: (hookName, handler) => {
1224
+ this.hooks[hookName].push(handler);
1225
+ this.logger.debug(`Plugin "${plugin.name}" registered hook: ${hookName}`);
1186
1226
  },
1187
- getProtocol: (name) => this.protocols.get(name),
1188
1227
  registerService: (name, service) => {
1189
1228
  this.services.set(name, service);
1190
1229
  },
@@ -1380,10 +1419,10 @@ var PluginManager = class {
1380
1419
  return Object.fromEntries(this.tools);
1381
1420
  }
1382
1421
  /**
1383
- * 运行时钩子获取
1422
+ * 获取指定类型的 hook 处理函数数组
1384
1423
  */
1385
- getRunHooks() {
1386
- return Array.from(this.runHooks.values());
1424
+ getHooks(hookName) {
1425
+ return [...this.hooks[hookName]];
1387
1426
  }
1388
1427
  /**
1389
1428
  * 服务获取
@@ -1391,12 +1430,6 @@ var PluginManager = class {
1391
1430
  getService(name) {
1392
1431
  return this.services.get(name);
1393
1432
  }
1394
- /**
1395
- * 协议获取
1396
- */
1397
- getProtocol(name) {
1398
- return this.protocols.get(name);
1399
- }
1400
1433
  /**
1401
1434
  * 获取插件状态
1402
1435
  */
@@ -1405,9 +1438,10 @@ var PluginManager = class {
1405
1438
  enginePlugins: Array.from(this.enginePlugins.keys()),
1406
1439
  userConfigPlugins: this.userConfigPlugins.map((c) => c.name || "unnamed"),
1407
1440
  tools: Array.from(this.tools.keys()),
1408
- runHooks: Array.from(this.runHooks.keys()),
1409
- services: Array.from(this.services.keys()),
1410
- protocols: Array.from(this.protocols.keys())
1441
+ hooks: Object.fromEntries(
1442
+ Object.entries(this.hooks).map(([k, v]) => [k, v.length])
1443
+ ),
1444
+ services: Array.from(this.services.keys())
1411
1445
  };
1412
1446
  }
1413
1447
  /**
@@ -1582,16 +1616,44 @@ var BuiltInSkillRegistry = class {
1582
1616
  return Array.from(this.skills.values());
1583
1617
  }
1584
1618
  /**
1585
- * 根据名称获取技能
1619
+ * 注册或更新一个技能(支持插件在运行期追加技能)
1620
+ */
1621
+ registerSkill(skill) {
1622
+ const name = skill.name?.trim();
1623
+ if (!name) {
1624
+ throw new Error("Skill name is required");
1625
+ }
1626
+ const existingKey = Array.from(this.skills.keys()).find((key) => key.toLowerCase() === name.toLowerCase());
1627
+ const replaced = existingKey !== void 0;
1628
+ if (existingKey && existingKey !== name) {
1629
+ this.skills.delete(existingKey);
1630
+ }
1631
+ this.skills.set(name, {
1632
+ ...skill,
1633
+ name
1634
+ });
1635
+ return {
1636
+ skillName: name,
1637
+ replaced,
1638
+ total: this.skills.size
1639
+ };
1640
+ }
1641
+ /**
1642
+ * 根据名称获取技能(大小写不敏感)
1586
1643
  */
1587
1644
  get(name) {
1588
- return this.skills.get(name);
1645
+ const exact = this.skills.get(name);
1646
+ if (exact) {
1647
+ return exact;
1648
+ }
1649
+ const target = name.toLowerCase();
1650
+ return this.getAll().find((skill) => skill.name.toLowerCase() === target);
1589
1651
  }
1590
1652
  /**
1591
1653
  * 检查技能是否存在
1592
1654
  */
1593
1655
  has(name) {
1594
- return this.skills.has(name);
1656
+ return !!this.get(name);
1595
1657
  }
1596
1658
  /**
1597
1659
  * 搜索技能(模糊匹配)
@@ -1606,7 +1668,7 @@ var BuiltInSkillRegistry = class {
1606
1668
  var skillToolSchema = import_zod10.z.object({
1607
1669
  name: import_zod10.z.string().describe("The name of the skill to execute")
1608
1670
  });
1609
- function generateSkillTool(skills) {
1671
+ function generateSkillTool(registry) {
1610
1672
  const getSkillsPrompt = (availableSkills) => {
1611
1673
  return [
1612
1674
  "If query matches an available skill's description or instruction [use skill], use the skill tool to get detailed instructions.",
@@ -1627,10 +1689,10 @@ function generateSkillTool(skills) {
1627
1689
  };
1628
1690
  return {
1629
1691
  name: "skill",
1630
- description: getSkillsPrompt(skills),
1692
+ description: getSkillsPrompt(registry.getAll()),
1631
1693
  inputSchema: skillToolSchema,
1632
1694
  execute: async ({ name }) => {
1633
- const skill = skills.find((skill2) => skill2.name === name);
1695
+ const skill = registry.get(name);
1634
1696
  if (!skill) {
1635
1697
  throw new Error(`Skill ${name} not found`);
1636
1698
  }
@@ -1644,14 +1706,21 @@ var builtInSkillsPlugin = {
1644
1706
  async initialize(context) {
1645
1707
  const registry = new BuiltInSkillRegistry();
1646
1708
  await registry.initialize(process.cwd());
1709
+ context.registerService("skillRegistry", registry);
1710
+ context.registerTool("skill", generateSkillTool(registry));
1711
+ context.registerHook("beforeRun", ({ tools }) => {
1712
+ return {
1713
+ tools: {
1714
+ ...tools,
1715
+ skill: generateSkillTool(registry)
1716
+ }
1717
+ };
1718
+ });
1647
1719
  const skills = registry.getAll();
1648
1720
  if (skills.length === 0) {
1649
1721
  console.log("[Skills] No skills found");
1650
1722
  return;
1651
1723
  }
1652
- const skillTool = generateSkillTool(skills);
1653
- context.registerTool("skill", skillTool);
1654
- context.registerService("skillRegistry", registry);
1655
1724
  console.log(`[Skills] Registered ${skills.length} skill(s)`);
1656
1725
  }
1657
1726
  };
@@ -1762,6 +1831,26 @@ var KNOWN_TOOL_META = {
1762
1831
  category: "other",
1763
1832
  risk: "low",
1764
1833
  description: "Ask the user a targeted clarification question."
1834
+ },
1835
+ task_create: {
1836
+ category: "other",
1837
+ risk: "low",
1838
+ description: "Create tracked task entries for planning and execution visibility."
1839
+ },
1840
+ task_get: {
1841
+ category: "other",
1842
+ risk: "low",
1843
+ description: "Inspect a task entry by ID."
1844
+ },
1845
+ task_list: {
1846
+ category: "other",
1847
+ risk: "low",
1848
+ description: "List task tracking entries and status summary."
1849
+ },
1850
+ task_update: {
1851
+ category: "other",
1852
+ risk: "low",
1853
+ description: "Update task tracking fields and status."
1765
1854
  }
1766
1855
  };
1767
1856
  var BuiltInPlanModeService = class {
@@ -1884,25 +1973,6 @@ var BuiltInPlanModeService = class {
1884
1973
  }
1885
1974
  return lines.join("\n");
1886
1975
  }
1887
- applyHooks(baseHooks) {
1888
- return {
1889
- onBeforeToolCall: async (name, input) => {
1890
- this.observePotentialPolicyViolation(name, input);
1891
- if (baseHooks?.onBeforeToolCall) {
1892
- const nextInput = await baseHooks.onBeforeToolCall(name, input);
1893
- return nextInput ?? input;
1894
- }
1895
- return input;
1896
- },
1897
- onAfterToolCall: async (name, input, output) => {
1898
- if (baseHooks?.onAfterToolCall) {
1899
- const nextOutput = await baseHooks.onAfterToolCall(name, input, output);
1900
- return nextOutput ?? output;
1901
- }
1902
- return output;
1903
- }
1904
- };
1905
- }
1906
1976
  getEvents(limit = 50) {
1907
1977
  return this.events.slice(-Math.max(0, limit));
1908
1978
  }
@@ -2039,21 +2109,18 @@ var builtInPlanModePlugin = {
2039
2109
  version: "1.0.0",
2040
2110
  async initialize(context) {
2041
2111
  const service = new BuiltInPlanModeService(context.logger, context.events, "executing");
2042
- context.registerRunHook("plan-mode", ({ context: runContext, tools, systemPrompt, hooks }) => {
2112
+ context.registerHook("beforeLLMCall", ({ context: runContext, tools, systemPrompt }) => {
2043
2113
  const mode = service.getMode();
2044
2114
  if (mode === "executing") {
2045
- return {
2046
- systemPrompt,
2047
- hooks
2048
- };
2115
+ return;
2049
2116
  }
2050
2117
  const transition = service.processContextMessages(runContext.messages);
2051
2118
  const append = service.buildPromptAppend(Object.keys(tools), transition);
2052
2119
  const finalSystemPrompt = appendSystemPrompt(systemPrompt, append);
2053
- return {
2054
- systemPrompt: finalSystemPrompt,
2055
- hooks: service.applyHooks(hooks)
2056
- };
2120
+ return { systemPrompt: finalSystemPrompt };
2121
+ });
2122
+ context.registerHook("beforeToolCall", ({ name, input }) => {
2123
+ service.observePotentialPolicyViolation(name, input);
2057
2124
  });
2058
2125
  context.registerService("planMode", service);
2059
2126
  context.registerService("planModeService", service);
@@ -2063,17 +2130,404 @@ var builtInPlanModePlugin = {
2063
2130
  }
2064
2131
  };
2065
2132
 
2066
- // src/built-in/sub-agent-plugin/index.ts
2067
- var import_zod11 = require("zod");
2133
+ // src/built-in/task-tracking-plugin/index.ts
2068
2134
  var import_fs10 = require("fs");
2069
2135
  var import_path4 = __toESM(require("path"), 1);
2136
+ var import_os2 = require("os");
2137
+ var import_crypto2 = require("crypto");
2138
+ var import_zod11 = require("zod");
2139
+ var TASK_STATUSES = ["pending", "in_progress", "completed", "blocked"];
2140
+ var CREATE_TASK_ITEM_SCHEMA = import_zod11.z.object({
2141
+ title: import_zod11.z.string().min(1).describe("Task title"),
2142
+ details: import_zod11.z.string().optional().describe("Task details / acceptance notes"),
2143
+ status: import_zod11.z.enum(TASK_STATUSES).optional().describe("Initial status; defaults to pending"),
2144
+ dependencies: import_zod11.z.array(import_zod11.z.string()).optional().describe("Task IDs that must be completed first"),
2145
+ blockedReason: import_zod11.z.string().optional().describe("Reason when status is blocked"),
2146
+ metadata: import_zod11.z.record(import_zod11.z.string(), import_zod11.z.any()).optional().describe("Additional machine-readable metadata")
2147
+ });
2148
+ var TASK_CREATE_INPUT_SCHEMA = import_zod11.z.object({
2149
+ title: import_zod11.z.string().min(1).optional().describe("Task title (single-task mode)"),
2150
+ details: import_zod11.z.string().optional().describe("Task details (single-task mode)"),
2151
+ status: import_zod11.z.enum(TASK_STATUSES).optional().describe("Initial status (single-task mode)"),
2152
+ dependencies: import_zod11.z.array(import_zod11.z.string()).optional().describe("Dependencies (single-task mode)"),
2153
+ blockedReason: import_zod11.z.string().optional().describe("Blocked reason (single-task mode)"),
2154
+ metadata: import_zod11.z.record(import_zod11.z.string(), import_zod11.z.any()).optional().describe("Metadata (single-task mode)"),
2155
+ tasks: import_zod11.z.array(CREATE_TASK_ITEM_SCHEMA).min(1).optional().describe("Batch create mode")
2156
+ }).refine((value) => !!value.tasks?.length || !!value.title, {
2157
+ message: "Either provide `tasks` or `title` for single-task mode."
2158
+ });
2159
+ var TASK_GET_INPUT_SCHEMA = import_zod11.z.object({
2160
+ id: import_zod11.z.string().min(1).describe("Task ID to retrieve")
2161
+ });
2162
+ var TASK_LIST_INPUT_SCHEMA = import_zod11.z.object({
2163
+ statuses: import_zod11.z.array(import_zod11.z.enum(TASK_STATUSES)).optional().describe("Filter by statuses"),
2164
+ includeCompleted: import_zod11.z.boolean().optional().describe("Set false to hide completed tasks"),
2165
+ limit: import_zod11.z.number().int().positive().max(500).optional().describe("Maximum tasks to return")
2166
+ });
2167
+ var TASK_TRACKING_SKILL = {
2168
+ name: "task-tracking-workflow",
2169
+ description: "Track complex execution with task tools and proceed directly without confirmation unless the user explicitly asks for confirmation.",
2170
+ location: "pulse-coder-engine/built-in/task-tracking-plugin",
2171
+ content: `# Task Tracking Workflow
2172
+
2173
+ ## When to use
2174
+ - The request has multiple phases or deliverables.
2175
+ - Work may span multiple tool calls or sessions.
2176
+ - You need explicit progress visibility and blocker management.
2177
+
2178
+ ## Execution autonomy
2179
+ - Default behavior: execute directly and call tools without asking for confirmation.
2180
+ - Only ask for confirmation when the user explicitly requires confirmation.
2181
+ - If critical information is missing, ask only the minimum clarifying question needed to continue.
2182
+
2183
+ ## Required flow
2184
+ 1. Start by reviewing existing tasks with \`task_list\`.
2185
+ 2. If no suitable tasks exist, create focused tasks with \`task_create\` (prefer batch mode).
2186
+ 3. Keep exactly one primary task in \`in_progress\` where possible.
2187
+ 4. Update status with \`task_update\` after meaningful progress.
2188
+ 5. Mark blockers with \`status=blocked\` and a concrete \`blockedReason\`.
2189
+ 6. Mark done tasks as \`completed\` and move to the next actionable item.
2190
+
2191
+ ## Quality rules
2192
+ - Keep tasks atomic and verifiable.
2193
+ - Avoid single oversized umbrella tasks.
2194
+ - Reuse existing in-progress tasks instead of duplicating.
2195
+ - Keep dependency links explicit when order matters.`
2196
+ };
2197
+ var TASK_UPDATE_INPUT_SCHEMA = import_zod11.z.object({
2198
+ id: import_zod11.z.string().min(1).describe("Task ID to update"),
2199
+ title: import_zod11.z.string().min(1).optional().describe("New title"),
2200
+ details: import_zod11.z.string().optional().describe("New details"),
2201
+ status: import_zod11.z.enum(TASK_STATUSES).optional().describe("New status"),
2202
+ dependencies: import_zod11.z.array(import_zod11.z.string()).optional().describe("Replace dependencies with this list"),
2203
+ blockedReason: import_zod11.z.string().optional().describe("Blocked reason, used with status=blocked"),
2204
+ metadata: import_zod11.z.record(import_zod11.z.string(), import_zod11.z.any()).optional().describe("Replace metadata object"),
2205
+ delete: import_zod11.z.boolean().optional().describe("Delete this task")
2206
+ });
2207
+ function normalizeTaskListId(raw) {
2208
+ const trimmed = raw.trim();
2209
+ if (!trimmed) {
2210
+ return "default";
2211
+ }
2212
+ return trimmed.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "default";
2213
+ }
2214
+ function now() {
2215
+ return Date.now();
2216
+ }
2217
+ function dedupeStrings(values) {
2218
+ if (!values?.length) {
2219
+ return [];
2220
+ }
2221
+ return Array.from(new Set(values.map((value) => String(value).trim()).filter(Boolean)));
2222
+ }
2223
+ var TaskListService = class _TaskListService {
2224
+ taskListId;
2225
+ storagePath;
2226
+ storageDir;
2227
+ initialized = false;
2228
+ createdAt = now();
2229
+ updatedAt = now();
2230
+ tasks = /* @__PURE__ */ new Map();
2231
+ constructor(taskListId = _TaskListService.resolveTaskListId(), storageDir = _TaskListService.resolveStorageDir()) {
2232
+ const normalized = normalizeTaskListId(taskListId);
2233
+ this.storageDir = storageDir;
2234
+ this.taskListId = normalized;
2235
+ this.storagePath = import_path4.default.join(this.storageDir, `${normalized}.json`);
2236
+ }
2237
+ static resolveTaskListId() {
2238
+ return process.env.PULSE_CODER_TASK_LIST_ID || process.env.CLAUDE_CODE_TASK_LIST_ID || "default";
2239
+ }
2240
+ static resolveStorageDir() {
2241
+ return process.env.PULSE_CODER_TASKS_DIR || import_path4.default.join((0, import_os2.homedir)(), ".pulse-coder", "tasks");
2242
+ }
2243
+ async initialize() {
2244
+ if (this.initialized) {
2245
+ return;
2246
+ }
2247
+ await this.loadTaskList(this.taskListId);
2248
+ }
2249
+ async setTaskListId(taskListId) {
2250
+ await this.initialize();
2251
+ const normalized = normalizeTaskListId(taskListId);
2252
+ if (normalized === this.taskListId) {
2253
+ return {
2254
+ switched: false,
2255
+ taskListId: this.taskListId,
2256
+ storagePath: this.storagePath
2257
+ };
2258
+ }
2259
+ await this.loadTaskList(normalized);
2260
+ return {
2261
+ switched: true,
2262
+ taskListId: this.taskListId,
2263
+ storagePath: this.storagePath
2264
+ };
2265
+ }
2266
+ async createTask(input) {
2267
+ await this.initialize();
2268
+ const timestamp = now();
2269
+ const status = input.status ?? "pending";
2270
+ const task = {
2271
+ id: (0, import_crypto2.randomUUID)(),
2272
+ title: input.title.trim(),
2273
+ details: input.details,
2274
+ status,
2275
+ dependencies: dedupeStrings(input.dependencies),
2276
+ blockedReason: status === "blocked" ? input.blockedReason ?? "Blocked without reason" : void 0,
2277
+ metadata: input.metadata,
2278
+ createdAt: timestamp,
2279
+ updatedAt: timestamp,
2280
+ completedAt: status === "completed" ? timestamp : void 0
2281
+ };
2282
+ this.tasks.set(task.id, task);
2283
+ this.updatedAt = timestamp;
2284
+ await this.persist();
2285
+ return task;
2286
+ }
2287
+ async createTasks(inputs) {
2288
+ const created = [];
2289
+ for (const input of inputs) {
2290
+ created.push(await this.createTask(input));
2291
+ }
2292
+ return created;
2293
+ }
2294
+ async listTasks(options) {
2295
+ await this.initialize();
2296
+ const statuses = options?.statuses?.length ? new Set(options.statuses) : null;
2297
+ const includeCompleted = options?.includeCompleted ?? true;
2298
+ let tasks = Array.from(this.tasks.values()).filter((task) => {
2299
+ if (!includeCompleted && task.status === "completed") {
2300
+ return false;
2301
+ }
2302
+ if (statuses && !statuses.has(task.status)) {
2303
+ return false;
2304
+ }
2305
+ return true;
2306
+ });
2307
+ tasks = tasks.sort((a, b) => a.createdAt - b.createdAt);
2308
+ if (options?.limit && options.limit > 0) {
2309
+ tasks = tasks.slice(0, options.limit);
2310
+ }
2311
+ return tasks;
2312
+ }
2313
+ async getTask(id) {
2314
+ await this.initialize();
2315
+ return this.tasks.get(id) ?? null;
2316
+ }
2317
+ async updateTask(input) {
2318
+ await this.initialize();
2319
+ if (input.delete) {
2320
+ const deleted = this.tasks.delete(input.id);
2321
+ if (!deleted) {
2322
+ return null;
2323
+ }
2324
+ this.updatedAt = now();
2325
+ await this.persist();
2326
+ return null;
2327
+ }
2328
+ const existing = this.tasks.get(input.id);
2329
+ if (!existing) {
2330
+ return null;
2331
+ }
2332
+ const timestamp = now();
2333
+ const nextStatus = input.status ?? existing.status;
2334
+ const next = {
2335
+ ...existing,
2336
+ title: input.title !== void 0 ? input.title : existing.title,
2337
+ details: input.details !== void 0 ? input.details : existing.details,
2338
+ status: nextStatus,
2339
+ dependencies: input.dependencies !== void 0 ? dedupeStrings(input.dependencies) : existing.dependencies,
2340
+ metadata: input.metadata !== void 0 ? input.metadata : existing.metadata,
2341
+ blockedReason: this.resolveBlockedReason(nextStatus, input.blockedReason, existing.blockedReason),
2342
+ updatedAt: timestamp,
2343
+ completedAt: nextStatus === "completed" ? existing.completedAt ?? timestamp : void 0
2344
+ };
2345
+ this.tasks.set(input.id, next);
2346
+ this.updatedAt = timestamp;
2347
+ await this.persist();
2348
+ return next;
2349
+ }
2350
+ async snapshot(options) {
2351
+ const tasks = await this.listTasks(options);
2352
+ return {
2353
+ taskListId: this.taskListId,
2354
+ storagePath: this.storagePath,
2355
+ createdAt: this.createdAt,
2356
+ updatedAt: this.updatedAt,
2357
+ total: tasks.length,
2358
+ tasks
2359
+ };
2360
+ }
2361
+ resolveBlockedReason(status, blockedReasonInput, previous) {
2362
+ if (status !== "blocked") {
2363
+ return void 0;
2364
+ }
2365
+ if (blockedReasonInput !== void 0) {
2366
+ return blockedReasonInput || "Blocked without reason";
2367
+ }
2368
+ return previous ?? "Blocked without reason";
2369
+ }
2370
+ async loadTaskList(taskListId) {
2371
+ const normalized = normalizeTaskListId(taskListId);
2372
+ this.taskListId = normalized;
2373
+ this.storagePath = import_path4.default.join(this.storageDir, `${normalized}.json`);
2374
+ await import_fs10.promises.mkdir(this.storageDir, { recursive: true });
2375
+ try {
2376
+ const raw = await import_fs10.promises.readFile(this.storagePath, "utf-8");
2377
+ const parsed = JSON.parse(raw);
2378
+ const parsedTasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
2379
+ this.tasks = new Map(parsedTasks.filter((task) => !!task?.id).map((task) => [task.id, task]));
2380
+ this.createdAt = typeof parsed.createdAt === "number" ? parsed.createdAt : now();
2381
+ this.updatedAt = typeof parsed.updatedAt === "number" ? parsed.updatedAt : now();
2382
+ } catch (error) {
2383
+ if (error?.code !== "ENOENT") {
2384
+ throw error;
2385
+ }
2386
+ const timestamp = now();
2387
+ this.tasks = /* @__PURE__ */ new Map();
2388
+ this.createdAt = timestamp;
2389
+ this.updatedAt = timestamp;
2390
+ await this.persist();
2391
+ }
2392
+ this.initialized = true;
2393
+ }
2394
+ async persist() {
2395
+ const payload = {
2396
+ taskListId: this.taskListId,
2397
+ createdAt: this.createdAt,
2398
+ updatedAt: this.updatedAt,
2399
+ tasks: Array.from(this.tasks.values()).sort((a, b) => a.createdAt - b.createdAt)
2400
+ };
2401
+ await import_fs10.promises.writeFile(this.storagePath, JSON.stringify(payload, null, 2), "utf-8");
2402
+ }
2403
+ };
2404
+ function buildTaskCreateTool(service) {
2405
+ return {
2406
+ name: "task_create",
2407
+ description: "Create one or more tracked tasks for complex work. Use this when work has multiple steps, dependencies, or blockers.",
2408
+ inputSchema: TASK_CREATE_INPUT_SCHEMA,
2409
+ execute: async ({ tasks, title, details, status, dependencies, blockedReason, metadata }) => {
2410
+ const items = tasks?.length ? tasks : [{ title, details, status, dependencies, blockedReason, metadata }];
2411
+ const created = await service.createTasks(items);
2412
+ return {
2413
+ taskListId: service.taskListId,
2414
+ storagePath: service.storagePath,
2415
+ createdCount: created.length,
2416
+ tasks: created
2417
+ };
2418
+ }
2419
+ };
2420
+ }
2421
+ function buildTaskGetTool(service) {
2422
+ return {
2423
+ name: "task_get",
2424
+ description: "Get full details for a single task by task ID.",
2425
+ inputSchema: TASK_GET_INPUT_SCHEMA,
2426
+ execute: async ({ id }) => {
2427
+ const task = await service.getTask(id);
2428
+ if (!task) {
2429
+ throw new Error(`Task not found: ${id}`);
2430
+ }
2431
+ return {
2432
+ taskListId: service.taskListId,
2433
+ storagePath: service.storagePath,
2434
+ task
2435
+ };
2436
+ }
2437
+ };
2438
+ }
2439
+ function buildTaskListTool(service) {
2440
+ return {
2441
+ name: "task_list",
2442
+ description: "List tasks and their current status. Use this to check progress before and after major changes.",
2443
+ inputSchema: TASK_LIST_INPUT_SCHEMA,
2444
+ execute: async ({ statuses, includeCompleted, limit }) => {
2445
+ const snapshot = await service.snapshot({ statuses, includeCompleted, limit });
2446
+ const completed = snapshot.tasks.filter((task) => task.status === "completed").length;
2447
+ const inProgress = snapshot.tasks.filter((task) => task.status === "in_progress").length;
2448
+ const pending = snapshot.tasks.filter((task) => task.status === "pending").length;
2449
+ const blocked = snapshot.tasks.filter((task) => task.status === "blocked").length;
2450
+ return {
2451
+ ...snapshot,
2452
+ summary: {
2453
+ total: snapshot.total,
2454
+ completed,
2455
+ inProgress,
2456
+ pending,
2457
+ blocked
2458
+ }
2459
+ };
2460
+ }
2461
+ };
2462
+ }
2463
+ function buildTaskUpdateTool(service) {
2464
+ return {
2465
+ name: "task_update",
2466
+ description: "Update task fields, move status (pending/in_progress/completed/blocked), or delete tasks.",
2467
+ inputSchema: TASK_UPDATE_INPUT_SCHEMA,
2468
+ execute: async (input) => {
2469
+ const task = await service.updateTask(input);
2470
+ if (input.delete) {
2471
+ return {
2472
+ taskListId: service.taskListId,
2473
+ storagePath: service.storagePath,
2474
+ deleted: true,
2475
+ id: input.id
2476
+ };
2477
+ }
2478
+ if (!task) {
2479
+ throw new Error(`Task not found: ${input.id}`);
2480
+ }
2481
+ return {
2482
+ taskListId: service.taskListId,
2483
+ storagePath: service.storagePath,
2484
+ deleted: false,
2485
+ task
2486
+ };
2487
+ }
2488
+ };
2489
+ }
2490
+ var builtInTaskTrackingPlugin = {
2491
+ name: "pulse-coder-engine/built-in-task-tracking",
2492
+ version: "1.0.0",
2493
+ dependencies: ["pulse-coder-engine/built-in-skills"],
2494
+ async initialize(context) {
2495
+ const service = new TaskListService();
2496
+ await service.initialize();
2497
+ context.registerService("taskListService", service);
2498
+ context.registerService("taskTracking", service);
2499
+ const skillRegistry = context.getService("skillRegistry");
2500
+ if (skillRegistry) {
2501
+ const registration = skillRegistry.registerSkill(TASK_TRACKING_SKILL);
2502
+ context.logger.info("[TaskTracking] Registered built-in task tracking skill", registration);
2503
+ } else {
2504
+ context.logger.warn("[TaskTracking] skillRegistry service unavailable; skipped task tracking skill registration");
2505
+ }
2506
+ context.registerTools({
2507
+ task_create: buildTaskCreateTool(service),
2508
+ task_get: buildTaskGetTool(service),
2509
+ task_list: buildTaskListTool(service),
2510
+ task_update: buildTaskUpdateTool(service)
2511
+ });
2512
+ context.logger.info("[TaskTracking] Registered task tools", {
2513
+ taskListId: service.taskListId,
2514
+ storagePath: service.storagePath,
2515
+ skillRegistryAvailable: !!skillRegistry
2516
+ });
2517
+ }
2518
+ };
2519
+
2520
+ // src/built-in/sub-agent-plugin/index.ts
2521
+ var import_zod12 = require("zod");
2522
+ var import_fs11 = require("fs");
2523
+ var import_path5 = __toESM(require("path"), 1);
2070
2524
  var ConfigLoader = class {
2071
2525
  async getAgentFilesInfo(configDirs) {
2072
2526
  const fileInfos = [];
2073
2527
  for (let configDir of configDirs) {
2074
2528
  try {
2075
- await import_fs10.promises.access(configDir);
2076
- const files = await import_fs10.promises.readdir(configDir);
2529
+ await import_fs11.promises.access(configDir);
2530
+ const files = await import_fs11.promises.readdir(configDir);
2077
2531
  fileInfos.push({ files, configDir });
2078
2532
  } catch {
2079
2533
  continue;
@@ -2090,7 +2544,7 @@ var ConfigLoader = class {
2090
2544
  const files = fileInfo.files;
2091
2545
  for (const file of files) {
2092
2546
  if (file.endsWith(".md")) {
2093
- const config = await this.parseConfig(import_path4.default.join(fileInfo.configDir, file));
2547
+ const config = await this.parseConfig(import_path5.default.join(fileInfo.configDir, file));
2094
2548
  if (config) configs.push(config);
2095
2549
  }
2096
2550
  }
@@ -2102,7 +2556,7 @@ var ConfigLoader = class {
2102
2556
  }
2103
2557
  async parseConfig(filePath) {
2104
2558
  try {
2105
- const content = await import_fs10.promises.readFile(filePath, "utf-8");
2559
+ const content = await import_fs11.promises.readFile(filePath, "utf-8");
2106
2560
  const lines = content.split("\n");
2107
2561
  let name = "";
2108
2562
  let description = "";
@@ -2131,7 +2585,7 @@ var ConfigLoader = class {
2131
2585
  }
2132
2586
  }
2133
2587
  if (!name) {
2134
- name = import_path4.default.basename(filePath, ".md");
2588
+ name = import_path5.default.basename(filePath, ".md");
2135
2589
  }
2136
2590
  return {
2137
2591
  name: name.trim(),
@@ -2182,9 +2636,9 @@ var SubAgentPlugin = class {
2182
2636
  const toolName = `${config.name}_agent`;
2183
2637
  const tool2 = {
2184
2638
  description: config.description,
2185
- inputSchema: import_zod11.z.object({
2186
- task: import_zod11.z.string().describe("\u8981\u6267\u884C\u7684\u4EFB\u52A1\u63CF\u8FF0"),
2187
- context: import_zod11.z.any().optional().describe("\u4EFB\u52A1\u4E0A\u4E0B\u6587\u4FE1\u606F")
2639
+ inputSchema: import_zod12.z.object({
2640
+ task: import_zod12.z.string().describe("\u8981\u6267\u884C\u7684\u4EFB\u52A1\u63CF\u8FF0"),
2641
+ context: import_zod12.z.any().optional().describe("\u4EFB\u52A1\u4E0A\u4E0B\u6587\u4FE1\u606F")
2188
2642
  }),
2189
2643
  execute: async ({ task, context: taskContext }) => {
2190
2644
  const tools = { ...BuiltinToolsMap, ...context.getTools() };
@@ -2211,6 +2665,7 @@ var builtInPlugins = [
2211
2665
  builtInMCPPlugin,
2212
2666
  builtInSkillsPlugin,
2213
2667
  builtInPlanModePlugin,
2668
+ builtInTaskTrackingPlugin,
2214
2669
  new SubAgentPlugin()
2215
2670
  ];
2216
2671
 
@@ -2264,52 +2719,70 @@ var Engine = class {
2264
2719
  // 默认启用扫描
2265
2720
  };
2266
2721
  }
2267
- async applyRunHooks(context, systemPrompt, hooks) {
2268
- let nextSystemPrompt = systemPrompt;
2269
- let nextHooks = hooks;
2270
- for (const runHook of this.pluginManager.getRunHooks()) {
2271
- const result = await runHook({
2272
- context,
2273
- messages: context.messages,
2274
- tools: this.tools,
2275
- systemPrompt: nextSystemPrompt,
2276
- hooks: nextHooks
2722
+ /**
2723
+ * Collect all hooks for a given loop invocation.
2724
+ * Merges plugin hooks with the legacy EngineOptions.hooks (ToolHooks).
2725
+ */
2726
+ collectLoopHooks() {
2727
+ const loopHooks = {
2728
+ beforeLLMCall: this.pluginManager.getHooks("beforeLLMCall"),
2729
+ afterLLMCall: this.pluginManager.getHooks("afterLLMCall"),
2730
+ beforeToolCall: [...this.pluginManager.getHooks("beforeToolCall")],
2731
+ afterToolCall: [...this.pluginManager.getHooks("afterToolCall")]
2732
+ };
2733
+ const legacyHooks = this.options.hooks;
2734
+ if (legacyHooks?.onBeforeToolCall) {
2735
+ const legacyBefore = legacyHooks.onBeforeToolCall;
2736
+ loopHooks.beforeToolCall.push(async ({ name, input }) => {
2737
+ const modified = await legacyBefore(name, input);
2738
+ return modified !== void 0 ? { input: modified } : void 0;
2277
2739
  });
2278
- if (!result) {
2279
- continue;
2280
- }
2281
- if ("systemPrompt" in result) {
2282
- nextSystemPrompt = result.systemPrompt;
2283
- }
2284
- if ("hooks" in result) {
2285
- nextHooks = result.hooks;
2286
- }
2287
2740
  }
2288
- return {
2289
- systemPrompt: nextSystemPrompt,
2290
- hooks: nextHooks
2291
- };
2741
+ if (legacyHooks?.onAfterToolCall) {
2742
+ const legacyAfter = legacyHooks.onAfterToolCall;
2743
+ loopHooks.afterToolCall.push(async ({ name, input, output }) => {
2744
+ const modified = await legacyAfter(name, input, output);
2745
+ return modified !== void 0 ? { output: modified } : void 0;
2746
+ });
2747
+ }
2748
+ return loopHooks;
2292
2749
  }
2293
2750
  /**
2294
2751
  * 运行AI循环
2295
2752
  */
2296
2753
  async run(context, options) {
2297
- const baseSystemPrompt = options?.systemPrompt ?? this.options.systemPrompt;
2298
- const baseHooks = options?.hooks ?? this.options.hooks;
2299
- const { systemPrompt, hooks } = await this.applyRunHooks(context, baseSystemPrompt, baseHooks);
2300
- return loop(context, {
2754
+ let systemPrompt = options?.systemPrompt ?? this.options.systemPrompt;
2755
+ let tools = { ...this.tools };
2756
+ const beforeRunHooks = this.pluginManager.getHooks("beforeRun");
2757
+ for (const hook of beforeRunHooks) {
2758
+ const result = await hook({ context, systemPrompt, tools });
2759
+ if (result) {
2760
+ if ("systemPrompt" in result && result.systemPrompt !== void 0) {
2761
+ systemPrompt = result.systemPrompt;
2762
+ }
2763
+ if ("tools" in result && result.tools !== void 0) {
2764
+ tools = result.tools;
2765
+ }
2766
+ }
2767
+ }
2768
+ const loopHooks = this.collectLoopHooks();
2769
+ const resultText = await loop(context, {
2301
2770
  ...options,
2302
- tools: this.tools,
2303
- // Engine 级别选项作为默认值;调用方通过 options 传入可在单次调用中覆盖
2771
+ tools,
2304
2772
  provider: options?.provider ?? this.options.llmProvider,
2305
2773
  model: options?.model ?? this.options.model,
2306
2774
  systemPrompt,
2307
- hooks,
2775
+ hooks: loopHooks,
2308
2776
  onToolCall: (toolCall) => {
2309
2777
  options?.onToolCall?.(toolCall);
2310
2778
  },
2311
2779
  onClarificationRequest: options?.onClarificationRequest
2312
2780
  });
2781
+ const afterRunHooks = this.pluginManager.getHooks("afterRun");
2782
+ for (const hook of afterRunHooks) {
2783
+ await hook({ context, result: resultText });
2784
+ }
2785
+ return resultText;
2313
2786
  }
2314
2787
  /**
2315
2788
  * 获取插件状态
@@ -2380,12 +2853,14 @@ var Engine = class {
2380
2853
  PluginManager,
2381
2854
  PulseAgent,
2382
2855
  ReadTool,
2856
+ TaskListService,
2383
2857
  TavilyTool,
2384
2858
  WriteTool,
2385
2859
  builtInMCPPlugin,
2386
2860
  builtInPlanModePlugin,
2387
2861
  builtInPlugins,
2388
2862
  builtInSkillsPlugin,
2863
+ builtInTaskTrackingPlugin,
2389
2864
  getFinalToolsMap,
2390
2865
  loop,
2391
2866
  maybeCompactContext,