@dreb/coding-agent 2.61.1 → 2.62.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"tab-title.js","sourceRoot":"","sources":["../../src/core/tab-title.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Auto-generates a terminal tab title from session context after a threshold\n * number of tool calls. Uses a lightweight single-shot LLM call to produce a\n * concise title — the model aims for a configurable soft target (default 60\n * characters, via TabTitleSettings.maxTitleLength) and the result is hard-capped\n * at 300 characters — then sets it via the terminal's OSC 0 escape.\n *\n * Fires at most once per session. Final failures are reported through the\n * optional onError hook while preserving fire-and-forget behavior.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Api, Context, Model } from \"@dreb/ai\";\nimport { completeSimple } from \"@dreb/ai\";\nimport { CONFIG_DIR_NAME, getPackageDir } from \"../config.js\";\nimport { extractUserText, labelMessageEnd, labelToolEnd, RollingContextBuffer } from \"./context-buffer.js\";\nimport type { ModelRegistry } from \"./model-registry.js\";\nimport type { TabTitleSettings } from \"./settings-manager.js\";\nimport { parseAgentFrontmatter, resolveModelForSubagentSpawn } from \"./tools/subagent.js\";\n\nconst DEFAULT_TRIGGER_AFTER = 9;\n// Soft target communicated to the model — titles should aim for this length but\n// may run longer when clarity demands it. Configurable via TabTitleSettings.maxTitleLength.\nconst DEFAULT_TITLE_SOFT_TARGET = 60;\n// Hidden hard cap — a safety limit that titles are truncated to regardless of the\n// soft target. The dashboard has room for long names; the TUI truncates visually.\nconst TITLE_HARD_CAP = 300;\n// Cap on how much of a single user message we feed into the title context.\nconst MAX_USER_TEXT_CHARS = 2000;\nconst TITLE_GENERATION_TIMEOUT_MS = 60_000;\n\nfunction buildTitlePrompt(softTarget: number): string {\n\treturn (\n\t\t\"You are a headless terminal-tab title generator. You are NOT the assistant in the session — \" +\n\t\t\"you will never speak to the user. Your only job is to output a single short title string, nothing else. \" +\n\t\t\"No quotes, no explanation, no preamble. \" +\n\t\t\"The title disambiguates terminal windows for a human at a glance. \" +\n\t\t\"Base the title PRIMARILY on the user's actual request and the concrete actions taken in THIS session. \" +\n\t\t\"Describe what is being DONE (e.g. 'Fix auth bug', 'Plan subagent refactor', 'Review modal'), \" +\n\t\t\"not just label the invocation. \" +\n\t\t\"Branch, repo, and cwd metadata are SECONDARY — use them only to disambiguate when the user's \" +\n\t\t\"request is otherwise ambiguous. Never let a branch name override the user's actual intent: \" +\n\t\t\"if the branch is 'feature/dashboard-foundation' but the user asked to install a Playwright skill, \" +\n\t\t\"the title is about the Playwright skill, not the dashboard. \" +\n\t\t\"Avoid reference-only formats like '#N' or 'mach6-X #N'. \" +\n\t\t\"Do not include 'dreb' — the caller already adds it. \" +\n\t\t`Output ONLY the title text. Aim for about ${softTarget} characters; a little longer is fine if needed for clarity.`\n\t);\n}\n\nexport interface TabTitleDeps {\n\t/** Set the terminal tab title (OSC 0). */\n\tsetTitle: (title: string) => void;\n\t/** Persist the generated title as the session name. Called with the raw title (without \"dreb - \" prefix). */\n\tsetSessionName?: (name: string) => void;\n\t/**\n\t * Get the current session name, if the session is already named. When this returns a\n\t * non-empty string, title generation is skipped — auto-titling only names unnamed\n\t * sessions and must never overwrite an existing (e.g. resumed) session's name.\n\t */\n\tgetSessionName?: () => string | undefined;\n\t/** Get the current session messages (for context). */\n\tgetMessages: () => Array<{ role: string; content?: unknown }>;\n\t/** Get the current model (parent session model — used as fallback). */\n\tgetModel: () => Model<Api> | undefined;\n\t/** Get model registry for API key resolution. */\n\tgetModelRegistry: () => ModelRegistry;\n\t/** Get the parent provider name. */\n\tgetProvider: () => string | undefined;\n\t/**\n\t * Get the user's agentModels settings override for a given agent name, if any.\n\t * Returns a non-empty fallback list when the user has configured an override.\n\t */\n\tgetAgentModelsOverride?: (agentName: string) => string[] | undefined;\n\t/** Current git branch name, or null/undefined if unavailable. */\n\tgetBranch?: () => string | null | undefined;\n\t/** Repository name (e.g., dirname of cwd), or undefined. */\n\tgetRepo?: () => string | undefined;\n\t/** Current working directory, or undefined. */\n\tgetCwd?: () => string | undefined;\n\t/** Report final title-generation failures without breaking fire-and-forget callers. */\n\tonError?: (err: unknown) => void;\n}\n\nexport class TabTitleGenerator {\n\tprivate toolCallCount = 0;\n\tprivate fired = false;\n\tprivate readonly threshold: number;\n\tprivate readonly softTarget: number;\n\tprivate readonly contextBuffer: RollingContextBuffer;\n\n\tconstructor(\n\t\tprivate readonly settings: TabTitleSettings | undefined,\n\t\tprivate readonly deps: TabTitleDeps,\n\t) {\n\t\tthis.threshold = settings?.triggerAfter ?? DEFAULT_TRIGGER_AFTER;\n\t\t// Soft target is configurable but always clamped to the hidden hard cap.\n\t\tconst configured = settings?.maxTitleLength ?? DEFAULT_TITLE_SOFT_TARGET;\n\t\tthis.softTarget = Math.max(1, Math.min(configured, TITLE_HARD_CAP));\n\t\tthis.contextBuffer = new RollingContextBuffer({ maxEntries: 30, maxChars: 6000 });\n\t}\n\n\t/** Whether this generator is enabled. */\n\tget enabled(): boolean {\n\t\treturn this.settings?.enabled !== false;\n\t}\n\n\t/**\n\t * Called on each tool_execution_end event. Captures context from the event,\n\t * increments the counter, and fires title generation when threshold is reached.\n\t */\n\tonToolEnd(event?: { toolName?: string; isError?: boolean; result?: unknown }): void {\n\t\tif (event?.toolName) {\n\t\t\tthis.contextBuffer.append(labelToolEnd(event as { toolName: string; isError?: boolean; result?: unknown }));\n\t\t}\n\n\t\tif (this.fired || !this.enabled) return;\n\n\t\tthis.toolCallCount++;\n\t\tif (this.toolCallCount >= this.threshold) {\n\t\t\tthis.fired = true;\n\t\t\t// Fire-and-forget — report final failures without throwing into the event stream.\n\t\t\tthis.generateTitle().catch((err) => this.reportError(err));\n\t\t}\n\t}\n\n\t/** Called on message_end events — captures labeled context. */\n\tonMessageEnd(message: { role: string; content?: unknown }): void {\n\t\tif (this.fired) return; // no need to accumulate after fired\n\t\tfor (const entry of labelMessageEnd(message)) {\n\t\t\tthis.contextBuffer.append(entry);\n\t\t}\n\t}\n\n\t/** Exposed for testing — the current tool call count. */\n\tget currentCount(): number {\n\t\treturn this.toolCallCount;\n\t}\n\n\t/** Exposed for testing — whether the title has been generated. */\n\tget hasFired(): boolean {\n\t\treturn this.fired;\n\t}\n\n\tprivate async generateTitle(): Promise<void> {\n\t\t// Skip entirely if the session already has a name (e.g. resumed session).\n\t\t// This guards the race where a name is set between generator construction and firing.\n\t\tif (this.deps.getSessionName?.()) return;\n\n\t\t// Single timeout bounds the entire pipeline (model probing + API key + LLM call)\n\t\tconst signal = AbortSignal.timeout(TITLE_GENERATION_TIMEOUT_MS);\n\n\t\tconst model = await this.resolveModel(signal);\n\t\tif (!model) return;\n\n\t\tconst userContext = this.buildContext();\n\t\tif (!userContext) return;\n\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: buildTitlePrompt(this.softTarget),\n\t\t\tmessages: [{ role: \"user\", content: userContext, timestamp: Date.now() }],\n\t\t};\n\n\t\tconst response = await this.completeWithParentFallback(model, context, signal);\n\t\tconst title = this.sanitizeTitle(response);\n\t\tif (title) {\n\t\t\t// Re-check: a name may have landed during the async LLM call.\n\t\t\tif (this.deps.getSessionName?.()) return;\n\t\t\tthis.deps.setTitle(`dreb - ${title}`);\n\t\t\tthis.deps.setSessionName?.(title);\n\t\t}\n\t}\n\n\tprivate async completeWithParentFallback(\n\t\tmodel: Model<Api>,\n\t\tcontext: Context,\n\t\tsignal: AbortSignal,\n\t): Promise<unknown> {\n\t\tconst primaryErrorPrefix = `Tab title generation failed with model ${this.formatModel(model)}`;\n\t\ttry {\n\t\t\treturn await this.completeWithModel(model, context, signal);\n\t\t} catch (primaryErr) {\n\t\t\tconst parentModel = this.deps.getModel();\n\t\t\tif (!parentModel || this.isSameModel(model, parentModel)) {\n\t\t\t\tthrow new Error(`${primaryErrorPrefix}: ${this.formatError(primaryErr)}`, { cause: primaryErr });\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn await this.completeWithModel(parentModel, context, signal);\n\t\t\t} catch (parentErr) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${primaryErrorPrefix}: ${this.formatError(primaryErr)}. ` +\n\t\t\t\t\t\t`Parent fallback ${this.formatModel(parentModel)} also failed: ${this.formatError(parentErr)}`,\n\t\t\t\t\t{ cause: parentErr },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async completeWithModel(model: Model<Api>, context: Context, signal: AbortSignal): Promise<unknown> {\n\t\tconst registry = this.deps.getModelRegistry();\n\t\tconst apiKey = await registry.getApiKey(model);\n\t\treturn completeSimple(model, context, {\n\t\t\tapiKey,\n\t\t\tmaxRetryDelayMs: 0,\n\t\t\tsignal,\n\t\t});\n\t}\n\n\tprivate async resolveModel(signal?: AbortSignal): Promise<Model<Api> | undefined> {\n\t\t// Try to get the Explore agent's model fallback list\n\t\tconst exploreModels = this.getExploreAgentModels();\n\t\tconst parentModel = this.deps.getModel();\n\t\tconst parentProvider = this.deps.getProvider();\n\t\tconst registry = this.deps.getModelRegistry();\n\n\t\tif (exploreModels) {\n\t\t\tconst resolution = await resolveModelForSubagentSpawn(\n\t\t\t\texploreModels,\n\t\t\t\tparentProvider,\n\t\t\t\tregistry,\n\t\t\t\tparentModel?.id,\n\t\t\t\tsignal,\n\t\t\t\t\"[tab-title]\",\n\t\t\t);\n\t\t\tif (resolution.ok) {\n\t\t\t\tconst found = resolution.provider\n\t\t\t\t\t? registry.find(resolution.provider, resolution.modelId)\n\t\t\t\t\t: registry.getAvailable().find((m) => m.id === resolution.modelId);\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to parent session model\n\t\treturn parentModel;\n\t}\n\n\tprivate isSameModel(a: Model<Api>, b: Model<Api>): boolean {\n\t\treturn a.id === b.id && a.provider === b.provider;\n\t}\n\n\tprivate formatModel(model: Model<Api>): string {\n\t\treturn `${model.provider}/${model.id}`;\n\t}\n\n\tprivate formatError(err: unknown): string {\n\t\tif (err instanceof Error) return err.message;\n\t\tif (typeof err === \"string\") return err;\n\t\treturn String(err);\n\t}\n\n\tprivate reportError(err: unknown): void {\n\t\tconst reportedError =\n\t\t\terr instanceof Error\n\t\t\t\t? err\n\t\t\t\t: new Error(`Tab title generation failed: ${this.formatError(err)}`, { cause: err });\n\t\tthis.deps.onError?.(reportedError);\n\t}\n\n\tprivate getExploreAgentModels(): string | string[] | undefined {\n\t\t// Honor the user's agentModels settings override first. The settings key must\n\t\t// match the agent name exactly (\"Explore\", as declared in explore.md frontmatter).\n\t\tconst override = this.deps.getAgentModelsOverride?.(\"Explore\");\n\t\tif (override && override.length > 0) {\n\t\t\treturn override;\n\t\t}\n\n\t\t// Resolution order mirrors discoverAgentTypes: user override > project > package.\n\t\t// First match with a valid model wins.\n\t\tconst candidates = [\n\t\t\tjoin(homedir(), CONFIG_DIR_NAME, \"agents\", \"explore.md\"),\n\t\t\tjoin(process.cwd(), \".dreb\", \"agents\", \"explore.md\"),\n\t\t\tjoin(getPackageDir(), \"agents\", \"explore.md\"),\n\t\t];\n\n\t\tfor (const agentFile of candidates) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(agentFile, \"utf-8\");\n\t\t\t\tconst parsed = parseAgentFrontmatter(content);\n\t\t\t\tif (parsed.ok && parsed.config.model) {\n\t\t\t\t\treturn parsed.config.model;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate buildContext(): string | undefined {\n\t\tconst lines: string[] = [];\n\n\t\t// Primary signal: the user's actual request(s) from the current session.\n\t\t// Pin the FIRST user message (session-defining intent) and append the LATEST\n\t\t// user message when it differs, to catch mid-session pivots. Sourced from the\n\t\t// live message list rather than the rolling buffer so early intent is never\n\t\t// evicted by later tool activity.\n\t\tconst userIntent = this.collectUserIntent();\n\t\tif (userIntent.length > 0) {\n\t\t\tlines.push(\"User request(s):\");\n\t\t\tfor (const req of userIntent) {\n\t\t\t\tlines.push(`- ${req}`);\n\t\t\t}\n\t\t}\n\n\t\t// Secondary signal: concrete current-session actions (assistant text + tools).\n\t\tconst bufferContent = this.contextBuffer.build();\n\t\tif (bufferContent) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Session activity:\");\n\t\t\tlines.push(bufferContent);\n\t\t}\n\n\t\t// Tertiary signal: metadata, for disambiguation only.\n\t\tconst branch = this.deps.getBranch?.();\n\t\tconst repo = this.deps.getRepo?.();\n\t\tconst cwd = this.deps.getCwd?.();\n\t\tconst metadata: string[] = [];\n\t\tif (branch) metadata.push(`Branch: ${branch}`);\n\t\tif (repo) metadata.push(`Repo: ${repo}`);\n\t\tif (cwd) metadata.push(`Cwd: ${cwd}`);\n\t\tif (metadata.length > 0) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Context metadata (secondary — disambiguation only):\");\n\t\t\tlines.push(...metadata);\n\t\t}\n\n\t\tif (lines.length === 0) return undefined;\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\t/**\n\t * Collect the user's intent-bearing requests from the current session's messages.\n\t * Returns the first user message, plus the latest user message when it differs.\n\t */\n\tprivate collectUserIntent(): string[] {\n\t\tconst messages = this.deps.getMessages?.() ?? [];\n\t\tconst userTexts: string[] = [];\n\t\tfor (const message of messages) {\n\t\t\tconst text = extractUserText(message);\n\t\t\tif (text) userTexts.push(text.slice(0, MAX_USER_TEXT_CHARS));\n\t\t}\n\t\tif (userTexts.length === 0) return [];\n\n\t\tconst first = userTexts[0];\n\t\tconst last = userTexts[userTexts.length - 1];\n\t\treturn first === last ? [first] : [first, last];\n\t}\n\n\t/** Clean up LLM response to a usable tab title. */\n\tprivate sanitizeTitle(response: unknown): string | undefined {\n\t\tif (!response || typeof response !== \"object\") return undefined;\n\n\t\tconst msg = response as { content?: Array<{ type: string; text?: string }> };\n\t\tif (!msg.content || !Array.isArray(msg.content)) return undefined;\n\n\t\tconst textPart = msg.content.find((c) => c.type === \"text\");\n\t\tif (!textPart?.text) return undefined;\n\n\t\tlet title = textPart.text.trim();\n\t\t// Strip surrounding quotes if present\n\t\tif ((title.startsWith('\"') && title.endsWith('\"')) || (title.startsWith(\"'\") && title.endsWith(\"'\"))) {\n\t\t\ttitle = title.slice(1, -1).trim();\n\t\t}\n\t\t// Remove newlines\n\t\ttitle = title.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t// Truncate to the hidden hard cap (safety limit; the soft target is a prompt hint)\n\t\tif (title.length > TITLE_HARD_CAP) {\n\t\t\ttitle = title.slice(0, TITLE_HARD_CAP);\n\t\t}\n\t\treturn title || undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tab-title.js","sourceRoot":"","sources":["../../src/core/tab-title.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Auto-generates a terminal tab title from session context after a threshold\n * number of tool calls. Uses a lightweight single-shot LLM call to produce a\n * concise title — the model aims for a configurable soft target (default 60\n * characters, via TabTitleSettings.maxTitleLength) and the result is hard-capped\n * at 300 characters — then sets it via the terminal's OSC 0 escape.\n *\n * Fires at most once per session. Final failures are reported through the\n * optional onError hook while preserving fire-and-forget behavior.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Api, Context, Model } from \"@dreb/ai\";\nimport { completeSimple } from \"@dreb/ai\";\nimport { CONFIG_DIR_NAME, getPackageDir } from \"../config.js\";\nimport { extractUserText, labelMessageEnd, labelToolEnd, RollingContextBuffer } from \"./context-buffer.js\";\nimport type { ModelRegistry } from \"./model-registry.js\";\nimport type { TabTitleSettings } from \"./settings-manager.js\";\nimport { parseAgentFrontmatter, resolveModelForSubagentSpawn } from \"./tools/subagent.js\";\n\nconst DEFAULT_TRIGGER_AFTER = 9;\n// Soft target communicated to the model — titles should aim for this length but\n// may run longer when clarity demands it. Configurable via TabTitleSettings.maxTitleLength.\nconst DEFAULT_TITLE_SOFT_TARGET = 60;\n// Hidden hard cap — a safety limit that titles are truncated to regardless of the\n// soft target. The dashboard has room for long names; the TUI truncates visually.\nconst TITLE_HARD_CAP = 300;\n// Cap on how much of a single user message we feed into the title context.\nconst MAX_USER_TEXT_CHARS = 2000;\nconst TITLE_GENERATION_TIMEOUT_MS = 60_000;\n\nfunction buildTitlePrompt(softTarget: number): string {\n\treturn (\n\t\t\"You are a headless terminal-tab title generator. You are NOT the assistant in the session — \" +\n\t\t\"you will never speak to the user. Your only job is to output a single short title string, nothing else. \" +\n\t\t\"No quotes, no explanation, no preamble. \" +\n\t\t\"The title disambiguates terminal windows for a human at a glance. \" +\n\t\t\"Base the title PRIMARILY on the user's actual request and the concrete actions taken in THIS session. \" +\n\t\t\"Describe what is being DONE (e.g. 'Fix auth bug', 'Plan subagent refactor', 'Review modal'), \" +\n\t\t\"not just label the invocation. \" +\n\t\t\"Branch, repo, and cwd metadata are SECONDARY — use them only to disambiguate when the user's \" +\n\t\t\"request is otherwise ambiguous. Never let a branch name override the user's actual intent: \" +\n\t\t\"if the branch is 'feature/dashboard-foundation' but the user asked to install a Playwright skill, \" +\n\t\t\"the title is about the Playwright skill, not the dashboard. \" +\n\t\t\"Avoid reference-only formats like '#N' or 'mach6-X #N'. \" +\n\t\t\"Do not include 'dreb' — the caller already adds it. \" +\n\t\t`Output ONLY the title text. Aim for about ${softTarget} characters; a little longer is fine if needed for clarity.`\n\t);\n}\n\nexport interface TabTitleDeps {\n\t/** Set the terminal tab title (OSC 0). */\n\tsetTitle: (title: string) => void;\n\t/** Persist the generated title as the session name. Called with the raw title (without \"dreb - \" prefix). */\n\tsetSessionName?: (name: string) => void;\n\t/**\n\t * Get the current session name, if the session is already named. When this returns a\n\t * non-empty string, title generation is skipped — auto-titling only names unnamed\n\t * sessions and must never overwrite an existing (e.g. resumed) session's name.\n\t */\n\tgetSessionName?: () => string | undefined;\n\t/** Get the current session messages (for context). */\n\tgetMessages: () => Array<{ role: string; content?: unknown }>;\n\t/** Get the current model (parent session model — used as fallback). */\n\tgetModel: () => Model<Api> | undefined;\n\t/** Get model registry for API key resolution. */\n\tgetModelRegistry: () => ModelRegistry;\n\t/** Get the parent provider name. */\n\tgetProvider: () => string | undefined;\n\t/**\n\t * Get the user's agentModels settings override for a given agent name, if any.\n\t * Returns a non-empty fallback list when the user has configured an override.\n\t */\n\tgetAgentModelsOverride?: (agentName: string) => string[] | undefined;\n\t/** Current git branch name, or null/undefined if unavailable. */\n\tgetBranch?: () => string | null | undefined;\n\t/** Repository name (e.g., dirname of cwd), or undefined. */\n\tgetRepo?: () => string | undefined;\n\t/** Current working directory, or undefined. */\n\tgetCwd?: () => string | undefined;\n\t/** Report final title-generation failures without breaking fire-and-forget callers. */\n\tonError?: (err: unknown) => void;\n}\n\nexport class TabTitleGenerator {\n\tprivate toolCallCount = 0;\n\tprivate fired = false;\n\tprivate readonly threshold: number;\n\tprivate readonly softTarget: number;\n\tprivate readonly contextBuffer: RollingContextBuffer;\n\n\tconstructor(\n\t\tprivate readonly settings: TabTitleSettings | undefined,\n\t\tprivate readonly deps: TabTitleDeps,\n\t) {\n\t\tthis.threshold = settings?.triggerAfter ?? DEFAULT_TRIGGER_AFTER;\n\t\t// Soft target is configurable but always clamped to the hidden hard cap.\n\t\tconst configured = settings?.maxTitleLength ?? DEFAULT_TITLE_SOFT_TARGET;\n\t\tthis.softTarget = Math.max(1, Math.min(configured, TITLE_HARD_CAP));\n\t\tthis.contextBuffer = new RollingContextBuffer({ maxEntries: 30, maxChars: 6000 });\n\t}\n\n\t/** Whether this generator is enabled. */\n\tget enabled(): boolean {\n\t\treturn this.settings?.enabled !== false;\n\t}\n\n\t/**\n\t * Called on each tool_execution_end event. Captures context from the event,\n\t * increments the counter, and fires title generation when threshold is reached.\n\t */\n\tonToolEnd(event?: { toolName?: string; isError?: boolean; result?: unknown }): void {\n\t\tif (event?.toolName) {\n\t\t\tthis.contextBuffer.append(labelToolEnd(event as { toolName: string; isError?: boolean; result?: unknown }));\n\t\t}\n\n\t\tif (this.fired || !this.enabled) return;\n\n\t\tthis.toolCallCount++;\n\t\tif (this.toolCallCount >= this.threshold) {\n\t\t\tthis.fired = true;\n\t\t\t// Fire-and-forget — report final failures without throwing into the event stream.\n\t\t\tthis.generateTitle().catch((err) => this.reportError(err));\n\t\t}\n\t}\n\n\t/** Called on message_end events — captures labeled context. */\n\tonMessageEnd(message: { role: string; content?: unknown }): void {\n\t\tif (this.fired) return; // no need to accumulate after fired\n\t\tfor (const entry of labelMessageEnd(message)) {\n\t\t\tthis.contextBuffer.append(entry);\n\t\t}\n\t}\n\n\t/** Exposed for testing — the current tool call count. */\n\tget currentCount(): number {\n\t\treturn this.toolCallCount;\n\t}\n\n\t/** Exposed for testing — whether the title has been generated. */\n\tget hasFired(): boolean {\n\t\treturn this.fired;\n\t}\n\n\tprivate async generateTitle(): Promise<void> {\n\t\t// Skip entirely if the session already has a name (e.g. resumed session).\n\t\t// This guards the race where a name is set between generator construction and firing.\n\t\tif (this.deps.getSessionName?.()) return;\n\n\t\t// Single timeout bounds the entire pipeline (model probing + API key + LLM call)\n\t\tconst signal = AbortSignal.timeout(TITLE_GENERATION_TIMEOUT_MS);\n\n\t\tconst model = await this.resolveModel(signal);\n\t\tif (!model) return;\n\n\t\tconst userContext = this.buildContext();\n\t\tif (!userContext) return;\n\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: buildTitlePrompt(this.softTarget),\n\t\t\tmessages: [{ role: \"user\", content: userContext, timestamp: Date.now() }],\n\t\t};\n\n\t\tconst response = await this.completeWithParentFallback(model, context, signal);\n\t\tconst title = this.sanitizeTitle(response);\n\t\tif (title) {\n\t\t\t// Re-check: a name may have landed during the async LLM call.\n\t\t\tif (this.deps.getSessionName?.()) return;\n\t\t\tthis.deps.setTitle(`dreb - ${title}`);\n\t\t\tthis.deps.setSessionName?.(title);\n\t\t}\n\t}\n\n\tprivate async completeWithParentFallback(\n\t\tmodel: Model<Api>,\n\t\tcontext: Context,\n\t\tsignal: AbortSignal,\n\t): Promise<unknown> {\n\t\tconst primaryErrorPrefix = `Tab title generation failed with model ${this.formatModel(model)}`;\n\t\ttry {\n\t\t\treturn await this.completeWithModel(model, context, signal);\n\t\t} catch (primaryErr) {\n\t\t\tconst parentModel = this.deps.getModel();\n\t\t\tif (!parentModel || this.isSameModel(model, parentModel)) {\n\t\t\t\tthrow new Error(`${primaryErrorPrefix}: ${this.formatError(primaryErr)}`, { cause: primaryErr });\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn await this.completeWithModel(parentModel, context, signal);\n\t\t\t} catch (parentErr) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${primaryErrorPrefix}: ${this.formatError(primaryErr)}. ` +\n\t\t\t\t\t\t`Parent fallback ${this.formatModel(parentModel)} also failed: ${this.formatError(parentErr)}`,\n\t\t\t\t\t{ cause: parentErr },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async completeWithModel(model: Model<Api>, context: Context, signal: AbortSignal): Promise<unknown> {\n\t\tconst registry = this.deps.getModelRegistry();\n\t\tconst apiKey = await registry.getApiKey(model);\n\t\treturn completeSimple(model, context, {\n\t\t\tapiKey,\n\t\t\tmaxRetryDelayMs: 0,\n\t\t\tsignal,\n\t\t});\n\t}\n\n\tprivate async resolveModel(signal?: AbortSignal): Promise<Model<Api> | undefined> {\n\t\tconst parentModel = this.deps.getModel();\n\t\tconst parentProvider = this.deps.getProvider();\n\t\tconst registry = this.deps.getModelRegistry();\n\n\t\t// A dedicated exact model bypasses Explore-agent routing. Dashboard/RPC writes\n\t\t// validate availability; a stale hand-edited value falls back to the parent.\n\t\tconst configuredModel = this.settings?.model;\n\t\tif (configuredModel) {\n\t\t\tconst slash = configuredModel.indexOf(\"/\");\n\t\t\tif (slash > 0 && slash < configuredModel.length - 1) {\n\t\t\t\tconst found = registry.find(configuredModel.slice(0, slash), configuredModel.slice(slash + 1));\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t\treturn parentModel;\n\t\t}\n\n\t\t// Without a dedicated model, preserve the Explore agent fallback route.\n\t\tconst exploreModels = this.getExploreAgentModels();\n\t\tif (exploreModels) {\n\t\t\tconst resolution = await resolveModelForSubagentSpawn(\n\t\t\t\texploreModels,\n\t\t\t\tparentProvider,\n\t\t\t\tregistry,\n\t\t\t\tparentModel?.id,\n\t\t\t\tsignal,\n\t\t\t\t\"[tab-title]\",\n\t\t\t);\n\t\t\tif (resolution.ok) {\n\t\t\t\tconst found = resolution.provider\n\t\t\t\t\t? registry.find(resolution.provider, resolution.modelId)\n\t\t\t\t\t: registry.getAvailable().find((m) => m.id === resolution.modelId);\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to parent session model\n\t\treturn parentModel;\n\t}\n\n\tprivate isSameModel(a: Model<Api>, b: Model<Api>): boolean {\n\t\treturn a.id === b.id && a.provider === b.provider;\n\t}\n\n\tprivate formatModel(model: Model<Api>): string {\n\t\treturn `${model.provider}/${model.id}`;\n\t}\n\n\tprivate formatError(err: unknown): string {\n\t\tif (err instanceof Error) return err.message;\n\t\tif (typeof err === \"string\") return err;\n\t\treturn String(err);\n\t}\n\n\tprivate reportError(err: unknown): void {\n\t\tconst reportedError =\n\t\t\terr instanceof Error\n\t\t\t\t? err\n\t\t\t\t: new Error(`Tab title generation failed: ${this.formatError(err)}`, { cause: err });\n\t\tthis.deps.onError?.(reportedError);\n\t}\n\n\tprivate getExploreAgentModels(): string | string[] | undefined {\n\t\t// Honor the user's agentModels settings override first. The settings key must\n\t\t// match the agent name exactly (\"Explore\", as declared in explore.md frontmatter).\n\t\tconst override = this.deps.getAgentModelsOverride?.(\"Explore\");\n\t\tif (override && override.length > 0) {\n\t\t\treturn override;\n\t\t}\n\n\t\t// Resolution order mirrors discoverAgentTypes: user override > project > package.\n\t\t// First match with a valid model wins.\n\t\tconst candidates = [\n\t\t\tjoin(homedir(), CONFIG_DIR_NAME, \"agents\", \"explore.md\"),\n\t\t\tjoin(process.cwd(), \".dreb\", \"agents\", \"explore.md\"),\n\t\t\tjoin(getPackageDir(), \"agents\", \"explore.md\"),\n\t\t];\n\n\t\tfor (const agentFile of candidates) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(agentFile, \"utf-8\");\n\t\t\t\tconst parsed = parseAgentFrontmatter(content);\n\t\t\t\tif (parsed.ok && parsed.config.model) {\n\t\t\t\t\treturn parsed.config.model;\n\t\t\t\t}\n\t\t\t} catch {}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate buildContext(): string | undefined {\n\t\tconst lines: string[] = [];\n\n\t\t// Primary signal: the user's actual request(s) from the current session.\n\t\t// Pin the FIRST user message (session-defining intent) and append the LATEST\n\t\t// user message when it differs, to catch mid-session pivots. Sourced from the\n\t\t// live message list rather than the rolling buffer so early intent is never\n\t\t// evicted by later tool activity.\n\t\tconst userIntent = this.collectUserIntent();\n\t\tif (userIntent.length > 0) {\n\t\t\tlines.push(\"User request(s):\");\n\t\t\tfor (const req of userIntent) {\n\t\t\t\tlines.push(`- ${req}`);\n\t\t\t}\n\t\t}\n\n\t\t// Secondary signal: concrete current-session actions (assistant text + tools).\n\t\tconst bufferContent = this.contextBuffer.build();\n\t\tif (bufferContent) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Session activity:\");\n\t\t\tlines.push(bufferContent);\n\t\t}\n\n\t\t// Tertiary signal: metadata, for disambiguation only.\n\t\tconst branch = this.deps.getBranch?.();\n\t\tconst repo = this.deps.getRepo?.();\n\t\tconst cwd = this.deps.getCwd?.();\n\t\tconst metadata: string[] = [];\n\t\tif (branch) metadata.push(`Branch: ${branch}`);\n\t\tif (repo) metadata.push(`Repo: ${repo}`);\n\t\tif (cwd) metadata.push(`Cwd: ${cwd}`);\n\t\tif (metadata.length > 0) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Context metadata (secondary — disambiguation only):\");\n\t\t\tlines.push(...metadata);\n\t\t}\n\n\t\tif (lines.length === 0) return undefined;\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\t/**\n\t * Collect the user's intent-bearing requests from the current session's messages.\n\t * Returns the first user message, plus the latest user message when it differs.\n\t */\n\tprivate collectUserIntent(): string[] {\n\t\tconst messages = this.deps.getMessages?.() ?? [];\n\t\tconst userTexts: string[] = [];\n\t\tfor (const message of messages) {\n\t\t\tconst text = extractUserText(message);\n\t\t\tif (text) userTexts.push(text.slice(0, MAX_USER_TEXT_CHARS));\n\t\t}\n\t\tif (userTexts.length === 0) return [];\n\n\t\tconst first = userTexts[0];\n\t\tconst last = userTexts[userTexts.length - 1];\n\t\treturn first === last ? [first] : [first, last];\n\t}\n\n\t/** Clean up LLM response to a usable tab title. */\n\tprivate sanitizeTitle(response: unknown): string | undefined {\n\t\tif (!response || typeof response !== \"object\") return undefined;\n\n\t\tconst msg = response as { content?: Array<{ type: string; text?: string }> };\n\t\tif (!msg.content || !Array.isArray(msg.content)) return undefined;\n\n\t\tconst textPart = msg.content.find((c) => c.type === \"text\");\n\t\tif (!textPart?.text) return undefined;\n\n\t\tlet title = textPart.text.trim();\n\t\t// Strip surrounding quotes if present\n\t\tif ((title.startsWith('\"') && title.endsWith('\"')) || (title.startsWith(\"'\") && title.endsWith(\"'\"))) {\n\t\t\ttitle = title.slice(1, -1).trim();\n\t\t}\n\t\t// Remove newlines\n\t\ttitle = title.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t// Truncate to the hidden hard cap (safety limit; the soft target is a prompt hint)\n\t\tif (title.length > TITLE_HARD_CAP) {\n\t\t\ttitle = title.slice(0, TITLE_HARD_CAP);\n\t\t}\n\t\treturn title || undefined;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"watch-github-ci.d.ts","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,QAAA,MAAM,mBAAmB;;EAOvB,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAExE,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,CACL,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;KACvB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACxC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAgCD,wBAAgB,6BAA6B,IAAI,kBAAkB,CA0ClE;AAYD,wBAAgB,iCAAiC,CAChD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,cAAc,CAAC,OAAO,mBAAmB,EAAE,wBAAwB,CAAC,CAsHtE;AAED,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,SAAS,CAAC,OAAO,mBAAmB,CAAC,CAEvC;AAED,eAAO,MAAM,2BAA2B;;kCAAmD,CAAC;AAC5F,eAAO,MAAM,iBAAiB;;QAAyC,CAAC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\nfunction formatOutput(chunks: readonly Buffer[], omittedEarlierOutput: boolean): { text: string; truncated: boolean } {\n\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\tconst truncation = truncateTail(rendered);\n\tconst prefix = omittedEarlierOutput ? \"[Earlier watch output omitted]\\n\" : \"\";\n\treturn {\n\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst args = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\tlet chunksBytes = 0;\n\t\t\tlet omittedEarlierOutput = false;\n\t\t\tlet outputTruncated = false;\n\t\t\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\t\t\tconst currentOutput = (): string => {\n\t\t\t\tconst formatted = formatOutput(chunks, omittedEarlierOutput);\n\t\t\t\toutputTruncated = formatted.truncated;\n\t\t\t\treturn formatted.text;\n\t\t\t};\n\t\t\tconst onData = (data: Buffer) => {\n\t\t\t\tchunks.push(data);\n\t\t\t\tchunksBytes += data.length;\n\t\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\t\tconst first = chunks[0];\n\t\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\t\tchunks.shift();\n\t\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t\t}\n\t\t\t\t\tomittedEarlierOutput = true;\n\t\t\t\t}\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: currentOutput() }],\n\t\t\t\t\tdetails: details(\"watching\"),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\") });\n\n\t\t\tlet exitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode } = await operations.exec(args, cwd, {\n\t\t\t\t\tonData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...getShellEnv(),\n\t\t\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\t\t\tNO_COLOR: \"1\",\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = chunks.length > 0 ? `${currentOutput()}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst output = currentOutput();\n\t\t\tif (exitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${output}` }],\n\t\t\t\t\tdetails: details(\"passed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(output);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${output}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${output}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: details(\"failed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${output}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`GitHub CI watch exited unexpectedly${exitCode === null ? \" without an exit code\" : ` with code ${exitCode}`}.\\n\\n${output}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
1
+ {"version":3,"file":"watch-github-ci.d.ts","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,QAAA,MAAM,mBAAmB;;EAOvB,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAExE,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,CACL,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;KACvB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACxC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAgCD,wBAAgB,6BAA6B,IAAI,kBAAkB,CA0ClE;AA8CD,wBAAgB,iCAAiC,CAChD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,cAAc,CAAC,OAAO,mBAAmB,EAAE,wBAAwB,CAAC,CA6ItE;AAED,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,SAAS,CAAC,OAAO,mBAAmB,CAAC,CAEvC;AAED,eAAO,MAAM,2BAA2B;;kCAAmD,CAAC;AAC5F,eAAO,MAAM,iBAAiB;;QAAyC,CAAC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\ninterface OutputBuffer {\n\tappend(data: Buffer): void;\n\tformat(): { text: string; truncated: boolean };\n\thasOutput(): boolean;\n}\n\nfunction createOutputBuffer(omittedPrefix: string): OutputBuffer {\n\tconst chunks: Buffer[] = [];\n\tlet chunksBytes = 0;\n\tlet omittedEarlierOutput = false;\n\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\treturn {\n\t\tappend(data) {\n\t\t\tchunks.push(data);\n\t\t\tchunksBytes += data.length;\n\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\tconst first = chunks[0];\n\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\tchunks.shift();\n\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t} else {\n\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t}\n\t\t\t\tomittedEarlierOutput = true;\n\t\t\t}\n\t\t},\n\t\tformat() {\n\t\t\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\t\t\tconst truncation = truncateTail(rendered);\n\t\t\tconst prefix = omittedEarlierOutput ? `${omittedPrefix}\\n` : \"\";\n\t\t\treturn {\n\t\t\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\t\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t\t\t};\n\t\t},\n\t\thasOutput() {\n\t\t\treturn chunks.length > 0;\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst watchArgs = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst finalArgs = [\"pr\", \"checks\", ...(selector ? [selector] : [])];\n\t\t\tconst env = {\n\t\t\t\t...getShellEnv(),\n\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\tNO_COLOR: \"1\",\n\t\t\t};\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], outputTruncated: boolean, exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\n\t\t\tconst watchOutput = createOutputBuffer(\"[Earlier watch output omitted]\");\n\t\t\tconst onWatchData = (data: Buffer) => {\n\t\t\t\twatchOutput.append(data);\n\t\t\t\tconst formatted = watchOutput.format();\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\tdetails: details(\"watching\", formatted.truncated),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\", false) });\n\n\t\t\tlet watchExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: watchExitCode } = await operations.exec(watchArgs, cwd, {\n\t\t\t\t\tonData: onWatchData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = watchOutput.hasOutput() ? `${watchOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedWatchOutput = watchOutput.format();\n\t\t\tif (watchExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedWatchOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (watchExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (watchExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited unexpectedly${watchExitCode === null ? \" without an exit code\" : ` with code ${watchExitCode}`}.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst finalOutput = createOutputBuffer(\"[Earlier final checks output omitted]\");\n\t\t\tlet finalExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: finalExitCode } = await operations.exec(finalArgs, cwd, {\n\t\t\t\t\tonData: (data) => finalOutput.append(data),\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = finalOutput.hasOutput() ? `${finalOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI final checks query aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI final checks query could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedFinalOutput = finalOutput.format();\n\t\t\tif (finalExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedFinalOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI final checks query failed: the GitHub CLI reported an error (matched \"${cliFailure}\"). Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (finalExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query found checks still pending (exit code 8) after the watch reported a terminal result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (finalExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query exited unexpectedly${finalExitCode === null ? \" without an exit code\" : ` with code ${finalExitCode}`}.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (watchExitCode !== finalExitCode) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI status changed between watch completion (exit code ${watchExitCode}) and the final checks query (exit code ${finalExitCode}); refusing to report a contradictory result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (finalExitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${formattedFinalOutput.text}` }],\n\t\t\t\t\tdetails: details(\"passed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: details(\"failed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
@@ -79,13 +79,41 @@ export function createLocalGithubCiOperations() {
79
79
  }),
80
80
  };
81
81
  }
82
- function formatOutput(chunks, omittedEarlierOutput) {
83
- const rendered = renderTerminalOutput(Buffer.concat(chunks).toString("utf-8"));
84
- const truncation = truncateTail(rendered);
85
- const prefix = omittedEarlierOutput ? "[Earlier watch output omitted]\n" : "";
82
+ function createOutputBuffer(omittedPrefix) {
83
+ const chunks = [];
84
+ let chunksBytes = 0;
85
+ let omittedEarlierOutput = false;
86
+ const maxBufferedBytes = DEFAULT_MAX_BYTES * 2;
86
87
  return {
87
- text: `${prefix}${truncation.content || "(no output)"}`,
88
- truncated: omittedEarlierOutput || truncation.truncated,
88
+ append(data) {
89
+ chunks.push(data);
90
+ chunksBytes += data.length;
91
+ while (chunksBytes > maxBufferedBytes) {
92
+ const excess = chunksBytes - maxBufferedBytes;
93
+ const first = chunks[0];
94
+ if (first.length <= excess) {
95
+ chunks.shift();
96
+ chunksBytes -= first.length;
97
+ }
98
+ else {
99
+ chunks[0] = first.subarray(excess);
100
+ chunksBytes -= excess;
101
+ }
102
+ omittedEarlierOutput = true;
103
+ }
104
+ },
105
+ format() {
106
+ const rendered = renderTerminalOutput(Buffer.concat(chunks).toString("utf-8"));
107
+ const truncation = truncateTail(rendered);
108
+ const prefix = omittedEarlierOutput ? `${omittedPrefix}\n` : "";
109
+ return {
110
+ text: `${prefix}${truncation.content || "(no output)"}`,
111
+ truncated: omittedEarlierOutput || truncation.truncated,
112
+ };
113
+ },
114
+ hasOutput() {
115
+ return chunks.length > 0;
116
+ },
89
117
  };
90
118
  }
91
119
  export function createWatchGithubCiToolDefinition(cwd, options) {
@@ -108,91 +136,106 @@ export function createWatchGithubCiToolDefinition(cwd, options) {
108
136
  if (selector?.startsWith("-")) {
109
137
  throw new Error("Pull request selector must not begin with '-'.");
110
138
  }
111
- const args = ["pr", "checks", ...(selector ? [selector] : []), "--watch", "--fail-fast"];
112
- const chunks = [];
113
- let chunksBytes = 0;
114
- let omittedEarlierOutput = false;
115
- let outputTruncated = false;
116
- const maxBufferedBytes = DEFAULT_MAX_BYTES * 2;
117
- const details = (status, exitCode) => ({
139
+ const watchArgs = ["pr", "checks", ...(selector ? [selector] : []), "--watch", "--fail-fast"];
140
+ const finalArgs = ["pr", "checks", ...(selector ? [selector] : [])];
141
+ const env = {
142
+ ...getShellEnv(),
143
+ GH_PAGER: "cat",
144
+ GH_EDITOR: "cat",
145
+ NO_COLOR: "1",
146
+ };
147
+ const details = (status, outputTruncated, exitCode) => ({
118
148
  status,
119
149
  exitCode,
120
150
  outputTruncated,
121
151
  });
122
- const currentOutput = () => {
123
- const formatted = formatOutput(chunks, omittedEarlierOutput);
124
- outputTruncated = formatted.truncated;
125
- return formatted.text;
126
- };
127
- const onData = (data) => {
128
- chunks.push(data);
129
- chunksBytes += data.length;
130
- while (chunksBytes > maxBufferedBytes) {
131
- const excess = chunksBytes - maxBufferedBytes;
132
- const first = chunks[0];
133
- if (first.length <= excess) {
134
- chunks.shift();
135
- chunksBytes -= first.length;
136
- }
137
- else {
138
- chunks[0] = first.subarray(excess);
139
- chunksBytes -= excess;
140
- }
141
- omittedEarlierOutput = true;
142
- }
152
+ const watchOutput = createOutputBuffer("[Earlier watch output omitted]");
153
+ const onWatchData = (data) => {
154
+ watchOutput.append(data);
155
+ const formatted = watchOutput.format();
143
156
  onUpdate?.({
144
- content: [{ type: "text", text: currentOutput() }],
145
- details: details("watching"),
157
+ content: [{ type: "text", text: formatted.text }],
158
+ details: details("watching", formatted.truncated),
146
159
  });
147
160
  };
148
- onUpdate?.({ content: [], details: details("watching") });
149
- let exitCode;
161
+ onUpdate?.({ content: [], details: details("watching", false) });
162
+ let watchExitCode;
150
163
  try {
151
- ({ exitCode } = await operations.exec(args, cwd, {
152
- onData,
164
+ ({ exitCode: watchExitCode } = await operations.exec(watchArgs, cwd, {
165
+ onData: onWatchData,
153
166
  signal,
154
- env: {
155
- ...getShellEnv(),
156
- GH_PAGER: "cat",
157
- GH_EDITOR: "cat",
158
- NO_COLOR: "1",
159
- },
167
+ env,
160
168
  }));
161
169
  }
162
170
  catch (error) {
163
- const output = chunks.length > 0 ? `${currentOutput()}\n\n` : "";
171
+ const output = watchOutput.hasOutput() ? `${watchOutput.format().text}\n\n` : "";
164
172
  if (error instanceof Error && error.message === "aborted") {
165
173
  throw new Error(`${output}GitHub CI watch aborted.`);
166
174
  }
167
175
  const message = error instanceof Error ? error.message : String(error);
168
176
  throw new Error(`${output}GitHub CI watch could not run: ${message}`);
169
177
  }
170
- const output = currentOutput();
171
- if (exitCode === 0) {
172
- return {
173
- content: [{ type: "text", text: `GitHub CI checks passed.\n\n${output}` }],
174
- details: details("passed", exitCode),
175
- };
178
+ const formattedWatchOutput = watchOutput.format();
179
+ if (watchExitCode === 1) {
180
+ const cliFailure = detectGhCliFailure(formattedWatchOutput.text);
181
+ if (cliFailure !== null) {
182
+ throw new Error(`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched "${cliFailure}"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \`gh auth status\`.\n\n${formattedWatchOutput.text}`);
183
+ }
184
+ }
185
+ else if (watchExitCode === 8) {
186
+ throw new Error(`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\n\n${formattedWatchOutput.text}`);
187
+ }
188
+ else if (watchExitCode !== 0) {
189
+ throw new Error(`GitHub CI watch exited unexpectedly${watchExitCode === null ? " without an exit code" : ` with code ${watchExitCode}`}.\n\n${formattedWatchOutput.text}`);
176
190
  }
177
- if (exitCode === 1) {
178
- const cliFailure = detectGhCliFailure(output);
191
+ const finalOutput = createOutputBuffer("[Earlier final checks output omitted]");
192
+ let finalExitCode;
193
+ try {
194
+ ({ exitCode: finalExitCode } = await operations.exec(finalArgs, cwd, {
195
+ onData: (data) => finalOutput.append(data),
196
+ signal,
197
+ env,
198
+ }));
199
+ }
200
+ catch (error) {
201
+ const output = finalOutput.hasOutput() ? `${finalOutput.format().text}\n\n` : "";
202
+ if (error instanceof Error && error.message === "aborted") {
203
+ throw new Error(`${output}GitHub CI final checks query aborted.`);
204
+ }
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ throw new Error(`${output}GitHub CI final checks query could not run: ${message}`);
207
+ }
208
+ const formattedFinalOutput = finalOutput.format();
209
+ if (finalExitCode === 1) {
210
+ const cliFailure = detectGhCliFailure(formattedFinalOutput.text);
179
211
  if (cliFailure !== null) {
180
- throw new Error(`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched "${cliFailure}"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \`gh auth status\`.\n\n${output}`);
212
+ throw new Error(`GitHub CI final checks query failed: the GitHub CLI reported an error (matched "${cliFailure}"). Re-check the selector and \`gh auth status\`.\n\n${formattedFinalOutput.text}`);
181
213
  }
214
+ }
215
+ else if (finalExitCode === 8) {
216
+ throw new Error(`GitHub CI final checks query found checks still pending (exit code 8) after the watch reported a terminal result.\n\n${formattedFinalOutput.text}`);
217
+ }
218
+ else if (finalExitCode !== 0) {
219
+ throw new Error(`GitHub CI final checks query exited unexpectedly${finalExitCode === null ? " without an exit code" : ` with code ${finalExitCode}`}.\n\n${formattedFinalOutput.text}`);
220
+ }
221
+ if (watchExitCode !== finalExitCode) {
222
+ throw new Error(`GitHub CI status changed between watch completion (exit code ${watchExitCode}) and the final checks query (exit code ${finalExitCode}); refusing to report a contradictory result.\n\n${formattedFinalOutput.text}`);
223
+ }
224
+ if (finalExitCode === 0) {
182
225
  return {
183
- content: [
184
- {
185
- type: "text",
186
- text: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\n\n${output}`,
187
- },
188
- ],
189
- details: details("failed", exitCode),
226
+ content: [{ type: "text", text: `GitHub CI checks passed.\n\n${formattedFinalOutput.text}` }],
227
+ details: details("passed", formattedFinalOutput.truncated, finalExitCode),
190
228
  };
191
229
  }
192
- if (exitCode === 8) {
193
- throw new Error(`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\n\n${output}`);
194
- }
195
- throw new Error(`GitHub CI watch exited unexpectedly${exitCode === null ? " without an exit code" : ` with code ${exitCode}`}.\n\n${output}`);
230
+ return {
231
+ content: [
232
+ {
233
+ type: "text",
234
+ text: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\n\n${formattedFinalOutput.text}`,
235
+ },
236
+ ],
237
+ details: details("failed", formattedFinalOutput.truncated, finalExitCode),
238
+ };
196
239
  },
197
240
  };
198
241
  }
@@ -1 +1 @@
1
- {"version":3,"file":"watch-github-ci.js","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\nfunction formatOutput(chunks: readonly Buffer[], omittedEarlierOutput: boolean): { text: string; truncated: boolean } {\n\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\tconst truncation = truncateTail(rendered);\n\tconst prefix = omittedEarlierOutput ? \"[Earlier watch output omitted]\\n\" : \"\";\n\treturn {\n\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst args = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\tlet chunksBytes = 0;\n\t\t\tlet omittedEarlierOutput = false;\n\t\t\tlet outputTruncated = false;\n\t\t\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\t\t\tconst currentOutput = (): string => {\n\t\t\t\tconst formatted = formatOutput(chunks, omittedEarlierOutput);\n\t\t\t\toutputTruncated = formatted.truncated;\n\t\t\t\treturn formatted.text;\n\t\t\t};\n\t\t\tconst onData = (data: Buffer) => {\n\t\t\t\tchunks.push(data);\n\t\t\t\tchunksBytes += data.length;\n\t\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\t\tconst first = chunks[0];\n\t\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\t\tchunks.shift();\n\t\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t\t}\n\t\t\t\t\tomittedEarlierOutput = true;\n\t\t\t\t}\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: currentOutput() }],\n\t\t\t\t\tdetails: details(\"watching\"),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\") });\n\n\t\t\tlet exitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode } = await operations.exec(args, cwd, {\n\t\t\t\t\tonData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...getShellEnv(),\n\t\t\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\t\t\tNO_COLOR: \"1\",\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = chunks.length > 0 ? `${currentOutput()}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst output = currentOutput();\n\t\t\tif (exitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${output}` }],\n\t\t\t\t\tdetails: details(\"passed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(output);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${output}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${output}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: details(\"failed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${output}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`GitHub CI watch exited unexpectedly${exitCode === null ? \" without an exit code\" : ` with code ${exitCode}`}.\\n\\n${output}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
1
+ {"version":3,"file":"watch-github-ci.js","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\ninterface OutputBuffer {\n\tappend(data: Buffer): void;\n\tformat(): { text: string; truncated: boolean };\n\thasOutput(): boolean;\n}\n\nfunction createOutputBuffer(omittedPrefix: string): OutputBuffer {\n\tconst chunks: Buffer[] = [];\n\tlet chunksBytes = 0;\n\tlet omittedEarlierOutput = false;\n\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\treturn {\n\t\tappend(data) {\n\t\t\tchunks.push(data);\n\t\t\tchunksBytes += data.length;\n\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\tconst first = chunks[0];\n\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\tchunks.shift();\n\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t} else {\n\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t}\n\t\t\t\tomittedEarlierOutput = true;\n\t\t\t}\n\t\t},\n\t\tformat() {\n\t\t\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\t\t\tconst truncation = truncateTail(rendered);\n\t\t\tconst prefix = omittedEarlierOutput ? `${omittedPrefix}\\n` : \"\";\n\t\t\treturn {\n\t\t\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\t\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t\t\t};\n\t\t},\n\t\thasOutput() {\n\t\t\treturn chunks.length > 0;\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst watchArgs = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst finalArgs = [\"pr\", \"checks\", ...(selector ? [selector] : [])];\n\t\t\tconst env = {\n\t\t\t\t...getShellEnv(),\n\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\tNO_COLOR: \"1\",\n\t\t\t};\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], outputTruncated: boolean, exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\n\t\t\tconst watchOutput = createOutputBuffer(\"[Earlier watch output omitted]\");\n\t\t\tconst onWatchData = (data: Buffer) => {\n\t\t\t\twatchOutput.append(data);\n\t\t\t\tconst formatted = watchOutput.format();\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\tdetails: details(\"watching\", formatted.truncated),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\", false) });\n\n\t\t\tlet watchExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: watchExitCode } = await operations.exec(watchArgs, cwd, {\n\t\t\t\t\tonData: onWatchData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = watchOutput.hasOutput() ? `${watchOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedWatchOutput = watchOutput.format();\n\t\t\tif (watchExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedWatchOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (watchExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (watchExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited unexpectedly${watchExitCode === null ? \" without an exit code\" : ` with code ${watchExitCode}`}.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst finalOutput = createOutputBuffer(\"[Earlier final checks output omitted]\");\n\t\t\tlet finalExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: finalExitCode } = await operations.exec(finalArgs, cwd, {\n\t\t\t\t\tonData: (data) => finalOutput.append(data),\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = finalOutput.hasOutput() ? `${finalOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI final checks query aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI final checks query could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedFinalOutput = finalOutput.format();\n\t\t\tif (finalExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedFinalOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI final checks query failed: the GitHub CLI reported an error (matched \"${cliFailure}\"). Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (finalExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query found checks still pending (exit code 8) after the watch reported a terminal result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (finalExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query exited unexpectedly${finalExitCode === null ? \" without an exit code\" : ` with code ${finalExitCode}`}.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (watchExitCode !== finalExitCode) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI status changed between watch completion (exit code ${watchExitCode}) and the final checks query (exit code ${finalExitCode}); refusing to report a contradictory result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (finalExitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${formattedFinalOutput.text}` }],\n\t\t\t\t\tdetails: details(\"passed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: details(\"failed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
@@ -65,9 +65,9 @@ export declare function toRpcBackgroundAgentInfo(a: Readonly<BackgroundAgentInfo
65
65
  /** Discover available subagent types for RPC clients, sorted by display name. */
66
66
  export declare function listAgentTypesForRpc(cwd: string): RpcAgentTypeInfo[];
67
67
  /** The slice of SettingsManager the settings RPC handlers need. */
68
- type SettingsReader = Pick<SettingsManager, "getDefaultProvider" | "getDefaultModel" | "getDefaultThinkingLevel" | "getSteeringMode" | "getFollowUpMode" | "getCompactionEnabled" | "getRetryEnabled" | "getMaxConcurrentSubagents" | "getImageAutoResize" | "getBlockImages" | "getEnableSkillCommands" | "getGlobalContextTrustPolicy" | "getConfiguredTrustedContextFolders" | "getTransport" | "getHideThinkingBlock" | "getAgentModels" | "getGlobalSubagentArbiterSettings" | "getEnabledModels" | "hasProjectEnabledModelsOverride">;
68
+ type SettingsReader = Pick<SettingsManager, "getDefaultProvider" | "getDefaultModel" | "getDefaultThinkingLevel" | "getSteeringMode" | "getFollowUpMode" | "getCompactionEnabled" | "getRetryEnabled" | "getMaxConcurrentSubagents" | "getImageAutoResize" | "getBlockImages" | "getEnableSkillCommands" | "getGlobalContextTrustPolicy" | "getConfiguredTrustedContextFolders" | "getTransport" | "getHideThinkingBlock" | "getAgentModels" | "getGlobalSubagentArbiterSettings" | "getTabTitleSettings" | "getEnabledModels" | "hasProjectEnabledModelsOverride">;
69
69
  type SettingsRefresher = SettingsReader & Pick<SettingsManager, "reload" | "flush" | "drainErrors" | "hasGlobalSettingsLoadError" | "hasProjectSettingsLoadError">;
70
- type SettingsWriter = SettingsRefresher & Pick<SettingsManager, "setDefaultModelAndProvider" | "setDefaultThinkingLevel" | "setSteeringMode" | "setFollowUpMode" | "setCompactionEnabled" | "setRetryEnabled" | "setMaxConcurrentSubagents" | "setImageAutoResize" | "setBlockImages" | "setEnableSkillCommands" | "setAutoLoadNestedContext" | "setTrustedContextFolders" | "removeTrustedContextFolder" | "setContextTrust" | "setTransport" | "setHideThinkingBlock" | "setAgentModelsForAgent" | "removeAgentModelsForAgent" | "hasProjectAgentModelOverride" | "setGlobalSubagentArbiterSettings" | "setEnabledModels">;
70
+ type SettingsWriter = SettingsRefresher & Pick<SettingsManager, "setDefaultModelAndProvider" | "setDefaultThinkingLevel" | "setSteeringMode" | "setFollowUpMode" | "setCompactionEnabled" | "setRetryEnabled" | "setMaxConcurrentSubagents" | "setImageAutoResize" | "setBlockImages" | "setEnableSkillCommands" | "setAutoLoadNestedContext" | "setTrustedContextFolders" | "removeTrustedContextFolder" | "setContextTrust" | "setTransport" | "setHideThinkingBlock" | "setAgentModelsForAgent" | "removeAgentModelsForAgent" | "hasProjectAgentModelOverride" | "setGlobalSubagentArbiterSettings" | "setGlobalTabTitleSettings" | "setEnabledModels">;
71
71
  /**
72
72
  * Handle the `get_settings` RPC command: snapshot the persistent default settings.
73
73
  *