@matterailab/orbcode 0.2.3 → 0.2.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.
@@ -114,10 +114,13 @@ export class Agent {
114
114
  this.mcp = options.mcp;
115
115
  // Build the system prompt with AGENTS.md memory files and the skills
116
116
  // catalog injected, so the model sees project/user instructions and
117
- // knows which skills it can invoke.
118
- const memoryFiles = loadMemoryFiles(options.cwd);
119
- const skills = loadSkills(options.cwd);
120
- this.systemPrompt = buildSystemPrompt(options.cwd, { memoryFiles, skills });
117
+ // knows which skills it can invoke. An explicit override (from
118
+ // `orbcode -s <text>`) bypasses the default entirely.
119
+ const memoryFiles = options.systemPromptOverride ? [] : loadMemoryFiles(options.cwd);
120
+ const skills = options.systemPromptOverride ? new Map() : loadSkills(options.cwd);
121
+ this.systemPrompt = options.systemPromptOverride
122
+ ? options.systemPromptOverride
123
+ : buildSystemPrompt(options.cwd, { memoryFiles, skills });
121
124
  this.client = createLLMClient({
122
125
  token: options.token,
123
126
  modelId: options.modelId,
@@ -684,6 +687,9 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
684
687
  this.sessionApproveCommands = true;
685
688
  }
686
689
  }
690
+ // Yield so the UI can paint the "Working" indicator before a
691
+ // potentially long synchronous tool call blocks the event loop.
692
+ await new Promise(resolve => setImmediate(resolve));
687
693
  const result = await executeTool(toolCall.name, args, this.toolContext(), this.mcp);
688
694
  // PostToolUse can feed extra context (or a block reason) back to the
689
695
  // model. PreToolUse additionalContext is delivered here too.
package/dist/headless.js CHANGED
@@ -3,7 +3,7 @@ import { getAuthToken, getPendingProjectHooks, loadSettings } from "./config/set
3
3
  import { Agent } from "./core/agent.js";
4
4
  import { McpManager } from "./mcp/manager.js";
5
5
  /** Non-interactive `orbcode -p "prompt"` mode: prints the final response to stdout. */
6
- export async function runHeadless(prompt, yolo) {
6
+ export async function runHeadless(prompt, yolo, systemPromptOverride) {
7
7
  const settings = loadSettings();
8
8
  // An unknown --model (or MATTERAI_MODEL) silently resolves to the default; say
9
9
  // so on stderr instead of quietly running a different model than requested.
@@ -63,6 +63,7 @@ export async function runHeadless(prompt, yolo) {
63
63
  autoApproveSafeCommands: yolo,
64
64
  hooks: settings.hooks,
65
65
  mcp,
66
+ systemPromptOverride,
66
67
  callbacks: {
67
68
  onEvent: (event) => {
68
69
  switch (event.type) {
package/dist/index.js CHANGED
@@ -23,6 +23,9 @@ Usage:
23
23
  orbcode -p "…" --yolo non-interactive with auto-approved edits/commands
24
24
  orbcode --model <id> use a specific model for this run
25
25
  orbcode --resume <id> resume a previous session by id
26
+ orbcode -s "<prompt>" override the default system prompt (replaces it entirely;
27
+ accepts -s <text>, --system-prompt <text>, and
28
+ --system-prompt=<text> for values that start with '-')
26
29
  orbcode --version print version
27
30
  orbcode --help show this help
28
31
  `);
@@ -71,6 +74,33 @@ function takeFlagValue(args, name) {
71
74
  args.splice(index, 2);
72
75
  return value;
73
76
  }
77
+ /**
78
+ * Pop the `-s` / `--system-prompt` flag (and its value) out of `args`.
79
+ * Unlike `takeFlagValue` this accepts a value that starts with `-`, because
80
+ * a system-prompt override is free-form text. Supports three forms:
81
+ * -s "<text>" --value in next arg
82
+ * --system-prompt "<text>"
83
+ * --system-prompt="<text>"
84
+ * --system-prompt=-<text>
85
+ */
86
+ function takeSystemPrompt(args) {
87
+ const longWithEq = args.findIndex((a) => a.startsWith("--system-prompt="));
88
+ if (longWithEq !== -1) {
89
+ const value = args[longWithEq].slice("--system-prompt=".length);
90
+ args.splice(longWithEq, 1);
91
+ return value.length > 0 ? value : undefined;
92
+ }
93
+ const index = args.findIndex((a) => a === "-s" || a === "--system-prompt");
94
+ if (index === -1)
95
+ return undefined;
96
+ const value = args[index + 1];
97
+ if (value === undefined) {
98
+ console.error("Missing value after -s / --system-prompt");
99
+ process.exit(1);
100
+ }
101
+ args.splice(index, 2);
102
+ return value;
103
+ }
74
104
  async function main() {
75
105
  // Override Node's default process title so terminals (iTerm2 "current job
76
106
  // name", VSCode terminal status, etc.) don't append " (node)" next to our
@@ -110,6 +140,10 @@ async function main() {
110
140
  process.exit(1);
111
141
  }
112
142
  }
143
+ // `-s` / `--system-prompt` lets the user replace the default prompt
144
+ // entirely. Accepts values starting with `-` (unlike other flags), so
145
+ // `orbcode -s '- you are a code reviewer'` works.
146
+ const systemPromptOverride = takeSystemPrompt(args);
113
147
  const printIndex = args.findIndex((a) => a === "-p" || a === "--print");
114
148
  if (printIndex !== -1) {
115
149
  const prompt = args[printIndex + 1];
@@ -117,7 +151,7 @@ async function main() {
117
151
  console.error("Missing prompt after -p");
118
152
  process.exit(1);
119
153
  }
120
- await runHeadless(prompt, args.includes("--yolo"));
154
+ await runHeadless(prompt, args.includes("--yolo"), systemPromptOverride);
121
155
  return;
122
156
  }
123
157
  const initialView = args[0] === "login" ? "login" : undefined;
@@ -132,7 +166,7 @@ async function main() {
132
166
  // Fire-and-forget: a stale or no-network state just means the header shows
133
167
  // the "current version" line instead of an upgrade prompt.
134
168
  const updateCheckPromise = getUpdateInfo(PACKAGE_NAME, VERSION);
135
- render(_jsx(App, { initialView: initialView, initialPrompt: initialPrompt, initialSession: initialSession, updateCheck: updateCheckPromise }));
169
+ render(_jsx(App, { initialView: initialView, initialPrompt: initialPrompt, initialSession: initialSession, systemPromptOverride: systemPromptOverride, updateCheck: updateCheckPromise }));
136
170
  }
137
171
  main().catch((error) => {
138
172
  console.error(error);
package/dist/ui/App.js CHANGED
@@ -20,7 +20,7 @@ import { StatusBar } from "./components/StatusBar.js";
20
20
  import { ModelPicker } from "./components/ModelPicker.js";
21
21
  import { SessionPicker } from "./components/SessionPicker.js";
22
22
  import { listSessions } from "../core/sessions.js";
23
- import { RowView, formatToolName } from "./components/rows.js";
23
+ import { RowView } from "./components/rows.js";
24
24
  const SLASH_COMMANDS = [
25
25
  { name: "/help", description: "show available commands" },
26
26
  { name: "/model", description: "select the Axon model to use" },
@@ -161,7 +161,7 @@ let rowCounter = 0;
161
161
  function rowId() {
162
162
  return `row-${rowCounter++}`;
163
163
  }
164
- export function App({ initialView, initialPrompt, initialSession, updateCheck, }) {
164
+ export function App({ initialView, initialPrompt, initialSession, systemPromptOverride, updateCheck, }) {
165
165
  const { exit } = useApp();
166
166
  const [settings, setSettings] = useState(() => loadSettings());
167
167
  const [view, setView] = useState(initialView ?? (getAuthToken(settings) ? "chat" : "login"));
@@ -178,7 +178,6 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
178
178
  const [busyLabel, setBusyLabel] = useState("Thinking");
179
179
  const [streamingReasoning, setStreamingReasoning] = useState("");
180
180
  const [streamingText, setStreamingText] = useState("");
181
- const [runningTool, setRunningTool] = useState(null);
182
181
  const [pendingApproval, setPendingApproval] = useState(null);
183
182
  const [pendingFollowup, setPendingFollowup] = useState(null);
184
183
  // Set when the current project defines hooks that haven't been trusted yet;
@@ -234,6 +233,9 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
234
233
  const deferredPromptRef = useRef(null);
235
234
  // Guards endAndExit against double-invocation (Ctrl+D spam).
236
235
  const exitingRef = useRef(false);
236
+ // Mirror of the `-s` override so a fresh agent (created mid-session via
237
+ // /new or /resume) keeps the override instead of falling back to default.
238
+ const systemPromptOverrideRef = useRef(systemPromptOverride);
237
239
  const enqueueMessage = useCallback((text) => {
238
240
  queueRef.current = [...queueRef.current, text];
239
241
  setQueuedMessages(queueRef.current);
@@ -301,6 +303,7 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
301
303
  });
302
304
  reasoningBufferRef.current = "";
303
305
  setStreamingReasoning("");
306
+ setBusyLabel("Working");
304
307
  break;
305
308
  case "text-delta":
306
309
  textBufferRef.current += event.text;
@@ -311,18 +314,13 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
311
314
  pushRow({ kind: "assistant", text: textBufferRef.current });
312
315
  textBufferRef.current = "";
313
316
  setStreamingText("");
317
+ setBusyLabel("Working");
314
318
  break;
315
319
  case "tool-start":
316
- setRunningTool({
317
- id: event.id,
318
- name: event.name,
319
- summary: event.summary,
320
- });
321
- setBusyLabel(`Running ${event.name}`);
320
+ setBusyLabel("Working");
322
321
  break;
323
322
  case "tool-end":
324
- setRunningTool(null);
325
- setBusyLabel("Thinking");
323
+ setBusyLabel("Working");
326
324
  pushRow({
327
325
  kind: "tool",
328
326
  name: event.name,
@@ -366,7 +364,6 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
366
364
  reasoningBufferRef.current = "";
367
365
  setStreamingReasoning("");
368
366
  }
369
- setRunningTool(null);
370
367
  setBusy(false);
371
368
  maybeFetchTitle();
372
369
  refreshUsage();
@@ -409,6 +406,9 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
409
406
  hooks: current.hooks,
410
407
  mcp: mcpManagerRef.current,
411
408
  resume,
409
+ // The override (from `-s`) is captured in a ref so a /new or /resume
410
+ // mid-session still applies it to the new agent.
411
+ systemPromptOverride: systemPromptOverrideRef.current,
412
412
  callbacks: {
413
413
  onEvent: handleEvent,
414
414
  requestApproval: (request) => new Promise((resolve) => setPendingApproval({ request, resolve })),
@@ -902,7 +902,7 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
902
902
  !modelPickerOpen &&
903
903
  !mcpPickerOpen &&
904
904
  !resumableSessions;
905
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: rows, children: (row) => _jsx(RowView, { row: row }, row.id) }, staticKey), view === "login" ? (_jsx(LoginSection, { onLogin: handleLogin })) : (_jsxs(Box, { flexDirection: "column", children: [updateInfo?.updateAvailable && updateInfo.latest && (_jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, alignSelf: "flex-start", children: [_jsxs(Text, { color: COLORS.warning, bold: true, children: ["\u2191 Update available: v", updateInfo.current, " \u2192 v", updateInfo.latest] }), _jsxs(Text, { children: ["Run ", _jsx(Text, { color: COLORS.accent, children: "orbcode update" }), " to install the latest version, then relaunch."] })] })), streamingReasoning && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: COLORS.thinking, italic: true, children: "\u2726 Thinking\u2026" }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, italic: true, children: tail(streamingReasoning, expandReasoningRef.current ? 30 : 3) }) })] })), streamingText && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: COLORS.primary, children: "\u25CF " }), streamingText] }) })), runningTool && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: COLORS.warning, children: [formatToolName(runningTool.name), " ", _jsx(Text, { dimColor: true, children: runningTool.summary })] }) })), taskLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: "Tasks" }), taskLines.slice(0, 10).map((line, i) => (_jsx(Text, { dimColor: /^[-*]\s*\[x\]/i.test(line), children: line
905
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: rows, children: (row) => _jsx(RowView, { row: row }, row.id) }, staticKey), view === "login" ? (_jsx(LoginSection, { onLogin: handleLogin })) : (_jsxs(Box, { flexDirection: "column", children: [updateInfo?.updateAvailable && updateInfo.latest && (_jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, alignSelf: "flex-start", children: [_jsxs(Text, { color: COLORS.warning, bold: true, children: ["\u2191 Update available: v", updateInfo.current, " \u2192 v", updateInfo.latest] }), _jsxs(Text, { children: ["Run ", _jsx(Text, { color: COLORS.accent, children: "orbcode update" }), " to install the latest version, then relaunch."] })] })), streamingReasoning && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: COLORS.thinking, italic: true, children: "\u2726 Thinking\u2026" }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, italic: true, children: tail(streamingReasoning, expandReasoningRef.current ? 30 : 3) }) })] })), streamingText && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: COLORS.primary, children: "\u25CF " }), streamingText] }) })), taskLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: "Tasks" }), taskLines.slice(0, 10).map((line, i) => (_jsx(Text, { dimColor: /^[-*]\s*\[x\]/i.test(line), children: line
906
906
  .replace(/^[-*]\s*\[x\]/i, " ■")
907
907
  .replace(/^[-*]\s*\[-\]/, " ◧")
908
908
  .replace(/^[-*]\s*\[ \]/, " □") }, i))), taskLines.length > 10 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", taskLines.length - 10, " more"] }))] })), modelPickerOpen && (_jsx(Box, { marginTop: 1, children: _jsx(ModelPicker, { currentId: settings.model, onSelect: (modelId) => {
@@ -925,7 +925,8 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
925
925
  !pendingHookTrust &&
926
926
  !pendingMcpApproval &&
927
927
  !mcpPickerOpen &&
928
- !streamingText && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: busyLabel }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [queuedMessages.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, children: [_jsxs(Text, { dimColor: true, bold: true, children: ["Queue (", queuedMessages.length, ")"] }), queuedMessages.slice(0, 5).map((msg, i) => (_jsxs(Text, { dimColor: true, children: [i + 1, ". ", truncateForQueue(msg).replace(/\n/g, "↵")] }, i))), queuedMessages.length > 5 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", queuedMessages.length - 5, " more"] }))] })), _jsx(InputBox, { active: inputActive, slashCommands: SLASH_COMMANDS, onSubmit: handleSubmit }), _jsx(StatusBar, { modelId: settings.model, contextTokens: contextTokens, totalCost: totalCost, state: busy ? busyLabel : "", approvalMode: approvalMode, busy: busy, title: sessionTitle, plan: usage?.plan, usagePercentage: usage?.usagePercentage, tieredUsage: usage?.tieredUsage })] })] }))] }));
928
+ !streamingText &&
929
+ !streamingReasoning && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: busyLabel }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [queuedMessages.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, children: [_jsxs(Text, { dimColor: true, bold: true, children: ["Queue (", queuedMessages.length, ")"] }), queuedMessages.slice(0, 5).map((msg, i) => (_jsxs(Text, { dimColor: true, children: [i + 1, ". ", truncateForQueue(msg).replace(/\n/g, "↵")] }, i))), queuedMessages.length > 5 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", queuedMessages.length - 5, " more"] }))] })), _jsx(InputBox, { active: inputActive, slashCommands: SLASH_COMMANDS, onSubmit: handleSubmit }), _jsx(StatusBar, { modelId: settings.model, contextTokens: contextTokens, totalCost: totalCost, state: busy ? busyLabel : "", approvalMode: approvalMode, busy: busy, title: sessionTitle, plan: usage?.plan, usagePercentage: usage?.usagePercentage, tieredUsage: usage?.tieredUsage })] })] }))] }));
929
930
  }
930
931
  function tail(text, lines) {
931
932
  const all = text.split("\n").filter((l) => l.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterailab/orbcode",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "OrbCode CLI — agentic coding in your terminal, powered by Axon models by MatterAI",
5
5
  "type": "module",
6
6
  "bin": {