gyoshu 0.4.16 → 0.4.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gyoshu",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "description": "Scientific research agent extension for OpenCode - turns research goals into reproducible Jupyter notebooks",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gyoshu",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "description": "Scientific research agent extension for OpenCode",
5
5
  "files": {
6
6
  "agent": [
@@ -59,6 +59,6 @@
59
59
  "bridge": [
60
60
  "gyoshu_bridge.py"
61
61
  ],
62
- "plugin": []
62
+ "plugin": ["gyoshu-hooks.ts"]
63
63
  }
64
64
  }
package/src/index.ts CHANGED
@@ -867,32 +867,46 @@ function autoInstall(): InstallResult {
867
867
  }
868
868
 
869
869
  export const GyoshuPlugin: Plugin = async (ctx) => {
870
- const installResult = autoInstall();
870
+ // Always return a valid Hooks object, even on error
871
+ const emptyHooks = {};
872
+
873
+ try {
874
+ const installResult = autoInstall();
871
875
 
872
- if (installResult.fatal) {
873
- console.error(`❌ Gyoshu: Fatal installation error`);
874
- for (const error of installResult.errors) {
875
- console.error(` - ${error}`);
876
+ if (installResult.fatal) {
877
+ console.error(`❌ Gyoshu: Fatal installation error`);
878
+ for (const error of installResult.errors) {
879
+ console.error(` - ${error}`);
880
+ }
876
881
  }
877
- return GyoshuHooks(ctx);
878
- }
879
882
 
880
- if (installResult.installed > 0) {
881
- console.log(`🎓 Gyoshu: Installed ${installResult.installed} files to ~/.config/opencode/`);
882
- }
883
+ if (installResult.installed > 0) {
884
+ console.log(`🎓 Gyoshu: Installed ${installResult.installed} files to ~/.config/opencode/`);
885
+ }
883
886
 
884
- if (installResult.updated > 0) {
885
- console.log(`🎓 Gyoshu: Updated ${installResult.updated} files`);
886
- }
887
+ if (installResult.updated > 0) {
888
+ console.log(`🎓 Gyoshu: Updated ${installResult.updated} files`);
889
+ }
887
890
 
888
- if (installResult.errors.length > 0) {
889
- console.warn(`⚠️ Gyoshu: Some issues occurred:`);
890
- for (const error of installResult.errors) {
891
- console.warn(` - ${error}`);
891
+ if (installResult.errors.length > 0) {
892
+ console.warn(`⚠️ Gyoshu: Some issues occurred:`);
893
+ for (const error of installResult.errors) {
894
+ console.warn(` - ${error}`);
895
+ }
892
896
  }
893
- }
894
897
 
895
- return GyoshuHooks(ctx);
898
+ // Try to initialize hooks, return empty hooks on failure
899
+ try {
900
+ const hooks = await GyoshuHooks(ctx);
901
+ return hooks || emptyHooks;
902
+ } catch (hooksError) {
903
+ console.error(`❌ Gyoshu: Failed to initialize hooks: ${hooksError instanceof Error ? hooksError.message : String(hooksError)}`);
904
+ return emptyHooks;
905
+ }
906
+ } catch (err) {
907
+ console.error(`❌ Gyoshu: Plugin initialization failed: ${err instanceof Error ? err.message : String(err)}`);
908
+ return emptyHooks;
909
+ }
896
910
  };
897
911
 
898
912
  export default GyoshuPlugin;
@@ -473,8 +473,14 @@ export const GyoshuPlugin: Plugin = async ({ client }) => {
473
473
  idleCheckInterval = setInterval(killIdleBridges, IDLE_CHECK_INTERVAL_MS);
474
474
 
475
475
  return {
476
+ // NOTE: tool.execute.after receives:
477
+ // input: { tool: string; sessionID: string; callID: string; }
478
+ // output: { title: string; output: string; metadata: any; }
479
+ // The original tool args are in output.metadata (if the tool stored them there)
476
480
  "tool.execute.after": async (input, output) => {
477
- const args = (input as { args?: Record<string, unknown> }).args;
481
+ // Extract args from output.metadata (where tools may store their call args)
482
+ const metadata = output?.metadata as Record<string, unknown> | undefined;
483
+ const args = metadata?.args as Record<string, unknown> | undefined;
478
484
 
479
485
  const toolReportTitle = extractReportTitleFromArgs(args);
480
486
  if (toolReportTitle && activeAutoLoops.has(toolReportTitle)) {
@@ -504,7 +510,7 @@ export const GyoshuPlugin: Plugin = async ({ client }) => {
504
510
 
505
511
  if (input.tool === "gyoshu-completion") {
506
512
  const reportTitle = args?.reportTitle as string | undefined;
507
- const outputText = typeof output === "string" ? output : JSON.stringify(output);
513
+ const outputText = typeof output?.output === "string" ? output.output : JSON.stringify(output);
508
514
  if (reportTitle) {
509
515
  recentOutputBuffer.set(reportTitle, outputText);
510
516
  }
@@ -519,49 +525,27 @@ export const GyoshuPlugin: Plugin = async ({ client }) => {
519
525
  }
520
526
  },
521
527
 
522
- "agent.after": async ({ output }) => {
523
- const outputText = typeof output === "string" ? output : "";
524
-
525
- const extractedTags = extractPromiseTags(outputText);
526
- for (const tag of extractedTags) {
527
- if (TERMINAL_PROMISE_TAGS.includes(tag as typeof TERMINAL_PROMISE_TAGS[number])) {
528
- const matchedReportTitles = findReportTitlesInOutput(outputText);
529
- if (matchedReportTitles.length > 0) {
530
- for (const reportTitle of matchedReportTitles) {
531
- await deactivateAutoLoop(reportTitle);
532
- }
533
- }
534
- return;
535
- }
536
- }
537
-
538
- for (const [reportTitle] of activeAutoLoops) {
539
- const bufferedOutput = recentOutputBuffer.get(reportTitle) || "";
540
- const combinedOutput = bufferedOutput + outputText;
541
-
542
- const result = await checkAutoLoopContinuation(reportTitle, combinedOutput, client);
543
-
544
- if (result.budgetExhaustedMessage) {
545
- const sent = await trySendContinuationPrompt(client, result.budgetExhaustedMessage);
546
- if (sent) {
547
- await onSuccessfulContinuationSend(reportTitle);
548
- }
549
- } else if (result.prompt) {
550
- const sent = await trySendContinuationPrompt(client, result.prompt);
551
- if (sent) {
552
- await onSuccessfulContinuationSend(reportTitle);
553
- }
554
- }
555
-
556
- recentOutputBuffer.delete(reportTitle);
557
- }
558
- },
528
+ // NOTE: OpenCode does not have an "agent.after" hook.
529
+ // Auto-loop continuation logic that was here has been disabled.
530
+ // When/if OpenCode adds an agent completion hook, the following would need to be restored:
531
+ // - Detect terminal promise tags (GYOSHU_AUTO_COMPLETE, GYOSHU_AUTO_BLOCKED, GYOSHU_AUTO_BUDGET_EXHAUSTED)
532
+ // - Call checkAutoLoopContinuation() and trySendContinuationPrompt() for active auto-loops
533
+ // The auto-loop state management code is preserved for when this becomes available.
559
534
 
560
535
  event: async ({ event }) => {
561
536
  const eventType = event.type as string;
562
537
 
538
+ // Handle session cleanup (merged from removed 'cleanup' hook)
563
539
  if (eventType === "session.end" || eventType === "session.disposed") {
540
+ // Stop idle check interval
541
+ if (idleCheckInterval) {
542
+ clearInterval(idleCheckInterval);
543
+ idleCheckInterval = null;
544
+ }
545
+
546
+ // Clean up all bridges and session state
564
547
  cleanupAllBridges();
548
+ activeSessions.clear();
565
549
  activeAutoLoops.clear();
566
550
  recentOutputBuffer.clear();
567
551
  injectionInFlight.clear();
@@ -572,6 +556,9 @@ export const GyoshuPlugin: Plugin = async ({ client }) => {
572
556
  saveDebounceTimers.clear();
573
557
  }
574
558
 
559
+ // Auto-loop continuation on idle/message events
560
+ // NOTE: This is a partial replacement for the removed "agent.after" hook.
561
+ // It only triggers on explicit idle/message events, not on every agent turn completion.
575
562
  if (eventType === "agent.idle" || eventType === "message.completed") {
576
563
  for (const [reportTitle] of activeAutoLoops) {
577
564
  const bufferedOutput = recentOutputBuffer.get(reportTitle) || "";
@@ -593,23 +580,8 @@ export const GyoshuPlugin: Plugin = async ({ client }) => {
593
580
  }
594
581
  },
595
582
 
596
- cleanup: async () => {
597
- if (idleCheckInterval) {
598
- clearInterval(idleCheckInterval);
599
- idleCheckInterval = null;
600
- }
601
-
602
- cleanupAllBridges();
603
- activeSessions.clear();
604
- activeAutoLoops.clear();
605
- recentOutputBuffer.clear();
606
- injectionInFlight.clear();
607
- lastProcessedOutputHash.clear();
608
- for (const timer of saveDebounceTimers.values()) {
609
- clearTimeout(timer);
610
- }
611
- saveDebounceTimers.clear();
612
- },
583
+ // NOTE: OpenCode does not have a 'cleanup' hook.
584
+ // Cleanup logic has been moved to the 'event' hook, listening for 'session.end' and 'session.disposed'.
613
585
  };
614
586
  };
615
587