mono-pilot 0.2.9 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +270 -7
  2. package/dist/src/agents-paths.js +36 -0
  3. package/dist/src/brief/blocks.js +83 -0
  4. package/dist/src/brief/defaults.js +60 -0
  5. package/dist/src/brief/frontmatter.js +53 -0
  6. package/dist/src/brief/paths.js +10 -0
  7. package/dist/src/brief/reflection.js +27 -0
  8. package/dist/src/cli.js +62 -5
  9. package/dist/src/cluster/bus.js +102 -0
  10. package/dist/src/cluster/follower.js +137 -0
  11. package/dist/src/cluster/init.js +182 -0
  12. package/dist/src/cluster/leader.js +97 -0
  13. package/dist/src/cluster/log.js +49 -0
  14. package/dist/src/cluster/protocol.js +34 -0
  15. package/dist/src/cluster/services/bus.js +243 -0
  16. package/dist/src/cluster/services/embedding.js +12 -0
  17. package/dist/src/cluster/socket.js +86 -0
  18. package/dist/src/cluster/test-bus.js +175 -0
  19. package/dist/src/cluster_v2/connection-lifecycle.js +31 -0
  20. package/dist/src/cluster_v2/connection-lifecycle.test.js +24 -0
  21. package/dist/src/cluster_v2/connection.js +159 -0
  22. package/dist/src/cluster_v2/connection.test.js +55 -0
  23. package/dist/src/cluster_v2/events.js +102 -0
  24. package/dist/src/cluster_v2/index.js +2 -0
  25. package/dist/src/cluster_v2/observability.js +99 -0
  26. package/dist/src/cluster_v2/observability.test.js +46 -0
  27. package/dist/src/cluster_v2/rpc.js +389 -0
  28. package/dist/src/cluster_v2/rpc.test.js +110 -0
  29. package/dist/src/cluster_v2/runtime.failover.integration.test.js +156 -0
  30. package/dist/src/cluster_v2/runtime.js +531 -0
  31. package/dist/src/cluster_v2/runtime.lease-compromise.integration.test.js +91 -0
  32. package/dist/src/cluster_v2/runtime.lifecycle.integration.test.js +225 -0
  33. package/dist/src/cluster_v2/services/bus.integration.test.js +140 -0
  34. package/dist/src/cluster_v2/services/bus.js +450 -0
  35. package/dist/src/cluster_v2/services/discord/auth-store.js +82 -0
  36. package/dist/src/cluster_v2/services/discord/collector.js +569 -0
  37. package/dist/src/cluster_v2/services/discord/index.js +1 -0
  38. package/dist/src/cluster_v2/services/discord/oauth.js +87 -0
  39. package/dist/src/cluster_v2/services/discord/rpc-client.js +325 -0
  40. package/dist/src/cluster_v2/services/embedding.js +66 -0
  41. package/dist/src/cluster_v2/services/registry-cache.js +107 -0
  42. package/dist/src/cluster_v2/services/registry-cache.test.js +66 -0
  43. package/dist/src/cluster_v2/services/registry.js +36 -0
  44. package/dist/src/cluster_v2/services/twitter/collector.js +1055 -0
  45. package/dist/src/cluster_v2/services/twitter/index.js +1 -0
  46. package/dist/src/config/digest.js +78 -0
  47. package/dist/src/config/discord.js +143 -0
  48. package/dist/src/config/image-gen.js +48 -0
  49. package/dist/src/config/mono-pilot.js +31 -0
  50. package/dist/src/config/twitter.js +100 -0
  51. package/dist/src/extensions/cluster.js +311 -0
  52. package/dist/src/extensions/commands/build-memory.js +76 -0
  53. package/dist/src/extensions/commands/digest/backfill.js +779 -0
  54. package/dist/src/extensions/commands/digest/index.js +1133 -0
  55. package/dist/src/extensions/commands/image-model.js +214 -0
  56. package/dist/src/extensions/game/bus-injection.js +47 -0
  57. package/dist/src/extensions/game/identity.js +83 -0
  58. package/dist/src/extensions/game/mailbox.js +61 -0
  59. package/dist/src/extensions/game/system-prompt.js +134 -0
  60. package/dist/src/extensions/game/tools.js +28 -0
  61. package/dist/src/extensions/lifecycle.js +337 -0
  62. package/dist/src/extensions/mode-runtime.js +26 -2
  63. package/dist/src/extensions/mono-game.js +66 -0
  64. package/dist/src/extensions/mono-pilot.js +100 -18
  65. package/dist/src/extensions/nvim.js +47 -0
  66. package/dist/src/extensions/session-hints.js +60 -35
  67. package/dist/src/extensions/sftp.js +897 -0
  68. package/dist/src/extensions/status.js +676 -0
  69. package/dist/src/extensions/system-events.js +478 -0
  70. package/dist/src/extensions/system-prompt.js +24 -14
  71. package/dist/src/extensions/user-message.js +94 -50
  72. package/dist/src/lsp/client.js +235 -0
  73. package/dist/src/lsp/index.js +165 -0
  74. package/dist/src/lsp/runtime.js +67 -0
  75. package/dist/src/lsp/server.js +242 -0
  76. package/dist/src/mcp/config.js +112 -0
  77. package/dist/src/{utils/mcp-client.js → mcp/protocol.js} +1 -100
  78. package/dist/src/mcp/servers.js +90 -0
  79. package/dist/src/memory/build-memory.js +103 -0
  80. package/dist/src/memory/config/defaults.js +55 -0
  81. package/dist/src/memory/config/loader.js +29 -0
  82. package/dist/src/memory/config/paths.js +9 -0
  83. package/dist/src/memory/config/resolve.js +90 -0
  84. package/dist/src/memory/config/types.js +1 -0
  85. package/dist/src/memory/embeddings/batch-runner.js +39 -0
  86. package/dist/src/memory/embeddings/cache.js +47 -0
  87. package/dist/src/memory/embeddings/chunk-limits.js +26 -0
  88. package/dist/src/memory/embeddings/input-limits.js +48 -0
  89. package/dist/src/memory/embeddings/local.js +108 -0
  90. package/dist/src/memory/embeddings/types.js +1 -0
  91. package/dist/src/memory/index-manager.js +552 -0
  92. package/dist/src/memory/indexing/embeddings.js +67 -0
  93. package/dist/src/memory/indexing/files.js +180 -0
  94. package/dist/src/memory/indexing/index-file.js +105 -0
  95. package/dist/src/memory/log.js +38 -0
  96. package/dist/src/memory/paths.js +15 -0
  97. package/dist/src/memory/runtime/index.js +299 -0
  98. package/dist/src/memory/runtime/thread.js +116 -0
  99. package/dist/src/memory/search/fts.js +57 -0
  100. package/dist/src/memory/search/hybrid.js +50 -0
  101. package/dist/src/memory/search/text.js +30 -0
  102. package/dist/src/memory/search/vector.js +43 -0
  103. package/dist/src/memory/session/content-hash.js +7 -0
  104. package/dist/src/memory/session/entry.js +33 -0
  105. package/dist/src/memory/session/flush-policy.js +34 -0
  106. package/dist/src/memory/session/hook.js +191 -0
  107. package/dist/src/memory/session/paths.js +15 -0
  108. package/dist/src/memory/session/session-reader.js +88 -0
  109. package/dist/src/memory/session/transcript/content-hash.js +7 -0
  110. package/dist/src/memory/session/transcript/entry.js +28 -0
  111. package/dist/src/memory/session/transcript/flush.js +56 -0
  112. package/dist/src/memory/session/transcript/paths.js +28 -0
  113. package/dist/src/memory/session/transcript/reader.js +112 -0
  114. package/dist/src/memory/session/transcript/state.js +31 -0
  115. package/dist/src/memory/store/schema.js +89 -0
  116. package/dist/src/memory/store/sqlite.js +89 -0
  117. package/dist/src/memory/types.js +1 -0
  118. package/dist/src/memory/warm.js +25 -0
  119. package/dist/src/rules/discovery.js +41 -0
  120. package/dist/{tools → src/tools}/README.md +29 -3
  121. package/dist/{tools → src/tools}/apply-patch-description.md +8 -2
  122. package/dist/{tools → src/tools}/apply-patch.js +174 -104
  123. package/dist/{tools → src/tools}/apply-patch.test.js +52 -1
  124. package/dist/{tools/ask-question.js → src/tools/ask-user-question.js} +3 -3
  125. package/dist/src/tools/ast-grep.js +357 -0
  126. package/dist/src/tools/brief-write.js +122 -0
  127. package/dist/src/tools/bus-send.js +100 -0
  128. package/dist/{tools → src/tools}/call-mcp-tool.js +40 -124
  129. package/dist/src/tools/codex-apply-patch-description.md +52 -0
  130. package/dist/src/tools/codex-apply-patch.js +540 -0
  131. package/dist/{tools → src/tools}/delete.js +24 -0
  132. package/dist/src/tools/exit-plan-mode.js +83 -0
  133. package/dist/{tools → src/tools}/fetch-mcp-resource.js +56 -100
  134. package/dist/src/tools/generate-image.js +567 -0
  135. package/dist/{tools → src/tools}/glob.js +55 -1
  136. package/dist/{tools → src/tools}/list-mcp-resources.js +46 -57
  137. package/dist/{tools → src/tools}/list-mcp-tools.js +52 -63
  138. package/dist/src/tools/ls.js +48 -0
  139. package/dist/src/tools/lsp-diagnostics.js +67 -0
  140. package/dist/src/tools/lsp-symbols.js +54 -0
  141. package/dist/src/tools/mailbox.js +85 -0
  142. package/dist/src/tools/memory-get.js +90 -0
  143. package/dist/src/tools/memory-search.js +180 -0
  144. package/dist/{tools → src/tools}/plan-mode-reminder.md +3 -4
  145. package/dist/{tools → src/tools}/read-file.js +8 -19
  146. package/dist/{tools → src/tools}/rg.js +10 -20
  147. package/dist/{tools → src/tools}/shell.js +19 -42
  148. package/dist/{tools → src/tools}/subagent.js +255 -6
  149. package/dist/{tools → src/tools}/switch-mode.js +37 -6
  150. package/dist/{tools → src/tools}/web-fetch.js +105 -7
  151. package/dist/{tools → src/tools}/web-search.js +29 -1
  152. package/package.json +21 -9
  153. /package/dist/{tools → src/tools}/ask-mode-reminder.md +0 -0
  154. /package/dist/{tools → src/tools}/rg.test.js +0 -0
  155. /package/dist/{tools → src/tools}/semantic-search-description.md +0 -0
  156. /package/dist/{tools → src/tools}/semantic-search.js +0 -0
  157. /package/dist/{tools → src/tools}/shell-description.md +0 -0
  158. /package/dist/{tools → src/tools}/subagent-description.md +0 -0
@@ -0,0 +1,90 @@
1
+ import { keyHint } from "@mariozechner/pi-coding-agent";
2
+ import { Text } from "@mariozechner/pi-tui";
3
+ import { Type } from "@sinclair/typebox";
4
+ import { deriveAgentId } from "../agents-paths.js";
5
+ import { getMemorySearchManager } from "../memory/runtime/index.js";
6
+ const DESCRIPTION = "Read a snippet from a memory file returned by memory_search. Supports optional line ranges.";
7
+ const memoryGetSchema = Type.Object({
8
+ path: Type.String({ description: "Absolute path to a memory file." }),
9
+ from: Type.Optional(Type.Number({ description: "1-based line number to start from." })),
10
+ lines: Type.Optional(Type.Number({ description: "Number of lines to read." })),
11
+ });
12
+ export default function memoryGetExtension(pi) {
13
+ pi.registerTool({
14
+ label: "MemoryGet",
15
+ name: "MemoryGet",
16
+ description: DESCRIPTION,
17
+ parameters: memoryGetSchema,
18
+ renderCall(args, theme) {
19
+ const input = args;
20
+ const pathArg = typeof input.path === "string" && input.path.trim().length > 0
21
+ ? input.path
22
+ : "(missing path)";
23
+ const parts = [pathArg];
24
+ if (input.from !== undefined)
25
+ parts.push(`from=${input.from}`);
26
+ if (input.lines !== undefined)
27
+ parts.push(`lines=${input.lines}`);
28
+ let text = theme.fg("toolTitle", theme.bold("MemoryGet"));
29
+ text += ` ${theme.fg("toolOutput", parts.join(" "))}`;
30
+ return new Text(text, 0, 0);
31
+ },
32
+ renderResult(result, { expanded, isPartial }, theme) {
33
+ if (isPartial) {
34
+ return new Text(theme.fg("muted", "Reading..."), 0, 0);
35
+ }
36
+ const textBlock = result.content.find((entry) => entry.type === "text" && typeof entry.text === "string");
37
+ if (!textBlock || typeof textBlock.text !== "string") {
38
+ return new Text(theme.fg("error", "No text result returned."), 0, 0);
39
+ }
40
+ const fullText = textBlock.text;
41
+ const lineCount = fullText.split("\n").length;
42
+ if (!expanded) {
43
+ const summary = `${lineCount} lines (click or ${keyHint("expandTools", "to expand")})`;
44
+ return new Text(theme.fg("muted", summary), 0, 0);
45
+ }
46
+ let text = fullText
47
+ .split("\n")
48
+ .map((line) => theme.fg("toolOutput", line))
49
+ .join("\n");
50
+ text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
51
+ return new Text(text, 0, 0);
52
+ },
53
+ execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
54
+ const manager = await getMemorySearchManager({
55
+ workspaceDir: ctx.cwd,
56
+ agentId: deriveAgentId(ctx.cwd),
57
+ });
58
+ if (!manager) {
59
+ return {
60
+ content: [
61
+ { type: "text", text: "Memory get is disabled or unavailable." },
62
+ ],
63
+ details: { path: params.path, from: params.from, lines: params.lines, disabled: true },
64
+ };
65
+ }
66
+ try {
67
+ const result = await manager.get(params.path, params.from, params.lines);
68
+ return {
69
+ content: [{ type: "text", text: result.text }],
70
+ details: {
71
+ path: result.path,
72
+ from: params.from,
73
+ lines: params.lines,
74
+ },
75
+ };
76
+ }
77
+ catch (error) {
78
+ const message = error instanceof Error ? error.message : String(error);
79
+ return {
80
+ content: [{ type: "text", text: `Memory get failed: ${message}` }],
81
+ details: {
82
+ path: params.path,
83
+ from: params.from,
84
+ lines: params.lines,
85
+ },
86
+ };
87
+ }
88
+ },
89
+ });
90
+ }
@@ -0,0 +1,180 @@
1
+ import { keyHint } from "@mariozechner/pi-coding-agent";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { Text } from "@mariozechner/pi-tui";
4
+ import { deriveAgentId } from "../agents-paths.js";
5
+ import { getMemorySearchManager } from "../memory/runtime/index.js";
6
+ const DESCRIPTION = "Search the memory index for relevant snippets. Supports scope: self, agent, all. Returns paths, line ranges, and scored excerpts.";
7
+ const memorySearchSchema = Type.Object({
8
+ query: Type.String({ description: "Search query for memory snippets." }),
9
+ instruct: Type.Optional(Type.String({ description: "Task instruction for the embedding model to improve retrieval quality. Default: general code/doc retrieval." })),
10
+ maxResults: Type.Optional(Type.Number({ description: "Maximum number of results to return." })),
11
+ minScore: Type.Optional(Type.Number({ description: "Minimum relevance score to include." })),
12
+ scope: Type.Optional(Type.Union([Type.Literal("self"), Type.Literal("agent"), Type.Literal("all")], { description: "Search scope." })),
13
+ targetAgentId: Type.Optional(Type.String({ description: "Target agent ID (required when scope=agent)." })),
14
+ });
15
+ const MAX_RENDER_QUERY_CHARS = 120;
16
+ const MAX_RENDER_AGENTID_CHARS = 120;
17
+ function compactForCommandArg(value, maxLength) {
18
+ const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\\n").trim();
19
+ if (normalized.length <= maxLength)
20
+ return normalized;
21
+ return `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
22
+ }
23
+ function shellQuoteArg(value) {
24
+ if (value.length === 0)
25
+ return "''";
26
+ if (/^[A-Za-z0-9_./:=,+-]+$/.test(value))
27
+ return value;
28
+ return `'${value.replace(/'/g, `"'"'"'`)}'`;
29
+ }
30
+ function formatResultLine(params) {
31
+ const score = params.score.toFixed(3);
32
+ const location = `${params.path}:${params.startLine}-${params.endLine}`;
33
+ const scopePrefix = params.agentId ? `[${params.agentId}] ` : "";
34
+ return `${scopePrefix}${location} (score ${score})\n${params.snippet}`;
35
+ }
36
+ export default function memorySearchExtension(pi) {
37
+ pi.registerTool({
38
+ label: "MemorySearch",
39
+ name: "MemorySearch",
40
+ description: DESCRIPTION,
41
+ parameters: memorySearchSchema,
42
+ renderCall(args, theme) {
43
+ const input = args;
44
+ const rawQuery = typeof input.query === "string" ? input.query : "";
45
+ const query = rawQuery.trim().length > 0
46
+ ? compactForCommandArg(rawQuery, MAX_RENDER_QUERY_CHARS)
47
+ : "(missing query)";
48
+ const scope = input.scope;
49
+ const targetAgentId = typeof input.targetAgentId === "string" && input.targetAgentId.trim().length > 0
50
+ ? compactForCommandArg(input.targetAgentId, MAX_RENDER_AGENTID_CHARS)
51
+ : undefined;
52
+ const instruct = typeof input.instruct === "string" && input.instruct.trim().length > 0
53
+ ? compactForCommandArg(input.instruct, MAX_RENDER_QUERY_CHARS)
54
+ : undefined;
55
+ const commandArgs = [query];
56
+ if (instruct)
57
+ commandArgs.push("--instruct", instruct);
58
+ if (scope)
59
+ commandArgs.push("--scope", scope);
60
+ if (targetAgentId)
61
+ commandArgs.push("--target-agent-id", targetAgentId);
62
+ if (input.maxResults != null)
63
+ commandArgs.push("--max-results", String(input.maxResults));
64
+ if (input.minScore != null)
65
+ commandArgs.push("--min-score", String(input.minScore));
66
+ const commandText = commandArgs.map(shellQuoteArg).join(" ");
67
+ let text = theme.fg("toolTitle", theme.bold("MemorySearch"));
68
+ text += ` ${theme.fg("toolOutput", commandText)}`;
69
+ return new Text(text, 0, 0);
70
+ },
71
+ renderResult(result, { expanded, isPartial }, theme) {
72
+ if (isPartial) {
73
+ return new Text(theme.fg("muted", "Searching..."), 0, 0);
74
+ }
75
+ const textBlock = result.content.find((entry) => entry.type === "text" && typeof entry.text === "string");
76
+ if (!textBlock || typeof textBlock.text !== "string") {
77
+ return new Text(theme.fg("error", "No text result returned."), 0, 0);
78
+ }
79
+ const fullText = textBlock.text;
80
+ const details = result.details;
81
+ const resultCount = details?.resultCount ?? 0;
82
+ if (!expanded) {
83
+ const summary = `${resultCount} results (click or ${keyHint("expandTools", "to expand")})`;
84
+ return new Text(theme.fg("muted", summary), 0, 0);
85
+ }
86
+ let text = fullText
87
+ .split("\n")
88
+ .map((line) => theme.fg("toolOutput", line))
89
+ .join("\n");
90
+ text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
91
+ return new Text(text, 0, 0);
92
+ },
93
+ execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
94
+ const query = params.query.trim();
95
+ const instruct = params.instruct;
96
+ const maxResults = params.maxResults;
97
+ const minScore = params.minScore;
98
+ const scope = params.scope ?? "self";
99
+ const targetAgentId = params.targetAgentId;
100
+ if (scope === "agent" && !targetAgentId) {
101
+ return {
102
+ content: [
103
+ {
104
+ type: "text",
105
+ text: "targetAgentId is required when scope=agent.",
106
+ },
107
+ ],
108
+ details: {
109
+ query,
110
+ maxResults,
111
+ minScore,
112
+ scope,
113
+ targetAgentId,
114
+ resultCount: 0,
115
+ },
116
+ };
117
+ }
118
+ const manager = await getMemorySearchManager({
119
+ workspaceDir: ctx.cwd,
120
+ agentId: deriveAgentId(ctx.cwd),
121
+ });
122
+ if (!manager) {
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text",
127
+ text: "Memory search is disabled or unavailable.",
128
+ },
129
+ ],
130
+ details: {
131
+ query,
132
+ maxResults,
133
+ minScore,
134
+ scope,
135
+ targetAgentId,
136
+ resultCount: 0,
137
+ },
138
+ };
139
+ }
140
+ try {
141
+ const results = await manager.search(query, {
142
+ instruct,
143
+ maxResults,
144
+ minScore,
145
+ scope,
146
+ targetAgentId,
147
+ });
148
+ const includeAgentId = scope !== "self";
149
+ const lines = results.map((result) => formatResultLine({
150
+ path: result.path,
151
+ startLine: result.startLine,
152
+ endLine: result.endLine,
153
+ score: result.score,
154
+ snippet: result.snippet,
155
+ agentId: includeAgentId ? result.agentId : undefined,
156
+ }));
157
+ const output = lines.length > 0 ? lines.join("\n\n") : "No memory matches found.";
158
+ const details = {
159
+ query,
160
+ maxResults,
161
+ minScore,
162
+ scope,
163
+ targetAgentId,
164
+ resultCount: results.length,
165
+ };
166
+ return {
167
+ content: [{ type: "text", text: output }],
168
+ details,
169
+ };
170
+ }
171
+ catch (error) {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ return {
174
+ content: [{ type: "text", text: `Memory search failed: ${message}` }],
175
+ details: { query, maxResults, minScore, scope, targetAgentId, resultCount: 0 },
176
+ };
177
+ }
178
+ },
179
+ });
180
+ }
@@ -3,11 +3,11 @@ You are now in Plan mode. Continue with the task in the new mode.
3
3
  </system_reminder>
4
4
 
5
5
  <system_reminder>
6
- Plan mode is active, unless you have already seen the <end_plan_mode/> tag below. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received (for example, to make edits). Instead, you should:
6
+ Plan mode is active until you call ExitPlanMode. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received (for example, to make edits). Instead, you should:
7
7
 
8
8
  1. Answer the user's query comprehensively by searching to gather information
9
9
 
10
- 2. If you do not have enough information to create an accurate plan, you MUST ask the user for more information. If any of the user instructions are ambiguous, you MUST ask the user to clarify. Do not call the create_plan tool until the user has answered all your questions. Propose sensible defaults and avoid overwhelming the user with many questions about trivial details. Don't ask any questions in the plan itself, since the user can only Accept or Reject the plan.
10
+ 2. If you do not have enough information to create an accurate plan, you MUST ask the user for more information. If any of the user instructions are ambiguous, you MUST ask the user to clarify. Do not call ExitPlanMode until the user has answered all your questions. Propose sensible defaults and avoid overwhelming the user with many questions about trivial details.
11
11
 
12
12
  3. If the user's request is too broad, you MUST ask the user questions that narrow down the scope of the plan. ONLY ask 1-2 critical questions at a time.
13
13
 
@@ -15,7 +15,7 @@ Plan mode is active, unless you have already seen the <end_plan_mode/> tag below
15
15
 
16
16
  5. If you have determined that you will need to ask questions, you should ask them IMMEDIATELY at the start of the conversation. Prefer a small pre-read beforehand only if ≤5 files (~20s) will likely answer them.
17
17
 
18
- 6. When you're done researching, present your plan by calling the create_plan tool, which will prompt the user to confirm the plan. Do NOT make any file changes or run any tools that modify the system state in any way until the user has confirmed the plan.
18
+ 6. When you're done researching and your plan file is finalized, first present the plan to the user and wait for explicit approval. Only after the user approves should you call ExitPlanMode to leave Plan mode. Do NOT make any file changes or run any tools that modify the system state in any way until the user has confirmed the plan.
19
19
 
20
20
  7. The plan should be concise, specific and actionable. Cite specific file paths and, if the plan is for a targeted code change, essential snippets of code (only if concise, informative and non-obvious). When mentioning files, use markdown links with the full file path (for example, `[backend/src/foo.ts](backend/src/foo.ts)`). The plan should be formatted as markdown.
21
21
 
@@ -58,5 +58,4 @@ When writing mermaid diagrams:
58
58
  - Click events are disabled for security - don't use `click` syntax
59
59
  </mermaid_syntax>
60
60
 
61
- <begin_plan_mode/>
62
61
  </system_reminder>
@@ -18,7 +18,6 @@ Image Support:
18
18
  PDF Support:
19
19
  - PDF files are converted into text content automatically (subject to the same character limits as other files).`;
20
20
  const MAX_RENDER_PATH_CHARS = 120;
21
- const MAX_COLLAPSED_RESULT_LINES = 5;
22
21
  const readSchema = Type.Object({
23
22
  path: Type.String({ description: "The absolute path of the file to read." }),
24
23
  offset: Type.Optional(Type.Number({
@@ -112,19 +111,6 @@ function prefixLineNumbers(text, startLine, details) {
112
111
  .join("\n");
113
112
  return notice ? `${numberedText}\n\n${notice}` : numberedText;
114
113
  }
115
- function getCollapsedResultText(text, expanded) {
116
- if (text.length === 0) {
117
- return { output: text, remaining: 0 };
118
- }
119
- const lines = text.split("\n");
120
- if (expanded || lines.length <= MAX_COLLAPSED_RESULT_LINES) {
121
- return { output: text, remaining: 0 };
122
- }
123
- return {
124
- output: lines.slice(0, MAX_COLLAPSED_RESULT_LINES).join("\n"),
125
- remaining: lines.length - MAX_COLLAPSED_RESULT_LINES,
126
- };
127
- }
128
114
  export default function (pi) {
129
115
  // System prompt injection is handled centrally by system-prompt extension.
130
116
  pi.registerTool({
@@ -157,14 +143,17 @@ export default function (pi) {
157
143
  if (!textContent) {
158
144
  return new Text("", 0, 0);
159
145
  }
160
- const { output, remaining } = getCollapsedResultText(textContent.text, expanded);
161
- let text = output
146
+ const fullText = textContent.text;
147
+ const lineCount = fullText.split("\n").length;
148
+ if (!expanded) {
149
+ const summary = `${lineCount} lines (click or ${keyHint("expandTools", "to expand")})`;
150
+ return new Text(theme.fg("muted", summary), 0, 0);
151
+ }
152
+ let text = fullText
162
153
  .split("\n")
163
154
  .map((line) => theme.fg("toolOutput", line))
164
155
  .join("\n");
165
- if (!expanded && remaining > 0) {
166
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
167
- }
156
+ text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
168
157
  return new Text(text, 0, 0);
169
158
  },
170
159
  async execute(toolCallId, params, signal, onUpdate, ctx) {
@@ -298,21 +298,6 @@ function shellQuoteArg(value) {
298
298
  return value;
299
299
  return `'${value.replace(/'/g, `'"'"'`)}'`;
300
300
  }
301
- function getCollapsedResultText(text, expanded) {
302
- if (text.length === 0) {
303
- return { output: text, remaining: 0 };
304
- }
305
- const lines = text.split("\n");
306
- // Use 20 lines as the standard collapse threshold
307
- const MAX_COLLAPSED_RESULT_LINES = 20;
308
- if (expanded || lines.length <= MAX_COLLAPSED_RESULT_LINES) {
309
- return { output: text, remaining: 0 };
310
- }
311
- return {
312
- output: lines.slice(0, MAX_COLLAPSED_RESULT_LINES).join("\n"),
313
- remaining: lines.length - MAX_COLLAPSED_RESULT_LINES,
314
- };
315
- }
316
301
  export const __test__ = {
317
302
  applyPagination,
318
303
  buildRgArgs,
@@ -380,15 +365,20 @@ export default function (pi) {
380
365
  if (!textBlock || typeof textBlock.text !== "string") {
381
366
  return new Text(theme.fg("error", "No text result returned."), 0, 0);
382
367
  }
383
- const { output, remaining } = getCollapsedResultText(textBlock.text, expanded);
368
+ const fullText = textBlock.text;
369
+ const lineCount = fullText.split("\n").length;
370
+ const details = result.details;
371
+ const matchCount = details?.returned_entries ?? lineCount;
372
+ if (!expanded) {
373
+ const summary = `${matchCount} matches, ${lineCount} lines (click or ${keyHint("expandTools", "to expand")})`;
374
+ return new Text(theme.fg("muted", summary), 0, 0);
375
+ }
384
376
  const isErrorResult = result.isError === true || result.details?.is_error === true;
385
- let text = output
377
+ let text = fullText
386
378
  .split("\n")
387
379
  .map((line) => (isErrorResult ? theme.fg("error", line) : theme.fg("toolOutput", line)))
388
380
  .join("\n");
389
- if (!expanded && remaining > 0) {
390
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
391
- }
381
+ text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
392
382
  return new Text(text, 0, 0);
393
383
  },
394
384
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -1,17 +1,16 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, writeSync, } from "node:fs";
3
- import { tmpdir } from "node:os";
4
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, getShellConfig, keyHint, truncateTail, } from "@mariozechner/pi-coding-agent";
7
6
  import { Text } from "@mariozechner/pi-tui";
8
7
  import { Type } from "@sinclair/typebox";
8
+ import { deriveAgentId, getAgentTerminalsDir } from "../agents-paths.js";
9
9
  // Tool docs are surfaced via system-prompt extension functions namespace.
10
10
  const DEFAULT_BLOCK_UNTIL_MS = 30_000;
11
11
  const UPDATE_RUNNING_SECONDS_EVERY_MS = 5_000;
12
12
  const OUTPUT_CAPTURE_LIMIT_BYTES = DEFAULT_MAX_BYTES * 2;
13
13
  const MAX_RENDER_COMMAND_CHARS = 180;
14
- const MAX_COLLAPSED_RESULT_LINES = 5;
15
14
  const DESCRIPTION = readFileSync(fileURLToPath(new URL("./shell-description.md", import.meta.url)), "utf-8").trim();
16
15
  const shellSchema = Type.Object({
17
16
  command: Type.String({ description: "The command to execute" }),
@@ -32,24 +31,10 @@ function compactCommandForRender(command) {
32
31
  }
33
32
  return `${singleLine.slice(0, MAX_RENDER_COMMAND_CHARS - 1)}…`;
34
33
  }
35
- function encodeWorkspacePath(workspace) {
36
- return resolve(workspace)
37
- .replace(/^[A-Za-z]:/, (match) => match[0])
38
- .replace(/[\\/]/g, "-")
39
- .replace(/^-+/, "");
40
- }
41
- function getTerminalsDir(workspaceCwd) {
42
- const workspaceKey = encodeWorkspacePath(workspaceCwd);
43
- const primary = join(resolve(workspaceCwd), ".pi", "terminals");
44
- try {
45
- mkdirSync(primary, { recursive: true });
46
- return primary;
47
- }
48
- catch {
49
- const fallback = join(tmpdir(), "pi-shell", workspaceKey, "terminals");
50
- mkdirSync(fallback, { recursive: true });
51
- return fallback;
52
- }
34
+ function getTerminalsDir(agentId, sessionId) {
35
+ const dir = getAgentTerminalsDir(agentId, sessionId);
36
+ mkdirSync(dir, { recursive: true });
37
+ return dir;
53
38
  }
54
39
  function getNextTerminalId(terminalsDir) {
55
40
  const entries = readdirSync(terminalsDir, { withFileTypes: true });
@@ -84,21 +69,6 @@ function parseEnvSnapshot(snapshot) {
84
69
  }
85
70
  return parsed;
86
71
  }
87
- function getCollapsedResultText(text, expanded) {
88
- if (text.length === 0) {
89
- return { output: text, remaining: 0 };
90
- }
91
- const lines = text.split("\n");
92
- if (expanded || lines.length <= MAX_COLLAPSED_RESULT_LINES) {
93
- return { output: text, remaining: 0 };
94
- }
95
- // Show the *last* 20 lines for shell output (tail), since errors and final output usually appear at the end.
96
- // For backgrounded or partial it's fine, but typically we want the end of the log.
97
- return {
98
- output: lines.slice(-MAX_COLLAPSED_RESULT_LINES).join("\n"),
99
- remaining: lines.length - MAX_COLLAPSED_RESULT_LINES,
100
- };
101
- }
102
72
  function updateRunningSecondsInHeader(session) {
103
73
  const runningSeconds = Math.floor((Date.now() - session.startedAtMs) / 1000);
104
74
  const runningLine = `running_for_seconds: ${String(runningSeconds).padStart(10, "0")}\n`;
@@ -427,15 +397,20 @@ export default function (pi) {
427
397
  if (!textBlock || typeof textBlock.text !== "string") {
428
398
  return new Text("", 0, 0);
429
399
  }
430
- const { output, remaining } = getCollapsedResultText(textBlock.text, expanded);
431
- let text = output
400
+ const fullText = textBlock.text;
401
+ const lineCount = fullText.split("\n").length;
402
+ const details = result.details;
403
+ const exitCode = details?.exit_code;
404
+ const exitInfo = exitCode != null && exitCode !== 0 ? `, exit ${exitCode}` : "";
405
+ if (!expanded) {
406
+ const summary = `${lineCount} lines${exitInfo} (click or ${keyHint("expandTools", "to expand")})`;
407
+ return new Text(theme.fg(exitCode != null && exitCode !== 0 ? "error" : "muted", summary), 0, 0);
408
+ }
409
+ let text = fullText
432
410
  .split("\n")
433
411
  .map((line) => theme.fg("toolOutput", line))
434
412
  .join("\n");
435
- if (!expanded && remaining > 0) {
436
- // Since we tail the output, the remaining lines are *before* the shown ones.
437
- text = `${theme.fg("muted", `(... ${remaining} earlier lines, ${keyHint("expandTools", "to expand")})`)}\n${text}`;
438
- }
413
+ text += theme.fg("muted", `\n(click or ${keyHint("expandTools", "to collapse")})`);
439
414
  return new Text(text, 0, 0);
440
415
  },
441
416
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -450,7 +425,9 @@ export default function (pi) {
450
425
  const defaultWorkingDirectory = resolveWorkingDirectory(runtimeState.cwd, ctx.cwd);
451
426
  const workingDirectory = resolveWorkingDirectory(params.working_directory ?? defaultWorkingDirectory, ctx.cwd);
452
427
  const blockUntilMs = normalizeBlockUntilMs(params.block_until_ms);
453
- const terminalsDir = getTerminalsDir(ctx.cwd);
428
+ const agentId = deriveAgentId(ctx.cwd);
429
+ const sessionId = ctx.sessionManager?.getSessionId?.() ?? "default";
430
+ const terminalsDir = getTerminalsDir(agentId, sessionId);
454
431
  const session = createTerminalSession(terminalsDir, command, workingDirectory, runtimeState, activeSessions);
455
432
  const waitResult = await waitForeground(session, blockUntilMs, signal);
456
433
  if (!waitResult.completed || !waitResult.completion) {