index.ts 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. /**
  2. * Post-tool nudge - queues a delegation reminder after file reads/writes.
  3. * Catches the "inspect/edit files → implement myself" anti-pattern.
  4. */
  5. import { PHASE_REMINDER_TEXT } from '../../config/constants';
  6. const POST_FILE_TOOL_NUDGE = PHASE_REMINDER_TEXT;
  7. interface ToolExecuteAfterInput {
  8. tool: string;
  9. sessionID?: string;
  10. callID?: string;
  11. }
  12. interface ChatSystemTransformInput {
  13. sessionID?: string;
  14. }
  15. interface ChatSystemTransformOutput {
  16. system: string[];
  17. }
  18. interface EventInput {
  19. event: {
  20. type: string;
  21. properties?: {
  22. info?: { id?: string };
  23. sessionID?: string;
  24. };
  25. };
  26. }
  27. interface PostFileToolNudgeOptions {
  28. shouldInject?: (sessionID: string) => boolean;
  29. }
  30. const FILE_TOOLS = new Set(['Read', 'read', 'Write', 'write']);
  31. export function createPostFileToolNudgeHook(
  32. options: PostFileToolNudgeOptions = {},
  33. ) {
  34. const pendingSessionIds = new Set<string>();
  35. return {
  36. 'tool.execute.after': async (
  37. input: ToolExecuteAfterInput,
  38. _output: unknown,
  39. ): Promise<void> => {
  40. // Only nudge for Read/Write tools once the next model call is built.
  41. if (!FILE_TOOLS.has(input.tool) || !input.sessionID) {
  42. return;
  43. }
  44. pendingSessionIds.add(input.sessionID);
  45. },
  46. 'experimental.chat.system.transform': async (
  47. input: ChatSystemTransformInput,
  48. output: ChatSystemTransformOutput,
  49. ): Promise<void> => {
  50. if (!input.sessionID || !pendingSessionIds.delete(input.sessionID)) {
  51. return;
  52. }
  53. if (options.shouldInject && !options.shouldInject(input.sessionID)) {
  54. return;
  55. }
  56. output.system.push(POST_FILE_TOOL_NUDGE);
  57. },
  58. event: async (input: EventInput): Promise<void> => {
  59. if (input.event.type !== 'session.deleted') {
  60. return;
  61. }
  62. const sessionID =
  63. input.event.properties?.sessionID ?? input.event.properties?.info?.id;
  64. if (!sessionID) {
  65. return;
  66. }
  67. pendingSessionIds.delete(sessionID);
  68. },
  69. };
  70. }