openbot 0.4.2 → 0.4.3

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/dist/app/cli.js CHANGED
@@ -16,7 +16,7 @@ function checkNodeVersion() {
16
16
  }
17
17
  }
18
18
  checkNodeVersion();
19
- program.name('openbot').description('OpenBot CLI').version('0.4.2');
19
+ program.name('openbot').description('OpenBot CLI').version('0.4.3');
20
20
  program
21
21
  .command('start')
22
22
  .description('Start the OpenBot harness')
@@ -70,6 +70,8 @@ export const delegationPlugin = {
70
70
  channelId: context.state.channelId,
71
71
  threadId: context.state.threadId,
72
72
  publicBaseUrl: pluginContext.publicBaseUrl,
73
+ // Child events are re-yielded to the parent harness, which persists them once.
74
+ persistEvents: false,
73
75
  onEvent: async (outEvent) => {
74
76
  // Enrich events with parent metadata so the UI can track the hierarchy
75
77
  const enrichedEvent = {
@@ -229,6 +229,12 @@ const readJsonFile = async (filePath, fallback) => {
229
229
  throw e;
230
230
  }
231
231
  };
232
+ const writeJsonFileAtomically = async (filePath, data) => {
233
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
234
+ const tmp = `${filePath}.tmp`;
235
+ await fs.writeFile(tmp, JSON.stringify(data, null, 2), 'utf-8');
236
+ await fs.rename(tmp, filePath);
237
+ };
232
238
  const toVariablesRecord = (raw) => {
233
239
  if (!raw || typeof raw !== 'object') {
234
240
  return {};
@@ -408,8 +414,7 @@ export const storageService = {
408
414
  let displayName = name;
409
415
  let participants = [];
410
416
  try {
411
- const stateContent = await fs.readFile(statePath, 'utf-8');
412
- const parsed = JSON.parse(stateContent);
417
+ const parsed = await readJsonFile(statePath, {});
413
418
  const fields = readChannelStateFileFields(parsed);
414
419
  cwd = fields.cwd;
415
420
  displayName = fields.name ?? name;
@@ -479,7 +484,7 @@ export const storageService = {
479
484
  await fs.mkdir(channelDir, { recursive: true });
480
485
  await fs.writeFile(specPath, spec?.trim() ||
481
486
  `# ${normalizedChannelId}\n\n`);
482
- await fs.writeFile(statePath, JSON.stringify(finalState, null, 2));
487
+ await writeJsonFileAtomically(statePath, finalState);
483
488
  },
484
489
  deleteChannel: async ({ channelId }) => {
485
490
  const normalizedChannelId = channelId.trim();
@@ -540,8 +545,7 @@ export const storageService = {
540
545
  if (threadTitle?.trim()) {
541
546
  baseState.name = threadTitle.trim();
542
547
  }
543
- await fs.mkdir(threadDir, { recursive: true });
544
- await fs.writeFile(statePath, JSON.stringify(baseState, null, 2));
548
+ await writeJsonFileAtomically(statePath, baseState);
545
549
  },
546
550
  getThreads: async ({ channelId }) => {
547
551
  const threadsDir = resolvePath(resolveBaseDir() + '/' + DEFAULT_CHANNELS_DIR + '/' + channelId + '/threads');
@@ -560,8 +564,7 @@ export const storageService = {
560
564
  const threadStatePath = path.join(threadPath, 'state.json');
561
565
  let threadDisplayName = name;
562
566
  try {
563
- const threadStateRaw = await fs.readFile(threadStatePath, 'utf-8');
564
- const threadState = JSON.parse(threadStateRaw);
567
+ const threadState = await readJsonFile(threadStatePath, {});
565
568
  const threadName = typeof threadState.name === 'string' ? threadState.name.trim() : '';
566
569
  if (threadName) {
567
570
  threadDisplayName = threadName;
@@ -597,8 +600,7 @@ export const storageService = {
597
600
  const statePath = `${threadDir}/state.json`;
598
601
  let state = {};
599
602
  try {
600
- const stateContent = await fs.readFile(statePath, 'utf-8');
601
- state = JSON.parse(stateContent);
603
+ state = await readJsonFile(statePath, {});
602
604
  }
603
605
  catch (error) {
604
606
  if (error?.code !== 'ENOENT') {
@@ -630,8 +632,7 @@ export const storageService = {
630
632
  }
631
633
  let state = {};
632
634
  try {
633
- const stateContent = await fs.readFile(statePath, 'utf-8');
634
- state = JSON.parse(stateContent);
635
+ state = await readJsonFile(statePath, {});
635
636
  }
636
637
  catch (error) {
637
638
  if (error?.code !== 'ENOENT') {
@@ -662,8 +663,7 @@ export const storageService = {
662
663
  ...currentState,
663
664
  ...patch,
664
665
  };
665
- await fs.mkdir(channelDir, { recursive: true });
666
- await fs.writeFile(statePath, JSON.stringify(newState, null, 2));
666
+ await writeJsonFileAtomically(statePath, newState);
667
667
  }
668
668
  catch (error) {
669
669
  console.error(`Failed to patch channel state for channel ${channelId}`, error);
@@ -680,8 +680,7 @@ export const storageService = {
680
680
  ...currentState,
681
681
  ...patch,
682
682
  };
683
- await fs.mkdir(threadDir, { recursive: true });
684
- await fs.writeFile(statePath, JSON.stringify(newState, null, 2));
683
+ await writeJsonFileAtomically(statePath, newState);
685
684
  }
686
685
  catch (error) {
687
686
  console.error(`Failed to patch thread state for channel ${channelId} thread ${threadId}`, error);
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "openbot",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=20.12.0"
8
8
  },
9
+ "scripts": {
10
+ "dev": "tsx watch src/app/cli.ts start",
11
+ "build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
12
+ "start": "node dist/app/cli.js start"
13
+ },
9
14
  "bin": {
10
15
  "openbot": "./dist/app/cli.js"
11
16
  },
@@ -30,10 +35,5 @@
30
35
  "@types/node": "^20.10.1",
31
36
  "tsx": "^4.21.0",
32
37
  "typescript": "^5.9.3"
33
- },
34
- "scripts": {
35
- "dev": "tsx watch src/app/cli.ts start",
36
- "build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
37
- "start": "node dist/app/cli.js start"
38
38
  }
39
- }
39
+ }
package/src/app/cli.ts CHANGED
@@ -25,7 +25,7 @@ function checkNodeVersion() {
25
25
 
26
26
  checkNodeVersion();
27
27
 
28
- program.name('openbot').description('OpenBot CLI').version('0.4.2');
28
+ program.name('openbot').description('OpenBot CLI').version('0.4.3');
29
29
 
30
30
  program
31
31
  .command('start')
@@ -85,6 +85,8 @@ export const delegationPlugin: Plugin = {
85
85
  channelId: context.state.channelId,
86
86
  threadId: context.state.threadId,
87
87
  publicBaseUrl: pluginContext.publicBaseUrl,
88
+ // Child events are re-yielded to the parent harness, which persists them once.
89
+ persistEvents: false,
88
90
  onEvent: async (outEvent) => {
89
91
  // Enrich events with parent metadata so the UI can track the hierarchy
90
92
  const enrichedEvent = {
@@ -294,6 +294,13 @@ const readJsonFile = async <T>(filePath: string, fallback: T): Promise<T> => {
294
294
  }
295
295
  };
296
296
 
297
+ const writeJsonFileAtomically = async (filePath: string, data: unknown): Promise<void> => {
298
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
299
+ const tmp = `${filePath}.tmp`;
300
+ await fs.writeFile(tmp, JSON.stringify(data, null, 2), 'utf-8');
301
+ await fs.rename(tmp, filePath);
302
+ };
303
+
297
304
  const toVariablesRecord = (raw: unknown): Record<string, string> => {
298
305
  if (!raw || typeof raw !== 'object') {
299
306
  return {};
@@ -502,8 +509,7 @@ export const storageService = {
502
509
  let participants: string[] = [];
503
510
 
504
511
  try {
505
- const stateContent = await fs.readFile(statePath, 'utf-8');
506
- const parsed = JSON.parse(stateContent);
512
+ const parsed = await readJsonFile(statePath, {});
507
513
  const fields = readChannelStateFileFields(parsed);
508
514
  cwd = fields.cwd;
509
515
  displayName = fields.name ?? name;
@@ -597,7 +603,7 @@ export const storageService = {
597
603
  spec?.trim() ||
598
604
  `# ${normalizedChannelId}\n\n`,
599
605
  );
600
- await fs.writeFile(statePath, JSON.stringify(finalState, null, 2));
606
+ await writeJsonFileAtomically(statePath, finalState);
601
607
  },
602
608
  deleteChannel: async ({ channelId }: { channelId: string }): Promise<void> => {
603
609
  const normalizedChannelId = channelId.trim();
@@ -673,8 +679,7 @@ export const storageService = {
673
679
  baseState.name = threadTitle.trim();
674
680
  }
675
681
 
676
- await fs.mkdir(threadDir, { recursive: true });
677
- await fs.writeFile(statePath, JSON.stringify(baseState, null, 2));
682
+ await writeJsonFileAtomically(statePath, baseState);
678
683
  },
679
684
  getThreads: async ({ channelId }: { channelId: string }): Promise<Thread[]> => {
680
685
  const threadsDir = resolvePath(
@@ -698,8 +703,7 @@ export const storageService = {
698
703
  let threadDisplayName = name;
699
704
 
700
705
  try {
701
- const threadStateRaw = await fs.readFile(threadStatePath, 'utf-8');
702
- const threadState = JSON.parse(threadStateRaw) as Record<string, unknown>;
706
+ const threadState = await readJsonFile<Record<string, unknown>>(threadStatePath, {});
703
707
  const threadName =
704
708
  typeof threadState.name === 'string' ? threadState.name.trim() : '';
705
709
  if (threadName) {
@@ -748,8 +752,7 @@ export const storageService = {
748
752
 
749
753
  let state: unknown = {};
750
754
  try {
751
- const stateContent = await fs.readFile(statePath, 'utf-8');
752
- state = JSON.parse(stateContent);
755
+ state = await readJsonFile(statePath, {});
753
756
  } catch (error: unknown) {
754
757
  if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
755
758
  console.error(
@@ -787,8 +790,7 @@ export const storageService = {
787
790
 
788
791
  let state: unknown = {};
789
792
  try {
790
- const stateContent = await fs.readFile(statePath, 'utf-8');
791
- state = JSON.parse(stateContent);
793
+ state = await readJsonFile(statePath, {});
792
794
  } catch (error: unknown) {
793
795
  if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
794
796
  console.error(`Failed to read state file for channel ${channelId}`, error);
@@ -831,8 +833,7 @@ export const storageService = {
831
833
  ...(patch as Record<string, unknown>),
832
834
  };
833
835
 
834
- await fs.mkdir(channelDir, { recursive: true });
835
- await fs.writeFile(statePath, JSON.stringify(newState, null, 2));
836
+ await writeJsonFileAtomically(statePath, newState);
836
837
  } catch (error) {
837
838
  console.error(`Failed to patch channel state for channel ${channelId}`, error);
838
839
  throw error;
@@ -859,8 +860,7 @@ export const storageService = {
859
860
  ...(patch as Record<string, unknown>),
860
861
  };
861
862
 
862
- await fs.mkdir(threadDir, { recursive: true });
863
- await fs.writeFile(statePath, JSON.stringify(newState, null, 2));
863
+ await writeJsonFileAtomically(statePath, newState);
864
864
  } catch (error) {
865
865
  console.error(
866
866
  `Failed to patch thread state for channel ${channelId} thread ${threadId}`,