MCPcopy Create free account
hub / github.com/Tencent/teamai-cli / buildPluginSource

Function buildPluginSource

src/opencode-hooks.ts:75–151  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

73 * matcher-scoped pass.
74 */
75export function buildPluginSource(): string {
76 return `// ${TEAMAI_MARKER} hooks plugin — generated by teamai, do not edit by hand.
77//
78// Bridges OpenCode's plugin events to \`teamai hook-dispatch\`, mirroring the
79// Claude built-in hook set. Feeds the same STDIN JSON payload other agents send
80// so the track / hint handlers and project-scope gating work. Errors are
81// swallowed; OpenCode cannot inject a hook's stdout back into the session, so
82// this runs the dispatch purely for its side effects (status report / sync /
83// update).
84//
85// NOTE: OpenCode awaits its named hooks (chat.message, tool.execute.after), so
86// the dispatch is not truly fire-and-forget for those two — see the timeout
87// note below.
88
89/** OpenCode lowercase tool ids → Claude PascalCase matcher names. */
90const TOOL_MATCHER = { skill: 'Skill', todowrite: 'TodoWrite' };
91
92/** @param {{ $: any, directory?: string, worktree?: string }} ctx */
93export const TeamaiHooks = async ({ $, directory, worktree }) => {
94 const cwd = directory || worktree;
95 // Dispatch one hook event, forwarding a JSON payload on STDIN. \`payload\`
96 // fields (cwd / tool_name / tool_input / prompt) match what hook-dispatch's
97 // handlers read; \`matcher\` scopes PostToolUse handlers (Skill / TodoWrite).
98 const dispatch = async (event, matcher, payload) => {
99 try {
100 const args = ['hook-dispatch', event, '--tool', 'opencode'];
101 if (matcher) {
102 args.push('--matcher', matcher);
103 }
104 const stdin = JSON.stringify({ cwd, ...(payload || {}) });
105 // Redirect the payload into STDIN via a Response (Bun shell can only
106 // redirect Response/Buffer/Blob, not a bare string). .quiet() suppresses
107 // output; .nothrow() keeps a non-zero exit (e.g. no teamai on PATH) from
108 // throwing into the agent session.
109 await $\`teamai \${args} < \${new Response(stdin)}\`.quiet().nothrow();
110 } catch {
111 // never block the agent
112 }
113 };
114
115 return {
116 event: async ({ event }) => {
117 if (event.type === 'session.created') {
118 await dispatch('session-start');
119 } else if (event.type === 'session.idle') {
120 await dispatch('stop');
121 }
122 },
123 // A new user message maps to Claude's UserPromptSubmit. The prompt text
124 // lives in output.parts (text parts); forward it so track-slash can see
125 // slash-command usage.
126 'chat.message': async (_input, output) => {
127 let prompt = '';
128 const parts = (output && output.parts) || [];
129 for (const part of parts) {
130 if (part && part.type === 'text' && typeof part.text === 'string') {
131 prompt += part.text;
132 }

Callers 2

injectOpencodeHooksFunction · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected