open-research 1.2.0 → 1.2.1

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.
@@ -693,7 +693,7 @@ ${input.workflow}
693
693
  }
694
694
 
695
695
  // src/lib/cli/version.ts
696
- var PACKAGE_VERSION = "1.2.0";
696
+ var PACKAGE_VERSION = "1.2.1";
697
697
  function getPackageVersion() {
698
698
  return PACKAGE_VERSION;
699
699
  }
@@ -2070,6 +2070,24 @@ async function applyProposedUpdate(workspaceDir, update) {
2070
2070
  return absolutePath;
2071
2071
  }
2072
2072
 
2073
+ // src/lib/agent/tools/current-task.ts
2074
+ var currentTask = null;
2075
+ function executeSetCurrentTask(args) {
2076
+ currentTask = args.task;
2077
+ return `Current focus set: ${args.task}`;
2078
+ }
2079
+ function getCurrentTaskBlock() {
2080
+ if (!currentTask) return null;
2081
+ return `## Current Focus
2082
+ ${currentTask}`;
2083
+ }
2084
+ function getCurrentTask() {
2085
+ return currentTask;
2086
+ }
2087
+ function clearCurrentTask() {
2088
+ currentTask = null;
2089
+ }
2090
+
2073
2091
  // src/lib/memory/store.ts
2074
2092
  import fs6 from "fs/promises";
2075
2093
  import path6 from "path";
@@ -5023,6 +5041,7 @@ ${input.context}` : input.goal;
5023
5041
  let iterations = 0;
5024
5042
  let handoff;
5025
5043
  let activeSkills = [];
5044
+ const recentToolDescriptions = [];
5026
5045
  function emitProgress(currentTool, status = "running") {
5027
5046
  input.onProgress?.({
5028
5047
  agentId: input.agentId,
@@ -5030,9 +5049,14 @@ ${input.context}` : input.goal;
5030
5049
  goal: input.goal,
5031
5050
  currentTool,
5032
5051
  toolCount: totalToolCalls,
5052
+ recentTools: recentToolDescriptions.slice(-3),
5033
5053
  status
5034
5054
  });
5035
5055
  }
5056
+ function trackCompletedTool(description) {
5057
+ recentToolDescriptions.push(description);
5058
+ if (recentToolDescriptions.length > 3) recentToolDescriptions.shift();
5059
+ }
5036
5060
  emitProgress("");
5037
5061
  for (; ; ) {
5038
5062
  if (input.signal?.aborted) break;
@@ -5122,6 +5146,7 @@ ${input.context}` : input.goal;
5122
5146
  activeSkills.push(skill);
5123
5147
  }
5124
5148
  }
5149
+ trackCompletedTool(description);
5125
5150
  messages.push({
5126
5151
  role: "tool",
5127
5152
  tool_call_id: toolCall.id,
@@ -5260,21 +5285,6 @@ function describeSubAgentTool(name, args) {
5260
5285
  }
5261
5286
  }
5262
5287
 
5263
- // src/lib/agent/tools/current-task.ts
5264
- var currentTask = null;
5265
- function executeSetCurrentTask(args) {
5266
- currentTask = args.task;
5267
- return `Current focus set: ${args.task}`;
5268
- }
5269
- function getCurrentTaskBlock() {
5270
- if (!currentTask) return null;
5271
- return `## Current Focus
5272
- ${currentTask}`;
5273
- }
5274
- function clearCurrentTask() {
5275
- currentTask = null;
5276
- }
5277
-
5278
5288
  // src/lib/agent/tool-dispatcher.ts
5279
5289
  async function executeTool(name, args, ctx, activeSkills, homeDir, signal, provider, onSubAgentProgress, toolCallId, bridges) {
5280
5290
  switch (name) {
@@ -5746,16 +5756,27 @@ function buildSystemPrompt(ctx, activeSkills) {
5746
5756
  ${skill.prompt}`).join("\n\n");
5747
5757
  return [
5748
5758
  "You are Open Research \u2014 a research director who orchestrates specialized agents through a terminal CLI.",
5749
- "Your primary mode: understand the user's research question, decompose it into steps, delegate each step to the right sub-agent, and synthesize findings into clear conclusions.",
5750
5759
  "",
5751
- "## Decision: Delegate or Direct",
5752
- "**Handle directly**: Quick lookups, single searches, clarifications, workspace edits, simple questions.",
5753
- "**Delegate via sub-agent**: Multi-step research, literature reviews, experiments, evidence analysis, paper drafting \u2014 anything requiring sustained focus across many tool calls.",
5754
- "When in doubt, delegate. Sub-agents have fresh context windows and work more deeply than you can in a single turn.",
5760
+ "## Before You Act: Classify the Request",
5761
+ "Every user message falls into one of three categories. Identify which BEFORE doing anything:",
5762
+ "",
5763
+ "**1. ANSWER DIRECTLY** \u2014 The user asks a question you can answer from knowledge or with 1-2 tool calls.",
5764
+ " Examples: 'What is p-hacking?', 'Summarize the notes in my workspace', 'What model am I using?'",
5765
+ " \u2192 Just answer. No plan file. No sub-agent. Respond immediately.",
5766
+ "",
5767
+ "**2. CLARIFY FIRST** \u2014 The request is vague, ambiguous, or could mean multiple things.",
5768
+ " Examples: 'Research transformers', 'Help me with my paper', 'Find some papers'",
5769
+ " \u2192 Use `ask_user` to clarify BEFORE doing any work. Ask: What specific question? What scope? What output format?",
5770
+ " \u2192 Do NOT create a plan file or launch sub-agents until the goal is crystal clear.",
5771
+ "",
5772
+ "**3. RESEARCH (multi-step)** \u2014 The request is specific and requires sustained research work.",
5773
+ " Examples: 'Find papers on whether attention is necessary in transformers and summarize the evidence',",
5774
+ " 'Run an experiment comparing dropout rates on CIFAR-10', 'Draft a literature review on scaling laws'",
5775
+ " \u2192 Create a plan, delegate to sub-agents, synthesize results.",
5755
5776
  "",
5756
- "## How You Work (Multi-Step Tasks)",
5777
+ "## How You Work (Category 3 Only)",
5757
5778
  "1. Set your focus: `set_current_task('Planning: ...')`",
5758
- "2. Create a plan: `write_new_file` with key `path:run/archive/{YYYY-MM-DDTHH-MM-SS}/plan.md` \u2014 markdown checklist with context per step",
5779
+ "2. Create a plan: `write_new_file` with key `path:run/archive/{YYYY-MM-DDTHH-MM-SS}/plan.md`",
5759
5780
  "3. Delegate each step:",
5760
5781
  " `launch_subagent(type: 'research', skill: 'source-scout', goal: '...', context: '...')`",
5761
5782
  " Skills: source-scout, experiment-designer, data-analyst, devils-advocate, methodology-critic, evidence-adjudicator, novelty-checker, paper-explainer, draft-paper, reviewer-response",
@@ -5932,7 +5953,7 @@ ${agentsMd}` : null,
5932
5953
  });
5933
5954
  }
5934
5955
  if (input.workspace.workspaceDir) {
5935
- import("./manager-queue-NK5B47A4.js").then(({ enqueueOntologyManager }) => {
5956
+ import("./manager-queue-RWWN2TU3.js").then(({ enqueueOntologyManager }) => {
5936
5957
  enqueueOntologyManager({
5937
5958
  userMessage: input.message,
5938
5959
  agentResponse: fullText,
@@ -6429,6 +6450,7 @@ export {
6429
6450
  resetPendingQuestions,
6430
6451
  classifyUpdateRisk,
6431
6452
  applyProposedUpdate,
6453
+ getCurrentTask,
6432
6454
  clearCurrentTask,
6433
6455
  loadAllMemories,
6434
6456
  deleteMemory,
@@ -13,7 +13,7 @@ import {
13
13
  loginWithBrowser,
14
14
  runAgentTurn,
15
15
  scanWorkspace
16
- } from "./chunk-32Q63SUH.js";
16
+ } from "./chunk-G7IOFPGG.js";
17
17
  import {
18
18
  appendSessionEvent,
19
19
  listSessions,
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  createSkillScaffold,
11
11
  deleteMemory,
12
12
  getAuthStatus,
13
+ getCurrentTask,
13
14
  getPackageVersion,
14
15
  getPendingQuestion,
15
16
  hasConfiguredProvider,
@@ -25,7 +26,7 @@ import {
25
26
  scanWorkspace,
26
27
  validateSkillDirectory,
27
28
  writeAgentsMd
28
- } from "./chunk-32Q63SUH.js";
29
+ } from "./chunk-G7IOFPGG.js";
29
30
  import {
30
31
  createSessionUsage,
31
32
  estimateConversationTokens,
@@ -201,10 +202,10 @@ import path5 from "path";
201
202
  import React6, {
202
203
  startTransition as startTransition2,
203
204
  useDeferredValue,
204
- useEffect as useEffect4,
205
+ useEffect as useEffect5,
205
206
  useMemo as useMemo4,
206
207
  useRef as useRef2,
207
- useState as useState7
208
+ useState as useState8
208
209
  } from "react";
209
210
  import { Box as Box5, Static, Text as Text5, useApp, useInput as useInput5 } from "ink";
210
211
 
@@ -2300,9 +2301,10 @@ var AgentMessage = memo(function AgentMessage2({ text, width }) {
2300
2301
  /* @__PURE__ */ jsx4(Box4, { marginLeft: 2, width: bodyWidth, children: /* @__PURE__ */ jsx4(Text4, { wrap: "wrap", children: wrappedText }) })
2301
2302
  ] });
2302
2303
  });
2303
- function ThinkingIndicator({ frame, width }) {
2304
+ function ThinkingIndicator({ frame, width, currentTask }) {
2304
2305
  const theme = useTheme();
2305
2306
  const contentWidth = resolveWidth(width);
2307
+ const label = currentTask || "thinking...";
2306
2308
  return /* @__PURE__ */ jsxs3(Box4, { marginBottom: 1, width: contentWidth, children: [
2307
2309
  /* @__PURE__ */ jsxs3(Text4, { color: theme.secondary, bold: true, children: [
2308
2310
  GUTTER.agent,
@@ -2310,10 +2312,28 @@ function ThinkingIndicator({ frame, width }) {
2310
2312
  ] }),
2311
2313
  /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, children: [
2312
2314
  frame,
2313
- " thinking..."
2315
+ " ",
2316
+ label
2314
2317
  ] })
2315
2318
  ] });
2316
2319
  }
2320
+ function ActivityFeed({ items, frame, width }) {
2321
+ const theme = useTheme();
2322
+ const contentWidth = resolveWidth(width);
2323
+ const visible = items.slice(-6);
2324
+ if (visible.length === 0) return null;
2325
+ return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginBottom: 1, width: contentWidth, children: visible.map((item, i) => {
2326
+ const icon = item.status === "done" ? "\u2713" : frame;
2327
+ const color = item.status === "done" ? theme.muted : theme.secondary;
2328
+ const duration = item.durationMs !== void 0 ? ` (${(item.durationMs / 1e3).toFixed(1)}s)` : "";
2329
+ return /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text4, { color, dimColor: item.status === "done", children: [
2330
+ icon,
2331
+ " ",
2332
+ item.description,
2333
+ duration
2334
+ ] }) }, i);
2335
+ }) });
2336
+ }
2317
2337
  var ToolActivitySummary = memo(function ToolActivitySummary2({
2318
2338
  summary,
2319
2339
  tools,
@@ -2371,6 +2391,7 @@ function SubAgentIndicator({
2371
2391
  goal,
2372
2392
  currentTool,
2373
2393
  toolCount,
2394
+ recentTools,
2374
2395
  frame,
2375
2396
  width
2376
2397
  }) {
@@ -2406,8 +2427,14 @@ function SubAgentIndicator({
2406
2427
  " ",
2407
2428
  shortGoal
2408
2429
  ] }) }),
2430
+ recentTools && recentTools.length > 0 && recentTools.map((tool, i) => /* @__PURE__ */ jsx4(Box4, { width: innerWidth, children: /* @__PURE__ */ jsxs3(Text4, { color: theme.muted, dimColor: true, wrap: "wrap", children: [
2431
+ " \u2713 ",
2432
+ tool
2433
+ ] }) }, i)),
2409
2434
  /* @__PURE__ */ jsx4(Box4, { width: innerWidth, children: /* @__PURE__ */ jsxs3(Text4, { color: theme.text, wrap: "wrap", children: [
2410
- " \u2514 ",
2435
+ " ",
2436
+ currentTool ? "\u25D0" : "\u2514",
2437
+ " ",
2411
2438
  status
2412
2439
  ] }) })
2413
2440
  ]
@@ -2986,6 +3013,7 @@ function splitMessagesForRender(messages, busy) {
2986
3013
  }
2987
3014
  function createSentenceStreamBuffer({
2988
3015
  onFlush,
3016
+ isVisible = () => true,
2989
3017
  flushIntervalMs = STREAM_FLUSH_INTERVAL_MS,
2990
3018
  flushPattern = STREAM_FLUSH_PATTERN
2991
3019
  }) {
@@ -2996,8 +3024,9 @@ function createSentenceStreamBuffer({
2996
3024
  clearTimeout(timer);
2997
3025
  timer = null;
2998
3026
  };
2999
- const flush = () => {
3027
+ const flush = (options) => {
3000
3028
  if (!buffer) return "";
3029
+ if (!options?.force && !isVisible()) return "";
3001
3030
  clearPendingTimer();
3002
3031
  const text = buffer;
3003
3032
  buffer = "";
@@ -3024,7 +3053,7 @@ function createSentenceStreamBuffer({
3024
3053
  flush,
3025
3054
  dispose() {
3026
3055
  clearPendingTimer();
3027
- return flush();
3056
+ return flush({ force: true });
3028
3057
  }
3029
3058
  };
3030
3059
  }
@@ -3036,6 +3065,10 @@ import { useStdout as useStdout2 } from "ink";
3036
3065
  // src/tui/ink-stdout.ts
3037
3066
  var RAW_COLUMNS = /* @__PURE__ */ Symbol("open-research.raw-columns");
3038
3067
  var RAW_ROWS = /* @__PURE__ */ Symbol("open-research.raw-rows");
3068
+ var GET_VISIBILITY = /* @__PURE__ */ Symbol("open-research.get-visibility");
3069
+ var SET_FOCUS_VISIBILITY = /* @__PURE__ */ Symbol("open-research.set-focus-visibility");
3070
+ var ADD_VISIBILITY_LISTENER = /* @__PURE__ */ Symbol("open-research.add-visibility-listener");
3071
+ var REMOVE_VISIBILITY_LISTENER = /* @__PURE__ */ Symbol("open-research.remove-visibility-listener");
3039
3072
  function getStableRow(current, fallback) {
3040
3073
  return typeof current === "number" && current > 0 ? current : fallback;
3041
3074
  }
@@ -3057,6 +3090,20 @@ function hasRenderableTerminalDimensions(stdout) {
3057
3090
  const hasRows = rows === void 0 || rows > 0;
3058
3091
  return hasRows && typeof columns === "number" && columns >= MIN_TERMINAL_WIDTH;
3059
3092
  }
3093
+ function isTerminalVisible(stdout) {
3094
+ const stream = stdout;
3095
+ return stream[GET_VISIBILITY]?.() ?? hasRenderableTerminalDimensions(stdout);
3096
+ }
3097
+ function setTerminalFocusVisible(stdout, visible) {
3098
+ stdout[SET_FOCUS_VISIBILITY]?.(visible);
3099
+ }
3100
+ function observeTerminalVisibility(stdout, listener) {
3101
+ const stream = stdout;
3102
+ stream[ADD_VISIBILITY_LISTENER]?.(listener);
3103
+ return () => {
3104
+ stream[REMOVE_VISIBILITY_LISTENER]?.(listener);
3105
+ };
3106
+ }
3060
3107
  function createStableInkStdout(stdout, options) {
3061
3108
  const forceFullRedraw = options?.forceFullRedraw ?? process.env.OPEN_RESEARCH_FORCE_FULL_REDRAW === "1";
3062
3109
  const initialRows = getRawDimension(stdout.rows);
@@ -3064,6 +3111,26 @@ function createStableInkStdout(stdout, options) {
3064
3111
  let lastRows = getStableRow(initialRows, 24);
3065
3112
  let lastColumns = typeof initialColumns === "number" && initialColumns > 0 ? Math.max(MIN_TERMINAL_WIDTH, initialColumns) : 80;
3066
3113
  const resizeListeners = /* @__PURE__ */ new Map();
3114
+ const visibilityListeners = /* @__PURE__ */ new Set();
3115
+ let focusVisible = true;
3116
+ let visible = focusVisible && hasRenderableTerminalDimensions({
3117
+ [RAW_COLUMNS]: initialColumns,
3118
+ [RAW_ROWS]: initialRows
3119
+ });
3120
+ const syncVisibility = () => {
3121
+ const nextVisible = focusVisible && hasRenderableTerminalDimensions({
3122
+ [RAW_COLUMNS]: getRawDimension(stdout.columns),
3123
+ [RAW_ROWS]: getRawDimension(stdout.rows)
3124
+ });
3125
+ if (nextVisible === visible) {
3126
+ return visible;
3127
+ }
3128
+ visible = nextVisible;
3129
+ for (const listener of visibilityListeners) {
3130
+ listener(visible);
3131
+ }
3132
+ return visible;
3133
+ };
3067
3134
  return new Proxy(stdout, {
3068
3135
  get(target, prop, receiver) {
3069
3136
  if (prop === RAW_ROWS) {
@@ -3072,6 +3139,25 @@ function createStableInkStdout(stdout, options) {
3072
3139
  if (prop === RAW_COLUMNS) {
3073
3140
  return getRawDimension(Reflect.get(target, "columns", receiver));
3074
3141
  }
3142
+ if (prop === GET_VISIBILITY) {
3143
+ return () => visible;
3144
+ }
3145
+ if (prop === SET_FOCUS_VISIBILITY) {
3146
+ return (nextVisible) => {
3147
+ focusVisible = nextVisible;
3148
+ syncVisibility();
3149
+ };
3150
+ }
3151
+ if (prop === ADD_VISIBILITY_LISTENER) {
3152
+ return (listener) => {
3153
+ visibilityListeners.add(listener);
3154
+ };
3155
+ }
3156
+ if (prop === REMOVE_VISIBILITY_LISTENER) {
3157
+ return (listener) => {
3158
+ visibilityListeners.delete(listener);
3159
+ };
3160
+ }
3075
3161
  if (prop === "rows") {
3076
3162
  if (forceFullRedraw) {
3077
3163
  return 0;
@@ -3085,6 +3171,14 @@ function createStableInkStdout(stdout, options) {
3085
3171
  lastColumns = getStableColumn(columns, lastColumns);
3086
3172
  return lastColumns;
3087
3173
  }
3174
+ if (prop === "write") {
3175
+ return (chunk, ...args) => {
3176
+ if (!visible) {
3177
+ return true;
3178
+ }
3179
+ return Reflect.get(target, prop, receiver).call(target, chunk, ...args);
3180
+ };
3181
+ }
3088
3182
  if (prop === "on" || prop === "addListener" || prop === "once" || prop === "prependListener") {
3089
3183
  const method = prop;
3090
3184
  return (eventName, listener) => {
@@ -3094,6 +3188,7 @@ function createStableInkStdout(stdout, options) {
3094
3188
  const rawRows = getRawDimension(Reflect.get(target, "rows", receiver));
3095
3189
  lastColumns = getStableColumn(rawColumns, lastColumns);
3096
3190
  lastRows = getStableRow(rawRows, lastRows);
3191
+ syncVisibility();
3097
3192
  if (!hasRenderableTerminalDimensions({
3098
3193
  [RAW_COLUMNS]: rawColumns,
3099
3194
  [RAW_ROWS]: rawRows
@@ -3136,7 +3231,7 @@ function useAnimatedFrame(active) {
3136
3231
  return;
3137
3232
  }
3138
3233
  const timer = setInterval(() => {
3139
- if (!hasRenderableTerminalDimensions(stdout)) {
3234
+ if (!isTerminalVisible(stdout)) {
3140
3235
  return;
3141
3236
  }
3142
3237
  setIndex((v) => (v + 1) % SPINNER_FRAMES.length);
@@ -3146,16 +3241,60 @@ function useAnimatedFrame(active) {
3146
3241
  return SPINNER_FRAMES[index] ?? SPINNER_FRAMES[0];
3147
3242
  }
3148
3243
 
3244
+ // src/tui/hooks/use-terminal-visibility.ts
3245
+ import { useEffect as useEffect3, useState as useState6 } from "react";
3246
+ import { useStdin, useStdout as useStdout3 } from "ink";
3247
+ var ENABLE_FOCUS_REPORTING = "\x1B[?1004h";
3248
+ var DISABLE_FOCUS_REPORTING = "\x1B[?1004l";
3249
+ var FOCUS_IN = "\x1B[I";
3250
+ var FOCUS_OUT = "\x1B[O";
3251
+ function useTerminalVisibility() {
3252
+ const { stdout } = useStdout3();
3253
+ const { stdin } = useStdin();
3254
+ const [visible, setVisible] = useState6(
3255
+ () => isTerminalVisible(stdout)
3256
+ );
3257
+ useEffect3(() => {
3258
+ const output2 = stdout;
3259
+ const input2 = stdin;
3260
+ setVisible(isTerminalVisible(output2));
3261
+ const unsubscribe = observeTerminalVisibility(output2, setVisible);
3262
+ const handleData = (data) => {
3263
+ const text = typeof data === "string" ? data : data.toString("utf8");
3264
+ if (text.includes(FOCUS_OUT)) {
3265
+ setTerminalFocusVisible(output2, false);
3266
+ }
3267
+ if (text.includes(FOCUS_IN)) {
3268
+ setTerminalFocusVisible(output2, true);
3269
+ }
3270
+ };
3271
+ if (output2.isTTY) {
3272
+ output2.write(ENABLE_FOCUS_REPORTING);
3273
+ }
3274
+ input2.on?.("data", handleData);
3275
+ return () => {
3276
+ input2.off?.("data", handleData);
3277
+ input2.removeListener?.("data", handleData);
3278
+ unsubscribe();
3279
+ setTerminalFocusVisible(output2, true);
3280
+ if (output2.isTTY) {
3281
+ output2.write(DISABLE_FOCUS_REPORTING);
3282
+ }
3283
+ };
3284
+ }, [stdin, stdout]);
3285
+ return visible;
3286
+ }
3287
+
3149
3288
  // src/tui/hooks/use-terminal-width.ts
3150
- import { useState as useState6, useEffect as useEffect3 } from "react";
3151
- import { useStdout as useStdout3 } from "ink";
3289
+ import { useState as useState7, useEffect as useEffect4 } from "react";
3290
+ import { useStdout as useStdout4 } from "ink";
3152
3291
  var RESIZE_DEBOUNCE_MS = 50;
3153
3292
  function useTerminalWidth() {
3154
- const { stdout } = useStdout3();
3155
- const [terminalWidth, setTerminalWidth] = useState6(
3293
+ const { stdout } = useStdout4();
3294
+ const [terminalWidth, setTerminalWidth] = useState7(
3156
3295
  () => getObservedTerminalWidth(stdout.columns, process.stdout.columns)
3157
3296
  );
3158
- useEffect3(() => {
3297
+ useEffect4(() => {
3159
3298
  const stream = stdout;
3160
3299
  let resizeTimer = null;
3161
3300
  const commitWidth = () => {
@@ -4454,47 +4593,51 @@ function App({
4454
4593
  const abortRef = useRef2(null);
4455
4594
  const turnManagerRef = useRef2(null);
4456
4595
  const turnIndexRef = useRef2(0);
4457
- const [input2, setInput] = useState7("");
4458
- const [composerFocused, setComposerFocused] = useState7(true);
4459
- const [busy, setBusy] = useState7(false);
4460
- const [authStatus, setAuthStatus] = useState7(initialState.authStatus);
4461
- const [workspacePath, setWorkspacePath] = useState7(initialState.workspacePath);
4462
- const [workspaceFiles, setWorkspaceFiles] = useState7([]);
4463
- const [skills2, setSkills] = useState7([]);
4464
- const [messages, setMessages] = useState7([]);
4465
- const [history, setHistory] = useState7([]);
4466
- const [activeSkills, setActiveSkills] = useState7([]);
4467
- const [pendingUpdates, setPendingUpdates] = useState7(initialState.pendingUpdates);
4468
- const [statusLine, setStatusLine] = useState7("");
4469
- const [activeToolActivities, setActiveToolActivities] = useState7({});
4470
- const [turnToolCount, setTurnToolCount] = useState7(0);
4471
- const [latchedToolActivity, setLatchedToolActivity] = useState7("");
4472
- const [latchedToolCount, setLatchedToolCount] = useState7(0);
4473
- const [subAgentProgress, setSubAgentProgress] = useState7({});
4474
- const [toolActivityExpanded, setToolActivityExpanded] = useState7(false);
4596
+ const [input2, setInput] = useState8("");
4597
+ const [composerFocused, setComposerFocused] = useState8(true);
4598
+ const [busy, setBusy] = useState8(false);
4599
+ const [authStatus, setAuthStatus] = useState8(initialState.authStatus);
4600
+ const [workspacePath, setWorkspacePath] = useState8(initialState.workspacePath);
4601
+ const [workspaceFiles, setWorkspaceFiles] = useState8([]);
4602
+ const [skills2, setSkills] = useState8([]);
4603
+ const [messages, setMessages] = useState8([]);
4604
+ const [history, setHistory] = useState8([]);
4605
+ const [activeSkills, setActiveSkills] = useState8([]);
4606
+ const [pendingUpdates, setPendingUpdates] = useState8(initialState.pendingUpdates);
4607
+ const [statusLine, setStatusLine] = useState8("");
4608
+ const [activeToolActivities, setActiveToolActivities] = useState8({});
4609
+ const [turnToolCount, setTurnToolCount] = useState8(0);
4610
+ const [latchedToolActivity, setLatchedToolActivity] = useState8("");
4611
+ const [latchedToolCount, setLatchedToolCount] = useState8(0);
4612
+ const [subAgentProgress, setSubAgentProgress] = useState8({});
4613
+ const [toolActivityExpanded, setToolActivityExpanded] = useState8(false);
4475
4614
  const turnToolLogRef = useRef2([]);
4476
- const [sessionTokens, setSessionTokens] = useState7(() => createSessionUsage());
4477
- const [tokenDisplay, setTokenDisplay] = useState7("");
4478
- const [showSuggestions, setShowSuggestions] = useState7(false);
4479
- const [agentMode, setAgentMode] = useState7("manual-review");
4480
- const [theme, setTheme] = useState7("dark");
4481
- const [config, setConfig] = useState7(null);
4482
- const [cursorToEnd, setCursorToEnd] = useState7(0);
4483
- const [messageRenderVersion, setMessageRenderVersion] = useState7(0);
4484
- const [screen, setScreen] = useState7("main");
4485
- const [resumeSessions, setResumeSessions] = useState7([]);
4486
- const [ctrlCPending, setCtrlCPending] = useState7(false);
4487
- const [sessionId, setSessionId] = useState7(() => crypto.randomUUID());
4615
+ const [sessionTokens, setSessionTokens] = useState8(() => createSessionUsage());
4616
+ const [tokenDisplay, setTokenDisplay] = useState8("");
4617
+ const [showSuggestions, setShowSuggestions] = useState8(false);
4618
+ const [agentMode, setAgentMode] = useState8("manual-review");
4619
+ const [theme, setTheme] = useState8("dark");
4620
+ const [config, setConfig] = useState8(null);
4621
+ const [cursorToEnd, setCursorToEnd] = useState8(0);
4622
+ const [messageRenderVersion, setMessageRenderVersion] = useState8(0);
4623
+ const [screen, setScreen] = useState8("main");
4624
+ const [resumeSessions, setResumeSessions] = useState8([]);
4625
+ const [ctrlCPending, setCtrlCPending] = useState8(false);
4626
+ const [sessionId, setSessionId] = useState8(() => crypto.randomUUID());
4488
4627
  const deferredPendingUpdates = useDeferredValue(pendingUpdates);
4489
4628
  const visiblePendingUpdates = deferredPendingUpdates.length > 0 ? deferredPendingUpdates : pendingUpdates;
4629
+ const terminalVisible = useTerminalVisibility();
4490
4630
  const activityFrame = useAnimatedFrame(busy);
4491
4631
  const terminalWidth = useTerminalWidth();
4492
4632
  const contentWidth = insetWidth(terminalWidth, 2);
4493
4633
  const panelInnerWidth = insetWidth(contentWidth, 4);
4494
4634
  const panelBodyWidth = insetWidth(panelInnerWidth, 2);
4495
- const [agentQuestion, setAgentQuestion] = useState7(null);
4635
+ const [agentQuestion, setAgentQuestion] = useState8(null);
4496
4636
  const previewRef = useRef2(null);
4497
4637
  const ctrlCTimerRef = useRef2(null);
4638
+ const terminalVisibleRef = useRef2(terminalVisible);
4639
+ const previousTerminalVisibleRef = useRef2(terminalVisible);
4640
+ const streamBufferRef = useRef2(null);
4498
4641
  const isHome = messages.length === 0 && !busy;
4499
4642
  const { staticMessages, dynamicMessages } = useMemo4(
4500
4643
  () => splitMessagesForRender(messages, busy),
@@ -4514,18 +4657,12 @@ function App({
4514
4657
  [activeToolActivities]
4515
4658
  );
4516
4659
  const currentToolActivity = useMemo4(() => {
4517
- if (activeToolDescriptions.length === 0) {
4518
- return "";
4519
- }
4520
- if (activeToolDescriptions.length === 1 && turnToolCount === 0) {
4521
- return activeToolDescriptions[0] ?? "";
4522
- }
4523
- if (activeToolDescriptions.length === 1) {
4524
- return "Running 1 tool";
4525
- }
4660
+ if (activeToolDescriptions.length === 0) return "";
4661
+ if (activeToolDescriptions.length === 1) return activeToolDescriptions[0] ?? "";
4662
+ if (activeToolDescriptions.length === 2) return activeToolDescriptions.join(" \xB7 ");
4526
4663
  return `Running ${activeToolDescriptions.length} tools in parallel`;
4527
- }, [activeToolDescriptions, turnToolCount]);
4528
- useEffect4(() => {
4664
+ }, [activeToolDescriptions]);
4665
+ useEffect5(() => {
4529
4666
  if (!busy) {
4530
4667
  setLatchedToolActivity("");
4531
4668
  setLatchedToolCount(0);
@@ -4543,7 +4680,7 @@ function App({
4543
4680
  );
4544
4681
  const hasWorkspace = workspacePath !== null;
4545
4682
  const hasAuth = authStatus === "connected";
4546
- useEffect4(() => {
4683
+ useEffect5(() => {
4547
4684
  if (!busy) {
4548
4685
  setAgentQuestion(null);
4549
4686
  return;
@@ -4557,7 +4694,7 @@ function App({
4557
4694
  }, 200);
4558
4695
  return () => clearInterval(interval);
4559
4696
  }, [busy, agentQuestion]);
4560
- useEffect4(() => {
4697
+ useEffect5(() => {
4561
4698
  void (async () => {
4562
4699
  const [cfg, storedOrDiskProviderConfigured] = await Promise.all([
4563
4700
  ensureOpenResearchConfig({ homeDir }),
@@ -4574,7 +4711,7 @@ function App({
4574
4711
  });
4575
4712
  })();
4576
4713
  }, [homeDir]);
4577
- useEffect4(() => {
4714
+ useEffect5(() => {
4578
4715
  if (!workspacePath) return;
4579
4716
  let cancelled = false;
4580
4717
  void scanWorkspace(workspacePath).then((result) => {
@@ -4590,7 +4727,7 @@ function App({
4590
4727
  cancelled = true;
4591
4728
  };
4592
4729
  }, [workspacePath, sessionId]);
4593
- useEffect4(() => {
4730
+ useEffect5(() => {
4594
4731
  let cancelled = false;
4595
4732
  void listAvailableSkills({ homeDir }).then((available) => {
4596
4733
  if (cancelled) return;
@@ -4602,14 +4739,24 @@ function App({
4602
4739
  cancelled = true;
4603
4740
  };
4604
4741
  }, [homeDir]);
4605
- useEffect4(() => {
4742
+ useEffect5(() => {
4606
4743
  return () => {
4607
4744
  if (ctrlCTimerRef.current) {
4608
4745
  clearTimeout(ctrlCTimerRef.current);
4609
4746
  }
4610
4747
  };
4611
4748
  }, []);
4612
- const [selectedSuggestion, setSelectedSuggestion] = useState7(-1);
4749
+ useEffect5(() => {
4750
+ terminalVisibleRef.current = terminalVisible;
4751
+ if (terminalVisible) {
4752
+ streamBufferRef.current?.flush();
4753
+ }
4754
+ if (terminalVisible && !previousTerminalVisibleRef.current) {
4755
+ setMessageRenderVersion((current) => current + 1);
4756
+ }
4757
+ previousTerminalVisibleRef.current = terminalVisible;
4758
+ }, [terminalVisible]);
4759
+ const [selectedSuggestion, setSelectedSuggestion] = useState8(-1);
4613
4760
  const atMention = useMemo4(() => extractAtMention(input2), [input2]);
4614
4761
  const slashTrigger = useMemo4(() => extractSlashTrigger(input2), [input2]);
4615
4762
  const suggestions = useMemo4(() => {
@@ -4619,7 +4766,7 @@ function App({
4619
4766
  if (!slashTrigger) return [];
4620
4767
  return getUnifiedSuggestions(`/${slashTrigger.partial}`, skills2);
4621
4768
  }, [input2, skills2, atMention, slashTrigger, workspaceFiles]);
4622
- useEffect4(() => {
4769
+ useEffect5(() => {
4623
4770
  setSelectedSuggestion(-1);
4624
4771
  }, [suggestions.length, input2]);
4625
4772
  const dropdownVisible = suggestions.length > 0 && input2.length > 0;
@@ -4911,8 +5058,10 @@ function App({
4911
5058
  streamBuffer = createSentenceStreamBuffer({
4912
5059
  onFlush: (text) => {
4913
5060
  addAssistantMessage(text);
4914
- }
5061
+ },
5062
+ isVisible: () => terminalVisibleRef.current
4915
5063
  });
5064
+ streamBufferRef.current = streamBuffer;
4916
5065
  const result = await runAgentTurn({
4917
5066
  provider,
4918
5067
  message,
@@ -4949,6 +5098,10 @@ function App({
4949
5098
  durationMs: activity.durationMs
4950
5099
  });
4951
5100
  setTurnToolCount(turnToolLogRef.current.length);
5101
+ if (activity.name === "write_new_file" || activity.name === "update_existing_file") {
5102
+ const verb = activity.name === "write_new_file" ? "Created" : "Updated";
5103
+ addSystemMessage(` \u25CA ${verb}: ${activity.description ?? activity.name}`);
5104
+ }
4952
5105
  }
4953
5106
  },
4954
5107
  onSubAgentProgress: (progress) => {
@@ -5055,6 +5208,7 @@ ${error.stack}` : String(error)}` }
5055
5208
  }
5056
5209
  } finally {
5057
5210
  streamBuffer?.dispose();
5211
+ streamBufferRef.current = null;
5058
5212
  abortRef.current = null;
5059
5213
  setActiveToolActivities({});
5060
5214
  setSubAgentProgress({});
@@ -5164,7 +5318,25 @@ ${error.stack}` : String(error)}` }
5164
5318
  `conversation-static-${messageRenderVersion}`
5165
5319
  ),
5166
5320
  dynamicRenderItems.length > 0 && /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", marginBottom: 1, width: contentWidth, children: dynamicRenderItems }),
5167
- showThinking && /* @__PURE__ */ jsx6(ThinkingIndicator, { frame: activityFrame, width: contentWidth }),
5321
+ showThinking && /* @__PURE__ */ jsx6(ThinkingIndicator, { frame: activityFrame, width: contentWidth, currentTask: getCurrentTask() ?? void 0 }),
5322
+ busy && turnToolLogRef.current.length > 0 && /* @__PURE__ */ jsx6(
5323
+ ActivityFeed,
5324
+ {
5325
+ frame: activityFrame,
5326
+ width: contentWidth,
5327
+ items: [
5328
+ ...turnToolLogRef.current.map((t) => ({
5329
+ description: t.description,
5330
+ status: "done",
5331
+ durationMs: t.durationMs
5332
+ })),
5333
+ ...Object.values(activeToolActivities).map((desc) => ({
5334
+ description: desc,
5335
+ status: "active"
5336
+ }))
5337
+ ]
5338
+ }
5339
+ ),
5168
5340
  visibleSubAgents.map((progress) => /* @__PURE__ */ jsx6(
5169
5341
  SubAgentIndicator,
5170
5342
  {
@@ -5172,6 +5344,7 @@ ${error.stack}` : String(error)}` }
5172
5344
  goal: progress.goal,
5173
5345
  currentTool: progress.currentTool,
5174
5346
  toolCount: progress.toolCount,
5347
+ recentTools: progress.recentTools,
5175
5348
  frame: activityFrame,
5176
5349
  width: contentWidth
5177
5350
  },
@@ -5413,7 +5586,7 @@ source.command("add-url").argument("<url>").description("Fetch and add a URL sou
5413
5586
  });
5414
5587
  program.command("serve").description("Start the Open Research API server (headless mode).").option("-p, --port <port>", "Port to listen on", "3210").action(async (opts) => {
5415
5588
  const { serve } = await import("@hono/node-server");
5416
- const { createApp } = await import("./server-PLHMTHCG.js");
5589
+ const { createApp } = await import("./server-7CSQEGRQ.js");
5417
5590
  const port = parseInt(opts.port, 10);
5418
5591
  const { app } = createApp();
5419
5592
  console.log(`Open Research server listening on http://localhost:${port}`);
@@ -576,7 +576,7 @@ async function runOntologyManager(input) {
576
576
  args,
577
577
  ontology
578
578
  );
579
- if (updated !== ontology) mutated = true;
579
+ if (!READ_ONLY_TOOLS.has(toolCall.name)) mutated = true;
580
580
  ontology = updated;
581
581
  messages.push({
582
582
  role: "tool",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-W3MWVV2O.js";
4
- import "../chunk-32Q63SUH.js";
3
+ } from "../chunk-YFDZRMPM.js";
4
+ import "../chunk-G7IOFPGG.js";
5
5
  import "../chunk-EW27OWCA.js";
6
6
  import "../chunk-KOBMIIQM.js";
7
7
  import "../chunk-3GZIDCV2.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createApp
3
- } from "./chunk-W3MWVV2O.js";
4
- import "./chunk-32Q63SUH.js";
3
+ } from "./chunk-YFDZRMPM.js";
4
+ import "./chunk-G7IOFPGG.js";
5
5
  import "./chunk-EW27OWCA.js";
6
6
  import "./chunk-KOBMIIQM.js";
7
7
  import "./chunk-3GZIDCV2.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-research",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Local-first research CLI agent — discover papers, synthesize notes, run analysis, and draft artifacts from your terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",