skyloom 1.14.6 → 1.15.0

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 (157) hide show
  1. package/.github/workflows/ci.yml +2 -2
  2. package/.github/workflows/publish.yml +74 -0
  3. package/CONVERSION_PLAN.md +191 -191
  4. package/README.md +523 -220
  5. package/config/default.yaml +46 -43
  6. package/config/models.yaml +928 -155
  7. package/config/providers.yaml +109 -6
  8. package/dist/agents/snow.d.ts +2 -0
  9. package/dist/agents/snow.d.ts.map +1 -1
  10. package/dist/agents/snow.js +36 -5
  11. package/dist/agents/snow.js.map +1 -1
  12. package/dist/cli/loom_chat.d.ts.map +1 -1
  13. package/dist/cli/loom_chat.js +207 -1
  14. package/dist/cli/loom_chat.js.map +1 -1
  15. package/dist/cli/main.js +190 -40
  16. package/dist/cli/main.js.map +1 -1
  17. package/dist/cli/tui.d.ts.map +1 -1
  18. package/dist/cli/tui.js +6 -31
  19. package/dist/cli/tui.js.map +1 -1
  20. package/dist/core/agent.d.ts +6 -4
  21. package/dist/core/agent.d.ts.map +1 -1
  22. package/dist/core/agent.js +61 -20
  23. package/dist/core/agent.js.map +1 -1
  24. package/dist/core/catalog.d.ts.map +1 -1
  25. package/dist/core/catalog.js +30 -9
  26. package/dist/core/catalog.js.map +1 -1
  27. package/dist/core/commands.d.ts +110 -0
  28. package/dist/core/commands.d.ts.map +1 -0
  29. package/dist/core/commands.js +633 -0
  30. package/dist/core/commands.js.map +1 -0
  31. package/dist/core/concurrency.d.ts +38 -0
  32. package/dist/core/concurrency.d.ts.map +1 -0
  33. package/dist/core/concurrency.js +65 -0
  34. package/dist/core/concurrency.js.map +1 -0
  35. package/dist/core/factory.js +16 -16
  36. package/dist/core/file_checkpoint.d.ts +9 -0
  37. package/dist/core/file_checkpoint.d.ts.map +1 -1
  38. package/dist/core/file_checkpoint.js +33 -1
  39. package/dist/core/file_checkpoint.js.map +1 -1
  40. package/dist/core/llm.d.ts.map +1 -1
  41. package/dist/core/llm.js +66 -13
  42. package/dist/core/llm.js.map +1 -1
  43. package/dist/core/memory.js +51 -51
  44. package/dist/core/schemas.d.ts +16 -0
  45. package/dist/core/schemas.d.ts.map +1 -1
  46. package/dist/core/schemas.js +32 -0
  47. package/dist/core/schemas.js.map +1 -1
  48. package/dist/core/security.d.ts.map +1 -1
  49. package/dist/core/security.js +27 -0
  50. package/dist/core/security.js.map +1 -1
  51. package/dist/core/skymd.js +14 -14
  52. package/dist/core/trace.d.ts +105 -0
  53. package/dist/core/trace.d.ts.map +1 -0
  54. package/dist/core/trace.js +213 -0
  55. package/dist/core/trace.js.map +1 -0
  56. package/dist/tools/builtin.d.ts +2 -6
  57. package/dist/tools/builtin.d.ts.map +1 -1
  58. package/dist/tools/builtin.js +180 -125
  59. package/dist/tools/builtin.js.map +1 -1
  60. package/dist/tools/extra.d.ts +13 -0
  61. package/dist/tools/extra.d.ts.map +1 -0
  62. package/dist/tools/extra.js +827 -0
  63. package/dist/tools/extra.js.map +1 -0
  64. package/dist/tools/guards.d.ts +12 -0
  65. package/dist/tools/guards.d.ts.map +1 -0
  66. package/dist/tools/guards.js +143 -0
  67. package/dist/tools/guards.js.map +1 -0
  68. package/dist/tools/model_tool.d.ts.map +1 -1
  69. package/dist/tools/model_tool.js +24 -4
  70. package/dist/tools/model_tool.js.map +1 -1
  71. package/dist/web/markdown.d.ts +32 -0
  72. package/dist/web/markdown.d.ts.map +1 -0
  73. package/dist/web/markdown.js +202 -0
  74. package/dist/web/markdown.js.map +1 -0
  75. package/dist/web/server.d.ts +4 -0
  76. package/dist/web/server.d.ts.map +1 -1
  77. package/dist/web/server.js +14 -582
  78. package/dist/web/server.js.map +1 -1
  79. package/dist/web/ui.d.ts +31 -0
  80. package/dist/web/ui.d.ts.map +1 -0
  81. package/dist/web/ui.js +1009 -0
  82. package/dist/web/ui.js.map +1 -0
  83. package/docs/AESTHETIC_DESIGN.md +152 -152
  84. package/docs/OPTIMIZATION_PLAN.md +178 -178
  85. package/package.json +68 -68
  86. package/src/agents/snow.ts +38 -5
  87. package/src/cli/commands_md.ts +112 -112
  88. package/src/cli/input_macros.ts +83 -83
  89. package/src/cli/loom.ts +1041 -1041
  90. package/src/cli/loom_chat.ts +772 -603
  91. package/src/cli/main.ts +853 -723
  92. package/src/cli/tui.ts +264 -289
  93. package/src/core/agent/guard.ts +133 -133
  94. package/src/core/agent/task.ts +100 -100
  95. package/src/core/agent.ts +1630 -1590
  96. package/src/core/agent_helpers.ts +500 -500
  97. package/src/core/bus.ts +221 -221
  98. package/src/core/cache.ts +153 -153
  99. package/src/core/catalog.ts +199 -178
  100. package/src/core/circuit_breaker.ts +119 -119
  101. package/src/core/commands.ts +704 -0
  102. package/src/core/concurrency.ts +73 -0
  103. package/src/core/config.ts +365 -365
  104. package/src/core/constants.ts +95 -95
  105. package/src/core/factory.ts +656 -656
  106. package/src/core/file_checkpoint.ts +163 -136
  107. package/src/core/hooks.ts +126 -126
  108. package/src/core/llm.ts +972 -915
  109. package/src/core/logger.ts +143 -143
  110. package/src/core/mcp.ts +1001 -1001
  111. package/src/core/memory.ts +1201 -1201
  112. package/src/core/middleware.ts +350 -350
  113. package/src/core/model_config.ts +159 -159
  114. package/src/core/pipelines.ts +424 -424
  115. package/src/core/schemas.ts +319 -282
  116. package/src/core/security.ts +27 -0
  117. package/src/core/semantic.ts +211 -211
  118. package/src/core/skill.ts +384 -384
  119. package/src/core/skymd.ts +143 -143
  120. package/src/core/theme.ts +65 -65
  121. package/src/core/tool.ts +457 -457
  122. package/src/core/trace.ts +236 -0
  123. package/src/core/verify.ts +71 -71
  124. package/src/plugins/loader.ts +91 -91
  125. package/src/skills/loader.ts +75 -75
  126. package/src/tools/builtin.ts +571 -493
  127. package/src/tools/computer.ts +279 -279
  128. package/src/tools/extra.ts +662 -0
  129. package/src/tools/guards.ts +82 -0
  130. package/src/tools/model_tool.ts +93 -74
  131. package/src/tools/todo.ts +76 -76
  132. package/src/web/markdown.ts +193 -0
  133. package/src/web/server.ts +117 -693
  134. package/src/web/ui.ts +949 -0
  135. package/tests/agent.test.ts +211 -159
  136. package/tests/agent_helpers.test.ts +48 -48
  137. package/tests/catalog.test.ts +86 -86
  138. package/tests/checkpoint_commands.test.ts +124 -124
  139. package/tests/claude_compat.test.ts +110 -110
  140. package/tests/commands.test.ts +103 -0
  141. package/tests/concurrency.test.ts +102 -0
  142. package/tests/config.test.ts +41 -41
  143. package/tests/extra_tools.test.ts +212 -0
  144. package/tests/fence_plugin.test.ts +52 -52
  145. package/tests/guard.test.ts +75 -75
  146. package/tests/loom.test.ts +337 -337
  147. package/tests/memory.test.ts +170 -170
  148. package/tests/model_config.test.ts +109 -109
  149. package/tests/skymd.test.ts +146 -146
  150. package/tests/ssrf.test.ts +38 -38
  151. package/tests/structured_retry.test.ts +87 -0
  152. package/tests/task.test.ts +60 -60
  153. package/tests/todo_toolstats.test.ts +94 -94
  154. package/tests/trace.test.ts +128 -0
  155. package/tests/tui.test.ts +67 -67
  156. package/tests/web.test.ts +169 -0
  157. package/tsconfig.json +38 -38
@@ -0,0 +1,704 @@
1
+ /**
2
+ * 天空织机 · 命令注册中心 — Centralized Slash Command Registry
3
+ *
4
+ * Inspired by opencode's command architecture:
5
+ * - Each command has a name, aliases, description, category, and hints
6
+ * - Commands are organized by category for better UX
7
+ * - The registry provides list(), get(), and search() methods
8
+ * - Supports argument hints auto-detection from templates
9
+ *
10
+ * This registry is the single source of truth for the slash-command surface:
11
+ * the TUI palette and tab-completer derive their list from `slashItems()`, so
12
+ * adding a command here makes it show up everywhere — no parallel hand-kept
13
+ * arrays to drift out of sync.
14
+ *
15
+ * Two command types:
16
+ * 1. TUI commands — execute UI actions directly (undo, compact, etc.)
17
+ * 2. Prompt commands — expand to LLM prompt templates (init, review, etc.)
18
+ */
19
+
20
+ export type CommandCategory =
21
+ | 'session' // session management
22
+ | 'agent' // agent switching
23
+ | 'model' // model configuration
24
+ | 'memory' // memory operations
25
+ | 'context' // context & diagnostics
26
+ | 'workflow' // workflow & orchestration
27
+ | 'file' // file & checkpoint operations
28
+ | 'config' // configuration
29
+ | 'ui' // UI/display toggles
30
+ | 'system'; // system & exit
31
+
32
+ export interface CommandInfo {
33
+ /** Command name (without /). */
34
+ name: string;
35
+ /** Aliases that map to the same command. */
36
+ aliases: string[];
37
+ /** Short description shown in autocomplete (English). */
38
+ description: string;
39
+ /** Localized (zh) display label for the TUI palette. Falls back to `description`. */
40
+ label?: string;
41
+ /** Category for grouping. */
42
+ category: CommandCategory;
43
+ /** Argument hints (auto-detected or manual). */
44
+ hints: string[];
45
+ /** Whether this command takes arguments. */
46
+ takesArgs: boolean;
47
+ /**
48
+ * Command is meaningless without an argument (e.g. /resume, /task). The
49
+ * palette fills the input and waits for the argument instead of running on
50
+ * Enter, and the derived token carries a trailing space.
51
+ */
52
+ argRequired?: boolean;
53
+ /**
54
+ * Catalogued but not yet wired to a live handler. Kept in the registry for
55
+ * roadmap/documentation, but omitted from the palette so we never advertise
56
+ * a command that does nothing.
57
+ */
58
+ hidden?: boolean;
59
+ /** Optional: agent to route to. */
60
+ agent?: string;
61
+ /** Optional: model override. */
62
+ model?: string;
63
+ /** Whether this runs as a subtask (subagent). */
64
+ subtask?: boolean;
65
+ /** Source of the command. */
66
+ source: 'builtin' | 'config' | 'mcp' | 'skill' | 'custom';
67
+ }
68
+
69
+ export interface CommandHandler {
70
+ (args: {
71
+ input: string;
72
+ agent: any;
73
+ ctx: any;
74
+ mode: any;
75
+ ui?: any;
76
+ }): Promise<void> | void;
77
+ }
78
+
79
+ /**
80
+ * All built-in slash commands for Skyloom.
81
+ * Organized by category.
82
+ */
83
+ export const BUILTIN_COMMANDS: CommandInfo[] = [
84
+ // ── Session ─
85
+ {
86
+ name: 'new',
87
+ aliases: [],
88
+ description: 'Start a new session',
89
+ label: '开始新会话',
90
+ category: 'session',
91
+ hints: [],
92
+ takesArgs: false,
93
+ source: 'builtin',
94
+ },
95
+ {
96
+ name: 'sessions',
97
+ aliases: [],
98
+ description: 'List and switch sessions',
99
+ label: '会话列表',
100
+ category: 'session',
101
+ hints: ['<index|id>'],
102
+ takesArgs: true,
103
+ source: 'builtin',
104
+ },
105
+ {
106
+ name: 'resume',
107
+ aliases: [],
108
+ description: 'Resume a previous session',
109
+ label: '恢复会话(序号/id)',
110
+ category: 'session',
111
+ hints: ['<index|id>'],
112
+ takesArgs: true,
113
+ argRequired: true,
114
+ source: 'builtin',
115
+ },
116
+ {
117
+ name: 'export',
118
+ aliases: [],
119
+ description: 'Export conversation to Markdown',
120
+ label: '导出对话为 Markdown',
121
+ category: 'session',
122
+ hints: ['[filename]'],
123
+ takesArgs: true,
124
+ source: 'builtin',
125
+ },
126
+ {
127
+ name: 'share',
128
+ aliases: [],
129
+ description: 'Share current session',
130
+ label: '分享当前会话',
131
+ category: 'session',
132
+ hints: [],
133
+ takesArgs: false,
134
+ hidden: true,
135
+ source: 'builtin',
136
+ },
137
+ {
138
+ name: 'unshare',
139
+ aliases: [],
140
+ description: 'Unshare current session',
141
+ label: '取消分享',
142
+ category: 'session',
143
+ hints: [],
144
+ takesArgs: false,
145
+ hidden: true,
146
+ source: 'builtin',
147
+ },
148
+ {
149
+ name: 'move',
150
+ aliases: [],
151
+ description: 'Move session to another project directory',
152
+ label: '移动会话到项目目录',
153
+ category: 'session',
154
+ hints: ['<path>'],
155
+ takesArgs: true,
156
+ source: 'builtin',
157
+ },
158
+
159
+ // ── Agent ──
160
+ {
161
+ name: 'fog',
162
+ aliases: [],
163
+ description: 'Fog — research & insight',
164
+ label: '≋ 雾 · 探索洞察',
165
+ category: 'agent',
166
+ hints: [],
167
+ takesArgs: false,
168
+ source: 'builtin',
169
+ },
170
+ {
171
+ name: 'rain',
172
+ aliases: [],
173
+ description: 'Rain — creation & codegen',
174
+ label: '⸽ 雨 · 创造产出',
175
+ category: 'agent',
176
+ hints: [],
177
+ takesArgs: false,
178
+ source: 'builtin',
179
+ },
180
+ {
181
+ name: 'frost',
182
+ aliases: [],
183
+ description: 'Frost — review & quality',
184
+ label: '✱ 霜 · 精炼品质',
185
+ category: 'agent',
186
+ hints: [],
187
+ takesArgs: false,
188
+ source: 'builtin',
189
+ },
190
+ {
191
+ name: 'snow',
192
+ aliases: [],
193
+ description: 'Snow — planning & architecture',
194
+ label: '❉ 雪 · 架构规划',
195
+ category: 'agent',
196
+ hints: [],
197
+ takesArgs: false,
198
+ source: 'builtin',
199
+ },
200
+ {
201
+ name: 'dew',
202
+ aliases: [],
203
+ description: 'Dew — devops & reliability',
204
+ label: '∘ 露 · 可靠守护',
205
+ category: 'agent',
206
+ hints: [],
207
+ takesArgs: false,
208
+ source: 'builtin',
209
+ },
210
+ {
211
+ name: 'fair',
212
+ aliases: [],
213
+ description: 'Fair — companion & warmth',
214
+ label: '☼ 晴 · 情感陪伴',
215
+ category: 'agent',
216
+ hints: [],
217
+ takesArgs: false,
218
+ source: 'builtin',
219
+ },
220
+
221
+ // ── Model ──
222
+ {
223
+ name: 'model',
224
+ aliases: [],
225
+ description: 'Model info & switch',
226
+ label: '查看/切换模型(独立/统一)',
227
+ category: 'model',
228
+ hints: ['<id>', 'unified <id>', 'reset', 'key <key>'],
229
+ takesArgs: true,
230
+ source: 'builtin',
231
+ },
232
+ {
233
+ name: 'models',
234
+ aliases: [],
235
+ description: 'Browse all available models',
236
+ label: '浏览全部可用模型',
237
+ category: 'model',
238
+ hints: ['[provider]'],
239
+ takesArgs: true,
240
+ source: 'builtin',
241
+ },
242
+ {
243
+ name: 'connect',
244
+ aliases: [],
245
+ description: 'Add or configure a provider',
246
+ label: '添加/配置提供商',
247
+ category: 'model',
248
+ hints: ['<provider>'],
249
+ takesArgs: true,
250
+ source: 'builtin',
251
+ },
252
+
253
+ // ── Memory ──
254
+ {
255
+ name: 'memory',
256
+ aliases: [],
257
+ description: 'Memory stats',
258
+ label: '记忆状态',
259
+ category: 'memory',
260
+ hints: ['clear'],
261
+ takesArgs: true,
262
+ source: 'builtin',
263
+ },
264
+
265
+ // ── Context & Diagnostics ──
266
+ {
267
+ name: 'status',
268
+ aliases: [],
269
+ description: 'Agent overview',
270
+ label: '状态总览',
271
+ category: 'context',
272
+ hints: [],
273
+ takesArgs: false,
274
+ source: 'builtin',
275
+ },
276
+ {
277
+ name: 'cost',
278
+ aliases: [],
279
+ description: 'Usage & cost',
280
+ label: '费用统计',
281
+ category: 'context',
282
+ hints: ['reset'],
283
+ takesArgs: true,
284
+ source: 'builtin',
285
+ },
286
+ {
287
+ name: 'context',
288
+ aliases: [],
289
+ description: 'Token usage breakdown',
290
+ label: '上下文占用明细',
291
+ category: 'context',
292
+ hints: [],
293
+ takesArgs: false,
294
+ source: 'builtin',
295
+ },
296
+ {
297
+ name: 'tools',
298
+ aliases: [],
299
+ description: 'Tool call statistics',
300
+ label: '工具调用统计',
301
+ category: 'context',
302
+ hints: [],
303
+ takesArgs: false,
304
+ source: 'builtin',
305
+ },
306
+ {
307
+ name: 'trace',
308
+ aliases: [],
309
+ description: 'Run trace of the last turn (span tree)',
310
+ label: '本轮运行追踪(span 树)',
311
+ category: 'context',
312
+ hints: [],
313
+ takesArgs: false,
314
+ source: 'builtin',
315
+ },
316
+ {
317
+ name: 'workspace',
318
+ aliases: [],
319
+ description: 'Workspace info',
320
+ label: '工作空间',
321
+ category: 'context',
322
+ hints: [],
323
+ takesArgs: false,
324
+ source: 'builtin',
325
+ },
326
+ {
327
+ name: 'version',
328
+ aliases: [],
329
+ description: 'Version info',
330
+ label: '版本信息',
331
+ category: 'context',
332
+ hints: [],
333
+ takesArgs: false,
334
+ source: 'builtin',
335
+ },
336
+
337
+ // ─ Workflow ──
338
+ {
339
+ name: 'compact',
340
+ aliases: ['summarize'],
341
+ description: 'Compress/summarize context',
342
+ label: '压缩上下文',
343
+ category: 'workflow',
344
+ hints: [],
345
+ takesArgs: false,
346
+ source: 'builtin',
347
+ },
348
+ {
349
+ name: 'retry',
350
+ aliases: [],
351
+ description: 'Resend last message',
352
+ label: '重发上一条消息',
353
+ category: 'workflow',
354
+ hints: [],
355
+ takesArgs: false,
356
+ hidden: true,
357
+ source: 'builtin',
358
+ },
359
+ {
360
+ name: 'task',
361
+ aliases: [],
362
+ description: 'Multi-agent orchestration',
363
+ label: '多 Agent 编排',
364
+ category: 'workflow',
365
+ hints: ['<goal>'],
366
+ takesArgs: true,
367
+ argRequired: true,
368
+ source: 'builtin',
369
+ },
370
+ {
371
+ name: 'init',
372
+ aliases: [],
373
+ description: 'Generate SKY.md project memory',
374
+ label: '扫描项目生成 SKY.md',
375
+ category: 'workflow',
376
+ hints: [],
377
+ takesArgs: false,
378
+ source: 'builtin',
379
+ },
380
+ {
381
+ name: 'review',
382
+ aliases: [],
383
+ description: 'Code review of changes',
384
+ label: '审查代码改动',
385
+ category: 'workflow',
386
+ hints: ['[commit|branch|pr]'],
387
+ takesArgs: true,
388
+ subtask: true,
389
+ source: 'builtin',
390
+ },
391
+ {
392
+ name: 'verify',
393
+ aliases: [],
394
+ description: 'Run verification commands',
395
+ label: '运行项目验证命令',
396
+ category: 'workflow',
397
+ hints: [],
398
+ takesArgs: false,
399
+ source: 'builtin',
400
+ },
401
+ {
402
+ name: 'plan',
403
+ aliases: [],
404
+ description: 'Enter plan mode (read-only tools)',
405
+ label: '切换计划模式(只读出方案)',
406
+ category: 'workflow',
407
+ hints: [],
408
+ takesArgs: false,
409
+ source: 'builtin',
410
+ },
411
+ {
412
+ name: 'auto',
413
+ aliases: [],
414
+ description: 'Enter auto mode (no approval)',
415
+ label: '自动模式(免审批)',
416
+ category: 'workflow',
417
+ hints: [],
418
+ takesArgs: false,
419
+ source: 'builtin',
420
+ },
421
+ {
422
+ name: 'default',
423
+ aliases: [],
424
+ description: 'Return to default mode',
425
+ label: '返回默认模式',
426
+ category: 'workflow',
427
+ hints: [],
428
+ takesArgs: false,
429
+ source: 'builtin',
430
+ },
431
+
432
+ // ── File & Checkpoint ──
433
+ {
434
+ name: 'rewind',
435
+ aliases: ['undo'],
436
+ description: 'Undo last turn (revert file changes)',
437
+ label: '回退本轮文件改动',
438
+ category: 'file',
439
+ hints: ['[n]'],
440
+ takesArgs: true,
441
+ source: 'builtin',
442
+ },
443
+ {
444
+ name: 'redo',
445
+ aliases: [],
446
+ description: 'Redo a previously undone turn',
447
+ label: '重做已撤销的回合',
448
+ category: 'file',
449
+ hints: [],
450
+ takesArgs: false,
451
+ source: 'builtin',
452
+ },
453
+
454
+ // ── Config ──
455
+ {
456
+ name: 'setup',
457
+ aliases: [],
458
+ description: 'Setup wizard (provider, key, model)',
459
+ label: '配置向导',
460
+ category: 'config',
461
+ hints: [],
462
+ takesArgs: false,
463
+ source: 'builtin',
464
+ },
465
+ {
466
+ name: 'apikey',
467
+ aliases: [],
468
+ description: 'Manage API keys',
469
+ label: '管理 API 密钥',
470
+ category: 'config',
471
+ hints: ['set <provider> <key>'],
472
+ takesArgs: true,
473
+ source: 'builtin',
474
+ },
475
+ {
476
+ name: 'mcp',
477
+ aliases: [],
478
+ description: 'MCP server status',
479
+ label: 'MCP 服务器',
480
+ category: 'config',
481
+ hints: [],
482
+ takesArgs: false,
483
+ source: 'builtin',
484
+ },
485
+
486
+ // ── UI ──
487
+ {
488
+ name: 'clear',
489
+ aliases: [],
490
+ description: 'Clear the screen',
491
+ label: '清屏',
492
+ category: 'ui',
493
+ hints: [],
494
+ takesArgs: false,
495
+ source: 'builtin',
496
+ },
497
+ {
498
+ name: 'help',
499
+ aliases: [],
500
+ description: 'Show all commands',
501
+ label: '查看所有命令',
502
+ category: 'ui',
503
+ hints: [],
504
+ takesArgs: false,
505
+ source: 'builtin',
506
+ },
507
+ {
508
+ name: 'thinking',
509
+ aliases: [],
510
+ description: 'Toggle reasoning block visibility',
511
+ label: '切换思考过程显示',
512
+ category: 'ui',
513
+ hints: [],
514
+ takesArgs: false,
515
+ source: 'builtin',
516
+ },
517
+ {
518
+ name: 'details',
519
+ aliases: [],
520
+ description: 'Toggle tool execution details',
521
+ label: '切换工具执行详情',
522
+ category: 'ui',
523
+ hints: [],
524
+ takesArgs: false,
525
+ source: 'builtin',
526
+ },
527
+ {
528
+ name: 'skills',
529
+ aliases: [],
530
+ description: 'Browse available skills',
531
+ label: '浏览可用技能',
532
+ category: 'ui',
533
+ hints: [],
534
+ takesArgs: false,
535
+ source: 'builtin',
536
+ },
537
+ {
538
+ name: 'warp',
539
+ aliases: [],
540
+ description: 'Change workspace for this session',
541
+ label: '切换本会话工作区',
542
+ category: 'ui',
543
+ hints: ['<path>'],
544
+ takesArgs: true,
545
+ source: 'builtin',
546
+ },
547
+
548
+ // ─ System ──
549
+ {
550
+ name: 'quit',
551
+ aliases: ['exit'],
552
+ description: 'Exit chat',
553
+ label: '退出',
554
+ category: 'system',
555
+ hints: [],
556
+ takesArgs: false,
557
+ source: 'builtin',
558
+ },
559
+ ];
560
+
561
+ /**
562
+ * Command registry — provides lookup, listing, and search.
563
+ */
564
+ export class CommandRegistry {
565
+ private commands: Map<string, CommandInfo> = new Map();
566
+ private handlers: Map<string, CommandHandler> = new Map();
567
+
568
+ constructor() {
569
+ // Register all built-in commands
570
+ for (const cmd of BUILTIN_COMMANDS) {
571
+ this.register(cmd);
572
+ }
573
+ }
574
+
575
+ /** Register a command. */
576
+ register(info: CommandInfo, handler?: CommandHandler): void {
577
+ this.commands.set(info.name, info);
578
+ for (const alias of info.aliases) {
579
+ this.commands.set(alias, { ...info, name: alias });
580
+ }
581
+ if (handler) {
582
+ this.handlers.set(info.name, handler);
583
+ }
584
+ }
585
+
586
+ /** Get command info by name or alias. */
587
+ get(name: string): CommandInfo | undefined {
588
+ return this.commands.get(name);
589
+ }
590
+
591
+ /** Get handler for a command. */
592
+ getHandler(name: string): CommandHandler | undefined {
593
+ const info = this.commands.get(name);
594
+ if (!info) return undefined;
595
+ return this.handlers.get(info.name);
596
+ }
597
+
598
+ /** Register a handler for an existing command. */
599
+ setHandler(name: string, handler: CommandHandler): void {
600
+ this.handlers.set(name, handler);
601
+ }
602
+
603
+ /** List all commands, optionally filtered by category. */
604
+ list(category?: CommandCategory): CommandInfo[] {
605
+ const seen = new Set<string>();
606
+ const result: CommandInfo[] = [];
607
+ for (const cmd of BUILTIN_COMMANDS) {
608
+ if (category && cmd.category !== category) continue;
609
+ if (seen.has(cmd.name)) continue;
610
+ seen.add(cmd.name);
611
+ result.push(cmd);
612
+ }
613
+ return result;
614
+ }
615
+
616
+ /** List commands grouped by category. */
617
+ listByCategory(): Map<CommandCategory, CommandInfo[]> {
618
+ const groups = new Map<CommandCategory, CommandInfo[]>();
619
+ for (const cmd of this.list()) {
620
+ const existing = groups.get(cmd.category) || [];
621
+ existing.push(cmd);
622
+ groups.set(cmd.category, existing);
623
+ }
624
+ return groups;
625
+ }
626
+
627
+ /**
628
+ * Build the `[token, label]` pairs the TUI palette + tab-completer consume.
629
+ *
630
+ * - Hidden (catalogued-but-unwired) commands are omitted so the palette only
631
+ * advertises commands that actually run.
632
+ * - Argument-required commands get a trailing space in the token; the loom
633
+ * palette keys off that to fill the input and wait for the argument instead
634
+ * of executing on Enter.
635
+ * - `lang: 'zh'` uses the localized label (the default, product language);
636
+ * `'en'` uses the English description.
637
+ */
638
+ slashItems(lang: 'zh' | 'en' = 'zh'): [string, string][] {
639
+ // Lead with the agent switches — the most common action — then the rest in
640
+ // catalog order. This keeps the agents prominent at the top of the palette.
641
+ const visible = this.list().filter(c => !c.hidden);
642
+ const ordered = [
643
+ ...visible.filter(c => c.category === 'agent'),
644
+ ...visible.filter(c => c.category !== 'agent'),
645
+ ];
646
+ return ordered.map((cmd) => {
647
+ const token = '/' + cmd.name + (cmd.argRequired ? ' ' : '');
648
+ const label = lang === 'zh' ? (cmd.label ?? cmd.description) : cmd.description;
649
+ return [token, label] as [string, string];
650
+ });
651
+ }
652
+
653
+ /** Search commands by query (fuzzy match on name + description + label). */
654
+ search(query: string): CommandInfo[] {
655
+ if (!query) return this.list();
656
+ const q = query.toLowerCase();
657
+ const seen = new Set<string>();
658
+ const result: CommandInfo[] = [];
659
+ for (const cmd of BUILTIN_COMMANDS) {
660
+ if (seen.has(cmd.name)) continue;
661
+ const nameMatch = cmd.name.toLowerCase().includes(q);
662
+ const descMatch = cmd.description.toLowerCase().includes(q);
663
+ const labelMatch = (cmd.label || '').toLowerCase().includes(q);
664
+ const aliasMatch = cmd.aliases.some(a => a.toLowerCase().includes(q));
665
+ if (nameMatch || descMatch || labelMatch || aliasMatch) {
666
+ seen.add(cmd.name);
667
+ result.push(cmd);
668
+ }
669
+ }
670
+ return result;
671
+ }
672
+
673
+ /** Get all command names (for autocomplete). */
674
+ names(): string[] {
675
+ const seen = new Set<string>();
676
+ const result: string[] = [];
677
+ for (const cmd of BUILTIN_COMMANDS) {
678
+ if (seen.has(cmd.name)) continue;
679
+ seen.add(cmd.name);
680
+ result.push(cmd.name);
681
+ }
682
+ return result;
683
+ }
684
+
685
+ /** Get category display label. */
686
+ static categoryLabel(cat: CommandCategory): string {
687
+ const labels: Record<CommandCategory, string> = {
688
+ session: '会话管理',
689
+ agent: 'Agent 切换',
690
+ model: '模型配置',
691
+ memory: '记忆操作',
692
+ context: '上下文与诊断',
693
+ workflow: '工作流',
694
+ file: '文件与检查点',
695
+ config: '配置',
696
+ ui: '界面',
697
+ system: '系统',
698
+ };
699
+ return labels[cat] || cat;
700
+ }
701
+ }
702
+
703
+ /** Singleton registry instance. */
704
+ export const registry = new CommandRegistry();