mini-coder 0.5.5 → 0.5.7

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.
package/bun.lock CHANGED
@@ -5,9 +5,9 @@
5
5
  "": {
6
6
  "name": "mini-coder",
7
7
  "dependencies": {
8
- "@cel-tui/components": "0.7.2",
9
- "@cel-tui/core": "0.7.2",
10
- "@cel-tui/types": "0.7.2",
8
+ "@cel-tui/components": "^0.7.2",
9
+ "@cel-tui/core": "^0.7.2",
10
+ "@cel-tui/types": "^0.7.2",
11
11
  "@mariozechner/pi-ai": "^0.66.1",
12
12
  },
13
13
  "devDependencies": {
@@ -90,23 +90,23 @@
90
90
 
91
91
  "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
92
92
 
93
- "@biomejs/biome": ["@biomejs/biome@2.4.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.11", "@biomejs/cli-darwin-x64": "2.4.11", "@biomejs/cli-linux-arm64": "2.4.11", "@biomejs/cli-linux-arm64-musl": "2.4.11", "@biomejs/cli-linux-x64": "2.4.11", "@biomejs/cli-linux-x64-musl": "2.4.11", "@biomejs/cli-win32-arm64": "2.4.11", "@biomejs/cli-win32-x64": "2.4.11" }, "bin": { "biome": "bin/biome" } }, "sha512-nWxHX8tf3Opb/qRgZpBbsTOqOodkbrkJ7S+JxJAruxOReaDPPmPuLBAGQ8vigyUgo0QBB+oQltNEAvalLcjggA=="],
93
+ "@biomejs/biome": ["@biomejs/biome@2.4.12", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.12", "@biomejs/cli-darwin-x64": "2.4.12", "@biomejs/cli-linux-arm64": "2.4.12", "@biomejs/cli-linux-arm64-musl": "2.4.12", "@biomejs/cli-linux-x64": "2.4.12", "@biomejs/cli-linux-x64-musl": "2.4.12", "@biomejs/cli-win32-arm64": "2.4.12", "@biomejs/cli-win32-x64": "2.4.12" }, "bin": { "biome": "bin/biome" } }, "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA=="],
94
94
 
95
- "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wOt+ed+L2dgZanWyL6i29qlXMc088N11optzpo10peayObBaAshbTcxKUchzEMp9QSY8rh5h6VfAFE3WTS1rqg=="],
95
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng=="],
96
96
 
97
- "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-gZ6zR8XmZlExfi/Pz/PffmdpWOQ8Qhy7oBztgkR8/ylSRyLwfRPSadmiVCV8WQ8PoJ2MWUy2fgID9zmtgUUJmw=="],
97
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A=="],
98
98
 
99
- "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-avdJaEElXrKceK0va9FkJ4P5ci3N01TGkc6ni3P8l3BElqbOz42Wg2IyX3gbh0ZLEd4HVKEIrmuVu/AMuSeFFA=="],
99
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw=="],
100
100
 
101
- "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-+Sbo1OAmlegtdwqFE8iOxFIWLh1B3OEgsuZfBpyyN/kWuqZ8dx9ZEes6zVnDMo+zRHF2wLynRVhoQmV7ohxl2Q=="],
101
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig=="],
102
102
 
103
- "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.11", "", { "os": "linux", "cpu": "x64" }, "sha512-TagWV0iomp5LnEnxWFg4nQO+e52Fow349vaX0Q/PIcX6Zhk4GGBgp3qqZ8PVkpC+cuehRctMf3+6+FgQ8jCEFQ=="],
103
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.12", "", { "os": "linux", "cpu": "x64" }, "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw=="],
104
104
 
105
- "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.11", "", { "os": "linux", "cpu": "x64" }, "sha512-bexd2IklK7ZgPhrz6jXzpIL6dEAH9MlJU1xGTrypx+FICxrXUp4CqtwfiuoDKse+UlgAlWtzML3jrMqeEAHEhA=="],
105
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.12", "", { "os": "linux", "cpu": "x64" }, "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew=="],
106
106
 
107
- "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-RJhaTnY8byzxDt4bDVb7AFPHkPcjOPK3xBip4ZRTrN3TEfyhjLRm3r3mqknqydgVTB74XG8l4jMLwEACEeihVg=="],
107
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig=="],
108
108
 
109
- "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.11", "", { "os": "win32", "cpu": "x64" }, "sha512-A8D3JM/00C2KQgUV3oj8Ba15EHEYwebAGCy5Sf9GAjr5Y3+kJIYOiESoqRDeuRZueuMdCsbLZIUqmPhpYXJE9A=="],
109
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.12", "", { "os": "win32", "cpu": "x64" }, "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA=="],
110
110
 
111
111
  "@cel-tui/components": ["@cel-tui/components@0.7.2", "", { "dependencies": { "@cel-tui/core": "0.7.2", "@cel-tui/types": "0.7.2", "lextide": "^0.2.1", "yoctomarkdown": "^0.1.0" }, "peerDependencies": { "typescript": ">=5.0.0" } }, "sha512-RHq7G/lS6A733l534cSStQp+cJECkxZZWrLghbPHwGBsSeDdJ2MkrQm+02roh6EH/N40ZAYjQqplq8acRrnFFQ=="],
112
112
 
@@ -368,7 +368,7 @@
368
368
 
369
369
  "path-expression-matcher": ["path-expression-matcher@1.2.1", "", {}, "sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw=="],
370
370
 
371
- "prettier": ["prettier@3.8.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q=="],
371
+ "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
372
372
 
373
373
  "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
374
374
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mini-coder",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "A small, fast CLI coding agent",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -24,10 +24,10 @@
24
24
  "@mariozechner/pi-ai": "^0.66.1"
25
25
  },
26
26
  "devDependencies": {
27
- "@biomejs/biome": "^2.4.11",
27
+ "@biomejs/biome": "^2.4.12",
28
28
  "@types/bun": "^1.3.12",
29
29
  "lefthook": "^2.1.5",
30
- "prettier": "^3.8.2",
30
+ "prettier": "^3.8.3",
31
31
  "typescript": "^6.0.2"
32
32
  },
33
33
  "license": "MIT"
package/src/agent.ts CHANGED
@@ -18,6 +18,7 @@ import type {
18
18
  Tool,
19
19
  ToolCall,
20
20
  ToolResultMessage,
21
+ UserMessage,
21
22
  } from "@mariozechner/pi-ai";
22
23
  import { streamSimple } from "@mariozechner/pi-ai";
23
24
  import { appendMessage } from "./session.ts";
@@ -79,6 +80,7 @@ export type AgentEvent =
79
80
  args: Record<string, unknown>;
80
81
  content: AssistantMessage["content"];
81
82
  }
83
+ | { type: "user_message"; message: UserMessage }
82
84
  | { type: "assistant_message"; message: AssistantMessage }
83
85
  | {
84
86
  type: "tool_start";
@@ -131,6 +133,8 @@ interface RunAgentOpts {
131
133
  signal?: AbortSignal;
132
134
  /** Callback for UI events. */
133
135
  onEvent?: (event: AgentEvent) => void;
136
+ /** Dequeue the next queued steering message, if one is waiting. */
137
+ takeQueuedUserMessage?: () => UserMessage | null;
134
138
  }
135
139
 
136
140
  /** Result of the agent loop. */
@@ -462,6 +466,19 @@ async function streamAssistantMessage(
462
466
  return mergeAssistantMessage(partialAssistantMessage, finalAssistantMessage);
463
467
  }
464
468
 
469
+ function appendUserMessage(
470
+ db: Database,
471
+ sessionId: string,
472
+ messages: Message[],
473
+ userMessage: UserMessage,
474
+ onEvent: RunAgentOpts["onEvent"],
475
+ ): number {
476
+ messages.push(userMessage);
477
+ const turn = appendMessage(db, sessionId, userMessage);
478
+ onEvent?.({ type: "user_message", message: userMessage });
479
+ return turn;
480
+ }
481
+
465
482
  function appendAssistantMessage(
466
483
  db: Database,
467
484
  sessionId: string,
@@ -490,15 +507,22 @@ function resolveLoopStopReason(
490
507
  return { messages, stopReason: assistantMessage.stopReason };
491
508
  }
492
509
 
493
- if (
494
- assistantMessage.stopReason === "stop" ||
495
- assistantMessage.stopReason === "length"
496
- ) {
497
- onEvent?.({ type: "done", message: assistantMessage });
498
- return { messages, stopReason: assistantMessage.stopReason };
510
+ return null;
511
+ }
512
+
513
+ function consumeQueuedUserMessage(
514
+ db: Database,
515
+ sessionId: string,
516
+ messages: Message[],
517
+ takeQueuedUserMessage: RunAgentOpts["takeQueuedUserMessage"],
518
+ onEvent: RunAgentOpts["onEvent"],
519
+ ): number | null {
520
+ const queuedUserMessage = takeQueuedUserMessage?.();
521
+ if (!queuedUserMessage) {
522
+ return null;
499
523
  }
500
524
 
501
- return null;
525
+ return appendUserMessage(db, sessionId, messages, queuedUserMessage, onEvent);
502
526
  }
503
527
 
504
528
  function getAssistantToolCalls(message: AssistantMessage): ToolCall[] {
@@ -581,6 +605,72 @@ function appendToolResultMessage(
581
605
  onEvent?.({ type: "tool_result", message: toolResultMessage });
582
606
  }
583
607
 
608
+ function finalizeStoppedAssistantMessage(
609
+ db: Database,
610
+ sessionId: string,
611
+ messages: Message[],
612
+ assistantMessage: AssistantMessage,
613
+ stopReason: "stop" | "length",
614
+ takeQueuedUserMessage: RunAgentOpts["takeQueuedUserMessage"],
615
+ onEvent: RunAgentOpts["onEvent"],
616
+ ): AgentLoopResult | number {
617
+ const queuedTurn = consumeQueuedUserMessage(
618
+ db,
619
+ sessionId,
620
+ messages,
621
+ takeQueuedUserMessage,
622
+ onEvent,
623
+ );
624
+ if (queuedTurn !== null) {
625
+ return queuedTurn;
626
+ }
627
+
628
+ onEvent?.({ type: "done", message: assistantMessage });
629
+ return { messages, stopReason };
630
+ }
631
+
632
+ async function executeAssistantToolCalls(
633
+ assistantMessage: AssistantMessage,
634
+ opts: Pick<
635
+ RunAgentOpts,
636
+ | "db"
637
+ | "sessionId"
638
+ | "messages"
639
+ | "toolHandlers"
640
+ | "cwd"
641
+ | "signal"
642
+ | "onEvent"
643
+ | "model"
644
+ >,
645
+ turn: number,
646
+ ): Promise<AgentLoopResult | null> {
647
+ for (const toolCall of getAssistantToolCalls(assistantMessage)) {
648
+ const result = await executeToolCall(toolCall, opts);
649
+ appendToolResultMessage(
650
+ opts.db,
651
+ opts.sessionId,
652
+ opts.messages,
653
+ toolCall,
654
+ result,
655
+ turn,
656
+ opts.onEvent,
657
+ );
658
+
659
+ if (opts.signal?.aborted) {
660
+ opts.onEvent?.({
661
+ type: "aborted",
662
+ message: buildIncompleteAssistantMessage(
663
+ { model: opts.model, signal: opts.signal },
664
+ assistantMessage,
665
+ ),
666
+ });
667
+ return { messages: opts.messages, stopReason: "aborted" };
668
+ }
669
+ }
670
+
671
+ return null;
672
+ }
673
+
584
674
  // ---------------------------------------------------------------------------
585
675
  // Agent loop
586
676
  // ---------------------------------------------------------------------------
@@ -609,7 +699,9 @@ export async function runAgentLoop(
609
699
  onEvent,
610
700
  toolHandlers,
611
701
  cwd,
702
+ takeQueuedUserMessage,
612
703
  } = opts;
704
+ let currentTurn = turn;
613
705
 
614
706
  while (true) {
615
707
  const assistantMessage = await streamAssistantMessage(opts);
@@ -618,7 +710,7 @@ export async function runAgentLoop(
618
710
  sessionId,
619
711
  messages,
620
712
  assistantMessage,
621
- turn,
713
+ currentTurn,
622
714
  onEvent,
623
715
  );
624
716
 
@@ -631,33 +723,53 @@ export async function runAgentLoop(
631
723
  return stopResult;
632
724
  }
633
725
 
634
- for (const toolCall of getAssistantToolCalls(assistantMessage)) {
635
- const result = await executeToolCall(toolCall, {
636
- toolHandlers,
637
- cwd,
638
- signal,
639
- onEvent,
640
- });
641
- appendToolResultMessage(
726
+ if (
727
+ assistantMessage.stopReason === "stop" ||
728
+ assistantMessage.stopReason === "length"
729
+ ) {
730
+ const finalResult = finalizeStoppedAssistantMessage(
642
731
  db,
643
732
  sessionId,
644
733
  messages,
645
- toolCall,
646
- result,
647
- turn,
734
+ assistantMessage,
735
+ assistantMessage.stopReason,
736
+ takeQueuedUserMessage,
648
737
  onEvent,
649
738
  );
650
-
651
- if (signal?.aborted) {
652
- onEvent?.({
653
- type: "aborted",
654
- message: buildIncompleteAssistantMessage(
655
- { model, signal },
656
- assistantMessage,
657
- ),
658
- });
659
- return { messages, stopReason: "aborted" };
739
+ if (typeof finalResult === "number") {
740
+ currentTurn = finalResult;
741
+ continue;
660
742
  }
743
+ return finalResult;
744
+ }
745
+
746
+ const toolStopResult = await executeAssistantToolCalls(
747
+ assistantMessage,
748
+ {
749
+ db,
750
+ sessionId,
751
+ messages,
752
+ toolHandlers,
753
+ cwd,
754
+ signal,
755
+ onEvent,
756
+ model,
757
+ },
758
+ currentTurn,
759
+ );
760
+ if (toolStopResult) {
761
+ return toolStopResult;
762
+ }
763
+
764
+ const queuedTurn = consumeQueuedUserMessage(
765
+ db,
766
+ sessionId,
767
+ messages,
768
+ takeQueuedUserMessage,
769
+ onEvent,
770
+ );
771
+ if (queuedTurn !== null) {
772
+ currentTurn = queuedTurn;
661
773
  }
662
774
  }
663
775
  }
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { homedir } from "node:os";
13
- import { dirname, join } from "node:path";
13
+ import { basename, dirname, join } from "node:path";
14
14
  import { isDeepStrictEqual } from "node:util";
15
15
  import type {
16
16
  KnownProvider,
@@ -19,6 +19,7 @@ import type {
19
19
  OAuthCredentials,
20
20
  ThinkingLevel,
21
21
  Tool,
22
+ UserMessage,
22
23
  } from "@mariozechner/pi-ai";
23
24
  import { getEnvApiKey, getModels, getProviders } from "@mariozechner/pi-ai";
24
25
  import { getOAuthApiKey, getOAuthProviders } from "@mariozechner/pi-ai/oauth";
@@ -561,6 +562,8 @@ export interface AppState {
561
562
  abortController: AbortController | null;
562
563
  /** Promise for the active conversational turn, used to serialize cleanup like `/undo`. */
563
564
  activeTurnPromise: Promise<void> | null;
565
+ /** Resolved user messages queued while the current run is still active. */
566
+ queuedUserMessages: UserMessage[];
564
567
  /** Whether to show thinking content. */
565
568
  showReasoning: boolean;
566
569
  /** Whether to show full (un-truncated) tool output. */
@@ -641,6 +644,7 @@ export async function init(): Promise<AppState> {
641
644
  running: false,
642
645
  abortController: null,
643
646
  activeTurnPromise: null,
647
+ queuedUserMessages: [],
644
648
  showReasoning: startup.showReasoning,
645
649
  verbose: startup.verbose,
646
650
  customModels: customResult.models,
@@ -648,6 +652,22 @@ export async function init(): Promise<AppState> {
648
652
  };
649
653
  }
650
654
 
655
+ /** Resolve the shell label shown in the system prompt. */
656
+ function resolvePromptShell(): string {
657
+ return basename(process.env.SHELL || "/bin/sh");
658
+ }
659
+
660
+ /** Resolve the normalized OS label shown in the system prompt. */
661
+ function resolvePromptOs(): "linux" | "mac" | "docker" {
662
+ if (process.platform === "darwin") {
663
+ return "mac";
664
+ }
665
+ if (existsSync("/.dockerenv") || existsSync("/run/.containerenv")) {
666
+ return "docker";
667
+ }
668
+ return "linux";
669
+ }
670
+
651
671
  /**
652
672
  * Build the system prompt for the current state.
653
673
  *
@@ -657,7 +677,12 @@ export async function init(): Promise<AppState> {
657
677
  export function buildPrompt(state: AppState): string {
658
678
  return buildSystemPrompt({
659
679
  cwd: state.cwd,
660
- date: new Date().toISOString().slice(0, 10),
680
+ modelLabel: state.model
681
+ ? `${state.model.provider}/${state.model.id}`
682
+ : "unknown",
683
+ os: resolvePromptOs(),
684
+ shell: resolvePromptShell(),
685
+ supportsImages: state.model?.input.includes("image") ?? false,
661
686
  git: state.git,
662
687
  agentsMd: state.agentsMd,
663
688
  skills: state.skills,
package/src/prompt.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * System prompt construction.
3
3
  *
4
- * Assembles the full system prompt from static base instructions and
4
+ * Assembles the full system prompt from the core prompt template plus
5
5
  * dynamic context: AGENTS.md files, skill catalog, plugin suffixes,
6
- * and a session footer with date, CWD, and git state.
6
+ * and the current environment block.
7
7
  *
8
8
  * @module
9
9
  */
@@ -30,8 +30,14 @@ export interface AgentsMdFile {
30
30
  interface BuildSystemPromptOpts {
31
31
  /** Current working directory. */
32
32
  cwd: string;
33
- /** Current date string (YYYY-MM-DD). */
34
- date: string;
33
+ /** Active provider/model identifier. */
34
+ modelLabel: string;
35
+ /** Normalized host OS label (`linux`, `mac`, or `docker`). */
36
+ os: string;
37
+ /** Active shell name (for example `bash` or `zsh`). */
38
+ shell: string;
39
+ /** Whether the active model supports image input. */
40
+ supportsImages?: boolean;
35
41
  /** Git repository state, or `null`/`undefined` if not in a repo. */
36
42
  git?: GitState | null;
37
43
  /** Discovered AGENTS.md files, ordered root-to-leaf. */
@@ -153,7 +159,7 @@ export function discoverAgentsMd(
153
159
  // ---------------------------------------------------------------------------
154
160
 
155
161
  /**
156
- * Format a git state snapshot into a single-line string for the session footer.
162
+ * Format a git state snapshot into a single-line string for the environment block.
157
163
  *
158
164
  * Fields are omitted when their values are zero. The git line format:
159
165
  * `Git: branch main | 3 staged, 1 modified, 2 untracked | +5 −2 vs origin/main`
@@ -185,67 +191,79 @@ export function formatGitLine(state: GitState): string {
185
191
  }
186
192
 
187
193
  // ---------------------------------------------------------------------------
188
- // Base instructions
194
+ // Core prompt template
189
195
  // ---------------------------------------------------------------------------
190
196
 
191
- const BASE_INSTRUCTIONS = `You are mini-coder, a coding agent running in the user's terminal.
197
+ function buildCorePrompt(opts: BuildSystemPromptOpts): string {
198
+ const lines = [
199
+ "You are mini-coder, the best software engineering assistant in the world.",
200
+ "",
201
+ "The current environment is:",
202
+ `- LLM in use: ${opts.modelLabel}`,
203
+ `- OS: ${opts.os}`,
204
+ `- Current working directory: ${opts.cwd}`,
205
+ ];
192
206
 
193
- # Role
194
-
195
- You are an autonomous, senior-level coding assistant. When the user gives a direction, proactively gather context, plan with the user, implement, and verify. Bias toward action: plan briefly when needed to clear important assumptions, then continue into implementation. First identify the task contract: required files, names, interfaces, output format, and checks for success. Treat those details as part of correctness, not as polish. Deliver working code, unless you are genuinely blocked.
196
-
197
- # Tools
198
-
199
- You have these core tools:
200
-
201
- - \`shell\` — run commands in the user's shell. Use this to explore the codebase, read tests/verifiers/examples, inspect required outputs, and run targeted checks, builds, or git commands. Prefer \`rg\` over \`grep\` for speed.
202
- - \`edit\` — make exact-text replacements in files. Provide the file path, the exact text to find, and the replacement text. The old text must match exactly one location in the file. To create a new file, use an empty old text and the full file content as new text. Use this to write the exact final file content the task requires.
203
-
204
- You may also have additional tools provided by plugins. Use them when they match the task.
205
-
206
- Workflow: **inspect with shell → mutate with edit → verify with shell**.
207
-
208
- # Code quality
209
-
210
- - Conform to the codebase's existing conventions: patterns, naming, formatting, language idioms.
211
- - Write correct, clear, minimal code. Prefer the simplest solution that satisfies the task's checks exactly. Don't over-engineer, don't add abstractions for hypothetical futures.
212
- - Reuse before creating. Search for existing helpers before writing new ones.
213
- - Tight error handling: no broad try/catch, no silent failures, no swallowed errors.
214
- - Keep type safety. Avoid \`any\` casts. Use proper types and guards.
215
- - Only add comments where the logic isn't self-evident.
216
-
217
- # Editing discipline
218
-
219
- - Read enough context before editing. Batch logical changes together rather than making many small edits.
220
- - Never revert changes you didn't make unless explicitly asked.
221
- - Never use destructive git commands (reset --hard, checkout --, clean -fd) unless the user requests it.
222
- - Default to ASCII. Only use non-ASCII characters when the file already uses them or there's clear justification.
223
-
224
- # Exploring the codebase
225
-
226
- - Think first: before any tool call, decide all files and information you need.
227
- - Early in the task, look for acceptance criteria in tests, verifier scripts, eval scripts, examples, and expected-output files. Do not rely on the task text alone when machine-checkable criteria are available.
228
- - Before relying on a binary, interpreter, or CLI helper you have not yet confirmed, probe availability with focused checks such as \`command -v <tool>\`, \`<tool> --version\`, or \`python3 -m pip --version\`, then adapt to what is actually installed.
229
- - Batch reads: if you need multiple files, read them together in parallel rather than one at a time.
230
- - Only make sequential calls when a later call genuinely depends on an earlier result.
207
+ if (opts.git) {
208
+ lines.push(`- ${formatGitLine(opts.git)}`);
209
+ }
231
210
 
232
- # Communication
211
+ lines.push(
212
+ `- Shell: ${opts.shell}. Use \`command -v <name>\` to check what is available to you; do not assume environment support.`,
213
+ "- Edit: Safe exact-text replacement in a single file.",
214
+ );
233
215
 
234
- - Be concise. Friendly coding teammate tone.
235
- - After making changes: lead with a quick explanation of what changed and why, then suggest logical next steps if any.
236
- - Don't dump large file contents you've written — reference file paths.
237
- - When discussing multiple options, use numbered lists so the user can reply with a number without turning it into a questionnaire.
238
- - If asked for a review, focus on bugs, risks, regressions, and missing tests. Findings first, ordered by severity.
216
+ if (opts.supportsImages) {
217
+ lines.push("- Read Image: Read an image from disk.");
218
+ }
239
219
 
240
- # Persistence
220
+ lines.push(
221
+ "",
222
+ "## Core working style:",
223
+ "",
224
+ "- Be concise, direct, and useful.",
225
+ "- Use a casual, solution-oriented technical tone. Avoid fluff and performative apologies.",
226
+ "- When the user gives a clear command, do it without adding extra work they did not ask for.",
227
+ "- Prefer the minimal implementation that satisfies the request exactly.",
228
+ "- Use YAGNI. Avoid speculative abstractions, future-proofing, and unnecessary compatibility shims.",
229
+ "- Preserve working behavior where possible. Prefer targeted fixes over rewrites.",
230
+ "- Be thorough, use fresh eyes and internal analysis before taking action.",
231
+ "- Make informed decisions based on the available information and best practices.",
232
+ "- Always verify the result of your actions.",
233
+ "",
234
+ "### Using the shell tool:",
235
+ "",
236
+ "- Always execute shell commands in non-interactive mode.",
237
+ "- Use the appropriate commands and package managers for the specified operating system.",
238
+ "- Don't assume the environment supports all commands; check before using them.",
239
+ "- Avoid destructive commands that can discard changes or override edits.",
240
+ "",
241
+ "### Working with code:",
242
+ "",
243
+ "- Describe changes before implementing them",
244
+ "- Prefer boring dependable solutions over clever ones",
245
+ "- Avoid creating extra files, systems or documentation outside of what was asked.",
246
+ "- Check requirements, and plan your changes before editing code.",
247
+ "- Implement the necessary changes, following good practices and proper error handling.",
248
+ "- Always verify your changes using compilation, testing, and manual verification when possible.",
249
+ "- Do not leave helpers, tests, or any other form of temporary files; clean up after yourself and leave no trace.",
250
+ "- Ensure you match the requested output exactly. This applies to file names, directory structure, number of files, output formats, and all other details.",
251
+ '- "Polish" is not optional; it counts just as much as solving the task.',
252
+ "",
253
+ "### Task management",
254
+ "",
255
+ "- Use the /tmp or equivalent temp directory to make a To-do list for your task.",
256
+ "- This to-do list is **extremely** helpful for breaking down your task into smaller, less complex steps.",
257
+ "- Not using a to-do list may cause you to forget important steps, or lose focus on the user's request; that would be unacceptable.",
258
+ "- Keep the to-do list up-to-date above all, mark your completions as soon as you verify them.",
259
+ "- A to-do item is only complete if verification was successful and thorough.",
260
+ '- You have the option to delegate tasks to copies of yourself with `mc -p "subtask prompt"` in the shell.',
261
+ "- Delegate when you are orchestrating a large to-do/plan execution.",
262
+ "",
263
+ );
241
264
 
242
- - Carry work through to completion within the current turn. Don't stop at analysis or partial fixes.
243
- - Once the contract is clear, create the required artifact early, then iterate and improve it. Do not spend most of the turn exploring.
244
- - Run the narrowest verifier or test as soon as there is a plausible first implementation or artifact. Do not delay the first verification run until the end.
245
- - When verification is down to a small number of failures, stop broad exploration and focus only on the remaining failing assertions or exact contract gaps until the last detail passes.
246
- - If you encounter an error, diagnose and fix it rather than reporting it and stopping.
247
- - Before concluding, run the smallest targeted verification that checks the exact contract: required files exist, names and signatures match, outputs are in the required format, and no forbidden extra artifacts were left behind.
248
- - Avoid excessive looping: if you're re-reading or re-editing the same files without progress, stop and ask the user.`;
265
+ return lines.join("\n");
266
+ }
249
267
 
250
268
  // ---------------------------------------------------------------------------
251
269
  // System prompt assembly
@@ -255,30 +273,27 @@ Workflow: **inspect with shell → mutate with edit → verify with shell**.
255
273
  * Build the full system prompt.
256
274
  *
257
275
  * Assembly order:
258
- * 1. Base instructions (static)
276
+ * 1. Core prompt template (including the current environment block)
259
277
  * 2. AGENTS.md content (project-specific)
260
278
  * 3. Skills catalog (XML)
261
279
  * 4. Plugin suffixes
262
- * 5. Session footer (date, CWD, git)
263
280
  *
264
281
  * @param opts - Prompt construction options.
265
282
  * @returns The assembled system prompt string.
266
283
  */
267
284
  export function buildSystemPrompt(opts: BuildSystemPromptOpts): string {
268
- const sections: string[] = [BASE_INSTRUCTIONS];
285
+ const sections: string[] = [buildCorePrompt(opts)];
269
286
 
270
287
  // 2. AGENTS.md content
271
288
  if (opts.agentsMd && opts.agentsMd.length > 0) {
272
- const agentsSection = [
273
- "\n# Project Context\n",
274
- "Project-specific instructions and guidelines:\n",
275
- ];
289
+ const agentsSection = [];
276
290
  for (const file of opts.agentsMd) {
277
- agentsSection.push(`## ${file.path}\n`);
291
+ agentsSection.push(`## ${file.path}`);
292
+ agentsSection.push("");
278
293
  agentsSection.push(file.content);
279
294
  agentsSection.push("");
280
295
  }
281
- sections.push(agentsSection.join("\n"));
296
+ sections.push(agentsSection.join("\n").trimEnd());
282
297
  }
283
298
 
284
299
  // 3. Skills catalog
@@ -294,14 +309,5 @@ export function buildSystemPrompt(opts: BuildSystemPromptOpts): string {
294
309
  }
295
310
  }
296
311
 
297
- // 5. Session footer
298
- const footer: string[] = [];
299
- footer.push(`Current date: ${opts.date}`);
300
- footer.push(`Current working directory: ${opts.cwd}`);
301
- if (opts.git) {
302
- footer.push(formatGitLine(opts.git));
303
- }
304
- sections.push(footer.join("\n"));
305
-
306
- return sections.join("\n");
312
+ return sections.join("\n\n");
307
313
  }