@pencil-agent/nano-pencil 1.14.2 → 1.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/build-meta.json +3 -3
  2. package/dist/builtin-extensions.d.ts +4 -0
  3. package/dist/builtin-extensions.js +16 -16
  4. package/dist/core/config/settings-manager.d.ts +8 -1
  5. package/dist/core/config/settings-manager.js +9 -0
  6. package/dist/core/extensions/runner.d.ts +70 -4
  7. package/dist/core/extensions/runner.js +188 -8
  8. package/dist/core/extensions/types.d.ts +8 -1
  9. package/dist/core/i18n/slash-commands.d.ts +12 -0
  10. package/dist/core/i18n/slash-commands.js +16 -4
  11. package/dist/core/i18n/slash-commands.zh.d.ts +12 -0
  12. package/dist/core/i18n/slash-commands.zh.js +16 -4
  13. package/dist/core/runtime/agent-session.d.ts +5 -0
  14. package/dist/core/runtime/agent-session.js +85 -27
  15. package/dist/core/runtime/extension-core-bindings.d.ts +3 -3
  16. package/dist/core/runtime/extension-core-bindings.js +73 -20
  17. package/dist/core/runtime/retry-coordinator.d.ts +10 -1
  18. package/dist/core/runtime/retry-coordinator.js +20 -4
  19. package/dist/core/runtime/sdk.js +2 -1
  20. package/dist/core/runtime/slash-command-catalog.d.ts +2 -1
  21. package/dist/core/runtime/slash-command-catalog.js +9 -1
  22. package/dist/core/slash-commands.d.ts +12 -1
  23. package/dist/core/slash-commands.js +81 -32
  24. package/dist/core/telemetry/batching-dispatcher.d.ts +41 -0
  25. package/dist/core/telemetry/batching-dispatcher.js +89 -0
  26. package/dist/core/telemetry/build-meta.d.ts +12 -0
  27. package/dist/core/telemetry/build-meta.js +57 -0
  28. package/dist/core/telemetry/caller-context.d.ts +32 -0
  29. package/dist/core/telemetry/caller-context.js +19 -0
  30. package/dist/core/telemetry/credentials.d.ts +27 -0
  31. package/dist/core/telemetry/credentials.js +87 -0
  32. package/dist/core/telemetry/ext-events.d.ts +89 -0
  33. package/dist/core/telemetry/ext-events.js +189 -0
  34. package/dist/core/telemetry/index.d.ts +13 -0
  35. package/dist/core/telemetry/index.js +6 -0
  36. package/dist/core/telemetry/insforge-base.d.ts +37 -0
  37. package/dist/core/telemetry/insforge-base.js +160 -0
  38. package/dist/core/telemetry/types.d.ts +33 -0
  39. package/dist/core/telemetry/types.js +7 -0
  40. package/dist/extensions/defaults/browser/index.js +14 -6
  41. package/dist/extensions/defaults/btw/index.js +2 -2
  42. package/dist/extensions/defaults/debug/index.js +38 -3
  43. package/dist/extensions/defaults/diagnostics/index.js +12 -0
  44. package/dist/extensions/defaults/grub/grub-parser.d.ts +15 -1
  45. package/dist/extensions/defaults/grub/grub-parser.js +31 -1
  46. package/dist/extensions/defaults/grub/index.d.ts +1 -1
  47. package/dist/extensions/defaults/grub/index.js +4 -3
  48. package/dist/extensions/defaults/interview/index.js +2 -2
  49. package/dist/extensions/defaults/link-world/index.js +14 -6
  50. package/dist/extensions/defaults/loop/cron/cron-scheduler.js +19 -0
  51. package/dist/extensions/defaults/loop/index.js +35 -0
  52. package/dist/extensions/defaults/mcp/index.js +18 -0
  53. package/dist/extensions/defaults/plan/index.js +29 -11
  54. package/dist/extensions/defaults/presence/index.d.ts +12 -2
  55. package/dist/extensions/defaults/presence/index.js +77 -23
  56. package/dist/extensions/defaults/presence/presence-memory.d.ts +2 -1
  57. package/dist/extensions/defaults/presence/presence-memory.js +37 -1
  58. package/dist/extensions/defaults/recap/index.js +12 -0
  59. package/dist/extensions/defaults/sal/eval/insforge-sink.d.ts +6 -17
  60. package/dist/extensions/defaults/sal/eval/insforge-sink.js +40 -183
  61. package/dist/extensions/defaults/sal/index.js +31 -8
  62. package/dist/extensions/defaults/sal/sal-config.d.ts +5 -0
  63. package/dist/extensions/defaults/sal/sal-config.js +15 -82
  64. package/dist/extensions/defaults/security-audit/index.js +141 -83
  65. package/dist/extensions/defaults/subagent/index.js +29 -5
  66. package/dist/extensions/defaults/team/index.js +10 -9
  67. package/dist/extensions/defaults/team/team-parser.d.ts +18 -0
  68. package/dist/extensions/defaults/team/team-parser.js +91 -3
  69. package/dist/extensions/defaults/team/team-ui.js +4 -14
  70. package/dist/extensions/defaults/token-save/index.js +14 -1
  71. package/dist/extensions/optional/export-html/index.js +19 -5
  72. package/dist/extensions/optional/simplify/index.js +11 -5
  73. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  74. package/dist/modes/interactive/interactive-mode.js +68 -19
  75. package/dist/modes/interactive/slash-command-arguments.d.ts +16 -0
  76. package/dist/modes/interactive/slash-command-arguments.js +97 -0
  77. package/dist/modes/rpc/rpc-mode.d.ts +3 -0
  78. package/dist/modes/rpc/rpc-mode.js +40 -31
  79. package/dist/modes/rpc/rpc-types.d.ts +3 -0
  80. package/dist/node_modules/@pencil-agent/agent-core/agent.d.ts +15 -1
  81. package/dist/node_modules/@pencil-agent/agent-core/agent.js +13 -1
  82. package/dist/node_modules/@pencil-agent/agent-core/index.d.ts +2 -1
  83. package/dist/node_modules/@pencil-agent/agent-core/index.js +2 -1
  84. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.d.ts +1 -1
  85. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.js +293 -20
  86. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-streaming-tool-executor.d.ts +33 -0
  87. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-streaming-tool-executor.js +189 -0
  88. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-tool-orchestration.d.ts +9 -0
  89. package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-tool-orchestration.js +30 -5
  90. package/dist/node_modules/@pencil-agent/agent-core/types.d.ts +90 -3
  91. package/dist/node_modules/@pencil-agent/agent-core/types.js +1 -1
  92. package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +0 -17
  93. package/dist/node_modules/@pencil-agent/ai/models.generated.js +29 -46
  94. package/dist/node_modules/@pencil-agent/tui/autocomplete.d.ts +8 -1
  95. package/dist/node_modules/@pencil-agent/tui/autocomplete.js +18 -2
  96. package/dist/node_modules/@pencil-agent/tui/index.d.ts +1 -1
  97. package/dist/node_modules/@pencil-agent/tui/tui.d.ts +8 -3
  98. package/dist/node_modules/@pencil-agent/tui/tui.js +68 -33
  99. package/dist/packages/mem-core/extension.js +154 -83
  100. package/dist/packages/mem-core/full-insights-sections.d.ts +48 -0
  101. package/dist/packages/mem-core/full-insights-sections.js +231 -0
  102. package/dist/packages/mem-core/full-insights.d.ts +1 -1
  103. package/dist/packages/mem-core/full-insights.js +102 -42
  104. package/package.json +2 -2
@@ -16,6 +16,13 @@ import { DEFAULT_SECURITY_CONFIG } from "./interface.js";
16
16
  // Security Audit Extension
17
17
  // ============================================================
18
18
  const SECURITY_MESSAGE_TYPE = "security-audit";
19
+ const SECURITY_ROOT_COMPLETIONS = [
20
+ { value: "dashboard", label: "dashboard", description: "Show recent warnings and blocked actions" },
21
+ { value: "logs", label: "logs", description: "Show detailed event history" },
22
+ { value: "stats", label: "stats", description: "Show event counts" },
23
+ { value: "clear", label: "clear", description: "Clear saved events" },
24
+ ];
25
+ const SECURITY_LOG_LIMIT_COMPLETIONS = ["10", "20", "50", "100"];
19
26
  // Security mode: "audit" (warn only), "strict" (block dangerous)
20
27
  const SECURITY_MODE = process.env.SECURITY_MODE || "strict";
21
28
  const detector = new DangerDetector({
@@ -42,6 +49,26 @@ function sendSecurityNotice(api, content) {
42
49
  display: true,
43
50
  }, { triggerTurn: true });
44
51
  }
52
+ function getSecurityArgumentCompletions(argumentPrefix, context) {
53
+ const prefix = argumentPrefix.trim().toLowerCase();
54
+ if (context?.previousTokens[0] === "logs" && context.tokenIndex === 1) {
55
+ const values = SECURITY_LOG_LIMIT_COMPLETIONS
56
+ .filter((value) => value.startsWith(prefix))
57
+ .map((value) => ({ value, label: value, description: `Show ${value} events` }));
58
+ return values.length > 0 ? values : null;
59
+ }
60
+ if (context && context.tokenIndex > 0)
61
+ return null;
62
+ const values = SECURITY_ROOT_COMPLETIONS.filter((item) => item.value.startsWith(prefix));
63
+ return values.length > 0 ? values.map((item) => ({ ...item })) : null;
64
+ }
65
+ function getSecurityLogLimitCompletions(argumentPrefix) {
66
+ const prefix = argumentPrefix.trim();
67
+ const values = SECURITY_LOG_LIMIT_COMPLETIONS
68
+ .filter((value) => value.startsWith(prefix))
69
+ .map((value) => ({ value, label: value, description: `Show ${value} events` }));
70
+ return values.length > 0 ? values : null;
71
+ }
45
72
  function auditAndGateToolCall(api, event, ctx) {
46
73
  const logger = getLogger(ctx);
47
74
  const toolName = event.toolName;
@@ -97,107 +124,138 @@ function auditAndGateToolCall(api, event, ctx) {
97
124
  }
98
125
  }
99
126
  export default function securityAuditExtension(api) {
127
+ const showDashboard = (ctx) => {
128
+ const logger = getLogger(ctx);
129
+ const stats = logger.getStats();
130
+ const logs = logger.query({ limit: 20 });
131
+ let content = `# 🔒 Security Audit\n\n`;
132
+ content += `## Statistics\n`;
133
+ content += `- Total Events: ${stats.totalEvents}\n`;
134
+ content += `- Dangerous: ${stats.byLevel.dangerous}\n`;
135
+ content += `- Warnings: ${stats.byLevel.warning}\n`;
136
+ content += `- Blocked: ${stats.byStatus.blocked}\n\n`;
137
+ if (stats.dangerousPatterns.length > 0) {
138
+ content += `## Dangerous Patterns\n`;
139
+ for (const p of stats.dangerousPatterns.slice(0, 5)) {
140
+ content += `- \`${p.pattern}\`: ${p.count}\n`;
141
+ }
142
+ content += `\n`;
143
+ }
144
+ content += `## Recent Events\n`;
145
+ content += `| Time | Type | Level | Target |\n`;
146
+ content += `|------|------|-------|--------|\n`;
147
+ for (const log of logs) {
148
+ const time = log.timestamp.split("T")[1].split(".")[0];
149
+ content += `| ${time} | ${log.type} | ${log.level} | \`${log.target.slice(0, 30)}...\` |\n`;
150
+ }
151
+ api.sendMessage({
152
+ customType: SECURITY_MESSAGE_TYPE,
153
+ content,
154
+ display: true,
155
+ }, { triggerTurn: false });
156
+ };
157
+ const showLogs = (args, ctx) => {
158
+ const logger = getLogger(ctx);
159
+ const limit = parseInt(args) || 50;
160
+ const logs = logger.query({ limit });
161
+ let content = `# Security Audit Logs (${logs.length} entries)\n\n`;
162
+ for (const log of logs) {
163
+ const icon = log.status === "blocked" ? "🔴" : log.level === "dangerous" ? "⚠️" : "✅";
164
+ content += `${icon} [${log.timestamp}] ${log.type}: ${log.operation}\n`;
165
+ content += ` Target: ${log.target}\n`;
166
+ content += ` Level: ${log.level}, Status: ${log.status}\n`;
167
+ if (log.reason) {
168
+ content += ` Reason: ${log.reason}\n`;
169
+ }
170
+ content += "\n";
171
+ }
172
+ api.sendMessage({
173
+ customType: SECURITY_MESSAGE_TYPE,
174
+ content,
175
+ display: true,
176
+ }, { triggerTurn: false });
177
+ };
178
+ const showStats = (ctx) => {
179
+ const logger = getLogger(ctx);
180
+ const stats = logger.getStats();
181
+ let content = `# Security Statistics\n\n`;
182
+ content += `## Overview\n`;
183
+ content += `- Period: ${stats.periodStart.split("T")[0]} ~ ${stats.periodEnd.split("T")[0]}\n`;
184
+ content += `- Total Events: ${stats.totalEvents}\n\n`;
185
+ content += `## By Level\n`;
186
+ content += `- Safe: ${stats.byLevel.safe}\n`;
187
+ content += `- Warning: ${stats.byLevel.warning}\n`;
188
+ content += `- Dangerous: ${stats.byLevel.dangerous}\n\n`;
189
+ content += `## By Status\n`;
190
+ content += `- Allowed: ${stats.byStatus.allowed}\n`;
191
+ content += `- Blocked: ${stats.byStatus.blocked}\n`;
192
+ content += `- Confirmed: ${stats.byStatus.confirmed}\n\n`;
193
+ if (stats.dangerousPatterns.length > 0) {
194
+ content += `## Dangerous Patterns\n`;
195
+ for (const p of stats.dangerousPatterns) {
196
+ content += `- \`${p.pattern}\`: ${p.count}\n`;
197
+ }
198
+ }
199
+ api.sendMessage({
200
+ customType: SECURITY_MESSAGE_TYPE,
201
+ content,
202
+ display: true,
203
+ }, { triggerTurn: false });
204
+ };
205
+ const clearLogs = (ctx) => {
206
+ const logger = getLogger(ctx);
207
+ logger.clear();
208
+ api.sendMessage({
209
+ customType: SECURITY_MESSAGE_TYPE,
210
+ content: "✅ Security audit logs cleared.",
211
+ display: true,
212
+ }, { triggerTurn: false });
213
+ };
100
214
  // /security - Show security dashboard
101
215
  api.registerCommand("security", {
102
- description: "Show security audit dashboard and logs",
103
- handler: async (_args, ctx) => {
104
- const logger = getLogger(ctx);
105
- const stats = logger.getStats();
106
- const logs = logger.query({ limit: 20 });
107
- let content = `# 🔒 Security Audit\n\n`;
108
- content += `## Statistics\n`;
109
- content += `- Total Events: ${stats.totalEvents}\n`;
110
- content += `- Dangerous: ${stats.byLevel.dangerous}\n`;
111
- content += `- Warnings: ${stats.byLevel.warning}\n`;
112
- content += `- Blocked: ${stats.byStatus.blocked}\n\n`;
113
- if (stats.dangerousPatterns.length > 0) {
114
- content += `## Dangerous Patterns\n`;
115
- for (const p of stats.dangerousPatterns.slice(0, 5)) {
116
- content += `- \`${p.pattern}\`: ${p.count}\n`;
117
- }
118
- content += `\n`;
119
- }
120
- content += `## Recent Events\n`;
121
- content += `| Time | Type | Level | Target |\n`;
122
- content += `|------|------|-------|--------|\n`;
123
- for (const log of logs) {
124
- const time = log.timestamp.split("T")[1].split(".")[0];
125
- content += `| ${time} | ${log.type} | ${log.level} | \`${log.target.slice(0, 30)}...\` |\n`;
216
+ description: "Review security activity, logs, stats, or clear saved events",
217
+ getArgumentCompletions: getSecurityArgumentCompletions,
218
+ handler: async (args, ctx) => {
219
+ const [action, ...rest] = args.trim().split(/\s+/);
220
+ switch (action) {
221
+ case "":
222
+ case "dashboard":
223
+ showDashboard(ctx);
224
+ return;
225
+ case "logs":
226
+ showLogs(rest.join(" "), ctx);
227
+ return;
228
+ case "stats":
229
+ showStats(ctx);
230
+ return;
231
+ case "clear":
232
+ clearLogs(ctx);
233
+ return;
234
+ default:
235
+ ctx.ui.notify("Usage: /security [dashboard|logs|stats|clear]", "info");
126
236
  }
127
- api.sendMessage({
128
- customType: SECURITY_MESSAGE_TYPE,
129
- content,
130
- display: true,
131
- }, { triggerTurn: false });
132
237
  },
133
238
  });
134
239
  // /security-logs - Show detailed logs
135
240
  api.registerCommand("security-logs", {
136
- description: "Show detailed security audit logs",
241
+ description: "Show detailed security event history",
242
+ getArgumentCompletions: getSecurityLogLimitCompletions,
137
243
  handler: async (args, ctx) => {
138
- const logger = getLogger(ctx);
139
- const limit = parseInt(args) || 50;
140
- const logs = logger.query({ limit });
141
- let content = `# Security Audit Logs (${logs.length} entries)\n\n`;
142
- for (const log of logs) {
143
- const icon = log.status === "blocked" ? "🔴" : log.level === "dangerous" ? "⚠️" : "✅";
144
- content += `${icon} [${log.timestamp}] ${log.type}: ${log.operation}\n`;
145
- content += ` Target: ${log.target}\n`;
146
- content += ` Level: ${log.level}, Status: ${log.status}\n`;
147
- if (log.reason) {
148
- content += ` Reason: ${log.reason}\n`;
149
- }
150
- content += "\n";
151
- }
152
- api.sendMessage({
153
- customType: SECURITY_MESSAGE_TYPE,
154
- content,
155
- display: true,
156
- }, { triggerTurn: false });
244
+ showLogs(args, ctx);
157
245
  },
158
246
  });
159
247
  // /security-stats - Show statistics
160
248
  api.registerCommand("security-stats", {
161
- description: "Show security audit statistics",
249
+ description: "Show security event counts",
162
250
  handler: async (_args, ctx) => {
163
- const logger = getLogger(ctx);
164
- const stats = logger.getStats();
165
- let content = `# Security Statistics\n\n`;
166
- content += `## Overview\n`;
167
- content += `- Period: ${stats.periodStart.split("T")[0]} ~ ${stats.periodEnd.split("T")[0]}\n`;
168
- content += `- Total Events: ${stats.totalEvents}\n\n`;
169
- content += `## By Level\n`;
170
- content += `- Safe: ${stats.byLevel.safe}\n`;
171
- content += `- Warning: ${stats.byLevel.warning}\n`;
172
- content += `- Dangerous: ${stats.byLevel.dangerous}\n\n`;
173
- content += `## By Status\n`;
174
- content += `- Allowed: ${stats.byStatus.allowed}\n`;
175
- content += `- Blocked: ${stats.byStatus.blocked}\n`;
176
- content += `- Confirmed: ${stats.byStatus.confirmed}\n\n`;
177
- if (stats.dangerousPatterns.length > 0) {
178
- content += `## Dangerous Patterns\n`;
179
- for (const p of stats.dangerousPatterns) {
180
- content += `- \`${p.pattern}\`: ${p.count}\n`;
181
- }
182
- }
183
- api.sendMessage({
184
- customType: SECURITY_MESSAGE_TYPE,
185
- content,
186
- display: true,
187
- }, { triggerTurn: false });
251
+ showStats(ctx);
188
252
  },
189
253
  });
190
254
  // /security-clear - Clear logs
191
255
  api.registerCommand("security-clear", {
192
- description: "Clear security audit logs",
256
+ description: "Clear saved security events",
193
257
  handler: async (_args, ctx) => {
194
- const logger = getLogger(ctx);
195
- logger.clear();
196
- api.sendMessage({
197
- customType: SECURITY_MESSAGE_TYPE,
198
- content: "✅ Security audit logs cleared.",
199
- display: true,
200
- }, { triggerTurn: false });
258
+ clearLogs(ctx);
201
259
  },
202
260
  });
203
261
  // Tool call is the authoritative pre-execution boundary. Returning
@@ -8,6 +8,17 @@ import { Box, Container, Spacer, Text } from "@pencil-agent/tui";
8
8
  import { SubAgentRunner } from "./subagent-runner.js";
9
9
  import { buildSubAgentHelp, parseSubAgentCommand } from "./subagent-parser.js";
10
10
  const SUBAGENT_MESSAGE_TYPE = "subagent";
11
+ const SUBAGENT_ROOT_COMPLETIONS = [
12
+ { value: "run", label: "run", description: "Start a background helper task" },
13
+ { value: "stop", label: "stop", description: "Stop the current helper task" },
14
+ { value: "status", label: "status", description: "Show current helper task status" },
15
+ { value: "report", label: "report", description: "Show the latest helper report" },
16
+ { value: "apply", label: "apply", description: "Copy isolated changes into this workspace" },
17
+ { value: "help", label: "help", description: "Show helper commands" },
18
+ ];
19
+ const SUBAGENT_RUN_COMPLETIONS = [
20
+ { value: "--write", label: "--write", description: "Allow changes in an isolated workspace" },
21
+ ];
11
22
  // Global runner instance
12
23
  let runner = null;
13
24
  let lastReport = null;
@@ -36,6 +47,7 @@ export default async function subagentExtension(api) {
36
47
  for (const commandName of commandNames) {
37
48
  api.registerCommand(commandName, {
38
49
  description: getCommandDescription(commandName),
50
+ getArgumentCompletions: (argumentPrefix, context) => getArgumentCompletions(commandName, argumentPrefix, context),
39
51
  handler: async (args, ctx) => {
40
52
  const parsed = parseSubAgentCommand(commandName, args);
41
53
  if (!parsed) {
@@ -172,19 +184,31 @@ export default async function subagentExtension(api) {
172
184
  });
173
185
  }
174
186
  }
187
+ function getArgumentCompletions(commandName, argumentPrefix, context) {
188
+ if (commandName === "subagent:run" && context && context.tokenIndex > 0)
189
+ return null;
190
+ const prefix = argumentPrefix.trim().toLowerCase();
191
+ const values = commandName === "subagent"
192
+ ? SUBAGENT_ROOT_COMPLETIONS
193
+ : commandName === "subagent:run"
194
+ ? SUBAGENT_RUN_COMPLETIONS
195
+ : [];
196
+ const matches = values.filter((value) => value.value.startsWith(prefix));
197
+ return matches.length > 0 ? matches.map((value) => ({ ...value })) : null;
198
+ }
175
199
  function getCommandDescription(commandName) {
176
200
  switch (commandName) {
177
201
  case "subagent:run":
178
- return "Run a SubAgent task (/subagent:run <task> [--write])";
202
+ return "Start a background helper task (/subagent:run <task> [--write])";
179
203
  case "subagent:stop":
180
- return "Stop the active SubAgent run";
204
+ return "Stop the active background helper task";
181
205
  case "subagent:status":
182
- return "Show current SubAgent run status";
206
+ return "Show current background helper status";
183
207
  case "subagent:report":
184
- return "Show the latest SubAgent report";
208
+ return "Show the latest background helper report";
185
209
  case "subagent:apply":
186
210
  return "Apply the latest isolated write run to the main workspace";
187
211
  default:
188
- return "SubAgent orchestration help";
212
+ return "Run or inspect a background helper task";
189
213
  }
190
214
  }
@@ -23,7 +23,7 @@
23
23
  * /team:mode <name> <plan|execute|review> - Switch mode
24
24
  */
25
25
  import { TeamRuntime } from "./team-runtime.js";
26
- import { buildTeamHelp, parseTeamCommand } from "./team-parser.js";
26
+ import { buildTeamHelp, getTeamArgumentCompletions, parseTeamCommand } from "./team-parser.js";
27
27
  import { executePreset, formatPresetResult } from "./team-presets.js";
28
28
  import { formatHarnessProgress } from "./team-harness.js";
29
29
  import { formatPsycheWeights } from "./team-psyche.js";
@@ -77,6 +77,7 @@ export default async function teamExtension(api) {
77
77
  for (const commandName of commandNames) {
78
78
  api.registerCommand(commandName, {
79
79
  description: getCommandDescription(commandName),
80
+ getArgumentCompletions: (argumentPrefix, context) => getTeamArgumentCompletions(commandName, argumentPrefix, context),
80
81
  handler: async (args, ctx) => {
81
82
  const parsed = parseTeamCommand(commandName, args);
82
83
  if (!parsed) {
@@ -594,32 +595,32 @@ function getCommandDescription(commandName) {
594
595
  case "team:spawn":
595
596
  return "Create a persistent teammate (/team:spawn <role> [--name <name>])";
596
597
  case "team:send":
597
- return "Send message to a teammate (/team:send <name> <message>)";
598
+ return "Send work or a note to a teammate (/team:send <name> <message>)";
598
599
  case "team:status":
599
600
  return "Show team or teammate status";
600
601
  case "team:stop":
601
602
  return "Stop teammate's current turn";
602
603
  case "team:terminate":
603
- return "Destroy a teammate";
604
+ return "Remove a teammate";
604
605
  case "team:approve":
605
- return "Approve a permission request";
606
+ return "Approve a teammate request";
606
607
  case "team:mode":
607
- return "Switch teammate mode (/team:mode <name> <plan|execute|review>)";
608
+ return "Change how a teammate works (/team:mode <name> <plan|execute|review>)";
608
609
  case "team:preset":
609
610
  return "Create teammates from a preset";
610
611
  case "team:dashboard":
611
612
  return "Toggle the team dashboard";
612
613
  case "team:progress":
613
- return "Show harness progress";
614
+ return "Show teammate progress";
614
615
  case "team:psyche":
615
- return "Show psyche weights";
616
+ return "Show teammate decision settings";
616
617
  case "team:task":
617
618
  return "Manage shared team tasks";
618
619
  case "team:mail":
619
- return "Route teammate-to-teammate mailbox messages";
620
+ return "Send a note from one teammate to another";
620
621
  case "team:allow-path":
621
622
  return "Grant a teammate write access to a path prefix";
622
623
  default:
623
- return "AgentTeam management";
624
+ return "Create or manage teammates";
624
625
  }
625
626
  }
@@ -56,6 +56,23 @@ export interface ParsedTeamCommand {
56
56
  /** For allow-path: path prefix */
57
57
  path?: string;
58
58
  }
59
+ export declare const TEAM_ROOT_COMPLETIONS: readonly ["help", "spawn", "send", "status", "stop", "terminate", "approve", "mode", "preset", "dashboard", "progress", "psyche", "task", "mail", "allow-path"];
60
+ export declare const VALID_ROLES: TeammateRole[];
61
+ export declare const VALID_MODES: TeammateMode[];
62
+ export declare const VALID_PRESETS: PresetName[];
63
+ export declare const VALID_TASK_ACTIONS: readonly ["add", "claim", "done", "block", "cancel", "list"];
64
+ type TeamArgumentCompletionContext = {
65
+ commandName: string;
66
+ argumentText: string;
67
+ argumentPrefix: string;
68
+ tokenIndex: number;
69
+ previousTokens: string[];
70
+ };
71
+ export declare function getTeamArgumentCompletions(commandName: string, argumentPrefix: string, context?: TeamArgumentCompletionContext): Array<{
72
+ value: string;
73
+ label: string;
74
+ description?: string;
75
+ }> | null;
59
76
  /**
60
77
  * Parse a /team command invocation.
61
78
  */
@@ -64,3 +81,4 @@ export declare function parseTeamCommand(commandName: string, args?: string): Pa
64
81
  * Build help text for /team commands.
65
82
  */
66
83
  export declare function buildTeamHelp(): string;
84
+ export {};
@@ -21,7 +21,24 @@
21
21
  * /team:approve <request-id> - Approve permission request
22
22
  * /team:mode <name> <plan|execute|review> - Switch mode
23
23
  */
24
- const VALID_ROLES = [
24
+ export const TEAM_ROOT_COMPLETIONS = [
25
+ "help",
26
+ "spawn",
27
+ "send",
28
+ "status",
29
+ "stop",
30
+ "terminate",
31
+ "approve",
32
+ "mode",
33
+ "preset",
34
+ "dashboard",
35
+ "progress",
36
+ "psyche",
37
+ "task",
38
+ "mail",
39
+ "allow-path",
40
+ ];
41
+ export const VALID_ROLES = [
25
42
  "pm",
26
43
  "architect",
27
44
  "developer",
@@ -34,8 +51,79 @@ const VALID_ROLES = [
34
51
  "verifier",
35
52
  "generic",
36
53
  ];
37
- const VALID_MODES = ["research", "plan", "execute", "review"];
38
- const VALID_PRESETS = ["solo", "duo", "squad"];
54
+ export const VALID_MODES = ["research", "plan", "execute", "review"];
55
+ export const VALID_PRESETS = ["solo", "duo", "squad"];
56
+ export const VALID_TASK_ACTIONS = ["add", "claim", "done", "block", "cancel", "list"];
57
+ const TEAM_SPAWN_FLAGS = ["--name", "--harness"];
58
+ const TEAM_COMPLETION_DESCRIPTIONS = {
59
+ help: "Show team commands",
60
+ spawn: "Create a teammate",
61
+ send: "Send work to a teammate",
62
+ status: "Show teammate status",
63
+ stop: "Stop a teammate's current turn",
64
+ terminate: "Remove a teammate",
65
+ approve: "Approve a teammate request",
66
+ mode: "Change how a teammate works",
67
+ preset: "Start with a preset team shape",
68
+ dashboard: "Show or hide the team panel",
69
+ progress: "Show teammate progress",
70
+ psyche: "Show teammate decision settings",
71
+ task: "Manage shared team tasks",
72
+ mail: "Send a teammate-to-teammate note",
73
+ "allow-path": "Allow a teammate to write in a path",
74
+ pm: "Plan priorities and coordinate work",
75
+ architect: "Design structure and break down trade-offs",
76
+ developer: "Have a teammate write code",
77
+ designer: "Review product and interaction details",
78
+ "data-analyst": "Check evidence, metrics, and results",
79
+ researcher: "Explore read-only context",
80
+ reviewer: "Review code or plans without writing",
81
+ implementer: "Have a teammate write code",
82
+ planner: "Have a teammate make a plan",
83
+ verifier: "Check whether the work is correct",
84
+ generic: "Use a flexible teammate role",
85
+ "--name": "Choose the teammate name",
86
+ "--harness": "Use structured progress tracking",
87
+ research: "Read and investigate without writing",
88
+ plan: "Write a plan before code changes",
89
+ execute: "Allow code changes",
90
+ review: "Review without writing",
91
+ solo: "Use one focused teammate",
92
+ duo: "Use two teammates with complementary roles",
93
+ squad: "Use a larger team for broader work",
94
+ add: "Add a shared task",
95
+ claim: "Assign a task to a teammate",
96
+ done: "Mark a task complete",
97
+ block: "Mark a task blocked",
98
+ cancel: "Cancel a task",
99
+ list: "List shared tasks",
100
+ };
101
+ function getTeamCompletionValues(commandName, context) {
102
+ switch (commandName) {
103
+ case "team":
104
+ return !context || context.tokenIndex === 0 ? TEAM_ROOT_COMPLETIONS : [];
105
+ case "team:spawn":
106
+ if (!context)
107
+ return [...VALID_ROLES, ...TEAM_SPAWN_FLAGS];
108
+ return context.tokenIndex === 0 ? VALID_ROLES : TEAM_SPAWN_FLAGS;
109
+ case "team:preset":
110
+ return !context || context.tokenIndex === 0 ? VALID_PRESETS : [];
111
+ case "team:task":
112
+ return !context || context.tokenIndex === 0 ? VALID_TASK_ACTIONS : [];
113
+ case "team:mode":
114
+ return context?.tokenIndex === 1 ? VALID_MODES : [];
115
+ default:
116
+ return [];
117
+ }
118
+ }
119
+ export function getTeamArgumentCompletions(commandName, argumentPrefix, context) {
120
+ const prefix = argumentPrefix.trim().toLowerCase();
121
+ const values = getTeamCompletionValues(commandName, context);
122
+ const matches = values.filter((value) => value.startsWith(prefix));
123
+ return matches.length > 0
124
+ ? matches.map((value) => ({ value, label: value, description: TEAM_COMPLETION_DESCRIPTIONS[value] }))
125
+ : null;
126
+ }
39
127
  /**
40
128
  * Parse a /team command invocation.
41
129
  */
@@ -11,12 +11,8 @@ import { renderTeamDashboard, renderTeamFooterStatus } from "./team-dashboard.js
11
11
  import { formatPsycheWeights } from "./team-psyche.js";
12
12
  export const TEAM_MESSAGE_TYPE = "team";
13
13
  let dashboardVisible = false;
14
- let dashboardAutoHideTimer;
15
14
  export function clearTeamDashboardTimer() {
16
- if (dashboardAutoHideTimer) {
17
- clearTimeout(dashboardAutoHideTimer);
18
- dashboardAutoHideTimer = undefined;
19
- }
15
+ // Kept for the extension shutdown contract; dashboard visibility is now explicit.
20
16
  }
21
17
  export function createTeamMessageRenderer() {
22
18
  return (message, _options, theme) => {
@@ -129,15 +125,9 @@ export function toggleTeamDashboard(ctx, teamRuntime) {
129
125
  export function updateTeamUi(ctx, teamRuntime) {
130
126
  const teammates = teamRuntime.getAllTeammates();
131
127
  const hasRunning = teammates.some((teammate) => teammate.status === "running");
132
- clearTeamDashboardTimer();
133
- ctx.ui.setStatus("team", renderTeamFooterStatus(teammates));
134
- ctx.ui.setWidget("team-dashboard", dashboardVisible || hasRunning || teammates.length > 0 ? renderTeamDashboard(teammates, 80, { expanded: dashboardVisible }) : undefined);
135
- if (!dashboardVisible && !hasRunning && teammates.length > 0) {
136
- dashboardAutoHideTimer = setTimeout(() => {
137
- ctx.ui.setWidget("team-dashboard", undefined);
138
- dashboardAutoHideTimer = undefined;
139
- }, 30_000);
140
- }
128
+ const shouldShowTeamUi = dashboardVisible || hasRunning;
129
+ ctx.ui.setStatus("team", shouldShowTeamUi ? renderTeamFooterStatus(teammates) : undefined);
130
+ ctx.ui.setWidget("team-dashboard", shouldShowTeamUi ? renderTeamDashboard(teammates, 80, { expanded: dashboardVisible }) : undefined);
141
131
  }
142
132
  export function setTeamActivity(ctx, lines) {
143
133
  const [firstLine] = lines;
@@ -4,6 +4,12 @@ import { applyTokenSavePlan } from "./runner.js";
4
4
  import { planCommand } from "./rewrite.js";
5
5
  import { applyTomlStyleFilter } from "./toml-dsl.js";
6
6
  import { TokenSaveTracker } from "./tracking.js";
7
+ const TOKENSAVE_COMMAND_COMPLETIONS = [
8
+ { value: "summary", label: "summary", description: "Show total shell output savings" },
9
+ { value: "history", label: "history", description: "Show recent shortened commands" },
10
+ { value: "reload", label: "reload", description: "Reload project output-shortening rules" },
11
+ { value: "plan", label: "plan", description: "Preview how a command will be shortened" },
12
+ ];
7
13
  function getTextContent(event) {
8
14
  const parts = event.content.filter((part) => part.type === "text");
9
15
  if (parts.length === 0)
@@ -29,7 +35,14 @@ export default function tokenSaveExtension(api) {
29
35
  configuredFilters = filters;
30
36
  });
31
37
  api.registerCommand("tokensave", {
32
- description: "Show TokenSave token savings for this session (/tokensave [summary|history|plan <cmd>])",
38
+ description: "Review shell output shortening. Usage: /tokensave [summary|history|reload|plan <cmd>]",
39
+ getArgumentCompletions: (argumentPrefix, context) => {
40
+ if (context && context.tokenIndex > 0)
41
+ return null;
42
+ const prefix = argumentPrefix.trim().toLowerCase();
43
+ const values = TOKENSAVE_COMMAND_COMPLETIONS.filter((value) => value.value.startsWith(prefix));
44
+ return values.length > 0 ? values : null;
45
+ },
33
46
  async handler(args, ctx) {
34
47
  const trimmed = args.trim();
35
48
  if (trimmed === "reload") {
@@ -16,6 +16,13 @@ import { SessionManager } from "../../../core/session/session-manager.js";
16
16
  import { APP_NAME, getExportTemplateDir } from "../../../config.js";
17
17
  import { getResolvedThemeColors, getThemeExportColors } from "../../../modes/interactive/theme/theme.js";
18
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const EXPORT_COMMAND_COMPLETIONS = [
20
+ {
21
+ value: "./nanopencil-session.html",
22
+ label: "./nanopencil-session.html",
23
+ description: "Choose the output file for this session export",
24
+ },
25
+ ];
19
26
  // Re-export core functions for use by AgentSession
20
27
  export { exportSessionToHtml, exportFromFile } from "../../../core/export-html/index.js";
21
28
  /**
@@ -239,16 +246,23 @@ export async function extExportFromFile(inputPath, options) {
239
246
  export default async function exportHtmlExtension(api) {
240
247
  // Register /export command
241
248
  api.registerCommand("export", {
242
- description: "Export session to HTML file",
243
- handler: async (_args, ctx) => {
249
+ description: "Save this session as a shareable HTML file",
250
+ getArgumentCompletions: (argumentPrefix, context) => {
251
+ if (context && context.tokenIndex > 0)
252
+ return null;
253
+ const prefix = argumentPrefix.trim().toLowerCase();
254
+ const values = EXPORT_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(prefix));
255
+ return values.length > 0 ? values : null;
256
+ },
257
+ handler: async (args, ctx) => {
244
258
  // Get session manager from context
245
259
  const sessionManager = ctx.sessionManager;
246
260
  if (!sessionManager) {
247
261
  throw new Error("Session manager not available");
248
262
  }
249
- // Export the session (use default output path)
250
- // Pass undefined for state since we don't have access to it here
251
- const filePath = await extExportSessionToHtml(sessionManager, undefined);
263
+ const outputPath = args.trim() || undefined;
264
+ // Pass undefined for state since we don't have access to it here.
265
+ const filePath = await extExportSessionToHtml(sessionManager, undefined, outputPath);
252
266
  console.error(`Session exported to: ${filePath}`);
253
267
  },
254
268
  });
@@ -17,6 +17,10 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
17
17
  import { isAbsolute, join, relative, resolve } from "path";
18
18
  import { DynamicBorder } from "../../../modes/interactive/components/dynamic-border.js";
19
19
  import { getMarkdownTheme } from "../../../modes/interactive/theme/theme.js";
20
+ const SIMPLIFY_COMMAND_COMPLETIONS = [
21
+ { value: "--dry-run", label: "--dry-run", description: "Preview changes without writing files" },
22
+ { value: "--no-test", label: "--no-test", description: "Skip the verification command after applying changes" },
23
+ ];
20
24
  // =============================================================================
21
25
  // Git Utilities
22
26
  // =============================================================================
@@ -416,11 +420,13 @@ async function executeSimplify(options, ctx) {
416
420
  // =============================================================================
417
421
  export default function simplifyExtension(api) {
418
422
  api.registerCommand("simplify", {
419
- description: "Simplify code to reduce cognitive load (Claude Code style)",
420
- getArgumentCompletions: (prefix) => {
421
- // Could add file completions here
422
- const options = ["--dry-run", "--no-test"];
423
- return options.filter((o) => o.startsWith(prefix)).map((o) => ({ value: o, label: o }));
423
+ description: "Suggest smaller code changes for files you choose",
424
+ getArgumentCompletions: (prefix, context) => {
425
+ if (context && context.tokenIndex > 0)
426
+ return null;
427
+ const normalizedPrefix = prefix.trim().toLowerCase();
428
+ const options = SIMPLIFY_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(normalizedPrefix));
429
+ return options.length > 0 ? options : null;
424
430
  },
425
431
  handler: async (args, ctx) => {
426
432
  const parts = args.trim().split(/\s+/).filter(Boolean);