@code-yeongyu/senpi 0.74.0 → 2026.5.13

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 (112) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +4 -0
  3. package/dist/core/agent-session-services.d.ts.map +1 -1
  4. package/dist/core/agent-session-services.js +3 -1
  5. package/dist/core/agent-session-services.js.map +1 -1
  6. package/dist/core/extensions/builtin/anthropic-web-search/index.d.ts.map +1 -1
  7. package/dist/core/extensions/builtin/anthropic-web-search/index.js +25 -14
  8. package/dist/core/extensions/builtin/anthropic-web-search/index.js.map +1 -1
  9. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.d.ts.map +1 -1
  10. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js +56 -7
  11. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js.map +1 -1
  12. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts +7 -3
  13. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
  14. package/dist/core/extensions/builtin/prompt-preset/presets.js +12 -4
  15. package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
  16. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -0
  17. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
  18. package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -1
  19. package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
  20. package/dist/core/extensions/builtin/todotools/continuation/runtime.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/todotools/continuation/runtime.js +7 -10
  22. package/dist/core/extensions/builtin/todotools/continuation/runtime.js.map +1 -1
  23. package/dist/core/extensions/builtin/todotools/index.d.ts +2 -2
  24. package/dist/core/extensions/builtin/todotools/index.d.ts.map +1 -1
  25. package/dist/core/extensions/builtin/todotools/index.js +2 -2
  26. package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
  27. package/dist/core/extensions/builtin/todotools/settings.d.ts +6 -0
  28. package/dist/core/extensions/builtin/todotools/settings.d.ts.map +1 -0
  29. package/dist/core/extensions/builtin/todotools/settings.js +58 -0
  30. package/dist/core/extensions/builtin/todotools/settings.js.map +1 -0
  31. package/dist/core/extensions/builtin/todotools/system-messages.d.ts +34 -0
  32. package/dist/core/extensions/builtin/todotools/system-messages.d.ts.map +1 -0
  33. package/dist/core/extensions/builtin/todotools/system-messages.js +82 -0
  34. package/dist/core/extensions/builtin/todotools/system-messages.js.map +1 -0
  35. package/dist/core/extensions/builtin/todotools/tools/todoread.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/todotools/tools/todoread.js +3 -1
  37. package/dist/core/extensions/builtin/todotools/tools/todoread.js.map +1 -1
  38. package/dist/core/extensions/builtin/todotools/tools/todowrite.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/todotools/tools/todowrite.js +3 -1
  40. package/dist/core/extensions/builtin/todotools/tools/todowrite.js.map +1 -1
  41. package/dist/core/keybindings.d.ts +5 -0
  42. package/dist/core/keybindings.d.ts.map +1 -1
  43. package/dist/core/keybindings.js +10 -3
  44. package/dist/core/keybindings.js.map +1 -1
  45. package/dist/core/model-registry.d.ts.map +1 -1
  46. package/dist/core/model-registry.js +8 -3
  47. package/dist/core/model-registry.js.map +1 -1
  48. package/dist/core/resource-loader.d.ts +8 -0
  49. package/dist/core/resource-loader.d.ts.map +1 -1
  50. package/dist/core/resource-loader.js +168 -17
  51. package/dist/core/resource-loader.js.map +1 -1
  52. package/dist/core/tools/bash.d.ts.map +1 -1
  53. package/dist/core/tools/bash.js +11 -4
  54. package/dist/core/tools/bash.js.map +1 -1
  55. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  56. package/dist/modes/interactive/components/bash-execution.js +11 -3
  57. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  58. package/dist/modes/interactive/components/favorite-models-selector.d.ts +10 -4
  59. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  60. package/dist/modes/interactive/components/favorite-models-selector.js +56 -78
  61. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  62. package/dist/modes/interactive/components/model-favorites.d.ts +10 -0
  63. package/dist/modes/interactive/components/model-favorites.d.ts.map +1 -0
  64. package/dist/modes/interactive/components/model-favorites.js +53 -0
  65. package/dist/modes/interactive/components/model-favorites.js.map +1 -0
  66. package/dist/modes/interactive/components/model-selector.d.ts +9 -1
  67. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  68. package/dist/modes/interactive/components/model-selector.js +52 -10
  69. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  70. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  71. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-mode.js +83 -94
  73. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  74. package/docs/models.md +26 -1
  75. package/docs/settings.md +14 -0
  76. package/package.json +5 -8
  77. package/dist/core/extensions/builtin/agent-system/agent-types.d.ts +0 -25
  78. package/dist/core/extensions/builtin/agent-system/agent-types.d.ts.map +0 -1
  79. package/dist/core/extensions/builtin/agent-system/agent-types.js +0 -38
  80. package/dist/core/extensions/builtin/agent-system/agent-types.js.map +0 -1
  81. package/dist/core/extensions/builtin/agent-system/builtin-agents.d.ts +0 -3
  82. package/dist/core/extensions/builtin/agent-system/builtin-agents.d.ts.map +0 -1
  83. package/dist/core/extensions/builtin/agent-system/builtin-agents.js +0 -32
  84. package/dist/core/extensions/builtin/agent-system/builtin-agents.js.map +0 -1
  85. package/dist/core/extensions/builtin/agent-system/index.d.ts +0 -3
  86. package/dist/core/extensions/builtin/agent-system/index.d.ts.map +0 -1
  87. package/dist/core/extensions/builtin/agent-system/index.js +0 -42
  88. package/dist/core/extensions/builtin/agent-system/index.js.map +0 -1
  89. package/dist/core/extensions/builtin/agent-system/loader.d.ts +0 -4
  90. package/dist/core/extensions/builtin/agent-system/loader.d.ts.map +0 -1
  91. package/dist/core/extensions/builtin/agent-system/loader.js +0 -59
  92. package/dist/core/extensions/builtin/agent-system/loader.js.map +0 -1
  93. package/dist/core/extensions/builtin/agent-system/permission.d.ts +0 -11
  94. package/dist/core/extensions/builtin/agent-system/permission.d.ts.map +0 -1
  95. package/dist/core/extensions/builtin/agent-system/permission.js +0 -24
  96. package/dist/core/extensions/builtin/agent-system/permission.js.map +0 -1
  97. package/dist/core/extensions/builtin/agent-system/registry.d.ts +0 -10
  98. package/dist/core/extensions/builtin/agent-system/registry.d.ts.map +0 -1
  99. package/dist/core/extensions/builtin/agent-system/registry.js +0 -50
  100. package/dist/core/extensions/builtin/agent-system/registry.js.map +0 -1
  101. package/dist/core/extensions/builtin/agent-system/types.d.ts +0 -9
  102. package/dist/core/extensions/builtin/agent-system/types.d.ts.map +0 -1
  103. package/dist/core/extensions/builtin/agent-system/types.js +0 -2
  104. package/dist/core/extensions/builtin/agent-system/types.js.map +0 -1
  105. package/dist/core/extensions/builtin/agent-system/wildcard.d.ts +0 -4
  106. package/dist/core/extensions/builtin/agent-system/wildcard.d.ts.map +0 -1
  107. package/dist/core/extensions/builtin/agent-system/wildcard.js +0 -58
  108. package/dist/core/extensions/builtin/agent-system/wildcard.js.map +0 -1
  109. package/dist/core/extensions/builtin/anthropic-web-fetch/index.d.ts +0 -7
  110. package/dist/core/extensions/builtin/anthropic-web-fetch/index.d.ts.map +0 -1
  111. package/dist/core/extensions/builtin/anthropic-web-fetch/index.js +0 -112
  112. package/dist/core/extensions/builtin/anthropic-web-fetch/index.js.map +0 -1
@@ -0,0 +1,82 @@
1
+ export const SANEPI_SYSTEM_PREFIX = "[system:sanepi]";
2
+ export const SANEPI_CONVERSATION_EVENT = "sanepi:conversation";
3
+ function prefixText(text) {
4
+ return text.startsWith(SANEPI_SYSTEM_PREFIX) ? text : `${SANEPI_SYSTEM_PREFIX}\n${text}`;
5
+ }
6
+ function prefixContent(content) {
7
+ if (typeof content === "string") {
8
+ return prefixText(content);
9
+ }
10
+ const firstTextIndex = content.findIndex((part) => part.type === "text");
11
+ if (firstTextIndex === -1) {
12
+ return [{ type: "text", text: SANEPI_SYSTEM_PREFIX }, ...content];
13
+ }
14
+ return content.map((part, index) => {
15
+ if (part.type !== "text" || index !== firstTextIndex) {
16
+ return part;
17
+ }
18
+ return {
19
+ ...part,
20
+ text: prefixText(part.text),
21
+ };
22
+ });
23
+ }
24
+ function extractText(content) {
25
+ if (typeof content === "string") {
26
+ return content;
27
+ }
28
+ return content
29
+ .filter((part) => part.type === "text")
30
+ .map((part) => part.text)
31
+ .join("\n");
32
+ }
33
+ function emitTodoConversationEvent(pi, event) {
34
+ pi.events.emit(SANEPI_CONVERSATION_EVENT, event);
35
+ }
36
+ function createBaseEvent(args) {
37
+ return {
38
+ version: 1,
39
+ source: "builtin",
40
+ action: args.action,
41
+ route: args.route,
42
+ sessionId: args.sessionId,
43
+ timestamp: Date.now(),
44
+ conversation: {
45
+ prefix: SANEPI_SYSTEM_PREFIX,
46
+ kind: "user_message",
47
+ deliverAs: args.deliverAs,
48
+ },
49
+ text: args.text,
50
+ errorMessage: args.errorMessage,
51
+ };
52
+ }
53
+ function hasUserMessageOptions(options) {
54
+ return options?.deliverAs !== undefined;
55
+ }
56
+ export function sendTodoUserMessage(pi, route, content, options) {
57
+ const prefixedContent = prefixContent(content);
58
+ emitTodoConversationEvent(pi, createBaseEvent({
59
+ action: "injected",
60
+ route,
61
+ sessionId: options?.sessionId,
62
+ text: extractText(prefixedContent),
63
+ deliverAs: options?.deliverAs,
64
+ }));
65
+ if (hasUserMessageOptions(options)) {
66
+ pi.sendUserMessage(prefixedContent, { deliverAs: options.deliverAs });
67
+ return;
68
+ }
69
+ pi.sendUserMessage(prefixedContent);
70
+ }
71
+ export function emitTodoSystemMessageFailure(pi, args) {
72
+ const prefixedContent = prefixContent(args.content);
73
+ emitTodoConversationEvent(pi, createBaseEvent({
74
+ action: "failed",
75
+ route: args.route,
76
+ sessionId: args.sessionId,
77
+ text: extractText(prefixedContent),
78
+ deliverAs: args.deliverAs,
79
+ errorMessage: args.errorMessage,
80
+ }));
81
+ }
82
+ //# sourceMappingURL=system-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-messages.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/todotools/system-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAPI } from \"../../types.js\";\n\nexport const SANEPI_SYSTEM_PREFIX = \"[system:sanepi]\";\nexport const SANEPI_CONVERSATION_EVENT = \"sanepi:conversation\";\n\nexport type TodoSystemMessageRoute = \"todotools.continuation\";\nexport type TodoConversationAction = \"injected\" | \"failed\";\n\nexport interface TodoConversationEvent {\n\tversion: 1;\n\tsource: \"builtin\";\n\taction: TodoConversationAction;\n\troute: TodoSystemMessageRoute;\n\tsessionId?: string;\n\ttimestamp: number;\n\tconversation: {\n\t\tprefix: typeof SANEPI_SYSTEM_PREFIX;\n\t\tkind: \"user_message\";\n\t\tdeliverAs?: \"steer\" | \"followUp\";\n\t};\n\ttext: string;\n\terrorMessage?: string;\n}\n\nexport interface TodoUserMessageOptions {\n\tsessionId?: string;\n\tdeliverAs?: \"steer\" | \"followUp\";\n}\n\nfunction prefixText(text: string): string {\n\treturn text.startsWith(SANEPI_SYSTEM_PREFIX) ? text : `${SANEPI_SYSTEM_PREFIX}\\n${text}`;\n}\n\nfunction prefixContent(content: string | (TextContent | ImageContent)[]): string | (TextContent | ImageContent)[] {\n\tif (typeof content === \"string\") {\n\t\treturn prefixText(content);\n\t}\n\n\tconst firstTextIndex = content.findIndex((part) => part.type === \"text\");\n\tif (firstTextIndex === -1) {\n\t\treturn [{ type: \"text\", text: SANEPI_SYSTEM_PREFIX }, ...content];\n\t}\n\n\treturn content.map((part, index) => {\n\t\tif (part.type !== \"text\" || index !== firstTextIndex) {\n\t\t\treturn part;\n\t\t}\n\n\t\treturn {\n\t\t\t...part,\n\t\t\ttext: prefixText(part.text),\n\t\t};\n\t});\n}\n\nfunction extractText(content: string | (TextContent | ImageContent)[]): string {\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\n\treturn content\n\t\t.filter((part): part is TextContent => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\");\n}\n\nfunction emitTodoConversationEvent(pi: ExtensionAPI, event: TodoConversationEvent): void {\n\tpi.events.emit(SANEPI_CONVERSATION_EVENT, event);\n}\n\nfunction createBaseEvent(args: {\n\taction: TodoConversationAction;\n\troute: TodoSystemMessageRoute;\n\tsessionId?: string;\n\tdeliverAs?: \"steer\" | \"followUp\";\n\ttext: string;\n\terrorMessage?: string;\n}): TodoConversationEvent {\n\treturn {\n\t\tversion: 1,\n\t\tsource: \"builtin\",\n\t\taction: args.action,\n\t\troute: args.route,\n\t\tsessionId: args.sessionId,\n\t\ttimestamp: Date.now(),\n\t\tconversation: {\n\t\t\tprefix: SANEPI_SYSTEM_PREFIX,\n\t\t\tkind: \"user_message\",\n\t\t\tdeliverAs: args.deliverAs,\n\t\t},\n\t\ttext: args.text,\n\t\terrorMessage: args.errorMessage,\n\t};\n}\n\nfunction hasUserMessageOptions(\n\toptions: TodoUserMessageOptions | undefined,\n): options is TodoUserMessageOptions & { deliverAs: \"steer\" | \"followUp\" } {\n\treturn options?.deliverAs !== undefined;\n}\n\nexport function sendTodoUserMessage(\n\tpi: ExtensionAPI,\n\troute: TodoSystemMessageRoute,\n\tcontent: string | (TextContent | ImageContent)[],\n\toptions?: TodoUserMessageOptions,\n): void {\n\tconst prefixedContent = prefixContent(content);\n\n\temitTodoConversationEvent(\n\t\tpi,\n\t\tcreateBaseEvent({\n\t\t\taction: \"injected\",\n\t\t\troute,\n\t\t\tsessionId: options?.sessionId,\n\t\t\ttext: extractText(prefixedContent),\n\t\t\tdeliverAs: options?.deliverAs,\n\t\t}),\n\t);\n\n\tif (hasUserMessageOptions(options)) {\n\t\tpi.sendUserMessage(prefixedContent, { deliverAs: options.deliverAs });\n\t\treturn;\n\t}\n\n\tpi.sendUserMessage(prefixedContent);\n}\n\nexport function emitTodoSystemMessageFailure(\n\tpi: ExtensionAPI,\n\targs: {\n\t\troute: TodoSystemMessageRoute;\n\t\tsessionId?: string;\n\t\tcontent: string | (TextContent | ImageContent)[];\n\t\tdeliverAs?: \"steer\" | \"followUp\";\n\t\terrorMessage: string;\n\t},\n): void {\n\tconst prefixedContent = prefixContent(args.content);\n\n\temitTodoConversationEvent(\n\t\tpi,\n\t\tcreateBaseEvent({\n\t\t\taction: \"failed\",\n\t\t\troute: args.route,\n\t\t\tsessionId: args.sessionId,\n\t\t\ttext: extractText(prefixedContent),\n\t\t\tdeliverAs: args.deliverAs,\n\t\t\terrorMessage: args.errorMessage,\n\t\t}),\n\t);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"todoread.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todoread.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAsB,KAAK,QAAQ,EAAyB,MAAM,aAAa,CAAC;AAIvF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,EAAE,GAAG,IAAI,CA6B9F","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI } from \"../../../types.js\";\nimport { getTodoResultLines, type TodoItem, type TodoWriteDetails } from \"../state.js\";\n\nconst TodoReadParams = Type.Object({});\n\nexport function registerTodoReadTool(pi: ExtensionAPI, getCurrentTodos: () => TodoItem[]): void {\n\tpi.registerTool({\n\t\tname: \"todoread\",\n\t\tlabel: \"TodoRead\",\n\t\tdescription: \"Read the current structured task list for the current coding session.\",\n\t\tpromptSnippet: \"Read the current todo list for the active coding session.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool when you need the current todo list before deciding how to update it.\",\n\t\t\t\"This tool returns the latest session todo list managed by todowrite.\",\n\t\t],\n\t\tparameters: TodoReadParams,\n\t\tasync execute() {\n\t\t\tconst currentTodos = getCurrentTodos();\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(_args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todoread\")), 0, 0);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? getCurrentTodos();\n\t\t\tconst [title, ...items] = getTodoResultLines(todos);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"todoread.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todoread.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAsB,KAAK,QAAQ,EAAyB,MAAM,aAAa,CAAC;AAIvF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,EAAE,GAAG,IAAI,CA+B9F","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI } from \"../../../types.js\";\nimport { getTodoResultLines, type TodoItem, type TodoWriteDetails } from \"../state.js\";\n\nconst TodoReadParams = Type.Object({});\n\nexport function registerTodoReadTool(pi: ExtensionAPI, getCurrentTodos: () => TodoItem[]): void {\n\tpi.registerTool({\n\t\tname: \"todoread\",\n\t\tlabel: \"TodoRead\",\n\t\tdescription: \"Read the current structured task list for the current coding session.\",\n\t\tpromptSnippet: \"Read the current todo list for the active coding session.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool when you need the current todo list before deciding how to update it.\",\n\t\t\t\"This tool returns the latest session todo list managed by todowrite.\",\n\t\t],\n\t\tparameters: TodoReadParams,\n\t\tasync execute() {\n\t\t\tconst currentTodos = getCurrentTodos();\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(_args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todoread\")), 0, 0);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? getCurrentTodos();\n\t\t\tconst lines = getTodoResultLines(todos);\n\t\t\tconst title = lines[0] ?? \"0 todos\";\n\t\t\tconst items = lines.slice(1);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -26,7 +26,9 @@ export function registerTodoReadTool(pi, getCurrentTodos) {
26
26
  renderResult(result, _options, theme) {
27
27
  const details = result.details;
28
28
  const todos = details?.todos ?? getCurrentTodos();
29
- const [title, ...items] = getTodoResultLines(todos);
29
+ const lines = getTodoResultLines(todos);
30
+ const title = lines[0] ?? "0 todos";
31
+ const items = lines.slice(1);
30
32
  const body = items.length > 0 ? `\n${items.join("\n")}` : "";
31
33
  return new Text(`${theme.fg("muted", title)}${body}`, 0, 0);
32
34
  },
@@ -1 +1 @@
1
- {"version":3,"file":"todoread.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todoread.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAwC,MAAM,aAAa,CAAC;AAEvF,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAEvC,MAAM,UAAU,oBAAoB,CAAC,EAAgB,EAAE,eAAiC,EAAQ;IAC/F,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,uEAAuE;QACpF,aAAa,EAAE,2DAA2D;QAC1E,gBAAgB,EAAE;YACjB,qFAAqF;YACrF,sEAAsE;SACtE;QACD,UAAU,EAAE,cAAc;QAC1B,KAAK,CAAC,OAAO,GAAG;YACf,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;YACvC,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBACxE,OAAO,EAAE,EAAE,KAAK,EAAE,YAAY,EAA6B;aAC3D,CAAC;QAAA,CACF;QACD,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE;YACxB,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CACrE;QACD,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;YACrC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAuC,CAAC;YAC/D,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,eAAe,EAAE,CAAC;YAClD,MAAM,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAC5D;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI } from \"../../../types.js\";\nimport { getTodoResultLines, type TodoItem, type TodoWriteDetails } from \"../state.js\";\n\nconst TodoReadParams = Type.Object({});\n\nexport function registerTodoReadTool(pi: ExtensionAPI, getCurrentTodos: () => TodoItem[]): void {\n\tpi.registerTool({\n\t\tname: \"todoread\",\n\t\tlabel: \"TodoRead\",\n\t\tdescription: \"Read the current structured task list for the current coding session.\",\n\t\tpromptSnippet: \"Read the current todo list for the active coding session.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool when you need the current todo list before deciding how to update it.\",\n\t\t\t\"This tool returns the latest session todo list managed by todowrite.\",\n\t\t],\n\t\tparameters: TodoReadParams,\n\t\tasync execute() {\n\t\t\tconst currentTodos = getCurrentTodos();\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(_args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todoread\")), 0, 0);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? getCurrentTodos();\n\t\t\tconst [title, ...items] = getTodoResultLines(todos);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"todoread.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todoread.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAwC,MAAM,aAAa,CAAC;AAEvF,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAEvC,MAAM,UAAU,oBAAoB,CAAC,EAAgB,EAAE,eAAiC,EAAQ;IAC/F,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,uEAAuE;QACpF,aAAa,EAAE,2DAA2D;QAC1E,gBAAgB,EAAE;YACjB,qFAAqF;YACrF,sEAAsE;SACtE;QACD,UAAU,EAAE,cAAc;QAC1B,KAAK,CAAC,OAAO,GAAG;YACf,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;YACvC,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBACxE,OAAO,EAAE,EAAE,KAAK,EAAE,YAAY,EAA6B;aAC3D,CAAC;QAAA,CACF;QACD,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE;YACxB,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CACrE;QACD,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;YACrC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAuC,CAAC;YAC/D,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,eAAe,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;YACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAC5D;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI } from \"../../../types.js\";\nimport { getTodoResultLines, type TodoItem, type TodoWriteDetails } from \"../state.js\";\n\nconst TodoReadParams = Type.Object({});\n\nexport function registerTodoReadTool(pi: ExtensionAPI, getCurrentTodos: () => TodoItem[]): void {\n\tpi.registerTool({\n\t\tname: \"todoread\",\n\t\tlabel: \"TodoRead\",\n\t\tdescription: \"Read the current structured task list for the current coding session.\",\n\t\tpromptSnippet: \"Read the current todo list for the active coding session.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool when you need the current todo list before deciding how to update it.\",\n\t\t\t\"This tool returns the latest session todo list managed by todowrite.\",\n\t\t],\n\t\tparameters: TodoReadParams,\n\t\tasync execute() {\n\t\t\tconst currentTodos = getCurrentTodos();\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(_args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todoread\")), 0, 0);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? getCurrentTodos();\n\t\t\tconst lines = getTodoResultLines(todos);\n\t\t\tconst title = lines[0] ?? \"0 todos\";\n\t\t\tconst items = lines.slice(1);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"todowrite.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todowrite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAGN,KAAK,QAAQ,EAGb,MAAM,aAAa,CAAC;AA2DrB,KAAK,aAAa,GAAG;IACpB,eAAe,EAAE,MAAM,QAAQ,EAAE,CAAC;IAClC,eAAe,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,GAAG,IAAI,CA0CtF","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../../types.js\";\nimport {\n\tgetTodoResultLines,\n\tTODO_STATE_ENTRY_TYPE,\n\ttype TodoItem,\n\ttype TodoStateEntry,\n\ttype TodoWriteDetails,\n} from \"../state.js\";\n\nconst DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.\n\n<todo_format>\n## Todo Format (MANDATORY)\n\nEach todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.\n\nFormat: \"[WHERE] [HOW] to [WHY] - expect [RESULT]\"\n\nGOOD:\n- \"src/utils/validation.ts: Add validateEmail() for input sanitization - returns boolean\"\n- \"UserService.create(): Call validateEmail() before DB insert - rejects invalid emails with 400\"\n- \"validation.test.ts: Add test for missing @ sign - expect validateEmail('foo') to return false\"\n\nBAD:\n- \"Implement email validation\" (where? how? what result?)\n- \"Add dark mode\" (feature, not a todo)\n- \"Fix auth\" (what file? what changes? what's expected?)\n</todo_format>\n\n<granularity_rules>\n## Granularity Rules\n\nEach todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.\n\n**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.\n</granularity_rules>\n\n<task_management>\n## Task Management\n- One in_progress at a time. Complete it before starting the next.\n- Mark completed immediately after finishing each item.\n- ALWAYS use todos. No \"trivial task\" exemptions.\n</task_management>`;\n\nconst TodoItemSchema = Type.Object({\n\tcontent: Type.String({\n\t\tdescription:\n\t\t\t\"Todo title encoding WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Must be a single atomic action completable in 1-3 tool calls.\",\n\t}),\n\tstatus: Type.String({\n\t\tdescription:\n\t\t\t\"Current status: pending (not started), in_progress (currently working - limit ONE at a time), completed (finished - mark IMMEDIATELY after done), cancelled (no longer needed)\",\n\t}),\n\tpriority: Type.String({\n\t\tdescription:\n\t\t\t\"Priority level: high (blocking or critical path), medium (important but not blocking), low (nice to have)\",\n\t}),\n});\n\nconst TodoWriteParams = Type.Object({\n\ttodos: Type.Array(TodoItemSchema, {\n\t\tdescription: \"The updated todo list\",\n\t\tminItems: 1,\n\t}),\n});\n\ntype TodoAccessors = {\n\tgetCurrentTodos: () => TodoItem[];\n\tsetCurrentTodos: (todos: TodoItem[]) => void;\n\tsyncWidget: (ctx: ExtensionContext) => void;\n};\n\nexport function registerTodoWriteTool(pi: ExtensionAPI, accessors: TodoAccessors): void {\n\tpi.registerTool({\n\t\tname: \"todowrite\",\n\t\tlabel: \"TodoWrite\",\n\t\tdescription: DESCRIPTION,\n\t\tpromptSnippet:\n\t\t\t\"MANDATORY for ALL tasks. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow. No exceptions.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Create todos for EVERY task. No 'trivial task' exemptions. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow always.\",\n\t\t\t\"Each todo title MUST encode WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Vague todos are useless.\",\n\t\t\t\"Each todo MUST be a single atomic action completable in 1-3 tool calls. If bigger, split it. Size test: one file edit or one command.\",\n\t\t\t\"Pass the complete updated todo list on every call instead of incremental operations.\",\n\t\t\t\"Exactly ONE todo with status in_progress at any time. Mark completed IMMEDIATELY after finishing - NEVER batch completions.\",\n\t\t\t\"OBSESSIVELY TRACK YOUR WORK. Every step gets a todo. Every completion gets marked immediately. No evidence = not complete.\",\n\t\t],\n\t\tparameters: TodoWriteParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst currentTodos = params.todos.map((todo) => ({ ...todo }));\n\t\t\taccessors.setCurrentTodos(currentTodos);\n\t\t\tpi.appendEntry(TODO_STATE_ENTRY_TYPE, { todos: currentTodos } satisfies TodoStateEntry);\n\t\t\taccessors.syncWidget(ctx);\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"todowrite \")) + theme.fg(\"muted\", `${args.todos.length} item(s)`),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? accessors.getCurrentTodos();\n\t\t\tconst [title, ...items] = getTodoResultLines(todos);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"todowrite.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todowrite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAGN,KAAK,QAAQ,EAGb,MAAM,aAAa,CAAC;AA2DrB,KAAK,aAAa,GAAG;IACpB,eAAe,EAAE,MAAM,QAAQ,EAAE,CAAC;IAClC,eAAe,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,GAAG,IAAI,CA4CtF","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../../types.js\";\nimport {\n\tgetTodoResultLines,\n\tTODO_STATE_ENTRY_TYPE,\n\ttype TodoItem,\n\ttype TodoStateEntry,\n\ttype TodoWriteDetails,\n} from \"../state.js\";\n\nconst DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.\n\n<todo_format>\n## Todo Format (MANDATORY)\n\nEach todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.\n\nFormat: \"[WHERE] [HOW] to [WHY] - expect [RESULT]\"\n\nGOOD:\n- \"src/utils/validation.ts: Add validateEmail() for input sanitization - returns boolean\"\n- \"UserService.create(): Call validateEmail() before DB insert - rejects invalid emails with 400\"\n- \"validation.test.ts: Add test for missing @ sign - expect validateEmail('foo') to return false\"\n\nBAD:\n- \"Implement email validation\" (where? how? what result?)\n- \"Add dark mode\" (feature, not a todo)\n- \"Fix auth\" (what file? what changes? what's expected?)\n</todo_format>\n\n<granularity_rules>\n## Granularity Rules\n\nEach todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.\n\n**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.\n</granularity_rules>\n\n<task_management>\n## Task Management\n- One in_progress at a time. Complete it before starting the next.\n- Mark completed immediately after finishing each item.\n- ALWAYS use todos. No \"trivial task\" exemptions.\n</task_management>`;\n\nconst TodoItemSchema = Type.Object({\n\tcontent: Type.String({\n\t\tdescription:\n\t\t\t\"Todo title encoding WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Must be a single atomic action completable in 1-3 tool calls.\",\n\t}),\n\tstatus: Type.String({\n\t\tdescription:\n\t\t\t\"Current status: pending (not started), in_progress (currently working - limit ONE at a time), completed (finished - mark IMMEDIATELY after done), cancelled (no longer needed)\",\n\t}),\n\tpriority: Type.String({\n\t\tdescription:\n\t\t\t\"Priority level: high (blocking or critical path), medium (important but not blocking), low (nice to have)\",\n\t}),\n});\n\nconst TodoWriteParams = Type.Object({\n\ttodos: Type.Array(TodoItemSchema, {\n\t\tdescription: \"The updated todo list\",\n\t\tminItems: 1,\n\t}),\n});\n\ntype TodoAccessors = {\n\tgetCurrentTodos: () => TodoItem[];\n\tsetCurrentTodos: (todos: TodoItem[]) => void;\n\tsyncWidget: (ctx: ExtensionContext) => void;\n};\n\nexport function registerTodoWriteTool(pi: ExtensionAPI, accessors: TodoAccessors): void {\n\tpi.registerTool({\n\t\tname: \"todowrite\",\n\t\tlabel: \"TodoWrite\",\n\t\tdescription: DESCRIPTION,\n\t\tpromptSnippet:\n\t\t\t\"MANDATORY for ALL tasks. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow. No exceptions.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Create todos for EVERY task. No 'trivial task' exemptions. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow always.\",\n\t\t\t\"Each todo title MUST encode WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Vague todos are useless.\",\n\t\t\t\"Each todo MUST be a single atomic action completable in 1-3 tool calls. If bigger, split it. Size test: one file edit or one command.\",\n\t\t\t\"Pass the complete updated todo list on every call instead of incremental operations.\",\n\t\t\t\"Exactly ONE todo with status in_progress at any time. Mark completed IMMEDIATELY after finishing - NEVER batch completions.\",\n\t\t\t\"OBSESSIVELY TRACK YOUR WORK. Every step gets a todo. Every completion gets marked immediately. No evidence = not complete.\",\n\t\t],\n\t\tparameters: TodoWriteParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst currentTodos = params.todos.map((todo) => ({ ...todo }));\n\t\t\taccessors.setCurrentTodos(currentTodos);\n\t\t\tpi.appendEntry(TODO_STATE_ENTRY_TYPE, { todos: currentTodos } satisfies TodoStateEntry);\n\t\t\taccessors.syncWidget(ctx);\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"todowrite \")) + theme.fg(\"muted\", `${args.todos.length} item(s)`),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? accessors.getCurrentTodos();\n\t\t\tconst lines = getTodoResultLines(todos);\n\t\t\tconst title = lines[0] ?? \"0 todos\";\n\t\t\tconst items = lines.slice(1);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -83,7 +83,9 @@ export function registerTodoWriteTool(pi, accessors) {
83
83
  renderResult(result, _options, theme) {
84
84
  const details = result.details;
85
85
  const todos = details?.todos ?? accessors.getCurrentTodos();
86
- const [title, ...items] = getTodoResultLines(todos);
86
+ const lines = getTodoResultLines(todos);
87
+ const title = lines[0] ?? "0 todos";
88
+ const items = lines.slice(1);
87
89
  const body = items.length > 0 ? `\n${items.join("\n")}` : "";
88
90
  return new Text(`${theme.fg("muted", title)}${body}`, 0, 0);
89
91
  },
@@ -1 +1 @@
1
- {"version":3,"file":"todowrite.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todowrite.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../../types.js\";\nimport {\n\tgetTodoResultLines,\n\tTODO_STATE_ENTRY_TYPE,\n\ttype TodoItem,\n\ttype TodoStateEntry,\n\ttype TodoWriteDetails,\n} from \"../state.js\";\n\nconst DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.\n\n<todo_format>\n## Todo Format (MANDATORY)\n\nEach todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.\n\nFormat: \"[WHERE] [HOW] to [WHY] - expect [RESULT]\"\n\nGOOD:\n- \"src/utils/validation.ts: Add validateEmail() for input sanitization - returns boolean\"\n- \"UserService.create(): Call validateEmail() before DB insert - rejects invalid emails with 400\"\n- \"validation.test.ts: Add test for missing @ sign - expect validateEmail('foo') to return false\"\n\nBAD:\n- \"Implement email validation\" (where? how? what result?)\n- \"Add dark mode\" (feature, not a todo)\n- \"Fix auth\" (what file? what changes? what's expected?)\n</todo_format>\n\n<granularity_rules>\n## Granularity Rules\n\nEach todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.\n\n**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.\n</granularity_rules>\n\n<task_management>\n## Task Management\n- One in_progress at a time. Complete it before starting the next.\n- Mark completed immediately after finishing each item.\n- ALWAYS use todos. No \"trivial task\" exemptions.\n</task_management>`;\n\nconst TodoItemSchema = Type.Object({\n\tcontent: Type.String({\n\t\tdescription:\n\t\t\t\"Todo title encoding WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Must be a single atomic action completable in 1-3 tool calls.\",\n\t}),\n\tstatus: Type.String({\n\t\tdescription:\n\t\t\t\"Current status: pending (not started), in_progress (currently working - limit ONE at a time), completed (finished - mark IMMEDIATELY after done), cancelled (no longer needed)\",\n\t}),\n\tpriority: Type.String({\n\t\tdescription:\n\t\t\t\"Priority level: high (blocking or critical path), medium (important but not blocking), low (nice to have)\",\n\t}),\n});\n\nconst TodoWriteParams = Type.Object({\n\ttodos: Type.Array(TodoItemSchema, {\n\t\tdescription: \"The updated todo list\",\n\t\tminItems: 1,\n\t}),\n});\n\ntype TodoAccessors = {\n\tgetCurrentTodos: () => TodoItem[];\n\tsetCurrentTodos: (todos: TodoItem[]) => void;\n\tsyncWidget: (ctx: ExtensionContext) => void;\n};\n\nexport function registerTodoWriteTool(pi: ExtensionAPI, accessors: TodoAccessors): void {\n\tpi.registerTool({\n\t\tname: \"todowrite\",\n\t\tlabel: \"TodoWrite\",\n\t\tdescription: DESCRIPTION,\n\t\tpromptSnippet:\n\t\t\t\"MANDATORY for ALL tasks. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow. No exceptions.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Create todos for EVERY task. No 'trivial task' exemptions. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow always.\",\n\t\t\t\"Each todo title MUST encode WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Vague todos are useless.\",\n\t\t\t\"Each todo MUST be a single atomic action completable in 1-3 tool calls. If bigger, split it. Size test: one file edit or one command.\",\n\t\t\t\"Pass the complete updated todo list on every call instead of incremental operations.\",\n\t\t\t\"Exactly ONE todo with status in_progress at any time. Mark completed IMMEDIATELY after finishing - NEVER batch completions.\",\n\t\t\t\"OBSESSIVELY TRACK YOUR WORK. Every step gets a todo. Every completion gets marked immediately. No evidence = not complete.\",\n\t\t],\n\t\tparameters: TodoWriteParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst currentTodos = params.todos.map((todo) => ({ ...todo }));\n\t\t\taccessors.setCurrentTodos(currentTodos);\n\t\t\tpi.appendEntry(TODO_STATE_ENTRY_TYPE, { todos: currentTodos } satisfies TodoStateEntry);\n\t\t\taccessors.syncWidget(ctx);\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"todowrite \")) + theme.fg(\"muted\", `${args.todos.length} item(s)`),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? accessors.getCurrentTodos();\n\t\t\tconst [title, ...items] = getTodoResultLines(todos);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"todowrite.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/todotools/tools/todowrite.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ExtensionAPI, ExtensionContext } from \"../../../types.js\";\nimport {\n\tgetTodoResultLines,\n\tTODO_STATE_ENTRY_TYPE,\n\ttype TodoItem,\n\ttype TodoStateEntry,\n\ttype TodoWriteDetails,\n} from \"../state.js\";\n\nconst DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.\n\n<todo_format>\n## Todo Format (MANDATORY)\n\nEach todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.\n\nFormat: \"[WHERE] [HOW] to [WHY] - expect [RESULT]\"\n\nGOOD:\n- \"src/utils/validation.ts: Add validateEmail() for input sanitization - returns boolean\"\n- \"UserService.create(): Call validateEmail() before DB insert - rejects invalid emails with 400\"\n- \"validation.test.ts: Add test for missing @ sign - expect validateEmail('foo') to return false\"\n\nBAD:\n- \"Implement email validation\" (where? how? what result?)\n- \"Add dark mode\" (feature, not a todo)\n- \"Fix auth\" (what file? what changes? what's expected?)\n</todo_format>\n\n<granularity_rules>\n## Granularity Rules\n\nEach todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.\n\n**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.\n</granularity_rules>\n\n<task_management>\n## Task Management\n- One in_progress at a time. Complete it before starting the next.\n- Mark completed immediately after finishing each item.\n- ALWAYS use todos. No \"trivial task\" exemptions.\n</task_management>`;\n\nconst TodoItemSchema = Type.Object({\n\tcontent: Type.String({\n\t\tdescription:\n\t\t\t\"Todo title encoding WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Must be a single atomic action completable in 1-3 tool calls.\",\n\t}),\n\tstatus: Type.String({\n\t\tdescription:\n\t\t\t\"Current status: pending (not started), in_progress (currently working - limit ONE at a time), completed (finished - mark IMMEDIATELY after done), cancelled (no longer needed)\",\n\t}),\n\tpriority: Type.String({\n\t\tdescription:\n\t\t\t\"Priority level: high (blocking or critical path), medium (important but not blocking), low (nice to have)\",\n\t}),\n});\n\nconst TodoWriteParams = Type.Object({\n\ttodos: Type.Array(TodoItemSchema, {\n\t\tdescription: \"The updated todo list\",\n\t\tminItems: 1,\n\t}),\n});\n\ntype TodoAccessors = {\n\tgetCurrentTodos: () => TodoItem[];\n\tsetCurrentTodos: (todos: TodoItem[]) => void;\n\tsyncWidget: (ctx: ExtensionContext) => void;\n};\n\nexport function registerTodoWriteTool(pi: ExtensionAPI, accessors: TodoAccessors): void {\n\tpi.registerTool({\n\t\tname: \"todowrite\",\n\t\tlabel: \"TodoWrite\",\n\t\tdescription: DESCRIPTION,\n\t\tpromptSnippet:\n\t\t\t\"MANDATORY for ALL tasks. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow. No exceptions.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Create todos for EVERY task. No 'trivial task' exemptions. Follow EXPLORE -> DEFINE -> PLAN -> TODO -> EXECUTE workflow always.\",\n\t\t\t\"Each todo title MUST encode WHERE, WHY, HOW, and EXPECTED RESULT. Format: '[WHERE] [HOW] to [WHY] - expect [RESULT]'. Vague todos are useless.\",\n\t\t\t\"Each todo MUST be a single atomic action completable in 1-3 tool calls. If bigger, split it. Size test: one file edit or one command.\",\n\t\t\t\"Pass the complete updated todo list on every call instead of incremental operations.\",\n\t\t\t\"Exactly ONE todo with status in_progress at any time. Mark completed IMMEDIATELY after finishing - NEVER batch completions.\",\n\t\t\t\"OBSESSIVELY TRACK YOUR WORK. Every step gets a todo. Every completion gets marked immediately. No evidence = not complete.\",\n\t\t],\n\t\tparameters: TodoWriteParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst currentTodos = params.todos.map((todo) => ({ ...todo }));\n\t\t\taccessors.setCurrentTodos(currentTodos);\n\t\t\tpi.appendEntry(TODO_STATE_ENTRY_TYPE, { todos: currentTodos } satisfies TodoStateEntry);\n\t\t\taccessors.syncWidget(ctx);\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(currentTodos, null, 2) }],\n\t\t\t\tdetails: { todos: currentTodos } satisfies TodoWriteDetails,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"todowrite \")) + theme.fg(\"muted\", `${args.todos.length} item(s)`),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst details = result.details as TodoWriteDetails | undefined;\n\t\t\tconst todos = details?.todos ?? accessors.getCurrentTodos();\n\t\t\tconst lines = getTodoResultLines(todos);\n\t\t\tconst title = lines[0] ?? \"0 todos\";\n\t\t\tconst items = lines.slice(1);\n\t\t\tconst body = items.length > 0 ? `\\n${items.join(\"\\n\")}` : \"\";\n\t\t\treturn new Text(`${theme.fg(\"muted\", title)}${body}`, 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -29,6 +29,7 @@ export interface AppKeybindings {
29
29
  "app.session.delete": true;
30
30
  "app.session.deleteNoninvasive": true;
31
31
  "app.models.save": true;
32
+ "app.models.toggleFavorite": true;
32
33
  "app.models.enableAll": true;
33
34
  "app.models.clearAll": true;
34
35
  "app.models.toggleProvider": true;
@@ -288,6 +289,10 @@ export declare const KEYBINDINGS: {
288
289
  readonly defaultKeys: "ctrl+s";
289
290
  readonly description: "Save model selection";
290
291
  };
292
+ readonly "app.models.toggleFavorite": {
293
+ readonly defaultKeys: "ctrl+f";
294
+ readonly description: "Toggle favorite model";
295
+ };
291
296
  readonly "app.models.enableAll": {
292
297
  readonly defaultKeys: "ctrl+a";
293
298
  readonly description: "Enable all models";
@@ -1 +1 @@
1
- {"version":3,"file":"keybindings.d.ts","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: unknown): KeybindingsConfig {\n\tif (!isRecord(value)) return {};\n\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
1
+ {"version":3,"file":"keybindings.d.ts","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.toggleFavorite\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.toggleFavorite\": {\n\t\tdefaultKeys: \"ctrl+f\",\n\t\tdescription: \"Toggle favorite model\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasOwn(value: object, key: PropertyKey): boolean {\n\treturn Object.hasOwn(value, key);\n}\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: unknown): KeybindingsConfig {\n\tif (!isRecord(value)) return {};\n\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
@@ -93,6 +93,10 @@ export const KEYBINDINGS = {
93
93
  defaultKeys: "ctrl+s",
94
94
  description: "Save model selection",
95
95
  },
96
+ "app.models.toggleFavorite": {
97
+ defaultKeys: "ctrl+f",
98
+ description: "Toggle favorite model",
99
+ },
96
100
  "app.models.enableAll": {
97
101
  defaultKeys: "ctrl+a",
98
102
  description: "Enable all models",
@@ -206,6 +210,9 @@ const KEYBINDING_NAME_MIGRATIONS = {
206
210
  function isRecord(value) {
207
211
  return typeof value === "object" && value !== null && !Array.isArray(value);
208
212
  }
213
+ function hasOwn(value, key) {
214
+ return Object.hasOwn(value, key);
215
+ }
209
216
  function isLegacyKeybindingName(key) {
210
217
  return key in KEYBINDING_NAME_MIGRATIONS;
211
218
  }
@@ -232,7 +239,7 @@ export function migrateKeybindingsConfig(rawConfig) {
232
239
  if (nextKey !== key) {
233
240
  migrated = true;
234
241
  }
235
- if (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {
242
+ if (key !== nextKey && hasOwn(rawConfig, nextKey)) {
236
243
  migrated = true;
237
244
  continue;
238
245
  }
@@ -243,12 +250,12 @@ export function migrateKeybindingsConfig(rawConfig) {
243
250
  function orderKeybindingsConfig(config) {
244
251
  const ordered = {};
245
252
  for (const keybinding of Object.keys(KEYBINDINGS)) {
246
- if (Object.hasOwn(config, keybinding)) {
253
+ if (hasOwn(config, keybinding)) {
247
254
  ordered[keybinding] = config[keybinding];
248
255
  }
249
256
  }
250
257
  const extras = Object.keys(config)
251
- .filter((key) => !Object.hasOwn(ordered, key))
258
+ .filter((key) => !hasOwn(ordered, key))
252
259
  .sort();
253
260
  for (const key of extras) {
254
261
  ordered[key] = config[key];
@@ -1 +1 @@
1
- {"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,eAAe,EACf,kBAAkB,IAAI,qBAAqB,GAC3C,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoD3C,MAAM,CAAC,MAAM,WAAW,GAAG;IAC1B,GAAG,eAAe;IAClB,eAAe,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;IAC1E,WAAW,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE;IACnE,UAAU,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE;IAC/E,aAAa,EAAE;QACd,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;QACzD,WAAW,EAAE,uBAAuB;KACpC;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,sBAAsB;KACnC;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,qBAAqB;KAClC;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,yBAAyB;KACtC;IACD,kBAAkB,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACjF,kBAAkB,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAE;IAChF,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,wBAAwB;KACrC;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,6BAA6B;KAC1C;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,sBAAsB;KACnC;IACD,sBAAsB,EAAE;QACvB,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,yBAAyB;KACtC;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,yBAAyB;KACtC;IACD,0BAA0B,EAAE;QAC3B,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;QAC9D,WAAW,EAAE,4BAA4B;KACzC;IACD,iBAAiB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,qBAAqB,EAAE;IAC1E,kBAAkB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,mBAAmB,EAAE;IACzE,kBAAkB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,sBAAsB,EAAE;IAC5E,oBAAoB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE;IAC1E,mBAAmB,EAAE;QACpB,WAAW,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC;QACtC,WAAW,EAAE,6BAA6B;KAC1C;IACD,uBAAuB,EAAE;QACxB,WAAW,EAAE,CAAC,YAAY,EAAE,WAAW,CAAC;QACxC,WAAW,EAAE,iCAAiC;KAC9C;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,SAAS;QACtB,WAAW,EAAE,iBAAiB;KAC9B;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,SAAS;QACtB,WAAW,EAAE,8BAA8B;KAC3C;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,6BAA6B;KAC1C;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,0BAA0B;KACvC;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gBAAgB;KAC7B;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gBAAgB;KAC7B;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,gBAAgB;QAC7B,WAAW,EAAE,oCAAoC;KACjD;IACD,iBAAiB,EAAE;QAClB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,sBAAsB;KACnC;IACD,sBAAsB,EAAE;QACvB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,mBAAmB;KAChC;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,kBAAkB;KAC/B;IACD,2BAA2B,EAAE;QAC5B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gCAAgC;KAC7C;IACD,sBAAsB,EAAE;QACvB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,wBAAwB;KACrC;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,UAAU;QACvB,WAAW,EAAE,0BAA0B;KACvC;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,2BAA2B;KACxC;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gCAAgC;KAC7C;IACD,0BAA0B,EAAE;QAC3B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,iCAAiC;KAC9C;IACD,6BAA6B,EAAE;QAC9B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,mCAAmC;KAChD;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,+BAA+B;KAC5C;IACD,8BAA8B,EAAE;QAC/B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,4BAA4B;KACzC;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,6BAA6B;KAC1C;CACwC,CAAC;AAE3C,MAAM,0BAA0B,GAAG;IAClC,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE,uBAAuB;IACnC,UAAU,EAAE,uBAAuB;IACnC,WAAW,EAAE,wBAAwB;IACrC,cAAc,EAAE,2BAA2B;IAC3C,eAAe,EAAE,4BAA4B;IAC7C,eAAe,EAAE,4BAA4B;IAC7C,aAAa,EAAE,0BAA0B;IACzC,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,yBAAyB;IACvC,MAAM,EAAE,mBAAmB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,kBAAkB,EAAE,+BAA+B;IACnD,iBAAiB,EAAE,8BAA8B;IACjD,kBAAkB,EAAE,+BAA+B;IACnD,iBAAiB,EAAE,8BAA8B;IACjD,iBAAiB,EAAE,8BAA8B;IACjD,eAAe,EAAE,4BAA4B;IAC7C,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,oBAAoB;IAC7B,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,mBAAmB;IAC5B,MAAM,EAAE,kBAAkB;IAC1B,GAAG,EAAE,eAAe;IACpB,IAAI,EAAE,gBAAgB;IACtB,QAAQ,EAAE,eAAe;IACzB,UAAU,EAAE,iBAAiB;IAC7B,YAAY,EAAE,mBAAmB;IACjC,cAAc,EAAE,qBAAqB;IACrC,aAAa,EAAE,oBAAoB;IACnC,YAAY,EAAE,mBAAmB;IACjC,SAAS,EAAE,eAAe;IAC1B,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,aAAa;IACtB,kBAAkB,EAAE,oBAAoB;IACxC,iBAAiB,EAAE,wBAAwB;IAC3C,kBAAkB,EAAE,yBAAyB;IAC7C,WAAW,EAAE,kBAAkB;IAC/B,WAAW,EAAE,kBAAkB;IAC/B,cAAc,EAAE,qBAAqB;IACrC,wBAAwB,EAAE,+BAA+B;IACzD,cAAc,EAAE,qBAAqB;IACrC,QAAQ,EAAE,sBAAsB;IAChC,OAAO,EAAE,qBAAqB;IAC9B,UAAU,EAAE,0BAA0B;IACtC,UAAU,EAAE,iBAAiB;IAC7B,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,kBAAkB;IACxB,MAAM,EAAE,oBAAoB;IAC5B,YAAY,EAAE,mBAAmB;IACjC,gBAAgB,EAAE,uBAAuB;IACzC,aAAa,EAAE,oBAAoB;IACnC,wBAAwB,EAAE,+BAA+B;IACzD,iBAAiB,EAAE,wBAAwB;IAC3C,iBAAiB,EAAE,wBAAwB;IAC3C,aAAa,EAAE,oBAAoB;IACnC,aAAa,EAAE,oBAAoB;IACnC,wBAAwB,EAAE,+BAA+B;CACX,CAAC;AAEhD,SAAS,QAAQ,CAAC,KAAc,EAAoC;IACnE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAAA,CAC5E;AAED,SAAS,sBAAsB,CAAC,GAAW,EAAkD;IAC5F,OAAO,GAAG,IAAI,0BAA0B,CAAC;AAAA,CACzC;AAED,SAAS,mBAAmB,CAAC,KAAc,EAAqB;IAC/D,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEhC,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAgB,CAAC;YAC/B,SAAS;QACV,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;YACnF,MAAM,CAAC,GAAG,CAAC,GAAG,OAAkB,CAAC;QAClC,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,MAAM,UAAU,wBAAwB,CAAC,SAAkC,EAGzE;IACD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,MAAM,OAAO,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACpF,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACrB,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YAC1D,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACV,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;IACzB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,sBAAsB,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;AAAA,CAC5D;AAED,SAAS,sBAAsB,CAAC,MAA+B,EAA2B;IACzF,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SAChC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;SAC7C,IAAI,EAAE,CAAC;IACT,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf;AAED,SAAS,aAAa,CAAC,IAAY,EAAuC;IACzE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAY,CAAC;QAClE,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AAAA,CACD;AAED,MAAM,OAAO,kBAAmB,SAAQ,qBAAqB;IACpD,UAAU,CAAqB;IAEvC,YAAY,YAAY,GAAsB,EAAE,EAAE,UAAmB,EAAE;QACtE,KAAK,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAAA,CAC7B;IAED,MAAM,CAAC,MAAM,CAAC,QAAQ,GAAW,WAAW,EAAE,EAAsB;QACnE,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,kBAAkB,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QACjE,OAAO,IAAI,kBAAkB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAAA,CACxD;IAED,MAAM,GAAS;QACd,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO;QAC7B,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAAA,CACvE;IAED,kBAAkB,GAAsB;QACvC,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAAA,CAClC;IAEO,MAAM,CAAC,YAAY,CAAC,IAAY,EAAqB;QAC5D,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,OAAO,mBAAmB,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IAAA,CACvE;CACD","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: unknown): KeybindingsConfig {\n\tif (!isRecord(value)) return {};\n\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
1
+ {"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.toggleFavorite\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.toggleFavorite\": {\n\t\tdefaultKeys: \"ctrl+f\",\n\t\tdescription: \"Toggle favorite model\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasOwn(value: object, key: PropertyKey): boolean {\n\treturn Object.hasOwn(value, key);\n}\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: unknown): KeybindingsConfig {\n\tif (!isRecord(value)) return {};\n\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\treturn isRecord(parsed) ? parsed : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}