open-research 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  <h3 align="center">The research-native CLI agent.</h3>
6
6
 
7
7
  <p align="center">
8
+ <a href="https://open-research.info">open-research.info</a> &nbsp;·&nbsp;
8
9
  <a href="https://www.npmjs.com/package/open-research"><img src="https://img.shields.io/npm/v/open-research.svg" alt="npm" /></a>
9
10
  <a href="https://github.com/gangj277/open-research/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/open-research.svg" alt="license" /></a>
10
11
  </p>
@@ -227,10 +227,14 @@ async function executeFetchUrl(args, signal) {
227
227
 
228
228
  // src/lib/fs/pdf.ts
229
229
  import fs from "fs/promises";
230
+ import path from "path";
231
+ import { createRequire } from "module";
232
+ var require2 = createRequire(import.meta.url);
233
+ var standardFontDataUrl = path.join(path.dirname(require2.resolve("pdfjs-dist/package.json")), "standard_fonts") + "/";
230
234
  async function extractPdfText(filePath, options) {
231
235
  const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
232
236
  const buffer = await fs.readFile(filePath);
233
- const document = await pdfjs.getDocument({ data: new Uint8Array(buffer) }).promise;
237
+ const document = await pdfjs.getDocument({ data: new Uint8Array(buffer), standardFontDataUrl }).promise;
234
238
  const totalPages = document.numPages;
235
239
  const start = Math.max(1, options?.startPage ?? 1);
236
240
  const end = Math.min(totalPages, options?.endPage ?? totalPages);
@@ -245,7 +249,7 @@ async function extractPdfText(filePath, options) {
245
249
  }
246
250
  async function extractPdfTextFromBuffer(buffer, options) {
247
251
  const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
248
- const document = await pdfjs.getDocument({ data: buffer }).promise;
252
+ const document = await pdfjs.getDocument({ data: buffer, standardFontDataUrl }).promise;
249
253
  const totalPages = document.numPages;
250
254
  const end = Math.min(totalPages, options?.maxPages ?? 20);
251
255
  const pages = [];
package/dist/cli.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  loadOpenResearchConfig,
27
27
  saveOpenResearchConfig,
28
28
  themeValues
29
- } from "./chunk-3KZN54JZ.js";
29
+ } from "./chunk-TJA4CAZE.js";
30
30
  import {
31
31
  readJsonFile,
32
32
  writeJsonFile
@@ -828,7 +828,7 @@ function formatDateTime(value) {
828
828
  }
829
829
 
830
830
  // src/lib/cli/version.ts
831
- var PACKAGE_VERSION = "1.1.0";
831
+ var PACKAGE_VERSION = "1.1.2";
832
832
  function getPackageVersion() {
833
833
  return PACKAGE_VERSION;
834
834
  }
@@ -5927,11 +5927,11 @@ async function discoverScholarlySources({
5927
5927
  const openAlexSearch = clients.searchOpenAlexWorks ?? searchOpenAlexWorks;
5928
5928
  const semanticScholarSearch = clients.searchSemanticScholarPapers ?? searchSemanticScholarPapers;
5929
5929
  const arxivSearch = clients.searchArxivPapers ?? searchArxivPapers;
5930
- const tasks2 = [];
5930
+ const tasks = [];
5931
5931
  if (openAlexApiKey?.trim()) {
5932
5932
  for (let qi = 0; qi < searchQueries.length; qi++) {
5933
5933
  const qIdx = qi;
5934
- tasks2.push(
5934
+ tasks.push(
5935
5935
  openAlexSearch({
5936
5936
  query: searchQueries[qIdx],
5937
5937
  apiKey: openAlexApiKey.trim(),
@@ -5950,7 +5950,7 @@ async function discoverScholarlySources({
5950
5950
  }
5951
5951
  for (let qi = 0; qi < searchQueries.length; qi++) {
5952
5952
  const qIdx = qi;
5953
- tasks2.push(
5953
+ tasks.push(
5954
5954
  semanticScholarSearch({
5955
5955
  query: searchQueries[qIdx],
5956
5956
  apiKey: semanticScholarApiKey?.trim(),
@@ -5975,7 +5975,7 @@ async function discoverScholarlySources({
5975
5975
  } : void 0;
5976
5976
  for (let qi = 0; qi < searchQueries.length; qi++) {
5977
5977
  const qIdx = qi;
5978
- tasks2.push(
5978
+ tasks.push(
5979
5979
  arxivSearch({
5980
5980
  query: searchQueries[qIdx],
5981
5981
  maxResults: perQueryResults,
@@ -5988,7 +5988,7 @@ async function discoverScholarlySources({
5988
5988
  );
5989
5989
  }
5990
5990
  }
5991
- const settled = await Promise.allSettled(tasks2);
5991
+ const settled = await Promise.allSettled(tasks);
5992
5992
  const candidates = settled.flatMap(
5993
5993
  (result) => result.status === "fulfilled" ? result.value : []
5994
5994
  );
@@ -6310,59 +6310,100 @@ function describeSubAgentTool(name, args) {
6310
6310
  // src/lib/agent/tools/tasks.ts
6311
6311
  import path11 from "path";
6312
6312
  import fs11 from "fs/promises";
6313
- var tasks = [];
6313
+ var currentTasks = [];
6314
6314
  var storePath = null;
6315
+ var currentSessionId = null;
6316
+ var pendingWrite = Promise.resolve();
6315
6317
  function shortId() {
6316
6318
  return crypto.randomUUID().replace(/-/g, "").slice(0, 8);
6317
6319
  }
6318
- async function persist() {
6319
- if (!storePath) return;
6320
- const live = tasks.filter((t) => t.status !== "deleted");
6321
- const tmpPath = storePath + ".tmp";
6322
- await fs11.mkdir(path11.dirname(storePath), { recursive: true });
6323
- await fs11.writeFile(tmpPath, JSON.stringify({ version: 1, tasks: live }, null, 2));
6324
- await fs11.rename(tmpPath, storePath);
6320
+ function cloneTasks(tasks) {
6321
+ return tasks.map((task) => ({ ...task }));
6325
6322
  }
6326
- async function initTaskStore(workspaceDir) {
6323
+ async function persistSnapshot(snapshot) {
6324
+ const data = await readJsonFile(snapshot.storePath, { version: 1, tasks: [] });
6325
+ const foreignTasks = (data.tasks ?? []).filter(
6326
+ (task) => task.status !== "deleted" && task.sessionId !== snapshot.sessionId
6327
+ );
6328
+ const liveSessionTasks = snapshot.tasks.filter((task) => task.status !== "deleted");
6329
+ const mergedTasks = [...foreignTasks, ...liveSessionTasks];
6330
+ const tmpPath = snapshot.storePath + ".tmp";
6331
+ await fs11.mkdir(path11.dirname(snapshot.storePath), { recursive: true });
6332
+ await fs11.writeFile(
6333
+ tmpPath,
6334
+ JSON.stringify({ version: 1, tasks: mergedTasks }, null, 2)
6335
+ );
6336
+ await fs11.rename(tmpPath, snapshot.storePath);
6337
+ }
6338
+ function schedulePersist() {
6339
+ if (!storePath || !currentSessionId) return pendingWrite;
6340
+ const snapshot = {
6341
+ storePath,
6342
+ sessionId: currentSessionId,
6343
+ tasks: cloneTasks(currentTasks)
6344
+ };
6345
+ pendingWrite = pendingWrite.catch(() => void 0).then(() => persistSnapshot(snapshot));
6346
+ void pendingWrite.catch(() => void 0);
6347
+ return pendingWrite;
6348
+ }
6349
+ function ensureSessionId() {
6350
+ if (!currentSessionId) {
6351
+ currentSessionId = crypto.randomUUID();
6352
+ }
6353
+ return currentSessionId;
6354
+ }
6355
+ async function initTaskStore(workspaceDir, sessionId2 = crypto.randomUUID()) {
6327
6356
  storePath = path11.join(workspaceDir, ".open-research", "tasks.json");
6328
6357
  const data = await readJsonFile(storePath, { version: 1, tasks: [] });
6329
- tasks = data.tasks ?? [];
6358
+ currentSessionId = sessionId2;
6359
+ currentTasks = (data.tasks ?? []).filter(
6360
+ (task) => task.status !== "deleted" && task.sessionId === currentSessionId
6361
+ );
6330
6362
  }
6331
6363
  function clearAllTasks() {
6332
- tasks = [];
6333
- void persist();
6364
+ currentTasks = [];
6365
+ void schedulePersist();
6334
6366
  }
6335
6367
  function executeCreateTasks(args) {
6368
+ const sessionId2 = ensureSessionId();
6336
6369
  const created = [];
6337
6370
  for (const item of args.tasks) {
6338
6371
  const task = {
6339
6372
  id: shortId(),
6373
+ sessionId: sessionId2,
6340
6374
  subject: item.subject,
6341
6375
  activeForm: item.activeForm,
6342
6376
  status: "pending",
6343
6377
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6344
6378
  };
6345
- tasks.push(task);
6379
+ currentTasks.push(task);
6346
6380
  created.push(task);
6347
6381
  }
6348
- void persist();
6382
+ void schedulePersist();
6349
6383
  return created.map((t) => `[${t.id}] ${t.subject}`).join("\n");
6350
6384
  }
6351
6385
  function executeUpdateTask(args) {
6352
- const task = tasks.find((t) => t.id === args.taskId);
6386
+ const task = currentTasks.find((t) => t.id === args.taskId);
6353
6387
  if (!task) return `Task not found: ${args.taskId}`;
6354
6388
  if (args.status) {
6355
6389
  task.status = args.status;
6356
- if (args.status === "completed") task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
6390
+ if (args.status === "completed") {
6391
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
6392
+ } else {
6393
+ delete task.completedAt;
6394
+ }
6357
6395
  }
6358
6396
  if (args.subject !== void 0) task.subject = args.subject;
6359
6397
  if (args.activeForm !== void 0) task.activeForm = args.activeForm;
6360
- void persist();
6398
+ if (task.status === "deleted") {
6399
+ currentTasks = currentTasks.filter((candidate) => candidate.id !== task.id);
6400
+ }
6401
+ void schedulePersist();
6361
6402
  const label = task.status === "deleted" ? "deleted" : `${task.subject} \u2192 ${task.status}`;
6362
6403
  return `Task updated: ${label}`;
6363
6404
  }
6364
6405
  function getTaskContextBlock() {
6365
- const live = tasks.filter((t) => t.status !== "deleted");
6406
+ const live = currentTasks.filter((t) => t.status !== "deleted");
6366
6407
  if (live.length === 0) return null;
6367
6408
  const lines = live.map((t) => {
6368
6409
  if (t.status === "completed") return `[x] ${t.subject}`;
@@ -6373,7 +6414,7 @@ function getTaskContextBlock() {
6373
6414
  ${lines.join("\n")}`;
6374
6415
  }
6375
6416
  function getVisibleTasks() {
6376
- return tasks.filter((t) => t.status !== "deleted");
6417
+ return currentTasks.filter((t) => t.status !== "deleted");
6377
6418
  }
6378
6419
 
6379
6420
  // src/lib/agent/tool-dispatcher.ts
@@ -6453,7 +6494,7 @@ async function executeTool(name, args, ctx, activeSkills, homeDir, signal, provi
6453
6494
  return { result: out.result, searchResults: out.sources };
6454
6495
  }
6455
6496
  case "web_search": {
6456
- const { executeWebSearch } = await import("./web-search-TKBFSU3M.js");
6497
+ const { executeWebSearch } = await import("./web-search-IBZ6UAXL.js");
6457
6498
  const out = await executeWebSearch(
6458
6499
  args,
6459
6500
  provider
@@ -7269,8 +7310,8 @@ var TOOL_DESCRIPTIONS = {
7269
7310
  return `Sub-agent (${type ?? "explore"}): ${goal?.slice(0, 60) ?? "task"}`;
7270
7311
  },
7271
7312
  create_tasks: (a) => {
7272
- const tasks2 = a.tasks;
7273
- return `Creating ${tasks2?.length ?? 0} task${(tasks2?.length ?? 0) !== 1 ? "s" : ""}`;
7313
+ const tasks = a.tasks;
7314
+ return `Creating ${tasks?.length ?? 0} task${(tasks?.length ?? 0) !== 1 ? "s" : ""}`;
7274
7315
  },
7275
7316
  update_task: (a) => {
7276
7317
  const status = a.status;
@@ -8341,15 +8382,15 @@ function ThinkingIndicator({ frame, width }) {
8341
8382
  ] });
8342
8383
  }
8343
8384
  function TaskPanel({
8344
- tasks: tasks2,
8385
+ tasks,
8345
8386
  frame,
8346
8387
  width
8347
8388
  }) {
8348
8389
  const theme = useTheme();
8349
8390
  const contentWidth = resolveWidth(width);
8350
- const completed = tasks2.filter((t) => t.status === "completed");
8351
- const active = tasks2.filter((t) => t.status !== "completed");
8352
- if (tasks2.length === 0) return null;
8391
+ const completed = tasks.filter((t) => t.status === "completed");
8392
+ const active = tasks.filter((t) => t.status !== "completed");
8393
+ if (tasks.length === 0) return null;
8353
8394
  return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, marginLeft: 2, width: indentedWidth(contentWidth), children: [
8354
8395
  active.map((task) => {
8355
8396
  const icon = task.status === "in_progress" ? frame : GUTTER.pending;
@@ -9090,26 +9131,127 @@ function createSentenceStreamBuffer({
9090
9131
 
9091
9132
  // src/tui/hooks/use-animated-frame.ts
9092
9133
  import { useState as useState5, useEffect as useEffect2 } from "react";
9134
+ import { useStdout as useStdout2 } from "ink";
9135
+
9136
+ // src/tui/ink-stdout.ts
9137
+ var RAW_COLUMNS = /* @__PURE__ */ Symbol("open-research.raw-columns");
9138
+ var RAW_ROWS = /* @__PURE__ */ Symbol("open-research.raw-rows");
9139
+ function getStableRow(current, fallback) {
9140
+ return typeof current === "number" && current > 0 ? current : fallback;
9141
+ }
9142
+ function getStableColumn(current, fallback) {
9143
+ return typeof current === "number" && current >= MIN_TERMINAL_WIDTH ? current : fallback;
9144
+ }
9145
+ function getRawDimension(current) {
9146
+ return typeof current === "number" && Number.isFinite(current) ? current : void 0;
9147
+ }
9148
+ function getRawTerminalDimensions(stdout) {
9149
+ const stream = stdout;
9150
+ return {
9151
+ columns: getRawDimension(stream[RAW_COLUMNS] ?? stream.columns),
9152
+ rows: getRawDimension(stream[RAW_ROWS] ?? stream.rows)
9153
+ };
9154
+ }
9155
+ function hasRenderableTerminalDimensions(stdout) {
9156
+ const { columns, rows } = getRawTerminalDimensions(stdout);
9157
+ const hasRows = rows === void 0 || rows > 0;
9158
+ return hasRows && typeof columns === "number" && columns >= MIN_TERMINAL_WIDTH;
9159
+ }
9160
+ function createStableInkStdout(stdout, options) {
9161
+ const forceFullRedraw = options?.forceFullRedraw ?? process.env.OPEN_RESEARCH_FORCE_FULL_REDRAW === "1";
9162
+ const initialRows = getRawDimension(stdout.rows);
9163
+ const initialColumns = getRawDimension(stdout.columns);
9164
+ let lastRows = getStableRow(initialRows, 24);
9165
+ let lastColumns = typeof initialColumns === "number" && initialColumns > 0 ? Math.max(MIN_TERMINAL_WIDTH, initialColumns) : 80;
9166
+ const resizeListeners = /* @__PURE__ */ new Map();
9167
+ return new Proxy(stdout, {
9168
+ get(target, prop, receiver) {
9169
+ if (prop === RAW_ROWS) {
9170
+ return getRawDimension(Reflect.get(target, "rows", receiver));
9171
+ }
9172
+ if (prop === RAW_COLUMNS) {
9173
+ return getRawDimension(Reflect.get(target, "columns", receiver));
9174
+ }
9175
+ if (prop === "rows") {
9176
+ if (forceFullRedraw) {
9177
+ return 0;
9178
+ }
9179
+ const rows = Reflect.get(target, prop, receiver);
9180
+ lastRows = getStableRow(rows, lastRows);
9181
+ return lastRows;
9182
+ }
9183
+ if (prop === "columns") {
9184
+ const columns = Reflect.get(target, prop, receiver);
9185
+ lastColumns = getStableColumn(columns, lastColumns);
9186
+ return lastColumns;
9187
+ }
9188
+ if (prop === "on" || prop === "addListener" || prop === "once" || prop === "prependListener") {
9189
+ const method = prop;
9190
+ return (eventName, listener) => {
9191
+ if (eventName === "resize" && !forceFullRedraw) {
9192
+ const wrapped = (...args) => {
9193
+ const rawColumns = getRawDimension(Reflect.get(target, "columns", receiver));
9194
+ const rawRows = getRawDimension(Reflect.get(target, "rows", receiver));
9195
+ lastColumns = getStableColumn(rawColumns, lastColumns);
9196
+ lastRows = getStableRow(rawRows, lastRows);
9197
+ if (!hasRenderableTerminalDimensions({
9198
+ [RAW_COLUMNS]: rawColumns,
9199
+ [RAW_ROWS]: rawRows
9200
+ })) {
9201
+ return;
9202
+ }
9203
+ listener(...args);
9204
+ };
9205
+ resizeListeners.set(listener, wrapped);
9206
+ Reflect.get(target, method, receiver).call(target, eventName, wrapped);
9207
+ return receiver;
9208
+ }
9209
+ Reflect.get(target, method, receiver).call(target, eventName, listener);
9210
+ return receiver;
9211
+ };
9212
+ }
9213
+ if (prop === "off" || prop === "removeListener") {
9214
+ const method = prop;
9215
+ return (eventName, listener) => {
9216
+ const wrapped = eventName === "resize" ? resizeListeners.get(listener) ?? listener : listener;
9217
+ resizeListeners.delete(listener);
9218
+ Reflect.get(target, method, receiver).call(target, eventName, wrapped);
9219
+ return receiver;
9220
+ };
9221
+ }
9222
+ const value = Reflect.get(target, prop, receiver);
9223
+ return typeof value === "function" ? value.bind(target) : value;
9224
+ }
9225
+ });
9226
+ }
9227
+
9228
+ // src/tui/hooks/use-animated-frame.ts
9093
9229
  var SPINNER_FRAMES = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
9094
9230
  function useAnimatedFrame(active) {
9231
+ const { stdout } = useStdout2();
9095
9232
  const [index, setIndex] = useState5(0);
9096
9233
  useEffect2(() => {
9097
9234
  if (!active) {
9098
9235
  setIndex(0);
9099
9236
  return;
9100
9237
  }
9101
- const timer = setInterval(() => setIndex((v) => (v + 1) % SPINNER_FRAMES.length), 120);
9238
+ const timer = setInterval(() => {
9239
+ if (!hasRenderableTerminalDimensions(stdout)) {
9240
+ return;
9241
+ }
9242
+ setIndex((v) => (v + 1) % SPINNER_FRAMES.length);
9243
+ }, 120);
9102
9244
  return () => clearInterval(timer);
9103
- }, [active]);
9245
+ }, [active, stdout]);
9104
9246
  return SPINNER_FRAMES[index] ?? SPINNER_FRAMES[0];
9105
9247
  }
9106
9248
 
9107
9249
  // src/tui/hooks/use-terminal-width.ts
9108
9250
  import { useState as useState6, useEffect as useEffect3 } from "react";
9109
- import { useStdout as useStdout2 } from "ink";
9251
+ import { useStdout as useStdout3 } from "ink";
9110
9252
  var RESIZE_DEBOUNCE_MS = 50;
9111
9253
  function useTerminalWidth() {
9112
- const { stdout } = useStdout2();
9254
+ const { stdout } = useStdout3();
9113
9255
  const [terminalWidth, setTerminalWidth] = useState6(
9114
9256
  () => getObservedTerminalWidth(stdout.columns, process.stdout.columns)
9115
9257
  );
@@ -10380,6 +10522,8 @@ function App({
10380
10522
  const [statusLine, setStatusLine] = useState7("");
10381
10523
  const [activeToolActivities, setActiveToolActivities] = useState7({});
10382
10524
  const [turnToolCount, setTurnToolCount] = useState7(0);
10525
+ const [latchedToolActivity, setLatchedToolActivity] = useState7("");
10526
+ const [latchedToolCount, setLatchedToolCount] = useState7(0);
10383
10527
  const [subAgentProgress, setSubAgentProgress] = useState7({});
10384
10528
  const [toolActivityExpanded, setToolActivityExpanded] = useState7(false);
10385
10529
  const [taskPanelVisible, setTaskPanelVisible] = useState7(true);
@@ -10400,7 +10544,7 @@ function App({
10400
10544
  const [screen, setScreen] = useState7("main");
10401
10545
  const [resumeSessions, setResumeSessions] = useState7([]);
10402
10546
  const [ctrlCPending, setCtrlCPending] = useState7(false);
10403
- const sessionId2 = useMemo4(() => crypto.randomUUID(), []);
10547
+ const [sessionId2, setSessionId] = useState7(() => crypto.randomUUID());
10404
10548
  const deferredPendingUpdates = useDeferredValue(pendingUpdates);
10405
10549
  const visiblePendingUpdates = deferredPendingUpdates.length > 0 ? deferredPendingUpdates : pendingUpdates;
10406
10550
  const activityFrame = useAnimatedFrame(busy);
@@ -10441,6 +10585,18 @@ function App({
10441
10585
  }
10442
10586
  return `Running ${activeToolDescriptions.length} tools in parallel`;
10443
10587
  }, [activeToolDescriptions, turnToolCount]);
10588
+ useEffect4(() => {
10589
+ if (!busy) {
10590
+ setLatchedToolActivity("");
10591
+ setLatchedToolCount(0);
10592
+ return;
10593
+ }
10594
+ if (!currentToolActivity) {
10595
+ return;
10596
+ }
10597
+ setLatchedToolActivity(currentToolActivity);
10598
+ setLatchedToolCount(turnToolCount);
10599
+ }, [busy, currentToolActivity, turnToolCount]);
10444
10600
  const visibleSubAgents = useMemo4(
10445
10601
  () => Object.values(subAgentProgress),
10446
10602
  [subAgentProgress]
@@ -10485,13 +10641,13 @@ function App({
10485
10641
  if (cancelled) return;
10486
10642
  startTransition2(() => setWorkspaceFiles(result.files));
10487
10643
  });
10488
- void initTaskStore(workspacePath).then(() => {
10644
+ void initTaskStore(workspacePath, sessionId2).then(() => {
10489
10645
  if (!cancelled) setTaskVersion((v) => v + 1);
10490
10646
  });
10491
10647
  return () => {
10492
10648
  cancelled = true;
10493
10649
  };
10494
- }, [workspacePath]);
10650
+ }, [workspacePath, sessionId2]);
10495
10651
  useEffect4(() => {
10496
10652
  let cancelled = false;
10497
10653
  void listAvailableSkills({ homeDir }).then((available) => {
@@ -10747,7 +10903,6 @@ function App({
10747
10903
  clearCtrlCPending();
10748
10904
  if (abortRef.current) {
10749
10905
  abortRef.current.abort();
10750
- addSystemMessage("Interrupting agent...");
10751
10906
  }
10752
10907
  return;
10753
10908
  }
@@ -10793,7 +10948,6 @@ function App({
10793
10948
  if (inputKey.escape) {
10794
10949
  if (busy && abortRef.current) {
10795
10950
  abortRef.current.abort();
10796
- addSystemMessage("Interrupting agent...");
10797
10951
  } else if (planningState.status === "charter-review") {
10798
10952
  rejectCharter();
10799
10953
  } else {
@@ -10871,11 +11025,14 @@ function App({
10871
11025
  if (!workspacePath) return;
10872
11026
  turnToolLogRef.current = [];
10873
11027
  setTurnToolCount(0);
11028
+ setLatchedToolActivity("");
11029
+ setLatchedToolCount(0);
10874
11030
  setActiveToolActivities({});
10875
11031
  setSubAgentProgress({});
10876
11032
  const controller = new AbortController();
10877
11033
  let streamBuffer = null;
10878
11034
  let focusPendingReviewOnComplete = false;
11035
+ const postTurnNotices = [];
10879
11036
  abortRef.current = controller;
10880
11037
  setBusy(true);
10881
11038
  startTransition2(() => {
@@ -10911,6 +11068,8 @@ function App({
10911
11068
  onTextDelta: (chunk) => {
10912
11069
  assistantText += chunk;
10913
11070
  streamBuffer?.push(chunk);
11071
+ setLatchedToolActivity("");
11072
+ setLatchedToolCount(0);
10914
11073
  },
10915
11074
  onToolActivity: (activity) => {
10916
11075
  streamBuffer?.flush();
@@ -10969,7 +11128,9 @@ function App({
10969
11128
  streamBuffer.flush();
10970
11129
  if (turnToolLogRef.current.length > 0) {
10971
11130
  const summary = buildToolSummary(turnToolLogRef.current);
10972
- addSystemMessage(`__tool_summary__${JSON.stringify({ summary, tools: turnToolLogRef.current })}`);
11131
+ postTurnNotices.push(
11132
+ `__tool_summary__${JSON.stringify({ summary, tools: turnToolLogRef.current })}`
11133
+ );
10973
11134
  turnToolLogRef.current = [];
10974
11135
  }
10975
11136
  startTransition2(() => {
@@ -11032,7 +11193,13 @@ ${error.stack}` : String(error)}` }
11032
11193
  setBusy(false);
11033
11194
  setComposerFocused(!focusPendingReviewOnComplete);
11034
11195
  if (controller.signal.aborted) {
11035
- addSystemMessage("Agent interrupted.");
11196
+ postTurnNotices.push("Interrupting agent...\nAgent interrupted.");
11197
+ }
11198
+ for (const notice of postTurnNotices) {
11199
+ addSystemMessage(notice);
11200
+ }
11201
+ if (controller.signal.aborted) {
11202
+ return;
11036
11203
  }
11037
11204
  }
11038
11205
  }
@@ -11174,6 +11341,9 @@ ${error.stack}` : String(error)}` }
11174
11341
  onSelect: async (session) => {
11175
11342
  try {
11176
11343
  const restored = await loadSessionHistory(workspacePath, session.id);
11344
+ setSessionId(session.id);
11345
+ await initTaskStore(workspacePath, session.id);
11346
+ setTaskVersion((version) => version + 1);
11177
11347
  replaceMessages(restored.messages);
11178
11348
  startTransition2(() => setHistory(restored.llmHistory));
11179
11349
  addSystemMessage(`Resumed session (${session.turnCount} turns). Continue where you left off.`);
@@ -11351,8 +11521,8 @@ ${error.stack}` : String(error)}` }
11351
11521
  width: contentWidth,
11352
11522
  busy,
11353
11523
  frame: activityFrame,
11354
- toolActivity: currentToolActivity,
11355
- toolCount: turnToolCount,
11524
+ toolActivity: currentToolActivity || latchedToolActivity,
11525
+ toolCount: currentToolActivity ? turnToolCount : latchedToolCount,
11356
11526
  statusParts,
11357
11527
  statusColor,
11358
11528
  tokenDisplay,
@@ -11364,35 +11534,6 @@ ${error.stack}` : String(error)}` }
11364
11534
  ] }) });
11365
11535
  }
11366
11536
 
11367
- // src/tui/ink-stdout.ts
11368
- function getStableDimension(current, fallback) {
11369
- return typeof current === "number" && current > 0 ? current : fallback;
11370
- }
11371
- function createStableInkStdout(stdout, options) {
11372
- const forceFullRedraw = options?.forceFullRedraw ?? process.env.OPEN_RESEARCH_FORCE_FULL_REDRAW === "1";
11373
- let lastRows = getStableDimension(stdout.rows, 24);
11374
- let lastColumns = getStableDimension(stdout.columns, 80);
11375
- return new Proxy(stdout, {
11376
- get(target, prop, receiver) {
11377
- if (prop === "rows") {
11378
- if (forceFullRedraw) {
11379
- return 0;
11380
- }
11381
- const rows = Reflect.get(target, prop, receiver);
11382
- lastRows = getStableDimension(rows, lastRows);
11383
- return lastRows;
11384
- }
11385
- if (prop === "columns") {
11386
- const columns = Reflect.get(target, prop, receiver);
11387
- lastColumns = getStableDimension(columns, lastColumns);
11388
- return lastColumns;
11389
- }
11390
- const value = Reflect.get(target, prop, receiver);
11391
- return typeof value === "function" ? value.bind(target) : value;
11392
- }
11393
- });
11394
- }
11395
-
11396
11537
  // src/cli.ts
11397
11538
  var program = new Command();
11398
11539
  program.name("open-research").version(getPackageVersion()).description("Local-first research CLI powered by OpenAI account auth or API keys.").argument("[workspacePath]", "Optional workspace path to open").action(async (workspacePath) => {
@@ -4,7 +4,7 @@ import {
4
4
  fetchAndParseContent,
5
5
  formatExtractionResults,
6
6
  loadOpenResearchConfig
7
- } from "./chunk-3KZN54JZ.js";
7
+ } from "./chunk-TJA4CAZE.js";
8
8
  import "./chunk-77Q5B5H7.js";
9
9
  import "./chunk-4HCPHCC2.js";
10
10
  import "./chunk-GVEVKDGV.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-research",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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",