diff --git a/packages/api/src/services/container-tasks-core.ts b/packages/api/src/services/container-tasks-core.ts index 7945aee4..03542238 100644 --- a/packages/api/src/services/container-tasks-core.ts +++ b/packages/api/src/services/container-tasks-core.ts @@ -27,9 +27,18 @@ const commandSuggestsSsh = (command: string): boolean => command.startsWith("ssh const commandSuggestsAgent = (command: string): boolean => interactiveAgentPattern.test(command) || command.includes("docker-git-agent-") -// Rust browser MCP helpers (`browser-connection`, `docker-git-browser-connection`) -// run on an allocated pty, so without this they would be misclassified as visible -// `ssh` terminals and flood the task manager (issue #383). +// CHANGE: classify browser-connection / docker-git-browser-connection helpers as system +// WHY: these Rust MCP helpers run on allocated ptys (pts/N); without this guard +// hasInteractiveTty classifies each as a visible "ssh" terminal and floods the +// task manager with duplicate rows (issue #383) +// QUOTE(ТЗ): n/a +// REF: https://github.com/ProverCoderAI/docker-git/issues/383 +// SOURCE: n/a +// FORMAT THEOREM: ∀ cmd ∈ Commands: commandSuggestsBrowserHelper(cmd) → classifyProcess(cmd) = "system" +// PURITY: CORE +// INVARIANT: browserConnectionPattern matches the binary name at start or after '/' +// with a word boundary — covers bare invocations and full absolute paths +// COMPLEXITY: O(1) time / O(1) space — single constant regex test const browserConnectionPattern = /(?:^|\/)(?:docker-git-browser-connection|browser-connection)\b/u const commandSuggestsBrowserHelper = (command: string): boolean => browserConnectionPattern.test(command) diff --git a/packages/api/tests/container-tasks-core.test.ts b/packages/api/tests/container-tasks-core.test.ts index fe8c1c89..7b30e0b7 100644 --- a/packages/api/tests/container-tasks-core.test.ts +++ b/packages/api/tests/container-tasks-core.test.ts @@ -1,3 +1,4 @@ +import fc from "fast-check" import { describe, expect, it } from "vitest" import { buildContainerTasks } from "../src/services/container-tasks-core.js" @@ -55,6 +56,42 @@ describe("container task classification", () => { expect(withSystem.map((task) => [task.pid, task.kind])).toEqual([[11502, "system"]]) }) + it("hides any browser-helper command variant regardless of path prefix or arguments", () => { + const optArgs = fc.option(fc.string({ maxLength: 40 }), { nil: undefined }) + + const browserCommandArbitrary = fc.oneof( + optArgs.map((args) => (args === undefined ? "browser-connection" : `browser-connection ${args}`)), + optArgs.map((args) => + args === undefined ? "docker-git-browser-connection" : `docker-git-browser-connection ${args}` + ), + optArgs.map((args) => + args === undefined ? "/usr/local/bin/browser-connection" : `/usr/local/bin/browser-connection ${args}` + ), + optArgs.map((args) => + args === undefined + ? "/usr/local/bin/docker-git-browser-connection" + : `/usr/local/bin/docker-git-browser-connection ${args}` + ) + ) + + fc.assert( + fc.property(browserCommandArbitrary, fc.integer({ min: 1, max: 99999 }), (command, pid) => { + const hidden = buildContainerTasks( + [processOf({ command, pid, ppid: 0, tty: "pts/1" })], + [], + false + ) + const shown = buildContainerTasks( + [processOf({ command, pid, ppid: 0, tty: "pts/1" })], + [], + true + ) + return hidden.length === 0 && shown.every((t) => t.kind === "system") + }) + ) + }) + + it("marks descendants of managed agent pid as agent tasks", () => { const tasks = buildContainerTasks( [