@cydm/pie 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +302 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -69314,6 +69314,75 @@ function findFirstChangedLine(oldText, newText) {
69314
69314
  return void 0;
69315
69315
  }
69316
69316
 
69317
+ // src/todo/render.ts
69318
+ var TODO_STATUS_ICONS = {
69319
+ completed: "\u2713",
69320
+ "in-progress": "\u25C9",
69321
+ "not-started": "\u25CB"
69322
+ };
69323
+ function formatTodoTitle(todo, activeTheme) {
69324
+ if (todo.status === "completed") {
69325
+ return activeTheme.fg("dim", activeTheme.strikethrough(todo.title));
69326
+ }
69327
+ if (todo.status === "in-progress") {
69328
+ return activeTheme.fg("warning", todo.title);
69329
+ }
69330
+ return activeTheme.fg("muted", todo.title);
69331
+ }
69332
+ function formatTodoIcon(todo, activeTheme) {
69333
+ const icon = TODO_STATUS_ICONS[todo.status] ?? "?";
69334
+ if (todo.status === "completed") {
69335
+ return activeTheme.fg("success", icon);
69336
+ }
69337
+ if (todo.status === "in-progress") {
69338
+ return activeTheme.fg("warning", icon);
69339
+ }
69340
+ return activeTheme.fg("muted", icon);
69341
+ }
69342
+ function renderManageTodoListCall(args, activeTheme = theme) {
69343
+ let text = activeTheme.fg("toolTitle", activeTheme.bold("manage_todo_list "));
69344
+ text += activeTheme.fg("muted", args.operation || "...");
69345
+ if (args.operation === "write" && Array.isArray(args.todoList)) {
69346
+ const count = args.todoList.length;
69347
+ text += activeTheme.fg("dim", ` (${count} item${count === 1 ? "" : "s"})`);
69348
+ }
69349
+ return text;
69350
+ }
69351
+ function renderManageTodoListResult(result, options, activeTheme = theme) {
69352
+ const details = result.details;
69353
+ if (!details) {
69354
+ const firstText = result.content?.find((item) => item.type === "text" && typeof item.text === "string")?.text;
69355
+ return firstText || "";
69356
+ }
69357
+ if (details.error) {
69358
+ return activeTheme.fg("error", `\u2717 ${details.error}`);
69359
+ }
69360
+ const todos = details.todos || [];
69361
+ if (todos.length === 0) {
69362
+ return activeTheme.fg("dim", "No todos");
69363
+ }
69364
+ const completed = todos.filter((todo) => todo.status === "completed").length;
69365
+ let text = `${activeTheme.fg("success", "\u2713")} ${activeTheme.fg("muted", `${completed}/${todos.length} completed`)}`;
69366
+ if (options.expanded) {
69367
+ for (const todo of todos) {
69368
+ text += `
69369
+ ${formatTodoIcon(todo, activeTheme)} ${activeTheme.fg("accent", `${todo.id}.`)} ${formatTodoTitle(todo, activeTheme)}`;
69370
+ }
69371
+ }
69372
+ return text;
69373
+ }
69374
+ function renderTodoWidgetLines(todos, activeTheme = theme) {
69375
+ if (todos.length === 0) {
69376
+ return [];
69377
+ }
69378
+ const completed = todos.filter((todo) => todo.status === "completed").length;
69379
+ const lines = [activeTheme.fg("muted", `Todo List - ${completed}/${todos.length} completed`)];
69380
+ for (const todo of todos) {
69381
+ lines.push(` ${formatTodoIcon(todo, activeTheme)} ${activeTheme.fg("accent", `${todo.id}.`)} ${formatTodoTitle(todo, activeTheme)}`);
69382
+ }
69383
+ return lines;
69384
+ }
69385
+
69317
69386
  // src/components/tool-execution.ts
69318
69387
  function formatSize3(bytes) {
69319
69388
  if (bytes < 1024) return `${bytes}B`;
@@ -69396,7 +69465,13 @@ var ToolExecutionComponent = class extends Container {
69396
69465
  formatToolExecution() {
69397
69466
  let text = "";
69398
69467
  const invalidArg = theme.fg("error", "[invalid arg]");
69399
- if (this.toolName === "read") {
69468
+ if (this.toolName === "manage_todo_list") {
69469
+ text = renderManageTodoListCall(this.args, theme);
69470
+ if (this.result) {
69471
+ text += `
69472
+ ${renderManageTodoListResult(this.result, { expanded: this.expanded }, theme)}`;
69473
+ }
69474
+ } else if (this.toolName === "read") {
69400
69475
  const rawPath = str(this.args?.file_path ?? this.args?.path);
69401
69476
  const path18 = rawPath !== null ? shortenPath(rawPath) : null;
69402
69477
  const offset = this.args?.offset;
@@ -69732,6 +69807,27 @@ ${theme.fg("error", "Error: " + errorOutput)}`;
69732
69807
  }
69733
69808
  };
69734
69809
 
69810
+ // src/components/todo-widget.ts
69811
+ var TodoWidgetComponent = class extends Container {
69812
+ state;
69813
+ constructor(state) {
69814
+ super();
69815
+ this.state = state;
69816
+ this.refresh();
69817
+ }
69818
+ refresh() {
69819
+ this.clear();
69820
+ const lines = renderTodoWidgetLines(this.state.read());
69821
+ if (lines.length === 0) {
69822
+ return;
69823
+ }
69824
+ this.addChild(new Spacer(1));
69825
+ for (const line of lines) {
69826
+ this.addChild(new Text(line, 1, 0));
69827
+ }
69828
+ }
69829
+ };
69830
+
69735
69831
  // src/components/tree-selector.ts
69736
69832
  var RESET = "\x1B[0m";
69737
69833
  var INVERSE = "\x1B[7m";
@@ -74718,6 +74814,8 @@ var InteractiveMode = class {
74718
74814
  footerData;
74719
74815
  editor;
74720
74816
  editorContainer;
74817
+ todoWidgetContainer;
74818
+ todoWidget;
74721
74819
  keybindings;
74722
74820
  sessionManager;
74723
74821
  skills;
@@ -74827,6 +74925,11 @@ var InteractiveMode = class {
74827
74925
  this.skillsSection = formatSkillsForPrompt(options.skills);
74828
74926
  }
74829
74927
  options;
74928
+ refreshTodoWidget() {
74929
+ this.todoWidget.refresh();
74930
+ this.todoWidgetContainer.invalidate?.();
74931
+ this.safeRequestRender();
74932
+ }
74830
74933
  async init() {
74831
74934
  const savedLevel = this.options.settingsManager.getDefaultThinkingLevel();
74832
74935
  if (savedLevel) {
@@ -74847,6 +74950,8 @@ var InteractiveMode = class {
74847
74950
  });
74848
74951
  this.updateTerminalTitle();
74849
74952
  this.setupUI();
74953
+ this.options.todoState.restoreFromMessages(this.sessionManager.getMessages());
74954
+ this.refreshTodoWidget();
74850
74955
  this.setupAutocomplete();
74851
74956
  this.setupAgentSubscription();
74852
74957
  this.setupEditorSubmitHandler();
@@ -75744,6 +75849,9 @@ ${newToolsSection}`
75744
75849
  contextWindow: this.options.model?.contextWindow ?? 0
75745
75850
  });
75746
75851
  this.editorContainer = new Container();
75852
+ this.todoWidgetContainer = new Container();
75853
+ this.todoWidget = new TodoWidgetComponent(this.options.todoState);
75854
+ this.todoWidgetContainer.addChild(this.todoWidget);
75747
75855
  const editorTheme = getEditorTheme();
75748
75856
  this.editor = new Editor(this.ui, editorTheme, { paddingX: 1 });
75749
75857
  this.editor.onChange = () => {
@@ -75757,6 +75865,7 @@ ${newToolsSection}`
75757
75865
  this.editorContainer.addChild(this.editor);
75758
75866
  this.ui.addChild(this.chatContainer);
75759
75867
  this.ui.addChild(this.pendingMessagesContainer);
75868
+ this.ui.addChild(this.todoWidgetContainer);
75760
75869
  this.ui.addChild(new Spacer());
75761
75870
  this.ui.addChild(this.footer);
75762
75871
  this.ui.addChild(this.editorContainer);
@@ -75996,7 +76105,18 @@ ${newToolsSection}`
75996
76105
  thinkingContent += content2.thinking || "";
75997
76106
  } else if (content2.type === "toolCall") {
75998
76107
  const toolCall = content2;
75999
- const toolId = toolCall.id || toolCall.name;
76108
+ const contentIndex = message.content.indexOf(content2);
76109
+ const partialToolId = `__partial_tool__:${contentIndex}`;
76110
+ const toolId = toolCall.id || partialToolId;
76111
+ if (toolCall.id && !this.pendingTools.has(toolCall.id) && this.pendingTools.has(partialToolId)) {
76112
+ const existingComponent = this.pendingTools.get(partialToolId);
76113
+ if (existingComponent) {
76114
+ this.pendingTools.delete(partialToolId);
76115
+ this.pendingTools.set(toolCall.id, existingComponent);
76116
+ existingComponent.updateArgs(toolCall.arguments);
76117
+ }
76118
+ continue;
76119
+ }
76000
76120
  if (!this.pendingTools.has(toolId)) {
76001
76121
  const toolComponent = new ToolExecutionComponent(
76002
76122
  toolCall.name,
@@ -76084,6 +76204,9 @@ ${newToolsSection}`
76084
76204
  );
76085
76205
  }
76086
76206
  }
76207
+ if (event.toolName === "manage_todo_list") {
76208
+ this.refreshTodoWidget();
76209
+ }
76087
76210
  this.footer.setState({ lifecycleEvent: "tool_execution_end" });
76088
76211
  this.emitExtensionEvent({
76089
76212
  type: "tool:execution:end",
@@ -77427,11 +77550,13 @@ ${newToolsSection}`
77427
77550
  this.updateTerminalTitle();
77428
77551
  const messages = this.sessionManager.getMessages();
77429
77552
  const conversationMessages = messages.filter(
77430
- (msg) => msg.role === "user" || msg.role === "assistant"
77553
+ (msg) => msg.role === "user" || msg.role === "assistant" || msg.role === "toolResult"
77431
77554
  );
77432
77555
  this.agent.resetProcessingState();
77433
77556
  this.agent.replaceMessages(conversationMessages);
77434
77557
  this.lastSyncedMessageCount = this.agent.state.messages.length;
77558
+ this.options.todoState.restoreFromMessages(messages);
77559
+ this.refreshTodoWidget();
77435
77560
  this.chatContainer.clear();
77436
77561
  this.lastStatusSpacer = void 0;
77437
77562
  this.lastStatusText = void 0;
@@ -78739,6 +78864,177 @@ var ModelRegistry = class {
78739
78864
  }
78740
78865
  };
78741
78866
 
78867
+ // src/todo/state-manager.ts
78868
+ var TodoStateManager = class {
78869
+ todos = [];
78870
+ read() {
78871
+ return this.todos.map((todo) => ({ ...todo }));
78872
+ }
78873
+ write(todos) {
78874
+ this.todos = todos.map((todo) => ({ ...todo }));
78875
+ }
78876
+ clear() {
78877
+ this.todos = [];
78878
+ }
78879
+ getStats() {
78880
+ const total = this.todos.length;
78881
+ const completed = this.todos.filter((todo) => todo.status === "completed").length;
78882
+ const inProgress = this.todos.filter((todo) => todo.status === "in-progress").length;
78883
+ return {
78884
+ total,
78885
+ completed,
78886
+ inProgress,
78887
+ notStarted: total - completed - inProgress
78888
+ };
78889
+ }
78890
+ validate(todos) {
78891
+ const errors = [];
78892
+ if (!Array.isArray(todos)) {
78893
+ return { valid: false, errors: ["todoList must be an array"] };
78894
+ }
78895
+ const validStatuses = /* @__PURE__ */ new Set(["not-started", "in-progress", "completed"]);
78896
+ for (let index = 0; index < todos.length; index++) {
78897
+ const item = todos[index];
78898
+ const prefix = `Item ${index + 1}`;
78899
+ if (typeof item?.id !== "number" || !Number.isInteger(item.id) || item.id < 1) {
78900
+ errors.push(`${prefix}: 'id' must be a positive integer`);
78901
+ }
78902
+ if (typeof item?.title !== "string" || item.title.trim().length === 0) {
78903
+ errors.push(`${prefix}: 'title' is required`);
78904
+ }
78905
+ if (typeof item?.description !== "string") {
78906
+ errors.push(`${prefix}: 'description' must be a string`);
78907
+ }
78908
+ if (!validStatuses.has(item?.status)) {
78909
+ errors.push(`${prefix}: 'status' must be one of: not-started, in-progress, completed`);
78910
+ }
78911
+ if (item?.id !== index + 1) {
78912
+ errors.push(`${prefix}: ids must be sequential starting from 1`);
78913
+ }
78914
+ }
78915
+ return { valid: errors.length === 0, errors };
78916
+ }
78917
+ restoreFromMessages(messages) {
78918
+ this.todos = [];
78919
+ for (const message of messages) {
78920
+ if (message.role !== "toolResult" || message.toolName !== "manage_todo_list") {
78921
+ continue;
78922
+ }
78923
+ const details = message.details;
78924
+ if (details?.todos) {
78925
+ this.todos = details.todos.map((todo) => ({ ...todo }));
78926
+ }
78927
+ }
78928
+ }
78929
+ };
78930
+
78931
+ // src/todo/tool.ts
78932
+ init_esm();
78933
+ function stringEnum(values, description) {
78934
+ return Type.Union(values.map((value) => Type.Literal(value)), { description });
78935
+ }
78936
+ var TodoItemSchema = Type.Object({
78937
+ id: Type.Number({ description: "Unique identifier for the todo. Use sequential numbers starting from 1." }),
78938
+ title: Type.String({ description: "Concise action-oriented todo label (3-7 words). Displayed in the UI." }),
78939
+ description: Type.String({
78940
+ description: "Detailed context, requirements, or implementation notes for the todo item."
78941
+ }),
78942
+ status: stringEnum(
78943
+ ["not-started", "in-progress", "completed"],
78944
+ "not-started: Not begun | in-progress: Currently working | completed: Fully finished with no blockers"
78945
+ )
78946
+ });
78947
+ var ManageTodoListParams = Type.Object({
78948
+ operation: stringEnum(
78949
+ ["write", "read"],
78950
+ "write: Replace entire todo list with new content. read: Retrieve current todo list."
78951
+ ),
78952
+ todoList: Type.Optional(
78953
+ Type.Array(TodoItemSchema, {
78954
+ description: "Complete array of all todo items. Required for write. Must include ALL items."
78955
+ })
78956
+ )
78957
+ });
78958
+ var MANAGE_TODO_LIST_DESCRIPTION = `Manage a structured todo list to track progress and plan tasks throughout your coding session.
78959
+
78960
+ Use this tool for complex multi-step work, especially when:
78961
+ - The task needs planning or progress tracking
78962
+ - The user provided multiple requests
78963
+ - You need to break a larger change into actionable steps
78964
+ - You are about to start or finish a tracked step
78965
+
78966
+ Workflow:
78967
+ 1. Write the full todo list with clear action items
78968
+ 2. Mark the current item as in-progress before starting it
78969
+ 3. Complete the work for that item
78970
+ 4. Mark it completed immediately
78971
+ 5. Continue until all items are done
78972
+
78973
+ Always send the full list on write. Partial updates are not supported.`;
78974
+ function successMessage(todos) {
78975
+ const total = todos.length;
78976
+ const completed = todos.filter((todo) => todo.status === "completed").length;
78977
+ let message = `Todos have been modified successfully. ${completed}/${total} completed. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable.`;
78978
+ if (total < 3) {
78979
+ message += "\n\nWarning: Small todo list (<3 items). This task might not need a todo list.";
78980
+ }
78981
+ return message;
78982
+ }
78983
+ function createManageTodoListTool(state) {
78984
+ return {
78985
+ name: "manage_todo_list",
78986
+ label: "manage_todo_list",
78987
+ description: MANAGE_TODO_LIST_DESCRIPTION,
78988
+ parameters: ManageTodoListParams,
78989
+ execute: async (_toolCallId, params) => {
78990
+ if (params.operation === "read") {
78991
+ const todos2 = state.read();
78992
+ return {
78993
+ content: [
78994
+ {
78995
+ type: "text",
78996
+ text: todos2.length ? JSON.stringify(todos2, null, 2) : "No todos. Use write operation to create a todo list."
78997
+ }
78998
+ ],
78999
+ details: { operation: "read", todos: todos2 }
79000
+ };
79001
+ }
79002
+ if (!params.todoList || !Array.isArray(params.todoList)) {
79003
+ return {
79004
+ content: [{ type: "text", text: "Error: todoList is required for write operation." }],
79005
+ details: { operation: "write", todos: state.read(), error: "todoList required" },
79006
+ isError: true
79007
+ };
79008
+ }
79009
+ const todoList = params.todoList;
79010
+ const validation = state.validate(todoList);
79011
+ if (!validation.valid) {
79012
+ return {
79013
+ content: [
79014
+ {
79015
+ type: "text",
79016
+ text: `Validation failed:
79017
+ ${validation.errors.map((error) => ` - ${error}`).join("\n")}`
79018
+ }
79019
+ ],
79020
+ details: {
79021
+ operation: "write",
79022
+ todos: state.read(),
79023
+ error: validation.errors.join("; ")
79024
+ },
79025
+ isError: true
79026
+ };
79027
+ }
79028
+ state.write(todoList);
79029
+ const todos = state.read();
79030
+ return {
79031
+ content: [{ type: "text", text: successMessage(todos) }],
79032
+ details: { operation: "write", todos }
79033
+ };
79034
+ }
79035
+ };
79036
+ }
79037
+
78742
79038
  // src/cli.ts
78743
79039
  enableEarlyLogBuffer();
78744
79040
  var logError = (msg, err) => {
@@ -78830,7 +79126,8 @@ async function startChat(initialPrompt, testCommand) {
78830
79126
  settingsManager.setDefaultModelAndProvider(provider, modelId);
78831
79127
  const cwd = process.cwd();
78832
79128
  const fsOptions = { allowlistedDirs: [CONFIG_DIR] };
78833
- const tools = [...createFileSystemTools(cwd, fsOptions), createBashTool(cwd)];
79129
+ const todoState = new TodoStateManager();
79130
+ const tools = [...createFileSystemTools(cwd, fsOptions), createBashTool(cwd), createManageTodoListTool(todoState)];
78834
79131
  const sessionManager = createSessionManager({ sessionsDir: SESSIONS_DIR });
78835
79132
  if (explicitSessionId) {
78836
79133
  const loaded = await sessionManager.loadSession(explicitSessionId);
@@ -78948,6 +79245,7 @@ Current working directory: ${cwd}${skillsSection}`;
78948
79245
  model,
78949
79246
  apiKey,
78950
79247
  tools,
79248
+ todoState,
78951
79249
  sessionManager,
78952
79250
  skills,
78953
79251
  cwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {