@tamsensedev/runner 0.10.0 → 0.13.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.
- package/README.md +8 -1
- package/dist/cli.js +790 -257
- package/dist/cli.js.map +1 -1
- package/dist/recorder.cjs +42 -6
- package/dist/recorder.cjs.map +1 -1
- package/library/agents/ui-tester.md +1 -1
- package/library/skills/ui-testing/SKILL.md +1 -1
- package/library/skills/ui-testing/references/headless-check.md +17 -2
- package/library/skills/ui-testing/references/recording.md +6 -3
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/api.ts","../src/browser.ts","../src/commands.ts","../src/capabilities.ts","../src/claude.ts","../src/askPerson.ts","../src/claudeResult.ts","../src/protocol.ts","../src/feed.ts","../src/handoff.ts","../src/helpers.ts","../src/input.ts","../src/knowledge.ts","../src/permissions.ts","../src/localWorkspace.ts","../src/folders.ts","../src/git.ts","../src/lock.ts","../src/workspace.ts","../src/results.ts","../src/specText.ts","../src/prompt.ts","../src/specTools.ts","../src/usageLimit.ts","../src/config.ts","../src/login.ts","../src/recorderEnv.ts","../src/recorder.ts","../src/scope.ts","../src/system.ts","../src/update.ts","../src/worker.ts","../src/feedQueue.ts","../src/library.ts","../src/recordings.ts","../src/screenshot.ts"],"sourcesContent":["import type { InstalledBrowser, InstallTools } from './browser'\r\nimport type { Command } from './commands'\r\nimport type { RunnerConfig } from './config'\r\nimport type { RunnerIdentityResponse } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { homedir } from 'node:os'\r\nimport process from 'node:process'\r\nimport { ApiError, createApi, DEFAULT_SERVER, serverOrigin } from './api'\r\nimport { browserStatusText, ensureBrowser, installBrowser, installedBrowser, removeBrowser, runProcess } from './browser'\r\nimport { probeCapabilities } from './capabilities'\r\nimport { runClaude } from './claude'\r\nimport { HELP_TEXT, LOGIN_COMMAND, parseCommand, START_COMMAND } from './commands'\r\nimport { readConfig, removeConfig, writeConfig } from './config'\r\nimport { inspectFolder } from './folders'\r\nimport { login } from './login'\r\nimport { IDENTITY_PATH } from './protocol'\r\nimport { bundledRecorder } from './recorderEnv'\r\nimport { describeDevice, teamKeyProblem } from './scope'\r\nimport { defaultDeviceName, openBrowser, platformLabel, sleep } from './system'\r\nimport { currentVersion, latestVersion, relaunch, updateToRun } from './update'\r\nimport { work } from './worker'\r\nimport { githubRemoteUrl } from './workspace'\r\n\r\nconst NOT_CONNECTED_TEXT = `This computer is not connected. Run ${LOGIN_COMMAND}`\r\nconst DISCONNECTED_TEXT = `This computer was disconnected on the website. Run ${LOGIN_COMMAND}`\r\nconst NO_TASKS_ACCESS_TEXT = 'Your account no longer has access to Tasks. Ask the project owner.'\r\nconst ACCESS_PROBLEMS: Record<number, string> = { 401: DISCONNECTED_TEXT, 403: NO_TASKS_ACCESS_TEXT }\r\nconst INTERRUPTED_EXIT_CODE = 130\r\n\r\nfunction print(line: string): void {\r\n process.stdout.write(`${line}\\n`)\r\n}\r\n\r\nfunction serverFor(flag: string | null, saved: RunnerConfig | null): string {\r\n return serverOrigin(flag ?? process.env.TAMSENSE_URL ?? saved?.server ?? DEFAULT_SERVER)\r\n}\r\n\r\n// Отказ в доступе объясняется человеку словами, остальные ошибки идут как есть\r\nfunction accessProblem(error: unknown): string | null {\r\n return error instanceof ApiError ? ACCESS_PROBLEMS[error.status] ?? null : null\r\n}\r\n\r\n// Прежний ключ этого компьютера отзывается: в списке на сайте не остаётся мёртвых записей\r\nasync function forgetDevice(config: RunnerConfig): Promise<void> {\r\n await createApi(config.server, config.token).request('DELETE', IDENTITY_PATH).catch(() => {})\r\n await removeConfig()\r\n}\r\n\r\nasync function runLogin(server: string | null, deviceName: string | null): Promise<void> {\r\n const saved = await readConfig()\r\n const api = createApi(serverFor(server, saved))\r\n if (saved) {\r\n await forgetDevice(saved)\r\n }\r\n const name = deviceName?.trim() || defaultDeviceName()\r\n const result = await login(\r\n { api, openBrowser, print, sleep, now: Date.now },\r\n { name, platform: platformLabel() },\r\n )\r\n await writeConfig({ server: api.server, token: result.token, deviceId: result.device.id, deviceName: result.device.name })\r\n print(`Connected: ${result.device.name}. To take your tasks, run ${START_COMMAND}`)\r\n}\r\n\r\n// Первый Ctrl+C останавливает текущую задачу и отчитывается о ней, второй закрывает программу сразу\r\nfunction stopOnInterrupt(): AbortSignal {\r\n const stop = new AbortController()\r\n process.once('SIGINT', () => {\r\n print('Stopping… Press Ctrl+C again to quit at once.')\r\n stop.abort()\r\n process.once('SIGINT', () => process.exit(INTERRUPTED_EXIT_CODE))\r\n })\r\n return stop.signal\r\n}\r\n\r\nfunction installTools(home: string): InstallTools {\r\n return { home, nodePath: process.execPath, env: process.env, print, run: runProcess, exists: existsSync }\r\n}\r\n\r\n// Браузер для проверок интерфейса ставится сам; --no-browser только не ставит его, уже поставленный работает\r\nasync function browserForStart(home: string, install: boolean): Promise<InstalledBrowser | null> {\r\n const installed = await installedBrowser(home)\r\n if (installed || !install) {\r\n return installed\r\n }\r\n return ensureBrowser({ print, install: () => installBrowser(installTools(home)) })\r\n}\r\n\r\n// Вышла новая версия: она запускается вместо этой, и компьютер всегда работает на последней\r\nasync function startedNewVersion(): Promise<boolean> {\r\n const update = await updateToRun(process.env, currentVersion(), () => latestVersion())\r\n if (!update) {\r\n return false\r\n }\r\n print(`TAMsense runner ${update.latest} is out, this is ${update.current}. Starting the new version...`)\r\n try {\r\n process.exitCode = await relaunch(process.argv.slice(2), process.env)\r\n return true\r\n }\r\n catch {\r\n print('Could not start the new version. Going on with this one.')\r\n return false\r\n }\r\n}\r\n\r\nasync function runStart(parallel: number, installsBrowser: boolean): Promise<void> {\r\n if (await startedNewVersion()) {\r\n return\r\n }\r\n const saved = await readConfig()\r\n if (!saved) {\r\n print(NOT_CONNECTED_TEXT)\r\n return\r\n }\r\n const api = createApi(saved.server, saved.token)\r\n const identity = await api.request<RunnerIdentityResponse>('GET', IDENTITY_PATH)\r\n const problem = teamKeyProblem(identity.device.team, process.env)\r\n if (problem) {\r\n throw new Error(problem)\r\n }\r\n print(`${identity.device.name} is connected to ${saved.server} as ${describeDevice(identity)}.`)\r\n const home = homedir()\r\n const browser = await browserForStart(home, installsBrowser)\r\n await work({ api, runAgent: runClaude, capabilities: probeCapabilities, print, sleep, home, remoteUrl: githubRemoteUrl, browser, recorder: bundledRecorder(), inspectFolder: path => inspectFolder(path, home) }, stopOnInterrupt(), parallel)\r\n}\r\n\r\nasync function runBrowser(remove: boolean): Promise<void> {\r\n const home = homedir()\r\n if (remove) {\r\n print(await removeBrowser(home) ? 'Removed the headless browser. The next start installs it again, unless you add --no-browser.' : 'No headless browser is installed.')\r\n return\r\n }\r\n const installed = await installedBrowser(home)\r\n if (installed) {\r\n print(browserStatusText(installed))\r\n return\r\n }\r\n await installBrowser(installTools(home))\r\n print('Claude uses it for interface checks from the next start.')\r\n}\r\n\r\nasync function runStatus(): Promise<void> {\r\n const saved = await readConfig()\r\n if (!saved) {\r\n print(NOT_CONNECTED_TEXT)\r\n return\r\n }\r\n const identity = await createApi(saved.server, saved.token).request<RunnerIdentityResponse>('GET', IDENTITY_PATH)\r\n print(`Connected: ${identity.device.name}, ${describeDevice(identity)}`)\r\n print(`Account: ${identity.email ?? 'unknown'}, project ${identity.projectName}`)\r\n print(`Server: ${saved.server}`)\r\n}\r\n\r\nasync function runLogout(): Promise<void> {\r\n const saved = await readConfig()\r\n if (!saved) {\r\n print(NOT_CONNECTED_TEXT)\r\n return\r\n }\r\n await forgetDevice(saved)\r\n print('Disconnected. This computer no longer takes your tasks.')\r\n}\r\n\r\nasync function run(command: Command): Promise<void> {\r\n if (command.name === 'login') {\r\n await runLogin(command.server, command.deviceName)\r\n }\r\n else if (command.name === 'start') {\r\n await runStart(command.parallel, command.browser)\r\n }\r\n else if (command.name === 'browser') {\r\n await runBrowser(command.remove)\r\n }\r\n else if (command.name === 'status') {\r\n await runStatus()\r\n }\r\n else if (command.name === 'logout') {\r\n await runLogout()\r\n }\r\n else {\r\n print(HELP_TEXT)\r\n }\r\n}\r\n\r\ntry {\r\n await run(parseCommand(process.argv.slice(2), process.env))\r\n}\r\ncatch (error) {\r\n process.stderr.write(`${accessProblem(error) ?? (error instanceof Error ? error.message : String(error))}\\n`)\r\n process.exitCode = 1\r\n}\r\n","export const DEFAULT_SERVER = 'https://my.tamsense.com'\r\n\r\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'\r\n\r\nexport type UploadBody = Uint8Array<ArrayBuffer> | ReadableStream<Uint8Array>\r\n\r\n// request: JSON туда и обратно; upload: файл как есть, без base64 (куски записи, видео), ответ JSON\r\nexport interface ApiClient {\r\n readonly server: string\r\n request: <Reply>(method: HttpMethod, path: string, body?: object) => Promise<Reply>\r\n upload: <Reply>(method: HttpMethod, path: string, body: UploadBody, contentType: string) => Promise<Reply>\r\n}\r\n\r\nexport class ApiError extends Error {\r\n readonly status: number\r\n readonly code: string | null\r\n\r\n constructor(status: number, code: string | null, message: string) {\r\n super(message)\r\n this.name = 'ApiError'\r\n this.status = status\r\n this.code = code\r\n }\r\n}\r\n\r\ninterface ErrorReply {\r\n message?: unknown\r\n statusMessage?: unknown\r\n data?: { code?: unknown }\r\n}\r\n\r\nexport function serverOrigin(address: string): string {\r\n let url: URL\r\n try {\r\n url = new URL(address)\r\n }\r\n catch {\r\n throw new Error(`\"${address}\" is not a valid address. Use a full address like ${DEFAULT_SERVER}`)\r\n }\r\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\r\n throw new Error(`\"${address}\" is not a web address. Use a full address like ${DEFAULT_SERVER}`)\r\n }\r\n return url.origin\r\n}\r\n\r\nfunction textOf(value: unknown): string | null {\r\n return typeof value === 'string' && value.trim() ? value : null\r\n}\r\n\r\nasync function errorOf(response: Response): Promise<ApiError> {\r\n const reply = await response.json().catch(() => ({})) as ErrorReply\r\n const code = textOf(reply.data?.code)\r\n const message = textOf(reply.message) ?? textOf(reply.statusMessage) ?? `TAMsense answered with status ${response.status}`\r\n return new ApiError(response.status, code, message)\r\n}\r\n\r\nexport function createApi(server: string, token: string | null = null, fetchImpl: typeof fetch = fetch): ApiClient {\r\n async function send<Reply>(method: HttpMethod, path: string, headers: Record<string, string>, body: BodyInit | undefined): Promise<Reply> {\r\n const all: Record<string, string> = { accept: 'application/json', ...headers }\r\n if (token) {\r\n all.authorization = `Bearer ${token}`\r\n }\r\n let response: Response\r\n try {\r\n // поток уходит по мере чтения файла: fetch в Node просит для этого duplex\r\n response = await fetchImpl(new URL(path, server), { method, headers: all, body, ...(body instanceof ReadableStream ? { duplex: 'half' } : {}) } as RequestInit)\r\n }\r\n catch (error) {\r\n throw new Error(`Could not reach ${server}. Check your internet connection.`, { cause: error })\r\n }\r\n if (!response.ok) {\r\n throw await errorOf(response)\r\n }\r\n return await response.json() as Reply\r\n }\r\n\r\n return {\r\n server,\r\n request: <Reply>(method: HttpMethod, path: string, body?: object) =>\r\n send<Reply>(method, path, body ? { 'content-type': 'application/json' } : {}, body && JSON.stringify(body)),\r\n upload: <Reply>(method: HttpMethod, path: string, body: UploadBody, contentType: string) =>\r\n send<Reply>(method, path, { 'content-type': contentType }, body),\r\n }\r\n}\r\n","import { spawn } from 'node:child_process'\r\nimport { existsSync } from 'node:fs'\r\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\r\nimport { delimiter, dirname, join } from 'node:path'\r\nimport { createInterface } from 'node:readline'\r\nimport { BROWSER_COMMAND } from './commands'\r\n\r\nexport const PLAYWRIGHT_VERSION = '1.64.1'\r\n\r\n// Сначала облегчённый headless Chromium; если для этой системы его нет, обычный\r\nconst BROWSER_NAMES = ['chromium-headless-shell', 'chromium']\r\nconst MARKER_FILE = 'tamsense-browser.json'\r\nconst OUTPUT_TAIL_LINES = 40\r\nconst ERROR_TAIL_LINES = 3\r\n// eslint-disable-next-line no-control-regex\r\nconst ANSI_PATTERN = /\\u001B\\[[\\d;]*m/g\r\nconst OFFLINE_PATTERN = /ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNREFUSED|ECONNRESET|ENETUNREACH|getaddrinfo/i\r\n\r\nexport const INSTALLING_TEXT = 'Interface checks need a headless browser. Installing it once into ~/.tamsense, about 150 MB. To start without it, add --no-browser.'\r\nconst OFFLINE_TEXT = `No connection to download it. Check your internet connection and run ${BROWSER_COMMAND} again.`\r\n\r\nexport interface BrowserFolders {\r\n prefix: string\r\n packageDir: string\r\n browsers: string\r\n marker: string\r\n}\r\n\r\nexport interface InstalledBrowser {\r\n packageDir: string\r\n browsers: string\r\n version: string\r\n browser: string\r\n}\r\n\r\nexport interface ProcessResult {\r\n code: number\r\n output: string\r\n}\r\n\r\nexport interface ProcessOptions {\r\n cwd: string\r\n env: NodeJS.ProcessEnv\r\n onLine: (line: string) => void\r\n}\r\n\r\nexport type RunProcess = (command: string, args: string[], options: ProcessOptions) => Promise<ProcessResult>\r\n\r\nexport interface InstallTools {\r\n home: string\r\n nodePath: string\r\n env: NodeJS.ProcessEnv\r\n print: (line: string) => void\r\n run: RunProcess\r\n exists: (path: string) => boolean\r\n}\r\n\r\nexport interface EnsureTools {\r\n print: (line: string) => void\r\n install: () => Promise<InstalledBrowser>\r\n}\r\n\r\nexport function browserFolders(home: string): BrowserFolders {\r\n const prefix = join(home, '.tamsense', 'playwright')\r\n return {\r\n prefix,\r\n packageDir: join(prefix, 'node_modules', 'playwright-core'),\r\n browsers: join(home, '.tamsense', 'browsers'),\r\n marker: join(prefix, MARKER_FILE),\r\n }\r\n}\r\n\r\n// Установленным считается только браузер, который докачался до конца и той же версии, что ждёт программа\r\nexport async function installedBrowser(home: string): Promise<InstalledBrowser | null> {\r\n const folders = browserFolders(home)\r\n let saved: { version?: unknown, browser?: unknown }\r\n try {\r\n saved = JSON.parse(await readFile(folders.marker, 'utf8')) as typeof saved\r\n }\r\n catch {\r\n return null\r\n }\r\n const complete = saved.version === PLAYWRIGHT_VERSION && typeof saved.browser === 'string'\r\n && existsSync(join(folders.packageDir, 'package.json')) && existsSync(folders.browsers)\r\n return complete ? { packageDir: folders.packageDir, browsers: folders.browsers, version: PLAYWRIGHT_VERSION, browser: String(saved.browser) } : null\r\n}\r\n\r\n// Свой браузер не подсовывается Playwright проекта: путь к браузерам скрипт проверки ставит сам, когда грузит эту копию\r\nexport function browserEnv(browser: InstalledBrowser): Record<string, string> {\r\n return { TAMSENSE_PLAYWRIGHT: browser.packageDir, TAMSENSE_PLAYWRIGHT_BROWSERS: browser.browsers }\r\n}\r\n\r\nexport function browserStatusText(browser: InstalledBrowser): string {\r\n return `The headless browser is installed: ${browser.browser} with playwright-core ${browser.version}, in ${dirname(browser.browsers)}. To remove it, run ${BROWSER_COMMAND} remove`\r\n}\r\n\r\n// npm запускается тем же node, что и программа: так он находится, даже если npm нет в PATH, и обходится без оболочки\r\nexport function findNpm(nodePath: string, env: NodeJS.ProcessEnv, exists: (path: string) => boolean): string | null {\r\n const pathKey = Object.keys(env).find(name => name.toUpperCase() === 'PATH') ?? 'PATH'\r\n const folders = [dirname(nodePath), ...(env[pathKey] ?? '').split(delimiter).filter(Boolean)]\r\n for (const folder of folders) {\r\n for (const cli of [join(folder, 'node_modules', 'npm', 'bin', 'npm-cli.js'), join(folder, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js')]) {\r\n if (exists(cli)) {\r\n return cli\r\n }\r\n }\r\n }\r\n return null\r\n}\r\n\r\nexport const runProcess: RunProcess = (command, args, options) => new Promise((resolve, reject) => {\r\n const child = spawn(command, args, { cwd: options.cwd, env: options.env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true })\r\n const tail: string[] = []\r\n for (const stream of [child.stdout, child.stderr]) {\r\n createInterface({ input: stream }).on('line', (line) => {\r\n const text = line.replace(ANSI_PATTERN, '').trim()\r\n if (text) {\r\n tail.push(text)\r\n tail.splice(0, Math.max(0, tail.length - OUTPUT_TAIL_LINES))\r\n options.onLine(text)\r\n }\r\n })\r\n }\r\n child.on('error', reject)\r\n child.on('close', code => resolve({ code: code ?? 1, output: tail.join('\\n') }))\r\n})\r\n\r\nfunction failureText(what: string, result: ProcessResult): string {\r\n if (OFFLINE_PATTERN.test(result.output)) {\r\n return `${what}: ${OFFLINE_TEXT}`\r\n }\r\n const lastLines = result.output.split('\\n').slice(-ERROR_TAIL_LINES).join(' ')\r\n return `${what} (exit code ${result.code})${lastLines ? `: ${lastLines}` : '.'}`\r\n}\r\n\r\n// Ставится в ~/.tamsense, не в проект: зависимости проектов и этой программы не меняются\r\nexport async function installBrowser(tools: InstallTools): Promise<InstalledBrowser> {\r\n const folders = browserFolders(tools.home)\r\n const npmCli = findNpm(tools.nodePath, tools.env, tools.exists)\r\n if (!npmCli) {\r\n throw new Error(`npm was not found next to ${tools.nodePath} or on PATH. Install Node.js with npm and run ${BROWSER_COMMAND} again.`)\r\n }\r\n const showLine = (line: string) => tools.print(` ${line}`)\r\n await rm(folders.marker, { force: true })\r\n await mkdir(folders.prefix, { recursive: true })\r\n\r\n tools.print(`Step 1 of 2: downloading playwright-core ${PLAYWRIGHT_VERSION}…`)\r\n const npmArgs = [npmCli, 'install', '--no-save', '--no-audit', '--no-fund', '--prefix', folders.prefix, `playwright-core@${PLAYWRIGHT_VERSION}`]\r\n const npm = await tools.run(tools.nodePath, npmArgs, { cwd: folders.prefix, env: tools.env, onLine: showLine })\r\n if (npm.code !== 0) {\r\n throw new Error(failureText('npm could not download playwright-core', npm))\r\n }\r\n\r\n tools.print('Step 2 of 2: downloading the browser, about 150 MB…')\r\n const env = { ...tools.env, PLAYWRIGHT_BROWSERS_PATH: folders.browsers }\r\n let result: ProcessResult = { code: 1, output: '' }\r\n for (const browser of BROWSER_NAMES) {\r\n result = await tools.run(tools.nodePath, [join(folders.packageDir, 'cli.js'), 'install', browser], { cwd: folders.prefix, env, onLine: showLine })\r\n if (result.code === 0) {\r\n await writeFile(folders.marker, `${JSON.stringify({ version: PLAYWRIGHT_VERSION, browser }, null, 4)}\\n`)\r\n tools.print(`Installed ${browser} into ${folders.browsers}.`)\r\n return { packageDir: folders.packageDir, browsers: folders.browsers, version: PLAYWRIGHT_VERSION, browser }\r\n }\r\n if (OFFLINE_PATTERN.test(result.output)) {\r\n break\r\n }\r\n }\r\n throw new Error(failureText('Could not download the browser', result))\r\n}\r\n\r\nexport async function removeBrowser(home: string): Promise<boolean> {\r\n const folders = browserFolders(home)\r\n const present = existsSync(folders.prefix) || existsSync(folders.browsers)\r\n await rm(folders.prefix, { recursive: true, force: true })\r\n await rm(folders.browsers, { recursive: true, force: true })\r\n return present\r\n}\r\n\r\n// Ставится сам, без вопроса и с терминалом или без; сбой установки не мешает брать задачи\r\nexport async function ensureBrowser(tools: EnsureTools): Promise<InstalledBrowser | null> {\r\n tools.print(INSTALLING_TEXT)\r\n try {\r\n return await tools.install()\r\n }\r\n catch (error) {\r\n tools.print(`Could not install the browser: ${error instanceof Error ? error.message : String(error)}`)\r\n tools.print(`Tasks still run without it. To try again, run ${BROWSER_COMMAND}`)\r\n return null\r\n }\r\n}\r\n","import { parseArgs } from 'node:util'\r\nimport { DEFAULT_SERVER } from './api'\r\n\r\nexport const LOGIN_COMMAND = 'npx @tamsensedev/runner@latest login'\r\nexport const START_COMMAND = 'npx @tamsensedev/runner@latest start'\r\nexport const BROWSER_COMMAND = 'npx @tamsensedev/runner@latest browser'\r\n\r\nexport const DEFAULT_PARALLEL = 2\r\nexport const MAX_PARALLEL = 4\r\n\r\nexport type Command\r\n = | { name: 'login', server: string | null, deviceName: string | null }\r\n | { name: 'start', parallel: number, browser: boolean }\r\n | { name: 'browser', remove: boolean }\r\n | { name: 'status' }\r\n | { name: 'logout' }\r\n | { name: 'help' }\r\n\r\nexport const HELP_TEXT = `Connects this computer to TAMsense so it can work on your tasks.\r\n\r\nUsage:\r\n ${LOGIN_COMMAND} connect this computer\r\n ${START_COMMAND} take your tasks and work on them until you press Ctrl+C\r\n ${BROWSER_COMMAND} install the headless browser for interface checks now, or show it\r\n ${BROWSER_COMMAND} remove delete that browser\r\n npx @tamsensedev/runner@latest status show the connection\r\n npx @tamsensedev/runner@latest logout disconnect this computer\r\n\r\nLogin options:\r\n --name <name> how this computer is called in TAMsense (default: its network name)\r\n --server <url> TAMsense address (default: ${DEFAULT_SERVER})\r\n\r\nStart options:\r\n --parallel <n> how many tasks to work on at once, 1 to ${MAX_PARALLEL} (default: ${DEFAULT_PARALLEL}, or TAMSENSE_PARALLEL)\r\n --no-browser do not install the headless browser for interface checks (start installs it when it is missing)`\r\n\r\n// Флаг важнее переменной окружения; неверное число останавливает запуск, а не тихо меняется на другое\r\nexport function parallelFor(flag: string | undefined, env: string | undefined): number {\r\n const [source, value] = flag !== undefined ? ['--parallel', flag] : ['TAMSENSE_PARALLEL', env?.trim() || undefined]\r\n if (value === undefined) {\r\n return DEFAULT_PARALLEL\r\n }\r\n const count = Number(value)\r\n if (!/^\\d+$/.test(value) || count < 1 || count > MAX_PARALLEL) {\r\n throw new Error(`${source} must be a whole number from 1 to ${MAX_PARALLEL}, got \"${value}\".`)\r\n }\r\n return count\r\n}\r\n\r\nexport function parseCommand(argv: string[], env: Record<string, string | undefined> = {}): Command {\r\n const { positionals, values } = parseArgs({\r\n args: argv,\r\n allowPositionals: true,\r\n strict: true,\r\n options: {\r\n 'server': { type: 'string' },\r\n 'name': { type: 'string' },\r\n 'parallel': { type: 'string' },\r\n 'no-browser': { type: 'boolean' },\r\n 'help': { type: 'boolean' },\r\n },\r\n })\r\n const [name, detail] = positionals\r\n if (values.help) {\r\n return { name: 'help' }\r\n }\r\n if (name === 'browser' && (detail === undefined || detail === 'remove') && positionals.length <= 2) {\r\n return { name: 'browser', remove: detail === 'remove' }\r\n }\r\n if (positionals.length !== 1) {\r\n return { name: 'help' }\r\n }\r\n if (name === 'login') {\r\n return { name: 'login', server: values.server ?? null, deviceName: values.name ?? null }\r\n }\r\n if (name === 'start') {\r\n return { name: 'start', parallel: parallelFor(values.parallel, env.TAMSENSE_PARALLEL), browser: !values['no-browser'] }\r\n }\r\n if (name === 'status' || name === 'logout') {\r\n return { name }\r\n }\r\n return { name: 'help' }\r\n}\r\n","import type { AccountInfo, ApiKeySource, SDKControlInitializeResponse, SDKSystemMessage, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerAccount, RunnerCapabilities, RunnerSession } from './protocol'\r\nimport { homedir } from 'node:os'\r\nimport { query } from '@anthropic-ai/claude-agent-sdk'\r\n\r\nconst PROBE_TIMEOUT_MS = 60_000\r\nconst NO_ANSWER_TEXT = 'Claude Code did not start in time.'\r\nconst STOPPED_TEXT = 'Stopped before Claude Code answered.'\r\n\r\n// apiKeySource по SDK: ключ из окружения, из apiKeyHelper или ключ Console после /login (user, project, org, temporary из старых версий);\r\n// 'none' (и старое 'oauth') значит без ключа, то есть вход через claude.ai\r\nconst API_KEY_SOURCES = new Set<string>(['ANTHROPIC_API_KEY', 'apiKeyHelper', '/login managed key', 'user', 'project', 'org', 'temporary'] satisfies ApiKeySource[])\r\nconst LOGIN_SOURCES = new Set<string>(['none', 'oauth'] satisfies ApiKeySource[])\r\n\r\nconst ACCOUNT_WORDS: Record<RunnerAccount, string> = {\r\n subscription: 'on your subscription',\r\n api_key: 'on an API key',\r\n}\r\n\r\nexport function accountOfKeySource(source: string | undefined): RunnerAccount | null {\r\n if (source && API_KEY_SOURCES.has(source)) {\r\n return 'api_key'\r\n }\r\n return source && LOGIN_SOURCES.has(source) ? 'subscription' : null\r\n}\r\n\r\n// Bedrock, Vertex и другие облака не подписка и не ключ Anthropic\r\nexport function accountOfInfo(info: AccountInfo): RunnerAccount | null {\r\n if (info.apiProvider && info.apiProvider !== 'firstParty') {\r\n return null\r\n }\r\n if (info.apiKeySource && API_KEY_SOURCES.has(info.apiKeySource)) {\r\n return 'api_key'\r\n }\r\n return info.subscriptionType ? 'subscription' : null\r\n}\r\n\r\nexport function sessionOf(init: SDKSystemMessage): RunnerSession {\r\n return {\r\n model: init.model || null,\r\n account: accountOfKeySource(init.apiKeySource),\r\n claudeVersion: init.claude_code_version || null,\r\n }\r\n}\r\n\r\nexport function capabilitiesOf(init: SDKControlInitializeResponse): RunnerCapabilities {\r\n return {\r\n account: accountOfInfo(init.account),\r\n claudeVersion: init.claude_code_version || null,\r\n models: init.models.map(model => ({ value: model.value, displayName: model.displayName, description: model.description })),\r\n }\r\n}\r\n\r\n// В строке только короткие имена (opus, sonnet, haiku): точные версии и «по умолчанию» уходят на сайт, но здесь лишние\r\nfunction isAlias(value: string): boolean {\r\n return value !== 'default' && !value.startsWith('claude-')\r\n}\r\n\r\nexport function capabilitiesLine(capabilities: RunnerCapabilities): string {\r\n const version = capabilities.claudeVersion ? `Claude Code ${capabilities.claudeVersion}` : 'Claude Code'\r\n const account = capabilities.account ? ` ${ACCOUNT_WORDS[capabilities.account]}` : ''\r\n const names = capabilities.models.filter(model => isAlias(model.value)).map(model => model.displayName)\r\n return names.length > 0 ? `${version}${account}: ${names.join(', ')}` : `${version}${account}`\r\n}\r\n\r\n// Claude Code запускается без единого сообщения: он отвечает на знакомство (вход, версия, модели) и закрывается, модель не вызывается\r\nexport async function probeCapabilities(stop?: AbortSignal): Promise<RunnerCapabilities> {\r\n stop?.throwIfAborted()\r\n let release = () => {}\r\n const released = new Promise<void>((resolve) => {\r\n release = resolve\r\n })\r\n const nothing: AsyncIterable<SDKUserMessage> = {\r\n [Symbol.asyncIterator]: () => ({\r\n next: async () => {\r\n await released\r\n return { done: true, value: undefined }\r\n },\r\n }),\r\n }\r\n const session = query({ prompt: nothing, options: { cwd: homedir(), settingSources: ['user'], persistSession: false, strictMcpConfig: true } })\r\n const timeout = AbortSignal.timeout(PROBE_TIMEOUT_MS)\r\n const giveUp = stop ? AbortSignal.any([stop, timeout]) : timeout\r\n const gaveUp = new Promise<never>((_, reject) => {\r\n giveUp.addEventListener('abort', () => reject(new Error(stop?.aborted ? STOPPED_TEXT : NO_ANSWER_TEXT)), { once: true })\r\n })\r\n try {\r\n return capabilitiesOf(await Promise.race([session.initializationResult(), gaveUp]))\r\n }\r\n finally {\r\n release()\r\n session.close()\r\n }\r\n}\r\n","import type { McpSdkServerConfigWithInstance, Options, Query } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { AgentOutcome } from './claudeResult'\r\nimport type { FeedDraft } from './feed'\r\nimport type { AgentInput, ModelSwitch } from './input'\r\nimport type { KnowledgeTools } from './knowledge'\r\nimport type { PermissionTools } from './permissions'\r\nimport type { TaskPrompt } from './prompt'\r\nimport type { RunnerAskBody, RunnerAskReply, RunnerEffort, RunnerHandoff, RunnerHandoffTarget, RunnerPerson, RunnerQuestions, RunnerSession, RunnerSpecResult, RunnerVerdict } from './protocol'\r\nimport type { HandOverTools, ResultTools } from './results'\r\nimport type { AttachRequest } from './screenshot'\r\nimport type { ReviewRequest } from './specTools'\r\nimport type { UsageLimitWatch } from './usageLimit'\r\nimport { createSdkMcpServer, query, tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { ALREADY_WAITING_TEXT, askedText, checkAsk, MAX_ASK_OPTIONS, MAX_ASK_TRIES } from './askPerson'\r\nimport { sessionOf } from './capabilities'\r\nimport { outcomeOf } from './claudeResult'\r\nimport { feedOf, hiddenStepsFilter } from './feed'\r\nimport { chooseHandoff, HANDOFF_OUTCOMES } from './handoff'\r\nimport { helperTasks } from './helpers'\r\nimport { DEFAULT_MODEL } from './input'\r\nimport { proposeKnowledgeTool, readKnowledgeTool, updateKnowledgeTool } from './knowledge'\r\nimport { askPermission, denyWhenAsked } from './permissions'\r\nimport { ASK_PERSON_TOOL_NAME, HANDOFF_TOOL_NAME, REPLAY_TOOL_NAME, SCREENSHOT_TOOL_NAME, VERDICT_TOOL_NAME, VIDEO_TOOL_NAME } from './prompt'\r\nimport { handOverTool, resultToolset } from './results'\r\nimport { briefTool, questionsTool, specTool } from './specTools'\r\nimport { USAGE_LIMIT_REPORT, usageLimitWatch } from './usageLimit'\r\n\r\nexport type { AgentOutcome }\r\n\r\n// wantsVerdict: шаг проверки, Claude сдаёт вердикт инструментом, а не текстом\r\n// handoffs: куда Claude может передать задачу инструментом hand_off; пусто: инструмента нет\r\n// resumeSessionId: сеанс Claude Code прошлого запуска шага, разговор продолжается\r\n// onFeed: каждая запись журнала, attach: скриншот, запись браузера или видео к задаче, ответ уходит Claude как результат инструмента\r\n// model и effort: null значит как настроено в Claude Code; onSession: какая модель и чей вход на самом деле начали работу\r\n// input: первое сообщение, сообщения людей по ходу работы и смена модели\r\n// specWork: шаг ТЗ, Claude сдаёт вопросы или ТЗ своими инструментами, а сайт их проверяет; null на шагах кода и проверки\r\n// asking: люди задачи и отправка вопроса одному из них; null: спрашивать некого\r\n// permissions: действия, о которых Claude Code спросил бы человека, уходят на сайт; null: такие действия отклоняются сразу\r\n// knowledge: проверка и правка знаний проекта и предложение новой заметки; null: сайт знаний не прислал\r\n// library: папка плагина TAMsense для этого запуска, вся библиотека или копия с навыками роли\r\n// results: доска без кода, шаг Do кладёт результат на задачу своими инструментами; null на доске с кодом и на других шагах\r\n// handOver: правило шага разрешает передать задачу на другую доску; null: инструмента нет\r\nexport interface AgentRequest {\r\n cwd: string\r\n library: string\r\n prompt: TaskPrompt\r\n input: AgentInput\r\n env: Record<string, string | undefined>\r\n signal: AbortSignal\r\n wantsVerdict: boolean\r\n specWork: SpecWorkTools | null\r\n asking: AskingTools | null\r\n permissions: PermissionTools | null\r\n knowledge: KnowledgeTools | null\r\n handoffs: RunnerHandoffTarget[]\r\n resumeSessionId: string | null\r\n model: string | null\r\n effort: RunnerEffort | null\r\n onFeed: (draft: FeedDraft) => void\r\n onSession: (session: RunnerSession) => void\r\n attach: (request: AttachRequest) => Promise<string>\r\n results?: ResultTools | null\r\n handOver?: HandOverTools | null\r\n}\r\n\r\n// brief: доска без кода, шаг ТЗ сдаёт бриф результата вместо ТЗ разработки\r\nexport interface SpecWorkTools {\r\n stage: 'interview' | 'spec'\r\n review: ReviewRequest\r\n brief?: boolean\r\n}\r\n\r\nexport interface AskingTools {\r\n people: RunnerPerson[]\r\n send: (body: RunnerAskBody) => Promise<RunnerAskReply>\r\n}\r\n\r\nexport type RunAgent = (request: AgentRequest) => Promise<AgentOutcome>\r\n\r\nexport type StartQuery = typeof query\r\n\r\nconst NO_RESULT_TEXT = 'Claude finished without a result.'\r\nconst SERVER_NAME = 'tamsense'\r\n\r\nconst CAPTION_FIELDS = {\r\n title: z.string().describe('short name of what it shows, at most 60 characters: \"Design option 1\", \"Feature testing\", \"Feature testing, attempt 2\". Never generic names like \"screenshot1\"'),\r\n purpose: z.string().describe('one line, at most 160 characters, on why it was made: \"Comparing two layouts for the empty orders state\"'),\r\n}\r\n\r\nconst ATTACH_TOOLS = {\r\n screenshot: {\r\n name: SCREENSHOT_TOOL_NAME,\r\n description: 'Attach a screenshot to the TAMsense task so people can see the result without opening the code. Attach the final look of each changed screen and anything a reviewer must see. PNG, JPEG or WebP up to 5 MB, at most 20 per run.',\r\n path: 'absolute path of the image file',\r\n },\r\n replay: {\r\n name: REPLAY_TOOL_NAME,\r\n description: 'Attach a browser replay (the .rrweb.ndjson file the TAMsense recorder wrote) to the task now, with your own title and purpose. Every recording is attached at the end of the run anyway; use this to attach one earlier or to name it better. Close the browser context first so the file is complete.',\r\n path: 'absolute path of the .rrweb.ndjson replay file',\r\n },\r\n video: {\r\n name: VIDEO_TOOL_NAME,\r\n description: 'Attach a browser video (.webm or .mp4, up to 200 MB) to the task now, with your own title and purpose. Videos recorded with video: true are attached at the end of the run anyway. Close the browser context first so the file is complete.',\r\n path: 'absolute path of the video file',\r\n },\r\n} as const\r\n\r\nfunction attachTool(kind: AttachRequest['kind'], attach: AgentRequest['attach']) {\r\n const spec = ATTACH_TOOLS[kind]\r\n return tool(\r\n spec.name,\r\n spec.description,\r\n { path: z.string().describe(spec.path), ...CAPTION_FIELDS },\r\n async args => ({ content: [{ type: 'text', text: await attach({ kind, ...args }) }] }),\r\n )\r\n}\r\n\r\n// Проверка, которая не смогла запуститься из-за этого компьютера, никогда не считается пройденной\r\nexport function verdictTool(onVerdict: (verdict: RunnerVerdict) => void) {\r\n return tool(\r\n VERDICT_TOOL_NAME,\r\n 'Hand in the result of this check. Call it exactly once, at the very end.',\r\n {\r\n passed: z.boolean().describe('true only if nothing must be fixed'),\r\n report: z.string().describe('what you checked and, if it failed, exactly what to fix with file and line'),\r\n couldNotRun: z.boolean().optional().describe('true when the check could not run at all because of this computer: Docker not running, no local setup, or settings that point outside this computer; passed must be false and the report gives the exact reason and what a person should do'),\r\n },\r\n async (args) => {\r\n onVerdict(args.couldNotRun ? { passed: false, report: args.report, couldNotRun: true } : { passed: args.passed, report: args.report })\r\n return { content: [{ type: 'text', text: 'Verdict received. You can stop now.' }] }\r\n },\r\n )\r\n}\r\n\r\n// Передача проверяется здесь же: ошибка с перечнем допустимых шагов уходит Claude, принятая заменяет прежнюю\r\nexport function handoffTool(targets: RunnerHandoffTarget[], onHandoff: (handoff: RunnerHandoff) => void) {\r\n return tool(\r\n HANDOFF_TOOL_NAME,\r\n 'Hand the task to the next step of the TAMsense board when you finish. The next agent and the task owner read your note. Only one handoff counts: calling it again replaces the earlier one.',\r\n {\r\n to: z.string().describe('name or id of the step to hand the task to'),\r\n outcome: z.enum(HANDOFF_OUTCOMES).describe('pass: the work is done; problems: something must be fixed; ask: a question for that step'),\r\n note: z.string().describe('for the next agent and the owner: what was done, what to check, what is open'),\r\n },\r\n async (args) => {\r\n const choice = chooseHandoff(targets, args)\r\n if ('error' in choice) {\r\n return { content: [{ type: 'text', text: choice.error }], isError: true }\r\n }\r\n onHandoff(choice.handoff)\r\n return { content: [{ type: 'text', text: 'Handoff recorded. It happens when you finish.' }] }\r\n },\r\n )\r\n}\r\n\r\n// Вопрос человеку уходит на сайт сразу; если без ответа агент не может продолжить, запуск ждёт этого вопроса\r\nexport function askPersonTool(asking: AskingTools, waiting: { waitingFor: number | null }) {\r\n return tool(\r\n ASK_PERSON_TOOL_NAME,\r\n 'Ask a person on this TAMsense task when you are stuck or the decision belongs to a person: intent, design, access, or how the team runs something locally. Never ask what the code or the project knowledge already answers. With blocking true the run pauses and frees the computer until the answer comes; with blocking false you keep working on your assumption.',\r\n {\r\n to: z.union([z.number(), z.string()]).describe('id of the person from the people list in your instructions: the assignee first; a topic question goes to the person whose role fits'),\r\n why: z.string().describe('one line on why this person: \"assignee, and the run is on her computer\", \"Designer, owns empty states\"'),\r\n question: z.string().describe('one plain question the person can answer without reading the code'),\r\n context: z.string().optional().describe('one line on what you saw that led to the question'),\r\n tried: z.array(z.object({\r\n did: z.string().describe('the command or step'),\r\n error: z.string().optional().describe('the exact error line, if there was one'),\r\n result: z.string().describe('what it showed, one line'),\r\n })).max(MAX_ASK_TRIES).optional().describe('what you already tried, the most telling first'),\r\n options: z.array(z.object({\r\n label: z.string().describe('a short answer the person can pick'),\r\n detail: z.string().describe('one line on what happens if it is picked'),\r\n })).max(MAX_ASK_OPTIONS).optional().describe('2 to 4 answers you suggest, the one you would pick first'),\r\n blocking: z.boolean().describe('true when you cannot go on without the answer'),\r\n assumption: z.string().optional().describe('when blocking is false: what you continue with until the answer comes'),\r\n },\r\n async (args) => {\r\n if (waiting.waitingFor !== null) {\r\n return { content: [{ type: 'text', text: ALREADY_WAITING_TEXT }], isError: true }\r\n }\r\n const choice = checkAsk(asking.people, args)\r\n if ('error' in choice) {\r\n return { content: [{ type: 'text', text: choice.error }], isError: true }\r\n }\r\n let reply: RunnerAskReply\r\n try {\r\n reply = await asking.send(choice.body)\r\n }\r\n catch (error) {\r\n return { content: [{ type: 'text', text: `Could not send the question: ${error instanceof Error ? error.message : String(error)}. Decide yourself and say so in your report.` }], isError: true }\r\n }\r\n if (choice.body.blocking) {\r\n waiting.waitingFor = reply.id\r\n }\r\n return { content: [{ type: 'text', text: askedText(reply.name, choice.body) }] }\r\n },\r\n )\r\n}\r\n\r\n// helperStarted: Claude уже звал свежего помощника в этом запуске; по нему инструмент ТЗ знает, была ли проверка свежим читателем;\r\n// waitingFor: вопрос человеку, ответа на который запуск ждёт\r\ninterface Submitted {\r\n verdict: RunnerVerdict | null\r\n handoff: RunnerHandoff | null\r\n questions: RunnerQuestions | null\r\n spec: RunnerSpecResult | null\r\n helperStarted: boolean\r\n waitingFor: number | null\r\n}\r\n\r\nfunction specWorkTools(specWork: SpecWorkTools | null, submitted: Submitted) {\r\n if (!specWork) {\r\n return []\r\n }\r\n if (specWork.stage === 'interview') {\r\n return [questionsTool(specWork.review, (questions) => { submitted.questions = questions })]\r\n }\r\n return specWork.brief\r\n ? [briefTool(specWork.review, (brief) => { submitted.spec = brief })]\r\n : [specTool(specWork.review, (spec) => { submitted.spec = spec }, () => submitted.helperStarted)]\r\n}\r\n\r\n// Знания: правка, если запуск получил заметки, чтение тех, что пришли по названиям, предложение, если агент спрашивает людей\r\nfunction knowledgeTools(knowledge: KnowledgeTools | null) {\r\n if (!knowledge) {\r\n return []\r\n }\r\n return [\r\n ...(knowledge.entryIds.length > 0 ? [updateKnowledgeTool(knowledge)] : []),\r\n ...(knowledge.moreIds.length > 0 ? [readKnowledgeTool(knowledge)] : []),\r\n ...(knowledge.canPropose ? [proposeKnowledgeTool(knowledge)] : []),\r\n ]\r\n}\r\n\r\n// Свои инструменты TAMsense: скриншоты, записи браузера и видео всегда, вердикт только на шаге проверки, вопросы или ТЗ на шаге ТЗ,\r\n// вопрос человеку, если есть кого спросить, знания проекта, передача, если доска её разрешает; на доске без кода результат задачи\r\n// и передача на другую доску\r\nfunction tamsenseServer(request: AgentRequest, submitted: Submitted): { server: McpSdkServerConfigWithInstance, allowed: string[] } {\r\n const tools = [\r\n attachTool('screenshot', request.attach),\r\n attachTool('replay', request.attach),\r\n attachTool('video', request.attach),\r\n ...(request.wantsVerdict ? [verdictTool((verdict) => { submitted.verdict = verdict })] : []),\r\n ...specWorkTools(request.specWork, submitted),\r\n ...(request.asking ? [askPersonTool(request.asking, submitted)] : []),\r\n ...knowledgeTools(request.knowledge),\r\n ...(request.handoffs.length > 0 ? [handoffTool(request.handoffs, (handoff) => { submitted.handoff = handoff })] : []),\r\n ...(request.results ? resultToolset(request.results) : []),\r\n ...(request.handOver ? [handOverTool(request.handOver)] : []),\r\n ]\r\n return {\r\n server: createSdkMcpServer({ name: SERVER_NAME, version: '1.0.0', tools }),\r\n allowed: tools.map(item => `mcp__${SERVER_NAME}__${item.name}`),\r\n }\r\n}\r\n\r\nexport function modelOptions(request: Pick<AgentRequest, 'model' | 'effort'>): Pick<Options, 'model' | 'effort'> {\r\n return {\r\n ...(request.model === null ? {} : { model: request.model }),\r\n ...(request.effort === null ? {} : { effort: request.effort }),\r\n }\r\n}\r\n\r\nexport async function applySwitch(target: Pick<Query, 'setModel' | 'applyFlagSettings'>, change: ModelSwitch): Promise<void> {\r\n if (change.model !== null) {\r\n await target.setModel(change.model === DEFAULT_MODEL ? undefined : change.model)\r\n }\r\n if (change.effort !== null) {\r\n await target.applyFlagSettings({ effortLevel: change.effort })\r\n }\r\n}\r\n\r\n// resumeFailed: Claude Code не смог продолжить прошлый сеанс и не начал работу\r\ninterface Attempt {\r\n outcome: AgentOutcome\r\n sessionId: string | null\r\n resumeFailed: boolean\r\n}\r\n\r\ninterface AttemptContext {\r\n startQuery: StartQuery\r\n abortController: AbortController\r\n submitted: Submitted\r\n limit: UsageLimitWatch\r\n // последнее, что Claude написал: им запрос на действие объясняет, зачем оно\r\n lastWords: { text: string }\r\n}\r\n\r\nasync function attempt(request: AgentRequest, context: AttemptContext, resume: string | null): Promise<Attempt> {\r\n let lastText = ''\r\n let sessionId: string | null = null\r\n let outcome: AgentOutcome = { completed: false, report: NO_RESULT_TEXT, model: null, usage: null, verdict: null }\r\n const tamsense = tamsenseServer(request, context.submitted)\r\n const shown = hiddenStepsFilter()\r\n const helpers = helperTasks()\r\n const messages = context.startQuery({\r\n prompt: request.input.start(request.prompt.prompt),\r\n options: {\r\n cwd: request.cwd,\r\n systemPrompt: { type: 'preset', preset: 'claude_code', append: request.prompt.instructions },\r\n settingSources: ['user', 'project', 'local'],\r\n permissionMode: 'auto',\r\n // краткий пересказ размышлений Claude идёт в журнал задачи, как в Paseo\r\n thinking: { type: 'adaptive', display: 'summarized' },\r\n // режим auto сам решает, что можно; о чём он спросил бы человека, решают правила проекта или люди задачи\r\n canUseTool: request.permissions ? askPermission(request.permissions, () => context.lastWords.text, request.cwd) : denyWhenAsked,\r\n plugins: [{ type: 'local', path: request.library }],\r\n mcpServers: { [SERVER_NAME]: tamsense.server },\r\n allowedTools: tamsense.allowed,\r\n abortController: context.abortController,\r\n env: request.env,\r\n ...modelOptions(request),\r\n ...(resume === null ? {} : { resume }),\r\n },\r\n })\r\n request.input.onSwitch(change => applySwitch(messages, change))\r\n request.input.onStopHelper(ref => helpers.stop(messages, ref))\r\n const failedToResume = () => resume !== null && sessionId === null && !request.signal.aborted\r\n try {\r\n for await (const message of messages) {\r\n if (message.type === 'system' && message.subtype === 'init') {\r\n sessionId = message.session_id\r\n request.onSession(sessionOf(message))\r\n }\r\n if (message.type === 'result' && (message.is_error || message.subtype !== 'success') && failedToResume()) {\r\n messages.close()\r\n return { outcome, sessionId, resumeFailed: true }\r\n }\r\n for (const draft of [...helpers.observe(message), ...feedOf(message).filter(shown).flatMap(helpers.adjust)]) {\r\n if (draft.kind === 'agent' && draft.parent === null) {\r\n context.submitted.helperStarted = true\r\n }\r\n request.onFeed(draft)\r\n if (draft.kind === 'text' && draft.parent === null) {\r\n lastText = draft.text\r\n context.lastWords.text = draft.text\r\n }\r\n }\r\n if (message.type === 'result') {\r\n outcome = outcomeOf(message, lastText)\r\n }\r\n context.limit.observe(message)\r\n request.input.observe(message)\r\n }\r\n }\r\n catch (error) {\r\n if (failedToResume()) {\r\n return { outcome, sessionId, resumeFailed: true }\r\n }\r\n throw error\r\n }\r\n return { outcome, sessionId, resumeFailed: false }\r\n}\r\n\r\n// Обычный Claude Code: его стандартные инструкции, настройки и навыки человека, вход человека в Claude.\r\n// Прошлый сеанс шага продолжается; если Claude Code не может его открыть, разговор начинается заново один раз\r\nexport function claudeRunner(startQuery: StartQuery): RunAgent {\r\n return async (request) => {\r\n request.signal.throwIfAborted()\r\n const abortController = new AbortController()\r\n request.signal.addEventListener('abort', () => {\r\n request.input.close()\r\n abortController.abort()\r\n }, { once: true })\r\n const submitted: Submitted = { verdict: null, handoff: null, questions: null, spec: null, helperStarted: false, waitingFor: null }\r\n const limit = usageLimitWatch()\r\n const context: AttemptContext = { startQuery, abortController, submitted, limit, lastWords: { text: '' } }\r\n try {\r\n let result = await attempt(request, context, request.resumeSessionId)\r\n if (result.resumeFailed) {\r\n result = await attempt(request, context, null)\r\n }\r\n const pausedUntil = limit.pausedUntil(result.outcome.completed)\r\n const outcome = pausedUntil ? { ...result.outcome, completed: false, report: USAGE_LIMIT_REPORT } : result.outcome\r\n return { ...outcome, verdict: submitted.verdict, handoff: submitted.handoff, questions: submitted.questions, spec: submitted.spec, sessionId: result.sessionId, pausedUntil, waitingFor: submitted.waitingFor }\r\n }\r\n finally {\r\n request.input.close()\r\n }\r\n }\r\n}\r\n\r\nexport const runClaude: RunAgent = claudeRunner(query)\r\n","import type { RunnerAskBody, RunnerAskOption, RunnerAskTry, RunnerPerson } from './protocol'\r\n\r\nexport const MAX_ASK_TRIES = 5\r\nexport const MAX_ASK_OPTIONS = 4\r\n\r\n// Как Claude присылает вопрос: человека можно назвать номером из списка или по имени\r\nexport interface AskRequest {\r\n to: number | string\r\n why: string\r\n question: string\r\n context?: string\r\n tried?: Array<{ did: string, error?: string, result: string }>\r\n options?: RunnerAskOption[]\r\n blocking: boolean\r\n assumption?: string\r\n}\r\n\r\nexport type AskChoice = { body: RunnerAskBody, person: RunnerPerson } | { error: string }\r\n\r\n// Роли команды приходят строками «название: описание»\r\nexport function personLine(person: RunnerPerson): string {\r\n const roles = person.roles.length > 0 ? `. Team roles: ${person.roles.join('; ')}` : ''\r\n return `${person.id}: ${person.name} (${person.relation}, ${person.access} access)${roles}`\r\n}\r\n\r\nfunction findPerson(people: RunnerPerson[], to: number | string): RunnerPerson | null {\r\n const key = String(to).trim().toLowerCase()\r\n return people.find(person => String(person.id) === key || person.name.toLowerCase() === key) ?? null\r\n}\r\n\r\nfunction cleanTry(item: NonNullable<AskRequest['tried']>[number]): RunnerAskTry {\r\n return { did: item.did.trim(), error: item.error?.trim() || null, result: item.result.trim() }\r\n}\r\n\r\n// Вопрос проверяется здесь же: ошибка с подсказкой уходит Claude, и он спрашивает заново\r\nexport function checkAsk(people: RunnerPerson[], request: AskRequest): AskChoice {\r\n const question = request.question.trim()\r\n const why = request.why.trim()\r\n const assumption = request.assumption?.trim() ?? ''\r\n if (!question) {\r\n return { error: 'Write the question: one plain sentence the person can answer without reading the code.' }\r\n }\r\n const person = findPerson(people, request.to)\r\n if (!person) {\r\n return { error: `\"${request.to}\" is not a person on this task. Ask one of them by id:\\n${people.map(personLine).join('\\n')}` }\r\n }\r\n if (!why) {\r\n return { error: `Say in one line why ${person.name} is the right person to ask.` }\r\n }\r\n if (!request.blocking && !assumption) {\r\n return { error: 'A question you do not wait for needs the assumption you continue with until the answer comes.' }\r\n }\r\n const tried = (request.tried ?? []).map(cleanTry).filter(item => item.did)\r\n const options = (request.options ?? []).map(option => ({ label: option.label.trim(), detail: option.detail.trim() })).filter(option => option.label)\r\n if (tried.length > MAX_ASK_TRIES || options.length > MAX_ASK_OPTIONS) {\r\n return { error: `Keep it short: at most ${MAX_ASK_TRIES} things you tried and at most ${MAX_ASK_OPTIONS} suggested answers.` }\r\n }\r\n return {\r\n person,\r\n body: { to: person.id, why, question, context: request.context?.trim() ?? '', tried, options, blocking: request.blocking, assumption: request.blocking ? null : assumption },\r\n }\r\n}\r\n\r\nexport function askedText(name: string, body: RunnerAskBody): string {\r\n if (body.blocking) {\r\n return `Sent to ${name}. This run now waits for the answer and frees the computer. Write one short line saying what you wait for, then end your turn without doing more work. When ${name} answers, this conversation continues here with the answer.`\r\n }\r\n return `Sent to ${name}. Keep working on your assumption: ${body.assumption}. The answer comes as a message from ${name} while you work, or with the next run if this one has ended. If it differs, change what you did.`\r\n}\r\n\r\nexport const ALREADY_WAITING_TEXT = 'You already asked a question this run waits for. End your turn now: the conversation continues when the answer comes.'\r\n","import type { ModelUsage, SDKResultMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerHandoff, RunnerQuestions, RunnerSpecResult, RunnerUsage, RunnerVerdict } from './protocol'\r\n\r\n// handoff: куда Claude передал задачу инструментом hand_off; sessionId: сеанс Claude Code для продолжения;\r\n// pausedUntil: Claude упёрся в лимит, когда можно повторить (ISO); questions и spec: что Claude сдал на шаге ТЗ;\r\n// waitingFor: вопрос человеку, ответа на который запуск ждёт\r\nexport interface AgentOutcome {\r\n completed: boolean\r\n report: string\r\n model: string | null\r\n usage: RunnerUsage | null\r\n verdict: RunnerVerdict | null\r\n handoff?: RunnerHandoff | null\r\n sessionId?: string | null\r\n pausedUntil?: string | null\r\n questions?: RunnerQuestions | null\r\n spec?: RunnerSpecResult | null\r\n waitingFor?: number | null\r\n}\r\n\r\nconst MAX_ACTIVITY_LENGTH = 120\r\nconst FILE_TOOLS = new Set(['Read', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit'])\r\n// помощник Claude Code в разных версиях называется Task или Agent\r\nconst HELPER_TOOLS = new Set(['Task', 'Agent'])\r\n\r\nfunction shorten(text: string): string {\r\n const line = text.replace(/\\s+/g, ' ').trim()\r\n return line.length > MAX_ACTIVITY_LENGTH ? `${line.slice(0, MAX_ACTIVITY_LENGTH - 1)}…` : line\r\n}\r\n\r\n// Строка для терминала: человек видит, чем Claude занят прямо сейчас\r\nexport function activityLine(tool: string, input: unknown): string {\r\n const fields = typeof input === 'object' && input !== null ? input as Record<string, unknown> : {}\r\n if (tool === 'Bash' && typeof fields.command === 'string') {\r\n return `$ ${shorten(fields.command)}`\r\n }\r\n if (FILE_TOOLS.has(tool) && typeof fields.file_path === 'string') {\r\n return `${tool} ${shorten(fields.file_path)}`\r\n }\r\n if (tool === 'Skill' && typeof fields.skill === 'string') {\r\n return `Skill ${shorten(fields.skill)}`\r\n }\r\n if (HELPER_TOOLS.has(tool) && typeof fields.subagent_type === 'string') {\r\n return `Agent ${shorten(fields.subagent_type)}`\r\n }\r\n return tool\r\n}\r\n\r\n// Расход по всем моделям запуска, включая помощников, которых Claude звал сам\r\nexport function usageOf(modelUsage: Record<string, ModelUsage>): RunnerUsage | null {\r\n const entries = Object.values(modelUsage)\r\n if (entries.length === 0) {\r\n return null\r\n }\r\n const sum = (pick: (usage: ModelUsage) => number) => entries.reduce((total, usage) => total + pick(usage), 0)\r\n const inputTokens = sum(usage => usage.inputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens)\r\n const outputTokens = sum(usage => usage.outputTokens)\r\n return { inputTokens, cachedInputTokens: sum(usage => usage.cacheReadInputTokens), outputTokens, totalTokens: inputTokens + outputTokens }\r\n}\r\n\r\n// Основной моделью считается та, что написала больше всего\r\nexport function mainModel(modelUsage: Record<string, ModelUsage>): string | null {\r\n const ranked = Object.entries(modelUsage).sort(([, first], [, second]) => second.outputTokens - first.outputTokens)\r\n return ranked[0]?.[0] ?? null\r\n}\r\n\r\nexport function outcomeOf(result: SDKResultMessage, lastText: string): AgentOutcome {\r\n const measured = { model: mainModel(result.modelUsage), usage: usageOf(result.modelUsage), verdict: null }\r\n if (result.subtype === 'success') {\r\n return { completed: !result.is_error, report: result.result || lastText, ...measured }\r\n }\r\n return { completed: false, report: result.errors.join('\\n') || `Claude stopped early: ${result.subtype}`, ...measured }\r\n}\r\n","export interface ConnectedDevice {\r\n id: number\r\n name: string\r\n platform: string\r\n team: boolean\r\n createdAt: string\r\n lastSeenAt: string | null\r\n}\r\n\r\nexport interface PairingStartResponse {\r\n deviceCode: string\r\n userCode: string\r\n verificationPath: string\r\n expiresAt: string\r\n intervalSeconds: number\r\n}\r\n\r\nexport type PairingTokenResponse\r\n = | { status: 'pending' }\r\n | { status: 'denied' }\r\n | { status: 'expired' }\r\n | { status: 'connected', token: string, device: ConnectedDevice }\r\n\r\nexport interface RunnerIdentityResponse {\r\n device: ConnectedDevice\r\n email: string | null\r\n projectName: string\r\n}\r\n\r\n// pullRequestUrl: открытый PR задачи в этом репозитории, если он уже есть;\r\n// folder: папка репозитория на этом компьютере, когда код доски берётся из папок, а не с GitHub\r\nexport interface RunnerRepository {\r\n fullName: string\r\n defaultBranch: string\r\n pullRequestUrl?: string\r\n folder?: string\r\n}\r\n\r\n// Шаг доски, ради которого компьютер получил задание: вопросы или ТЗ, код, проверка кода или тесты, и помощник, который его делает\r\n// columnId: шаг доски или проверка группы; у каждого шага своя папка задачи на компьютере (у старого сайта его нет)\r\nexport interface RunnerStage {\r\n kind: 'interview' | 'spec' | 'code' | 'review' | 'test'\r\n name: string\r\n helper: string | null\r\n instructions: string\r\n columnId?: number | null\r\n // роль шага; старый сайт её не присылает, тогда видна вся библиотека и доступ по виду шага\r\n role?: RunnerRole | null\r\n}\r\n\r\n// Роль запуска. version: версия своей роли, у встроенной null; skills: навыки библиотеки, которые роль разрешает, null: вся библиотека;\r\n// access read: роль только читает, на шаге кода ничего не коммитит и не отправляет\r\nexport interface RunnerRole {\r\n key: string\r\n version: number | null\r\n skills: string[] | null\r\n access: 'read' | 'edit'\r\n}\r\n\r\nexport interface RunnerThreadMessage {\r\n role: 'user' | 'assistant'\r\n text: string\r\n}\r\n\r\n// Работа шага ТЗ. round: 1 или 2 у вопросов, null у ТЗ; maxQuestions и takenIds: сколько вопросов можно задать и чьи id заняты;\r\n// brief: бриф раунда вопросов; linkedAction: находка аналитики, из которой создана задача; thread: ответы и замечания владельца\r\nexport interface RunnerSpecWork {\r\n round: number | null\r\n maxQuestions: number\r\n takenIds: string[]\r\n brief: unknown\r\n linkedAction: string | null\r\n thread: RunnerThreadMessage[]\r\n}\r\n\r\nexport interface RunnerJob {\r\n runId: number\r\n taskId: number\r\n title: string\r\n description: string\r\n spec: unknown\r\n branch: string\r\n repositories: RunnerRepository[]\r\n review: string | null\r\n stage: RunnerStage\r\n // контекст проекта от команды из настроек доски, пустая строка, если его нет\r\n projectContext: string\r\n // модель Claude (opus, sonnet, haiku) и усилие для этого запуска; null: как настроено в Claude Code на компьютере\r\n model: string | null\r\n effort: RunnerEffort | null\r\n // сообщения людей, которые ждали следующего запуска этого шага\r\n messages: RunnerQueuedMessage[]\r\n // куда агент может передать задачу с этого шага по правилам доски\r\n allowedHandoffs: RunnerHandoffTarget[]\r\n // записка от предыдущего шага: что сделано и что проверить\r\n handoffNote: string | null\r\n // сеанс Claude Code прошлого запуска этого шага на этом компьютере: разговор продолжается\r\n resumeSessionId: string | null\r\n // вопросы или ТЗ на шаге ТЗ; null на шагах кода и проверки\r\n specWork: RunnerSpecWork | null\r\n // люди задачи, которых агент может спросить инструментом ask_person; пусто на шаге ТЗ, старый сайт их не присылает\r\n people?: RunnerPerson[]\r\n // запуск продолжает тот, что ждал ответа человека: папка задачи остаётся как была\r\n keepWorkspace?: boolean\r\n // правила безопасности проекта текстом: что можно самому, о чём спросить, чего никогда; старый сайт их не присылает\r\n safetyRules?: string\r\n // компьютер запуска и его хозяин, знания проекта и этого компьютера; старый сайт их не присылает\r\n computer?: RunnerComputer | null\r\n knowledge?: RunnerKnowledge\r\n // folder: доска без кода, репозиториев нет: работа идёт в папке задачи, результат кладётся на задачу инструментами.\r\n // local: репозитории лежат в папках этого компьютера, ветка задачи создаётся в них самих, GitHub не нужен.\r\n // Старый сайт поля не присылает: тогда репозитории\r\n workspace?: 'repositories' | 'folder' | 'local'\r\n // результат задачи, который читают шаг Do и проверка; старый сайт его не присылает\r\n results?: RunnerResult[]\r\n // доски, на которые агент может передать задачу по правилу шага\r\n handOverBoards?: RunnerBoardTarget[]\r\n}\r\n\r\n// Результат задачи на доске без кода для агента: последняя версия документа или таблицы, файл или ссылка.\r\n// fileName у файла: его можно скачать запросом resultFilePath\r\nexport interface RunnerResult {\r\n id: number\r\n kind: 'document' | 'table' | 'file' | 'link'\r\n title: string\r\n version: number | null\r\n content: string | null\r\n url: string | null\r\n fileName: string | null\r\n}\r\n\r\n// Другая доска проекта, на которую агент может передать задачу по правилу шага\r\nexport interface RunnerBoardTarget {\r\n id: number\r\n name: string\r\n}\r\n\r\n// Агент кладёт результат на задачу: документ (Markdown) или таблицу (CSV) новой версией, или ссылку\r\nexport type RunnerResultBody\r\n = | { kind: 'document' | 'table', title: string, content: string, note: string }\r\n | { kind: 'link', title: string, url: string }\r\n\r\n// Файл результата; data в base64\r\nexport interface RunnerResultFileBody {\r\n name: string\r\n title: string\r\n contentType: string\r\n data: string\r\n}\r\n\r\n// Агент передаёт задачу на другую доску; wait: эта задача ждёт связанную на своём шаге\r\nexport interface RunnerHandOverBody {\r\n board: string\r\n title: string\r\n note: string\r\n wait: boolean\r\n}\r\n\r\n// Ответ уходит агенту как результат инструмента\r\nexport interface RunnerToolReply {\r\n text: string\r\n}\r\n\r\nexport const MAX_RESULT_FILE_BYTES = 10 * 1024 * 1024\r\n\r\n// Файл результата задачи для проверки; data в base64\r\nexport interface RunnerResultFile {\r\n name: string\r\n contentType: string\r\n data: string\r\n}\r\n\r\nexport interface RunnerComputer {\r\n name: string\r\n ownerName: string\r\n platform: string\r\n}\r\n\r\n// checkedAt: когда агент последний раз нашёл заметку верной (ISO), null: ещё не проверяли\r\nexport interface RunnerKnowledgeEntry {\r\n id: number\r\n title: string\r\n text: string\r\n checkedAt: string | null\r\n}\r\n\r\nexport interface RunnerKnowledgeTitle {\r\n id: number\r\n title: string\r\n scope: 'project' | 'computer'\r\n}\r\n\r\n// Знания проекта и этого компьютера, самые свежие первыми; что не вошло по размеру, идёт одними названиями\r\nexport interface RunnerKnowledge {\r\n project: RunnerKnowledgeEntry[]\r\n computer: RunnerKnowledgeEntry[]\r\n more: RunnerKnowledgeTitle[]\r\n}\r\n\r\n// Заметка, которую агент предлагает сохранить; question: вопрос, из ответа на который она взята, null: агент нашёл её сам\r\nexport interface RunnerKnowledgeProposal {\r\n title: string\r\n text: string\r\n scope: 'project' | 'computer'\r\n question: number | null\r\n}\r\n\r\n// stillTrue: агент опирался на заметку и она верна; иначе text: новый текст, evidence: что показал код\r\nexport interface RunnerKnowledgeUpdate {\r\n stillTrue: boolean\r\n text: string\r\n evidence: string\r\n}\r\n\r\nexport interface RunnerKnowledgeReply {\r\n id: number\r\n}\r\n\r\n// Человек задачи: relation (assignee, owner, step owner, watcher), access (роль доступа в проекте),\r\n// roles: роли в команде строками «название: описание», по ним агент выбирает, кого спросить о теме\r\nexport interface RunnerPerson {\r\n id: number\r\n name: string\r\n relation: string\r\n access: string\r\n roles: string[]\r\n}\r\n\r\n// Что агент пробовал перед вопросом: шаг или команда, точная ошибка и что вышло\r\nexport interface RunnerAskTry {\r\n did: string\r\n error: string | null\r\n result: string\r\n}\r\n\r\nexport interface RunnerAskOption {\r\n label: string\r\n detail: string\r\n}\r\n\r\n// Вопрос человеку задачи. blocking: запуск ждёт ответа и освобождает компьютер; иначе агент идёт дальше на допущении assumption\r\nexport interface RunnerAskBody {\r\n to: number\r\n why: string\r\n question: string\r\n context: string\r\n tried: RunnerAskTry[]\r\n options: RunnerAskOption[]\r\n blocking: boolean\r\n assumption: string | null\r\n}\r\n\r\nexport interface RunnerAskReply {\r\n id: number\r\n name: string\r\n}\r\n\r\n// Действие, о котором Claude Code спросил бы человека: инструмент, точная команда или цель, что агент сказал о нём перед этим.\r\n// folder: папка задачи на компьютере; outsideFolder: Claude Code сообщил путь за ней\r\nexport interface RunnerPermissionBody {\r\n tool: string\r\n action: string\r\n title: string\r\n reason: string\r\n folder: string\r\n outsideFolder: boolean\r\n}\r\n\r\n// allow и deny: решено правилами проекта или человеком, message уходит Claude; wait: спрашивать снова, но не дольше waitUntil\r\nexport interface RunnerPermissionReply {\r\n id: number\r\n decision: 'allow' | 'deny' | 'wait'\r\n message: string\r\n waitUntil: string\r\n}\r\n\r\n// outcome: pass (готово), problems (нашлись проблемы), ask (спросить другой шаг)\r\nexport type RunnerHandoffOutcome = 'pass' | 'problems' | 'ask'\r\n\r\nexport interface RunnerHandoffTarget {\r\n columnId: number\r\n name: string\r\n role: string | null\r\n outcome: RunnerHandoffOutcome\r\n label: string\r\n}\r\n\r\n// Передача, которую выбрал агент: куда, с каким итогом и с какой запиской для следующего шага и владельца\r\nexport interface RunnerHandoff {\r\n toColumnId: number | null\r\n outcome: RunnerHandoffOutcome\r\n note: string\r\n}\r\n\r\nexport interface RunnerQueuedMessage {\r\n id: number\r\n text: string\r\n authorName: string\r\n}\r\n\r\n// Сообщение человека во время работы: steer сразу в идущий ход, queue когда ход закончится\r\nexport interface RunnerInboxMessage extends RunnerQueuedMessage {\r\n mode: 'steer' | 'queue'\r\n}\r\n\r\n// ack: сообщения, которые программа уже передала Claude\r\nexport interface RunnerInboxBody {\r\n ack: number[]\r\n}\r\n\r\n// Человек остановил одного помощника: ref его вызова в журнале, name: вид помощника, byName: кто остановил\r\nexport interface RunnerHelperStop {\r\n ref: string\r\n name: string\r\n byName: string\r\n}\r\n\r\n// keepGoing: false, если запуск закрыт на сайте (остановили или сочли пропавшим);\r\n// model и effort: смена модели, которую человек выбрал по ходу работы;\r\n// helperStops: помощники, которых люди остановили за весь запуск; старый сайт их не присылает\r\nexport interface RunnerInbox {\r\n keepGoing: boolean\r\n messages: RunnerInboxMessage[]\r\n model: string | null\r\n effort: RunnerEffort | null\r\n helperStops?: RunnerHelperStop[]\r\n}\r\n\r\nexport const RUNNER_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'] as const\r\nexport type RunnerEffort = typeof RUNNER_EFFORTS[number]\r\n\r\n// Чей Claude: подписка человека или ключ API; null, если Claude Code не сказал\r\nexport type RunnerAccount = 'subscription' | 'api_key'\r\n\r\nexport interface RunnerModel {\r\n value: string\r\n displayName: string\r\n description: string\r\n}\r\n\r\n// Что умеет Claude на этом компьютере: программа сообщает при запуске и потом раз в полчаса\r\nexport interface RunnerCapabilities {\r\n account: RunnerAccount | null\r\n claudeVersion: string | null\r\n models: RunnerModel[]\r\n}\r\n\r\n// Какая модель на самом деле начала работу: приходит, как только Claude Code запустился\r\nexport interface RunnerSession {\r\n model: string | null\r\n account: RunnerAccount | null\r\n claudeVersion: string | null\r\n}\r\n\r\n// Запись журнала: что Claude думает, пишет и делает. ref связывает инструмент с его результатом,\r\n// parent: запись сделал помощник, которого Claude позвал этим инструментом.\r\n// У итога остановленного помощника tool равен HELPER_STOPPED, у остальных результатов null\r\nexport const HELPER_STOPPED = 'stopped'\r\n\r\nexport type RunnerFeedKind = 'thinking' | 'text' | 'tool' | 'result' | 'skill' | 'agent'\r\n\r\nexport interface RunnerFeedEntry {\r\n seq: number\r\n kind: RunnerFeedKind\r\n text: string\r\n tool: string | null\r\n ref: string | null\r\n parent: string | null\r\n failed: boolean\r\n}\r\n\r\nexport interface RunnerFeedBody {\r\n entries: RunnerFeedEntry[]\r\n}\r\n\r\nexport const ATTACHMENT_TYPES = ['image/png', 'image/jpeg', 'image/webp'] as const\r\nexport type RunnerAttachmentType = typeof ATTACHMENT_TYPES[number]\r\nexport const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024\r\nexport const MAX_ATTACHMENTS_PER_RUN = 20\r\n\r\n// Скриншот, который Claude прикладывает к задаче; data в base64. title: что на нём, purpose: зачем он сделан\r\nexport interface RunnerAttachmentBody {\r\n name: string\r\n title: string\r\n purpose: string\r\n contentType: RunnerAttachmentType\r\n data: string\r\n}\r\n\r\nexport const VIDEO_TYPES = ['video/webm', 'video/mp4'] as const\r\nexport type RunnerVideoType = typeof VIDEO_TYPES[number]\r\n// сайт принимает видео до 200 МБ, если у него не настроено другое\r\nexport const MAX_VIDEO_BYTES = 200 * 1024 * 1024\r\n\r\nexport interface RunnerRecordingMark {\r\n at: number\r\n label: string\r\n}\r\n\r\n// Запись браузера или видео уже лежит на сайте по своему upload: этим запросом она появляется в задаче\r\nexport interface RunnerRecordingBody {\r\n kind: 'replay' | 'video'\r\n upload: string\r\n name: string\r\n title: string\r\n purpose: string\r\n contentType: string\r\n durationMs: number | null\r\n marks: RunnerRecordingMark[]\r\n}\r\n\r\nexport interface RunnerAttachmentResponse {\r\n id: number\r\n}\r\n\r\nexport interface RunnerNextResponse {\r\n job: RunnerJob | null\r\n}\r\n\r\nexport interface RunnerGitTokenResponse {\r\n token: string\r\n expiresAt: string\r\n}\r\n\r\nexport interface RunnerHeartbeatResponse {\r\n keepGoing: boolean\r\n}\r\n\r\n// local: ветка в папке на компьютере; у веток, отправленных на GitHub ключом TAMsense, его нет\r\nexport interface RunnerPushedBranch {\r\n repository: string\r\n branch: string\r\n sha: string\r\n local?: RunnerLocalBranch\r\n}\r\n\r\n// Ветка задачи в папке: сколько в ней коммитов поверх основной ветки, куда она отправлена своим входом человека\r\n// (remoteUrl null: осталась только в папке) и почему не отправилась\r\nexport interface RunnerLocalBranch {\r\n folder: string\r\n baseBranch: string\r\n commits: number\r\n remoteUrl: string | null\r\n pushProblem: string | null\r\n}\r\n\r\n// Папка, которую человек добавил на доску для этого компьютера: программа проверяет, что это репозиторий git\r\nexport interface RunnerFolderCheck {\r\n id: number\r\n path: string\r\n}\r\n\r\nexport interface RunnerFolderChecksResponse {\r\n checks: RunnerFolderCheck[]\r\n}\r\n\r\n// Что программа нашла в папке; адрес удалённого репозитория без логина и пароля\r\nexport type RunnerFolderReport\r\n = | { ok: true, repository: string, currentBranch: string | null, defaultBranch: string, remoteUrl: string | null }\r\n | { ok: false, problem: string }\r\n\r\nexport interface RunnerUsage {\r\n inputTokens: number\r\n cachedInputTokens: number\r\n outputTokens: number\r\n totalTokens: number\r\n}\r\n\r\n// Вердикт сдаёт Claude на шагах проверки: прошло или нет и почему.\r\n// couldNotRun: проверка не запустилась из-за компьютера, задача ждёт человека на этом шаге\r\nexport interface RunnerVerdict {\r\n passed: boolean\r\n report: string\r\n couldNotRun?: boolean\r\n}\r\n\r\n// Вопросы с брифом, которые Claude сдал инструментом submit_questions; форму проверяет сайт\r\nexport interface RunnerQuestions {\r\n brief: unknown\r\n questions: unknown[]\r\n}\r\n\r\n// ТЗ, которое Claude сдал инструментом submit_spec, и замечания проверки, которые в нём остались\r\nexport interface RunnerSpecResult {\r\n spec: unknown\r\n problems: string[]\r\n}\r\n\r\n// Проверка на сайте теми же правилами, что в облаке: вопросы раунда или ТЗ; repairs: сколько раз ТЗ уже возвращали\r\nexport type RunnerReviewBody = { questions: RunnerQuestions } | { spec: unknown, repairs: number }\r\n\r\n// accepted: можно сдавать; иначе feedback уходит Claude, и он присылает исправленное\r\nexport interface RunnerReviewReply {\r\n accepted: boolean\r\n feedback: string\r\n problems: string[]\r\n}\r\n\r\nexport interface RunnerFinishBody {\r\n outcome: 'succeeded' | 'failed'\r\n report: string\r\n pushed: RunnerPushedBranch[]\r\n model: string | null\r\n usage: RunnerUsage | null\r\n verdict: RunnerVerdict | null\r\n // передача, которую выбрал агент инструментом hand_off; null: по правилам доски\r\n handoff: RunnerHandoff | null\r\n // сеанс Claude Code, чтобы следующий запуск шага продолжил разговор\r\n sessionId: string | null\r\n // Claude упёрся в лимит подписки: когда можно повторить (ISO); null, если не упёрся\r\n pausedUntil: string | null\r\n // итог шага ТЗ: вопросы раунда или ТЗ; null, если Claude их не сдал\r\n questions: RunnerQuestions | null\r\n spec: RunnerSpecResult | null\r\n // вопрос, без ответа на который агент не может продолжить: запуск ждёт человека; null, если не ждёт\r\n waitingFor: number | null\r\n}\r\n\r\nexport const PAIRINGS_PATH = '/api/runner/pairings'\r\nexport const PAIRING_TOKEN_PATH = '/api/runner/pairings/token'\r\nexport const IDENTITY_PATH = '/api/runner/me'\r\nexport const NEXT_RUN_PATH = '/api/runner/runs/next'\r\n\r\nexport const CAPABILITIES_PATH = '/api/runner/me/capabilities'\r\n\r\nexport const FOLDER_CHECKS_PATH = '/api/runner/folders/checks'\r\n\r\nexport function folderCheckPath(folderId: number): string {\r\n return `/api/runner/folders/${folderId}`\r\n}\r\n\r\nexport function runPath(runId: number, action: 'git-token' | 'heartbeat' | 'finish' | 'feed' | 'attachments' | 'recordings' | 'session' | 'inbox' | 'review' | 'questions' | 'permissions' | 'knowledge' | 'results' | 'result-files' | 'hand-over'): string {\r\n return `/api/runner/runs/${runId}/${action}`\r\n}\r\n\r\n// Файл результата задачи: проверка кладёт его в папку results\r\nexport function resultFilePath(runId: number, resultId: number): string {\r\n return `/api/runner/runs/${runId}/results/${resultId}`\r\n}\r\n\r\n// Проверка или правка одной заметки знаний\r\nexport function knowledgePath(runId: number, entryId: number): string {\r\n return `/api/runner/runs/${runId}/knowledge/${entryId}`\r\n}\r\n\r\n// Решён ли запрос на действие, которого ждёт вызов Claude\r\nexport function permissionPath(runId: number, permissionId: number): string {\r\n return `/api/runner/runs/${runId}/permissions/${permissionId}`\r\n}\r\n\r\n// Куски записи и тело видео идут отдельными запросами под одним upload, потом запись появляется в задаче через recordings\r\nexport function recordingUploadPath(runId: number, upload: string, part: 'chunks' | 'video'): string {\r\n return `/api/runner/runs/${runId}/recordings/${upload}/${part}`\r\n}\r\n","import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerFeedEntry } from './protocol'\r\nimport { activityLine } from './claudeResult'\r\nimport { HELPER_STOPPED } from './protocol'\r\n\r\nexport type FeedDraft = Omit<RunnerFeedEntry, 'seq'>\r\n\r\nconst MAX_TEXT_LENGTH = 8_000\r\nconst MAX_RESULT_LENGTH = 2_000\r\nconst MAX_SUMMARY_LENGTH = 500\r\nconst HELPER_TOOLS = new Set(['Task', 'Agent'])\r\nconst MCP_PREFIX = /^mcp__[^_]+(?:_[^_]+)*__/\r\nconst ATTACH_LABELS: Record<string, string> = { attach_screenshot: 'Screenshot', attach_replay: 'Replay', attach_video: 'Video' }\r\n\r\ninterface ContentBlock {\r\n type: string\r\n text?: string\r\n thinking?: string\r\n id?: string\r\n name?: string\r\n input?: unknown\r\n tool_use_id?: string\r\n content?: unknown\r\n is_error?: boolean\r\n}\r\n\r\nfunction clip(text: string, max: number): string {\r\n const trimmed = text.trim()\r\n return trimmed.length > max ? `${trimmed.slice(0, max - 1)}…` : trimmed\r\n}\r\n\r\nfunction fieldsOf(input: unknown): Record<string, unknown> {\r\n return typeof input === 'object' && input !== null ? input as Record<string, unknown> : {}\r\n}\r\n\r\nfunction textField(fields: Record<string, unknown>, name: string): string | null {\r\n const value = fields[name]\r\n return typeof value === 'string' && value.trim() ? value : null\r\n}\r\n\r\n// Вызов инструмента одной строкой: команда, файл, навык, помощник или свой инструмент TAMsense\r\nfunction toolDraft(block: ContentBlock, parent: string | null): FeedDraft {\r\n const tool = (block.name ?? 'tool').replace(MCP_PREFIX, '')\r\n const fields = fieldsOf(block.input)\r\n const base = { tool, ref: block.id ?? null, parent, failed: false }\r\n if (tool === 'Skill' && textField(fields, 'skill')) {\r\n return { ...base, kind: 'skill', text: clip(textField(fields, 'skill')!, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (HELPER_TOOLS.has(tool)) {\r\n const who = textField(fields, 'subagent_type') ?? 'general-purpose'\r\n const what = textField(fields, 'description')\r\n return { ...base, kind: 'agent', text: clip(what ? `${who}: ${what}` : who, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (tool === 'submit_verdict') {\r\n const verdict = fields.couldNotRun === true ? 'Could not run' : fields.passed === true ? 'Passed' : 'Did not pass'\r\n return { ...base, kind: 'tool', text: clip(`${verdict}. ${textField(fields, 'report') ?? ''}`, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (tool === 'hand_off') {\r\n return { ...base, kind: 'tool', text: clip(`Hand off to ${textField(fields, 'to') ?? '?'}: ${textField(fields, 'note') ?? ''}`, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (tool in ATTACH_LABELS) {\r\n return { ...base, kind: 'tool', text: clip(`${ATTACH_LABELS[tool]}: ${textField(fields, 'title') ?? textField(fields, 'path') ?? ''}`, MAX_SUMMARY_LENGTH) }\r\n }\r\n const command = textField(fields, 'command')\r\n const summary = tool === 'Bash' && command ? `$ ${command}` : activityLine(tool, block.input)\r\n return { ...base, kind: 'tool', text: clip(summary, MAX_SUMMARY_LENGTH) }\r\n}\r\n\r\nfunction resultText(content: unknown): string {\r\n if (typeof content === 'string') {\r\n return content\r\n }\r\n if (!Array.isArray(content)) {\r\n return ''\r\n }\r\n return content\r\n .map((part: ContentBlock) => part.type === 'text' ? part.text ?? '' : part.type === 'image' ? '[image]' : '')\r\n .filter(Boolean)\r\n .join('\\n')\r\n}\r\n\r\nexport type HelperEnd = 'completed' | 'failed' | 'stopped'\r\n\r\n// Итог помощника по его концу: ответ, ошибка или остановка; у остановленного ответа нет, есть пометка\r\nexport function helperEndDraft(ref: string, parent: string | null, end: HelperEnd, text: string): FeedDraft {\r\n const stopped = end === 'stopped'\r\n return { kind: 'result', text: stopped ? '' : clip(text, MAX_RESULT_LENGTH), tool: stopped ? HELPER_STOPPED : null, ref, parent, failed: end === 'failed' }\r\n}\r\n\r\n// Сообщение Claude Code превращается в записи журнала; служебные сообщения и итог запуска в журнал не идут\r\nexport function feedOf(message: SDKMessage): FeedDraft[] {\r\n if (message.type !== 'assistant' && message.type !== 'user') {\r\n return []\r\n }\r\n const parent = message.parent_tool_use_id ?? null\r\n const content = message.message.content\r\n if (!Array.isArray(content)) {\r\n return []\r\n }\r\n const drafts: FeedDraft[] = []\r\n for (const block of content as ContentBlock[]) {\r\n if (message.type === 'assistant' && block.type === 'thinking' && block.thinking?.trim()) {\r\n drafts.push({ kind: 'thinking', text: clip(block.thinking, MAX_TEXT_LENGTH), tool: null, ref: null, parent, failed: false })\r\n }\r\n else if (message.type === 'assistant' && block.type === 'text' && block.text?.trim()) {\r\n drafts.push({ kind: 'text', text: clip(block.text, MAX_TEXT_LENGTH), tool: null, ref: null, parent, failed: false })\r\n }\r\n else if (message.type === 'assistant' && block.type === 'tool_use') {\r\n drafts.push(toolDraft(block, parent))\r\n }\r\n else if (message.type === 'user' && block.type === 'tool_result') {\r\n drafts.push({ kind: 'result', text: clip(resultText(block.content), MAX_RESULT_LENGTH), tool: null, ref: block.tool_use_id ?? null, parent, failed: block.is_error === true })\r\n }\r\n }\r\n return drafts\r\n}\r\n\r\nconst CONSOLE_LABELS: Partial<Record<FeedDraft['kind'], string>> = { tool: '', skill: 'Skill ', agent: 'Agent ' }\r\nconst MAX_CONSOLE_LENGTH = 120\r\n\r\n// В окне программы только действия Claude, его размышления и ответы видны на сайте\r\nexport function consoleLine(draft: FeedDraft): string | null {\r\n const label = CONSOLE_LABELS[draft.kind]\r\n return label === undefined ? null : clip(`${label}${draft.text}`.replace(/\\s+/g, ' '), MAX_CONSOLE_LENGTH)\r\n}\r\n\r\n// Служебные вызовы Claude Code человеку ничего не говорят: ни они, ни их результаты в журнал не идут\r\nconst HIDDEN_TOOLS = new Set(['ToolSearch', 'SubagentHandback'])\r\n\r\nexport function hiddenStepsFilter(): (draft: FeedDraft) => boolean {\r\n const hidden = new Set<string>()\r\n return (draft) => {\r\n if (draft.kind === 'tool' && draft.tool && HIDDEN_TOOLS.has(draft.tool)) {\r\n if (draft.ref) {\r\n hidden.add(draft.ref)\r\n }\r\n return false\r\n }\r\n return !(draft.kind === 'result' && draft.ref && hidden.has(draft.ref))\r\n }\r\n}\r\n","import type { RunnerHandoff, RunnerHandoffOutcome, RunnerHandoffTarget } from './protocol'\r\n\r\nexport const HANDOFF_OUTCOMES = ['pass', 'problems', 'ask'] as const satisfies readonly RunnerHandoffOutcome[]\r\n\r\nexport interface HandoffRequest {\r\n to: string\r\n outcome: RunnerHandoffOutcome\r\n note: string\r\n}\r\n\r\nexport type HandoffChoice = { handoff: RunnerHandoff } | { error: string }\r\n\r\nexport function handoffTargetLine(target: RunnerHandoffTarget): string {\r\n return target.label ? `${target.name} (${target.outcome}: ${target.label})` : `${target.name} (${target.outcome})`\r\n}\r\n\r\nfunction validTargets(targets: RunnerHandoffTarget[]): string {\r\n return `Valid targets: ${targets.map(handoffTargetLine).join(', ')}.`\r\n}\r\n\r\n// Шаг ищется по имени без учёта регистра или по номеру; передать можно только по правилу доски с этим итогом\r\nexport function chooseHandoff(targets: RunnerHandoffTarget[], request: HandoffRequest): HandoffChoice {\r\n const to = request.to.trim()\r\n const note = request.note.trim()\r\n if (!note) {\r\n return { error: 'Write a note for the next agent and the owner: what was done, what to check and what is open.' }\r\n }\r\n const matching = targets.filter(target => target.name.toLowerCase() === to.toLowerCase() || String(target.columnId) === to)\r\n if (matching.length === 0) {\r\n return { error: `\"${to}\" is not a step this task can be handed to. ${validTargets(targets)}` }\r\n }\r\n const edge = matching.find(target => target.outcome === request.outcome)\r\n if (!edge) {\r\n return { error: `${matching[0]!.name} does not take the outcome \"${request.outcome}\". ${validTargets(targets)}` }\r\n }\r\n return { handoff: { toColumnId: edge.columnId, outcome: edge.outcome, note } }\r\n}\r\n","import type { Query, SDKMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { FeedDraft } from './feed'\r\nimport type { RunnerHelperStop } from './protocol'\r\nimport { helperEndDraft } from './feed'\r\n\r\n// Помощник глазами Claude Code: номер задачи для остановки, работает ли он в фоне, остановлен ли и записан ли его итог\r\ninterface HelperTask {\r\n taskId: string\r\n background: boolean\r\n stopped: boolean\r\n over: boolean\r\n}\r\n\r\n// stopped: помощник остановлен; finished: он уже кончил работу; unknown: Claude Code не сообщил о нём, остановить его можно только просьбой\r\nexport type HelperStopResult = 'stopped' | 'finished' | 'unknown'\r\n\r\nexport interface HelperTasks {\r\n // записи журнала по служебному сообщению: итог помощника, который работал в фоне\r\n observe: (message: SDKMessage) => FeedDraft[]\r\n // запись журнала по ходу Claude: заглушка фонового помощника пропадает, итог остановленного помечается остановкой\r\n adjust: (draft: FeedDraft) => FeedDraft[]\r\n stop: (target: Pick<Query, 'stopTask'>, ref: string) => Promise<HelperStopResult>\r\n}\r\n\r\n// Claude Code сообщает о помощниках задачами: начало с номером вызова, уход в фон и конец.\r\n// Помощник в фоне сразу отвечает заглушкой «запущен», а настоящий итог приходит уведомлением о конце задачи.\r\n// Остановленный помощник отвечает «прервано»: такой итог в журнале остановка, а не ошибка\r\nexport function helperTasks(): HelperTasks {\r\n const byRef = new Map<string, HelperTask>()\r\n const refByTask = new Map<string, string>()\r\n const parents = new Map<string, string | null>()\r\n\r\n function observe(message: SDKMessage): FeedDraft[] {\r\n if (message.type !== 'system') {\r\n return []\r\n }\r\n if (message.subtype === 'task_started' && message.task_type === 'local_agent' && message.tool_use_id) {\r\n byRef.set(message.tool_use_id, { taskId: message.task_id, background: message.is_backgrounded === true, stopped: false, over: false })\r\n refByTask.set(message.task_id, message.tool_use_id)\r\n return []\r\n }\r\n if (message.subtype === 'task_updated' && message.patch.is_backgrounded !== undefined) {\r\n const ref = refByTask.get(message.task_id)\r\n const task = ref ? byRef.get(ref) : undefined\r\n if (task) {\r\n task.background = message.patch.is_backgrounded\r\n }\r\n return []\r\n }\r\n if (message.subtype !== 'task_notification') {\r\n return []\r\n }\r\n const ref = message.tool_use_id ?? refByTask.get(message.task_id)\r\n const task = ref ? byRef.get(ref) : undefined\r\n if (!ref || !task || task.over) {\r\n return []\r\n }\r\n task.stopped = message.status === 'stopped'\r\n if (!task.background) {\r\n return []\r\n }\r\n task.over = true\r\n return [helperEndDraft(ref, parents.get(ref) ?? null, message.status, message.summary)]\r\n }\r\n\r\n function adjust(draft: FeedDraft): FeedDraft[] {\r\n if (draft.kind === 'agent' && draft.ref) {\r\n parents.set(draft.ref, draft.parent)\r\n }\r\n const task = draft.kind === 'result' && draft.ref ? byRef.get(draft.ref) : undefined\r\n if (!task || !draft.ref) {\r\n return [draft]\r\n }\r\n if (task.background) {\r\n return []\r\n }\r\n task.over = true\r\n return task.stopped ? [helperEndDraft(draft.ref, draft.parent, 'stopped', '')] : [draft]\r\n }\r\n\r\n async function stop(target: Pick<Query, 'stopTask'>, ref: string): Promise<HelperStopResult> {\r\n const task = byRef.get(ref)\r\n if (!task) {\r\n return 'unknown'\r\n }\r\n if (task.over || task.stopped) {\r\n return 'finished'\r\n }\r\n await target.stopTask(task.taskId)\r\n return 'stopped'\r\n }\r\n\r\n return { observe, adjust, stop }\r\n}\r\n\r\n// Claude узнаёт, кто остановил помощника, и идёт дальше без него\r\nexport function helperStoppedNote(stop: RunnerHelperStop): string {\r\n return `${stop.byName} stopped your helper ${stop.name} on TAMsense. Go on without it and say in your result what it did not finish. Start it again only if a person asks.`\r\n}\r\n\r\n// Claude Code не сообщил о помощнике как о задаче: остановить его может только сам Claude\r\nexport function helperStopRequest(stop: RunnerHelperStop): string {\r\n return `${stop.byName} asked on TAMsense to stop your helper ${stop.name}. Stop it now, go on without it and say in your result what it did not finish. Start it again only if a person asks.`\r\n}\r\n","import type { SDKMessage, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { HelperStopResult } from './helpers'\r\nimport type { RunnerEffort, RunnerInboxMessage } from './protocol'\r\n\r\n// Смена модели по ходу работы; model 'default' значит вернуться к модели Claude Code на компьютере\r\nexport interface ModelSwitch {\r\n model: string | null\r\n effort: RunnerEffort | null\r\n}\r\n\r\nexport const DEFAULT_MODEL = 'default'\r\n\r\n// Что видит запуск Claude: первое сообщение, ходы Claude, смена модели и остановка одного помощника\r\nexport interface AgentInput {\r\n start: (prompt: string) => AsyncIterable<SDKUserMessage>\r\n observe: (message: SDKMessage) => void\r\n onSwitch: (apply: (change: ModelSwitch) => Promise<void>) => void\r\n onStopHelper: (stop: (ref: string) => Promise<HelperStopResult>) => void\r\n close: () => void\r\n}\r\n\r\n// Что видит программа: сообщения людей, проверка входящих и что уже передано Claude.\r\n// note: сообщение от самой программы, сайт о нём не знает; stopHelper: unknown, пока Claude не начал работу\r\nexport interface InputChannel extends AgentInput {\r\n readonly closed: boolean\r\n push: (message: RunnerInboxMessage) => boolean\r\n note: (text: string, mode: RunnerInboxMessage['mode']) => boolean\r\n stopHelper: (ref: string) => Promise<HelperStopResult>\r\n checking: (work: () => Promise<void>) => Promise<void>\r\n takeDelivered: () => number[]\r\n restoreDelivered: (ids: number[]) => void\r\n hasDelivered: () => boolean\r\n switchModel: (change: ModelSwitch) => Promise<boolean>\r\n}\r\n\r\n// onIdle: Claude закончил ход, пора проверить, не написали ли ему ещё;\r\n// quietMs: сколько после хода ждать, не начнёт ли Claude Code следующий сам;\r\n// idleLimitMs: сколько ждать проверки после хода, прежде чем закрыть вход в любом случае\r\nexport interface InputOptions {\r\n onIdle?: () => void\r\n quietMs?: number\r\n idleLimitMs?: number\r\n}\r\n\r\nconst QUIET_MS = 1_000\r\nconst IDLE_LIMIT_MS = 30_000\r\n\r\ninterface Queued {\r\n ids: number[]\r\n message: SDKUserMessage\r\n}\r\n\r\nfunction userMessage(text: string): SDKUserMessage {\r\n return { type: 'user', message: { role: 'user', content: text }, parent_tool_use_id: null }\r\n}\r\n\r\nfunction humanText(message: RunnerInboxMessage): string {\r\n return `Message from ${message.authorName}: ${message.text}`\r\n}\r\n\r\nfunction priorityOf(mode: RunnerInboxMessage['mode']): SDKUserMessage['priority'] {\r\n return mode === 'steer' ? 'now' : 'next'\r\n}\r\n\r\n// steer: now прерывает текущий шаг Claude и начинает новый ход с сообщением; queue: next доходит на ближайшей границе шагов\r\nexport function humanMessage(message: RunnerInboxMessage): SDKUserMessage {\r\n return { ...userMessage(humanText(message)), priority: priorityOf(message.mode), client_composed: true }\r\n}\r\n\r\nexport function switchLine(change: ModelSwitch): string {\r\n const model = change.model === DEFAULT_MODEL ? 'the default model' : change.model\r\n const effort = change.effort && `${change.effort} effort`\r\n return `Switched to ${[model, effort].filter(Boolean).join(', ')}`\r\n}\r\n\r\n// Признаки того, что Claude Code работает над ходом: после итога они значат, что начался следующий\r\nfunction isActivity(message: SDKMessage): boolean {\r\n return message.type === 'assistant' || message.type === 'user' || (message.type === 'system' && message.subtype === 'init')\r\n}\r\n\r\n// Вход закрывается, когда Claude закончил ход, ждать нечего и проверка после хода прошла:\r\n// тогда запуск кончается как раньше, а сообщение, пришедшее под конец, ещё успевает дойти\r\nexport function inputChannel(options: InputOptions = {}): InputChannel {\r\n const queue: Queued[] = []\r\n const seen = new Set<number>()\r\n let delivered: number[] = []\r\n let started = false\r\n // первое сообщение запуска и номер потока, который сейчас читает Claude Code\r\n let opening = ''\r\n let generation = 0\r\n let closed = false\r\n // ход кончился, и с тех пор ничего не началось\r\n let idle = false\r\n // Claude Code уже отозвался на текущий ход\r\n let active = false\r\n // сколько итогов прерванных ходов ещё придёт: steer прерывает ход, и тот кончается своим итогом\r\n let interrupted = 0\r\n let drained = false\r\n let quiet = false\r\n let idleEpoch = 0\r\n let inFlight = 0\r\n let statesSeen = false\r\n let wake: (() => void) | null = null\r\n let timers: Array<ReturnType<typeof setTimeout>> = []\r\n let applySwitch: ((change: ModelSwitch) => Promise<void>) | null = null\r\n let applyStop: ((ref: string) => Promise<HelperStopResult>) | null = null\r\n let waitingSwitch: { change: ModelSwitch, resolve: (applied: boolean) => void } | null = null\r\n\r\n function wakeUp(): void {\r\n wake?.()\r\n wake = null\r\n }\r\n\r\n function clearTimers(): void {\r\n timers.forEach(clearTimeout)\r\n timers = []\r\n }\r\n\r\n function close(): void {\r\n if (closed) {\r\n return\r\n }\r\n closed = true\r\n clearTimers()\r\n waitingSwitch?.resolve(false)\r\n waitingSwitch = null\r\n wakeUp()\r\n }\r\n\r\n function maybeClose(): void {\r\n if (started && idle && drained && quiet && queue.length === 0 && inFlight === 0) {\r\n close()\r\n }\r\n }\r\n\r\n function turnStarted(): void {\r\n idle = false\r\n clearTimers()\r\n }\r\n\r\n function turnEnded(): void {\r\n if (closed || idle) {\r\n return\r\n }\r\n idle = true\r\n idleEpoch += 1\r\n drained = !options.onIdle\r\n quiet = false\r\n timers = [\r\n setTimeout(() => {\r\n quiet = true\r\n maybeClose()\r\n }, options.quietMs ?? QUIET_MS),\r\n setTimeout(close, options.idleLimitMs ?? IDLE_LIMIT_MS),\r\n ]\r\n options.onIdle?.()\r\n }\r\n\r\n async function* stream(own: number): AsyncGenerator<SDKUserMessage> {\r\n for (;;) {\r\n if (closed || own !== generation) {\r\n return\r\n }\r\n const next = queue.shift()\r\n if (!next) {\r\n await new Promise<void>((resolve) => {\r\n wake = resolve\r\n })\r\n continue\r\n }\r\n if (active && !idle && next.message.priority === 'now') {\r\n interrupted += 1\r\n }\r\n turnStarted()\r\n delivered.push(...next.ids)\r\n yield next.message\r\n }\r\n }\r\n\r\n return {\r\n get closed() {\r\n return closed\r\n },\r\n // Сообщения, пришедшие, пока готовился код, идут вместе с задачей.\r\n // Повторный start для нового Claude Code (прошлый сеанс не открылся) шлёт то же первое сообщение снова, а прежний поток кончается\r\n start(prompt) {\r\n if (started) {\r\n queue.unshift({ ids: [], message: userMessage(opening) })\r\n }\r\n else {\r\n started = true\r\n const early = queue.splice(0)\r\n opening = [prompt, ...early.map(item => item.message.message.content as string)].join('\\n\\n')\r\n queue.push({ ids: early.flatMap(item => item.ids), message: userMessage(opening) })\r\n }\r\n generation += 1\r\n wakeUp()\r\n return stream(generation)\r\n },\r\n // Если Claude Code сообщает состояние сессии, верим только ему: idle приходит после фоновых задач\r\n observe(message) {\r\n if (message.type === 'system' && message.subtype === 'session_state_changed') {\r\n statesSeen = true\r\n if (message.state === 'idle') {\r\n turnEnded()\r\n }\r\n else {\r\n turnStarted()\r\n }\r\n return\r\n }\r\n if (statesSeen) {\r\n return\r\n }\r\n if (message.type === 'result') {\r\n active = false\r\n if (interrupted > 0) {\r\n interrupted -= 1\r\n return\r\n }\r\n turnEnded()\r\n }\r\n else if (isActivity(message)) {\r\n active = true\r\n turnStarted()\r\n }\r\n },\r\n onSwitch(apply) {\r\n applySwitch = apply\r\n if (waitingSwitch) {\r\n const { change, resolve } = waitingSwitch\r\n waitingSwitch = null\r\n void apply(change).then(() => resolve(true), () => resolve(false))\r\n }\r\n },\r\n onStopHelper(stop) {\r\n applyStop = stop\r\n },\r\n close,\r\n push(message) {\r\n if (closed || seen.has(message.id)) {\r\n return false\r\n }\r\n seen.add(message.id)\r\n queue.push({ ids: [message.id], message: humanMessage(message) })\r\n wakeUp()\r\n return true\r\n },\r\n note(text, mode) {\r\n if (closed) {\r\n return false\r\n }\r\n queue.push({ ids: [], message: { ...userMessage(text), priority: priorityOf(mode) } })\r\n wakeUp()\r\n return true\r\n },\r\n async stopHelper(ref) {\r\n return closed || !applyStop ? 'unknown' : await applyStop(ref)\r\n },\r\n async checking(work) {\r\n const epoch = idle ? idleEpoch : null\r\n inFlight += 1\r\n try {\r\n await work()\r\n }\r\n finally {\r\n inFlight -= 1\r\n if (idle && epoch === idleEpoch) {\r\n drained = true\r\n }\r\n maybeClose()\r\n }\r\n },\r\n takeDelivered() {\r\n const ids = delivered\r\n delivered = []\r\n return ids\r\n },\r\n restoreDelivered(ids) {\r\n delivered = [...ids, ...delivered]\r\n },\r\n hasDelivered: () => delivered.length > 0,\r\n // До запуска Claude смена ждёт его; после конца работы менять уже нечего\r\n async switchModel(change) {\r\n if (closed) {\r\n return false\r\n }\r\n if (applySwitch) {\r\n await applySwitch(change)\r\n return true\r\n }\r\n const earlier = waitingSwitch\r\n earlier?.resolve(false)\r\n const merged = { model: change.model ?? earlier?.change.model ?? null, effort: change.effort ?? earlier?.change.effort ?? null }\r\n return new Promise<boolean>((resolve) => {\r\n waitingSwitch = { change: merged, resolve }\r\n })\r\n },\r\n }\r\n}\r\n","import type { RunnerComputer, RunnerKnowledge, RunnerKnowledgeEntry, RunnerKnowledgeProposal, RunnerKnowledgeReply, RunnerKnowledgeUpdate } from './protocol'\r\nimport { tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\n\r\nexport const PROPOSE_KNOWLEDGE_TOOL_NAME = 'propose_knowledge'\r\nexport const UPDATE_KNOWLEDGE_TOOL_NAME = 'update_knowledge'\r\nexport const READ_KNOWLEDGE_TOOL_NAME = 'read_knowledge'\r\n\r\n// Как на сайте: заметка короткая, иначе её не прочтут ни люди, ни агенты\r\nexport const MAX_KNOWLEDGE_TITLE = 80\r\nexport const MAX_KNOWLEDGE_TEXT = 600\r\n\r\n// entryIds: заметки, которые этот запуск может прочесть, проверить или поправить; moreIds: те из них, что пришли одними названиями;\r\n// canPropose: агент спрашивает людей и может предложить заметку\r\nexport interface KnowledgeTools {\r\n entryIds: number[]\r\n moreIds: number[]\r\n canPropose: boolean\r\n propose: (body: RunnerKnowledgeProposal) => Promise<RunnerKnowledgeReply>\r\n update: (entryId: number, body: RunnerKnowledgeUpdate) => Promise<RunnerKnowledgeReply>\r\n read: (entryId: number) => Promise<RunnerKnowledgeEntry>\r\n}\r\n\r\nconst UPDATE_RULE = `Rely on this knowledge instead of asking people again. When you relied on an entry and found it true, call ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue true. When the code shows an entry is out of date (a file moved, a command or a port changed), fix it yourself at once: ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue false, the full new text and the evidence (the commit, the file or the command output that shows it). The change applies right away and people can revert it. Never ask a person whether an entry is still right, and never move facts of this computer into project knowledge.`\r\n\r\nconst PROPOSE_RULE = `When a person's answer to your question teaches something later runs need (how the team runs the local stand, where something lives, a convention), propose one short note with ${PROPOSE_KNOWLEDGE_TOOL_NAME} and the question number from the answer. Scope computer is for what is true only on this computer (folders, ports, installed tools, local quirks); scope project is for what comes from the repository and works on every computer. Never propose secrets, personal data or decisions about this one task. A person approves every note; keep working while they decide.`\r\n\r\nfunction dateOf(iso: string | null): string {\r\n return iso ? `checked ${iso.slice(0, 10)}` : 'not checked yet'\r\n}\r\n\r\nfunction entryText(entry: RunnerKnowledgeEntry): string {\r\n return `[${entry.id}] ${entry.title}: ${entry.text.replace(/\\s+/g, ' ').trim()} (${dateOf(entry.checkedAt)})`\r\n}\r\n\r\nfunction entryLine(entry: RunnerKnowledgeEntry): string {\r\n return `- ${entryText(entry)}`\r\n}\r\n\r\n// Где идёт работа: чей это компьютер, папка задачи и какие коммиты в ней. Чужие папки и инструменты агент не трогает\r\nexport function workplaceText(computer: RunnerComputer | null | undefined, folder: string): string | null {\r\n if (!computer) {\r\n return null\r\n }\r\n return `You run on ${computer.name}, the ${computer.platform} computer of ${computer.ownerName}. Your task folder is ${folder}; everything outside it belongs to ${computer.ownerName}.`\r\n}\r\n\r\nfunction hasKnowledge(knowledge: RunnerKnowledge | undefined): knowledge is RunnerKnowledge {\r\n return Boolean(knowledge && (knowledge.project.length > 0 || knowledge.computer.length > 0 || knowledge.more.length > 0))\r\n}\r\n\r\n// Знания проекта и этого компьютера отдельно: агент не путает факты одного компьютера с фактами проекта\r\nexport function knowledgeText(knowledge: RunnerKnowledge | undefined, computer: RunnerComputer | null | undefined, canPropose: boolean): string | null {\r\n const rules = canPropose ? [PROPOSE_RULE] : []\r\n if (!hasKnowledge(knowledge)) {\r\n return rules.length > 0 ? `Knowledge of this project is empty so far. ${rules.join(' ')}` : null\r\n }\r\n const here = computer ? computer.name : 'this computer'\r\n const sections = [\r\n knowledge.project.length > 0 ? `Project knowledge (approved by the team, true on every computer):\\n${knowledge.project.map(entryLine).join('\\n')}` : null,\r\n knowledge.computer.length > 0 ? `Knowledge for ${here} only (true here, not on other computers):\\n${knowledge.computer.map(entryLine).join('\\n')}` : null,\r\n knowledge.more.length > 0 ? `More entries, titles only to keep this short: ${knowledge.more.map(entry => `[${entry.id}] ${entry.title} (${entry.scope})`).join('; ')}. When a title touches your work, read the entry with ${READ_KNOWLEDGE_TOOL_NAME} and its number.` : null,\r\n ]\r\n return [...sections, UPDATE_RULE, ...rules].filter(Boolean).join('\\n\\n')\r\n}\r\n\r\n// Все заметки, которые получил запуск: и целиком, и по названиям\r\nexport function knowledgeIds(knowledge: RunnerKnowledge | undefined): number[] {\r\n return knowledge ? [...knowledge.project, ...knowledge.computer, ...knowledge.more].map(entry => entry.id) : []\r\n}\r\n\r\nfunction failed(text: string) {\r\n return { content: [{ type: 'text' as const, text }], isError: true }\r\n}\r\n\r\nfunction done(text: string) {\r\n return { content: [{ type: 'text' as const, text }] }\r\n}\r\n\r\nfunction unknownEntry(tools: KnowledgeTools, entry: number) {\r\n return failed(`There is no entry ${entry} in this run. Use a number in square brackets from your instructions: ${tools.entryIds.join(', ') || 'none'}.`)\r\n}\r\n\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error)\r\n}\r\n\r\nexport function proposeKnowledgeTool(tools: KnowledgeTools) {\r\n return tool(\r\n PROPOSE_KNOWLEDGE_TOOL_NAME,\r\n 'Propose a short note for the TAMsense project knowledge after a person answered your question, so later runs do not ask again. A person on the task approves it: for the whole project, for this computer only, or not at all.',\r\n {\r\n title: z.string().describe(`a few words, at most ${MAX_KNOWLEDGE_TITLE} characters: \"Local stand and database\"`),\r\n text: z.string().describe(`the fact in one to three plain sentences, at most ${MAX_KNOWLEDGE_TEXT} characters, with exact commands and paths in backticks`),\r\n scope: z.enum(['project', 'computer']).describe('computer: true only on this computer (folders, ports, installed tools); project: comes from the repository and works everywhere'),\r\n question: z.number().optional().describe('the number of the question whose answer this comes from, as in \"(question 31)\"'),\r\n },\r\n async (args) => {\r\n const title = args.title.trim()\r\n const text = args.text.trim()\r\n if (!title || !text || title.length > MAX_KNOWLEDGE_TITLE || text.length > MAX_KNOWLEDGE_TEXT) {\r\n return failed(`Write a title of at most ${MAX_KNOWLEDGE_TITLE} characters and a text of at most ${MAX_KNOWLEDGE_TEXT}.`)\r\n }\r\n try {\r\n await tools.propose({ title, text, scope: args.scope, question: args.question ?? null })\r\n }\r\n catch (error) {\r\n return failed(`The note was not suggested: ${messageOf(error)}`)\r\n }\r\n return done('Suggested. A person on the task decides whether to save it; keep working.')\r\n },\r\n )\r\n}\r\n\r\nexport function updateKnowledgeTool(tools: KnowledgeTools) {\r\n return tool(\r\n UPDATE_KNOWLEDGE_TOOL_NAME,\r\n 'Keep the TAMsense knowledge current. stillTrue true: you relied on the entry and it holds. stillTrue false: the code shows it is out of date; the new text applies at once and people see your evidence.',\r\n {\r\n entry: z.number().describe('the entry number in square brackets from your instructions'),\r\n stillTrue: z.boolean(),\r\n text: z.string().optional().describe(`when stillTrue is false: the full new text, at most ${MAX_KNOWLEDGE_TEXT} characters`),\r\n evidence: z.string().optional().describe('when stillTrue is false: one line on what shows the old text is wrong, such as a commit, a file or a command output'),\r\n },\r\n async (args) => {\r\n if (!tools.entryIds.includes(args.entry)) {\r\n return unknownEntry(tools, args.entry)\r\n }\r\n const text = args.text?.trim() ?? ''\r\n const evidence = args.evidence?.trim() ?? ''\r\n if (!args.stillTrue && (!text || !evidence || text.length > MAX_KNOWLEDGE_TEXT)) {\r\n return failed(`An update needs the full new text (at most ${MAX_KNOWLEDGE_TEXT} characters) and the evidence.`)\r\n }\r\n try {\r\n await tools.update(args.entry, { stillTrue: args.stillTrue, text, evidence })\r\n }\r\n catch (error) {\r\n return failed(`The entry was not updated: ${messageOf(error)}`)\r\n }\r\n return done(args.stillTrue ? 'Marked as checked today.' : 'Updated. People see the old text, your evidence and can revert it.')\r\n },\r\n )\r\n}\r\n\r\n// Заметка, которую запуск видел только по названию: сайт отдаёт её текст и записывает, что агент её прочёл\r\nexport function readKnowledgeTool(tools: KnowledgeTools) {\r\n return tool(\r\n READ_KNOWLEDGE_TOOL_NAME,\r\n 'Read a TAMsense knowledge entry that your instructions list by title only.',\r\n {\r\n entry: z.number().describe('the entry number in square brackets from your instructions'),\r\n },\r\n async (args) => {\r\n if (!tools.entryIds.includes(args.entry)) {\r\n return unknownEntry(tools, args.entry)\r\n }\r\n try {\r\n return done(entryText(await tools.read(args.entry)))\r\n }\r\n catch (error) {\r\n return failed(`The entry was not read: ${messageOf(error)}`)\r\n }\r\n },\r\n )\r\n}\r\n","import type { CanUseTool, PermissionResult } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerPermissionBody, RunnerPermissionReply } from './protocol'\r\n\r\n// send: запрос уходит на сайт; check: решён ли он; sleep: пауза между проверками, прерываемая остановкой запуска\r\nexport interface PermissionTools {\r\n send: (body: RunnerPermissionBody) => Promise<RunnerPermissionReply>\r\n check: (id: number) => Promise<RunnerPermissionReply>\r\n sleep: (ms: number, signal?: AbortSignal) => Promise<void>\r\n now?: () => number\r\n}\r\n\r\nexport const NOBODY_TO_ASK = 'Nobody is at the computer to approve this during a TAMsense run. Do it another way, or explain in your report why it is needed.'\r\nexport const CHECK_INTERVAL_MS = 5_000\r\n// сайт сам отказывает по сроку; запас на случай, если он не отвечает\r\nconst SITE_SILENCE_MS = 60_000\r\nconst MAX_ACTION_LENGTH = 4_000\r\nconst STOPPED_TEXT = 'The run is stopping, so this was not done.'\r\nconst SITE_SILENT_TEXT = 'TAMsense did not answer about this action, so it was not done. Do it another way, or explain in your report why it is needed.'\r\n// вопросы и план Claude задаёт людям не так: для вопроса есть ask_person\r\nconst NEVER_SENT = new Set(['AskUserQuestion', 'ExitPlanMode'])\r\nconst ACTION_FIELDS = ['command', 'file_path', 'notebook_path', 'url', 'query', 'path']\r\n\r\n// Что именно Claude хочет сделать: команда оболочки, путь файла, адрес или входные данные инструмента\r\nexport function actionOf(input: Record<string, unknown>): string {\r\n const field = ACTION_FIELDS.map(key => input[key]).find(value => typeof value === 'string' && value.trim())\r\n return (typeof field === 'string' ? field.trim() : JSON.stringify(input)).slice(0, MAX_ACTION_LENGTH)\r\n}\r\n\r\n// Название действия для людей: пояснение Claude к команде, иначе строка запроса Claude Code\r\nexport function titleOf(input: Record<string, unknown>, options: { title?: string, displayName?: string }): string {\r\n const described = typeof input.description === 'string' ? input.description.trim() : ''\r\n return described || options.title?.trim() || options.displayName?.trim() || ''\r\n}\r\n\r\nfunction deny(message: string): PermissionResult {\r\n return { behavior: 'deny', message }\r\n}\r\n\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error)\r\n}\r\n\r\n// Ждёт решения: сайт отвечает wait, пока решения нет и срок не вышел. Сбой проверки не отказ: следующая проверка повторит\r\nasync function decisionOf(tools: PermissionTools, first: RunnerPermissionReply, signal: AbortSignal): Promise<RunnerPermissionReply | string> {\r\n const now = tools.now ?? Date.now\r\n const giveUpAt = Date.parse(first.waitUntil) + SITE_SILENCE_MS\r\n let reply = first\r\n while (reply.decision === 'wait') {\r\n await tools.sleep(CHECK_INTERVAL_MS, signal)\r\n if (signal.aborted) {\r\n return STOPPED_TEXT\r\n }\r\n if (now() > giveUpAt) {\r\n return SITE_SILENT_TEXT\r\n }\r\n reply = await tools.check(reply.id).catch(() => reply)\r\n }\r\n return reply\r\n}\r\n\r\n// Вместо отказа «спросить некого»: то, о чём Claude Code спросил бы человека, уходит на сайт. Правила проекта решают сразу,\r\n// иначе вызов ждёт решения исполнителя или владельца задачи, а остальная работа идёт. lastWords: последнее, что Claude написал;\r\n// folder: папка задачи, в которой работает Claude\r\nexport function askPermission(tools: PermissionTools, lastWords: () => string, folder: string): CanUseTool {\r\n return async (toolName, input, options) => {\r\n if (NEVER_SENT.has(toolName)) {\r\n return deny(NOBODY_TO_ASK)\r\n }\r\n const body: RunnerPermissionBody = {\r\n tool: toolName,\r\n action: actionOf(input),\r\n title: titleOf(input, options),\r\n reason: lastWords() || options.decisionReason || '',\r\n folder,\r\n outsideFolder: Boolean(options.blockedPath),\r\n }\r\n let first: RunnerPermissionReply\r\n try {\r\n first = await tools.send(body)\r\n }\r\n catch (error) {\r\n return deny(`Could not ask for permission on TAMsense: ${messageOf(error)}. Do it another way, or explain in your report why it is needed.`)\r\n }\r\n const reply = await decisionOf(tools, first, options.signal)\r\n if (typeof reply === 'string') {\r\n return deny(reply)\r\n }\r\n return reply.decision === 'allow' ? { behavior: 'allow', updatedInput: input } : deny(reply.message)\r\n }\r\n}\r\n\r\n// Шаг ТЗ: запросов с него нет, режим auto сам решает, что можно, а остальное отклоняется сразу\r\nexport const denyWhenAsked: CanUseTool = async () => deny(NOBODY_TO_ASK)\r\n","import type { RunnerJob, RunnerPushedBranch, RunnerRepository } from './protocol'\r\nimport type { LocalSource, Workspace, WorkspaceRepository } from './workspace'\r\nimport { existsSync } from 'node:fs'\r\nimport { join, resolve } from 'node:path'\r\nimport { hasRef, isInside, remoteOf, remoteWithoutSecrets } from './folders'\r\nimport { git, personalGitEnv, runGit } from './git'\r\nimport { keyedLock } from './lock'\r\nimport { branchHolders, folderNames, isReadOnlyCheck, removeWorktree, tamsenseFolder, taskFolder } from './workspace'\r\n\r\nexport interface LocalTools {\r\n home: string\r\n print: (line: string) => void\r\n}\r\n\r\n// сеть без ответа не держит работу: без свежих веток задача идёт с тем, что уже есть в папке\r\nconst NETWORK_TIMEOUT_MS = 120_000\r\n\r\n// Папку человека git меняет по очереди: два шага одного репозитория не создают ветки одновременно\r\nconst sourceLock = keyedLock()\r\n\r\nexport function isLocalJob(job: Pick<RunnerJob, 'workspace'>): boolean {\r\n return job.workspace === 'local'\r\n}\r\n\r\n// Где теперь работа шага: ветка в папке человека и, если удалось, на её удалённом репозитории\r\nexport function localBranchLine(branch: RunnerPushedBranch): string {\r\n const local = branch.local\r\n if (!local) {\r\n return `Pushed ${branch.branch} in ${branch.repository}.`\r\n }\r\n const where = `Branch ${branch.branch} has ${local.commits} commit(s) in ${local.folder}`\r\n if (local.pushProblem) {\r\n return `${where}. It was not pushed: ${local.pushProblem}`\r\n }\r\n return local.remoteUrl ? `${where}, pushed to ${local.remoteUrl}.` : `${where}.`\r\n}\r\n\r\n// Вопросы и ТЗ только читают код: ветку задачи они не создают, иначе шаг кода начал бы со старого снимка\r\nfunction readsOnly(job: RunnerJob): boolean {\r\n return isReadOnlyCheck(job) || job.stage.kind === 'interview' || job.stage.kind === 'spec'\r\n}\r\n\r\nasync function isAncestor(folder: string, older: string, newer: string): Promise<boolean> {\r\n return (await runGit(['merge-base', '--is-ancestor', older, newer], { cwd: folder })).code === 0\r\n}\r\n\r\n// Свежие ветки удалённого репозитория папки своим входом человека; без сети или входа работа идёт без них\r\nasync function fetchRemote(source: string, remote: string, print: (line: string) => void): Promise<void> {\r\n const fetched = await runGit(['fetch', '--prune', remote], { cwd: source, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS })\r\n if (fetched.code !== 0) {\r\n print(`Could not fetch from ${remote} in ${source}, going on with what the folder has: ${fetched.stderr || `exit code ${fetched.code}`}`)\r\n }\r\n}\r\n\r\n// Основная ветка: своя ветка папки или та же ветка удалённого, если она новее и своя за ней не ушла вперёд\r\nasync function baseBranchRef(source: string, branch: string, remote: string | null): Promise<string> {\r\n const own = await hasRef(source, `refs/heads/${branch}`) ? branch : null\r\n const fetched = remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null\r\n if (own && fetched) {\r\n return await isAncestor(source, own, fetched) ? fetched : own\r\n }\r\n const found = own ?? fetched\r\n if (!found) {\r\n throw new Error(`The main branch ${branch} is not in ${source}. Check the folder again in the board settings, on the Code tab.`)\r\n }\r\n return found\r\n}\r\n\r\n// Ветка задачи в папке или на её удалённом репозитории; своя ветка без работы поверх основной не в счёт\r\nasync function taskBranchRef(source: string, branch: string, remote: string | null, base: string): Promise<string | null> {\r\n if (await hasRef(source, `refs/heads/${branch}`) && Number(await git(['rev-list', '--count', `${base}..${branch}`], { cwd: source })) > 0) {\r\n return branch\r\n }\r\n return remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null\r\n}\r\n\r\n// Откуда начинается папка шага: ветка задачи, если работа уже есть; проверке без ветки задачи смотреть не на что\r\nasync function startRef(source: string, job: RunnerJob, local: LocalSource): Promise<string> {\r\n const base = await baseBranchRef(source, local.baseBranch, local.remote)\r\n const task = await taskBranchRef(source, job.branch, local.remote, base)\r\n if (task || !isReadOnlyCheck(job)) {\r\n return task ?? base\r\n }\r\n const where = local.remote ? `${source} or on its remote ${local.remote}` : source\r\n throw new Error(`Branch ${job.branch} is not in ${where}. It was made on another computer: push it to a remote this folder can fetch, or run this check on that computer.`)\r\n}\r\n\r\n// Ветку задачи держит одна папка TAMsense; если она открыта в рабочей папке самого человека, её не трогают\r\nasync function releaseBranch(source: string, branch: string, keep: string, home: string): Promise<void> {\r\n const listed = await runGit(['worktree', 'list', '--porcelain'], { cwd: source })\r\n const work = tamsenseFolder(home, 'work')\r\n for (const holder of branchHolders(listed.stdout, branch).filter(path => resolve(path) !== resolve(keep))) {\r\n if (!isInside(holder, work)) {\r\n throw new Error(`Branch ${branch} is checked out in ${resolve(holder)}. Switch that folder to another branch, then start the step again.`)\r\n }\r\n await removeWorktree(source, holder)\r\n }\r\n}\r\n\r\n// Продолжение после ответа человека берёт папку шага как есть, если в ней та же ветка\r\nasync function keptBase(path: string, job: RunnerJob): Promise<string | null> {\r\n if (!job.keepWorkspace || !existsSync(join(path, '.git'))) {\r\n return null\r\n }\r\n const branch = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: path })\r\n if (branch.code !== 0 || branch.stdout !== (readsOnly(job) ? 'HEAD' : job.branch)) {\r\n return null\r\n }\r\n return git(['rev-parse', 'HEAD'], { cwd: path })\r\n}\r\n\r\nfunction sourceOf(repository: RunnerRepository): string {\r\n if (!repository.folder) {\r\n throw new Error(`TAMsense did not say which folder holds ${repository.fullName} on this computer.`)\r\n }\r\n return repository.folder\r\n}\r\n\r\nasync function prepareRepository(job: RunnerJob, repository: RunnerRepository, path: string, tools: LocalTools): Promise<{ baseSha: string, local: LocalSource }> {\r\n const source = sourceOf(repository)\r\n if ((await runGit(['rev-parse', '--show-toplevel'], { cwd: source }).catch(() => null))?.code !== 0) {\r\n throw new Error(`${source} is no longer a git repository on this computer. Check the folder again in the board settings, on the Code tab.`)\r\n }\r\n const local: LocalSource = { source, remote: await remoteOf(source), baseBranch: repository.defaultBranch }\r\n if (local.remote) {\r\n await fetchRemote(source, local.remote, tools.print)\r\n }\r\n const kept = await keptBase(path, job)\r\n if (kept) {\r\n return { baseSha: kept, local }\r\n }\r\n await runGit(['worktree', 'prune'], { cwd: source })\r\n if (existsSync(path)) {\r\n await removeWorktree(source, path)\r\n }\r\n const start = await startRef(source, job, local)\r\n if (readsOnly(job)) {\r\n await git(['worktree', 'add', '--detach', path, start], { cwd: source })\r\n }\r\n else {\r\n await releaseBranch(source, job.branch, path, tools.home)\r\n await git(['worktree', 'add', '-B', job.branch, path, start], { cwd: source })\r\n }\r\n return { baseSha: await git(['rev-parse', 'HEAD'], { cwd: path }), local }\r\n}\r\n\r\n// Папка шага для каждого репозитория: рабочая копия git из папки человека в ~/.tamsense/work. Рабочая папка человека,\r\n// её текущая ветка и остальные ветки не меняются: шаг кода пишет только в ветку задачи\r\nexport async function prepareLocalWorkspace(job: RunnerJob, tools: LocalTools): Promise<Workspace> {\r\n const root = taskFolder(tools.home, job)\r\n const folders = folderNames(job.repositories)\r\n const repositories: WorkspaceRepository[] = []\r\n for (const [index, repository] of job.repositories.entries()) {\r\n const folder = folders[index] ?? repository.fullName\r\n const path = join(root, folder)\r\n const prepared = await sourceLock.run(resolve(sourceOf(repository)), () => prepareRepository(job, repository, path, tools))\r\n repositories.push({ fullName: repository.fullName, folder, path, baseSha: prepared.baseSha, local: prepared.local })\r\n }\r\n return { root, repositories }\r\n}\r\n\r\n// Ветка задачи отправляется на удалённый репозиторий папки входом человека, только сама ветка задачи; основная ветка никогда.\r\n// Не отправилась: работа остаётся в ветке папки, человек видит почему\r\nasync function pushBranch(path: string, local: LocalSource, branch: string): Promise<{ remoteUrl: string | null, pushProblem: string | null }> {\r\n if (!local.remote) {\r\n return { remoteUrl: null, pushProblem: null }\r\n }\r\n if (branch === local.baseBranch) {\r\n return { remoteUrl: null, pushProblem: `TAMsense never pushes the main branch ${branch}.` }\r\n }\r\n const pushed = await runGit(['push', local.remote, `HEAD:refs/heads/${branch}`], { cwd: path, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS })\r\n if (pushed.code !== 0) {\r\n return { remoteUrl: null, pushProblem: pushed.stderr || `exit code ${pushed.code}` }\r\n }\r\n return { remoteUrl: await git(['remote', 'get-url', local.remote], { cwd: path }), pushProblem: null }\r\n}\r\n\r\n// Незакоммиченное Claude досохраняется одним коммитом в ветке задачи; ветка с новой работой отправляется, если у папки есть удалённый\r\nasync function commitAndPush(repository: WorkspaceRepository, local: LocalSource, job: RunnerJob, identity: Record<string, string>) {\r\n const options = { cwd: repository.path, env: identity }\r\n await git(['add', '--all'], options)\r\n if ((await runGit(['diff', '--cached', '--quiet'], options)).code === 1) {\r\n await git(['commit', '--message', `Task #${job.taskId}: ${job.title}`], options)\r\n }\r\n if (Number(await git(['rev-list', '--count', `${repository.baseSha}..HEAD`], options)) === 0) {\r\n return null\r\n }\r\n const base = await baseBranchRef(repository.path, local.baseBranch, local.remote)\r\n const commits = Number(await git(['rev-list', '--count', `${base}..HEAD`], options))\r\n const sent = await pushBranch(repository.path, local, job.branch)\r\n return { sha: await git(['rev-parse', 'HEAD'], options), commits, ...sent }\r\n}\r\n\r\nexport async function publishLocalWorkspace(workspace: Workspace, job: RunnerJob, identity: Record<string, string>): Promise<RunnerPushedBranch[]> {\r\n const pushed: RunnerPushedBranch[] = []\r\n for (const repository of workspace.repositories) {\r\n const local = repository.local\r\n if (!local) {\r\n continue\r\n }\r\n const branch = await sourceLock.run(resolve(local.source), async () => {\r\n const published = await commitAndPush(repository, local, job, identity)\r\n // папка шага отпускает ветку задачи: человек может открыть её у себя, работа осталась в ней\r\n await runGit(['checkout', '--quiet', '--detach'], { cwd: repository.path })\r\n return published\r\n })\r\n if (branch) {\r\n pushed.push({\r\n repository: repository.fullName,\r\n branch: job.branch,\r\n sha: branch.sha,\r\n local: { folder: local.source, baseBranch: local.baseBranch, commits: branch.commits, remoteUrl: branch.remoteUrl && remoteWithoutSecrets(branch.remoteUrl), pushProblem: branch.pushProblem },\r\n })\r\n }\r\n }\r\n return pushed\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { RunnerFolderChecksResponse, RunnerFolderReport } from './protocol'\r\nimport { existsSync, realpathSync, statSync } from 'node:fs'\r\nimport { basename, isAbsolute, join, relative, resolve } from 'node:path'\r\nimport process from 'node:process'\r\nimport { ApiError } from './api'\r\nimport { runGit } from './git'\r\nimport { FOLDER_CHECKS_PATH, folderCheckPath } from './protocol'\r\n\r\nconst FIRST_REMOTE = 'origin'\r\nconst USUAL_BASES = ['main', 'master']\r\nconst CHECKS_INTERVAL_MS = 5_000\r\n// адрес по сети (https://, ssh://) или в виде git@host:владелец/имя; у локального пути и file:// имени владельца нет\r\nconst WEB_REMOTE = /^[a-z][\\w+.-]*:\\/\\//i\r\nconst NETWORK_REMOTE = /^(?!file:)[a-z][\\w+.-]*:\\/\\/|^[^/@]+@[^/:]+:/i\r\n\r\nexport interface FolderTools {\r\n api: ApiClient\r\n home: string\r\n print: (line: string) => void\r\n sleep: (ms: number, signal?: AbortSignal) => Promise<void>\r\n inspect: (path: string) => Promise<RunnerFolderReport>\r\n}\r\n\r\nfunction problem(text: string): RunnerFolderReport {\r\n return { ok: false, problem: text }\r\n}\r\n\r\nasync function gitText(args: string[], cwd: string): Promise<string | null> {\r\n const result = await runGit(args, { cwd })\r\n return result.code === 0 && result.stdout ? result.stdout : null\r\n}\r\n\r\n// ~ в начале пути: папка человека\r\nexport function expandHome(path: string, home: string): string {\r\n const trimmed = path.trim()\r\n return trimmed === '~' || trimmed.startsWith('~/') || trimmed.startsWith('~\\\\') ? join(home, trimmed.slice(1)) : trimmed\r\n}\r\n\r\n// Логин и пароль в адресе остаются на компьютере: на сайт уходит адрес без них\r\nexport function remoteWithoutSecrets(url: string): string {\r\n if (!WEB_REMOTE.test(url)) {\r\n return url\r\n }\r\n try {\r\n const parsed = new URL(url)\r\n if (parsed.username || parsed.password) {\r\n parsed.username = ''\r\n parsed.password = ''\r\n }\r\n return parsed.toString().replace(/\\/$/, '')\r\n }\r\n catch {\r\n return url\r\n }\r\n}\r\n\r\n// Имя репозитория по адресу удалённого (владелец/имя, как на GitHub), без него по имени папки\r\nexport function repositoryName(remoteUrl: string | null, folder: string): string {\r\n const parts = remoteUrl && NETWORK_REMOTE.test(remoteUrl) ? remoteUrl.replace(/\\/+$/, '').replace(/\\.git$/, '').split(/[/:]/).filter(Boolean) : []\r\n return parts.length >= 3 ? `${parts.at(-2)}/${parts.at(-1)}` : basename(folder)\r\n}\r\n\r\n// Удалённый репозиторий папки: origin, а если его нет, первый из списка\r\nexport async function remoteOf(folder: string): Promise<string | null> {\r\n const remotes = (await gitText(['remote'], folder))?.split('\\n').map(line => line.trim()).filter(Boolean) ?? []\r\n return remotes.includes(FIRST_REMOTE) ? FIRST_REMOTE : remotes[0] ?? null\r\n}\r\n\r\nexport async function hasRef(folder: string, ref: string): Promise<boolean> {\r\n return (await runGit(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], { cwd: folder })).code === 0\r\n}\r\n\r\n// Основная ветка: та, на которую смотрит HEAD удалённого, иначе main или master, иначе текущая\r\nasync function baseBranchOf(folder: string, remote: string | null, current: string | null): Promise<string | null> {\r\n const remoteHead = remote ? await gitText(['symbolic-ref', '--quiet', '--short', `refs/remotes/${remote}/HEAD`], folder) : null\r\n if (remoteHead?.startsWith(`${remote}/`)) {\r\n return remoteHead.slice(remote!.length + 1)\r\n }\r\n for (const name of USUAL_BASES) {\r\n if (await hasRef(folder, `refs/heads/${name}`) || (remote && await hasRef(folder, `refs/remotes/${remote}/${name}`))) {\r\n return name\r\n }\r\n }\r\n return current\r\n}\r\n\r\n// git называет папку её настоящим путём: ссылки и короткие имена Windows раскрываются до сравнения\r\nfunction samePath(first: string, second: string): boolean {\r\n const [a, b] = [realpathSync.native(first), realpathSync.native(second)]\r\n return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b\r\n}\r\n\r\nexport function isInside(path: string, folder: string): boolean {\r\n const between = relative(resolve(folder), resolve(path))\r\n return between === '' || (!between.startsWith('..') && !isAbsolute(between))\r\n}\r\n\r\n// Проверка только читает git папки: что это верхняя папка репозитория, его ветки и удалённый адрес\r\nexport async function inspectFolder(path: string, home: string): Promise<RunnerFolderReport> {\r\n const folder = expandHome(path, home)\r\n if (!isAbsolute(folder)) {\r\n return problem('Enter the full path of the folder, for example /Users/anna/code/shop or C:\\\\code\\\\shop.')\r\n }\r\n if (!existsSync(folder) || !statSync(folder).isDirectory()) {\r\n return problem(`There is no folder ${folder} on this computer.`)\r\n }\r\n if (isInside(folder, join(home, '.tamsense'))) {\r\n return problem('This is a TAMsense work folder. Choose your own copy of the repository.')\r\n }\r\n const top = await gitText(['rev-parse', '--show-toplevel'], folder)\r\n if (!top) {\r\n return problem(`${folder} is not a git repository. Choose the folder of a cloned repository, or run git init there.`)\r\n }\r\n if (!samePath(top, folder)) {\r\n return problem(`${folder} is inside the repository ${resolve(top)}. Add the top folder of the repository instead.`)\r\n }\r\n if (!(await hasRef(folder, 'HEAD'))) {\r\n return problem('The repository has no commits yet. Make the first commit, then check the folder again.')\r\n }\r\n const remote = await remoteOf(folder)\r\n const remoteUrl = remote ? await gitText(['remote', 'get-url', remote], folder) : null\r\n const currentBranch = await gitText(['symbolic-ref', '--quiet', '--short', 'HEAD'], folder)\r\n const defaultBranch = await baseBranchOf(folder, remote, currentBranch)\r\n if (!defaultBranch) {\r\n return problem('Could not tell the main branch of the repository. Check out the main branch in this folder, then check it again.')\r\n }\r\n const safeUrl = remoteUrl && remoteWithoutSecrets(remoteUrl)\r\n return { ok: true, repository: repositoryName(safeUrl, folder), currentBranch, defaultBranch, remoteUrl: safeUrl }\r\n}\r\n\r\nfunction reportLine(path: string, report: RunnerFolderReport): string {\r\n return report.ok ? `Checked the folder ${path}: ${report.repository}, main branch ${report.defaultBranch}.` : `The folder ${path} cannot be used: ${report.problem}`\r\n}\r\n\r\n// Папки, которые люди добавили на доски для этого компьютера, проверяются здесь же: сайт спрашивает, программа отвечает.\r\n// Старый сайт этих запросов не знает: тогда проверка молча выключается\r\nexport async function checkFolders(tools: FolderTools, stop: AbortSignal): Promise<void> {\r\n let complained = false\r\n while (!stop.aborted) {\r\n try {\r\n const { checks } = await tools.api.request<RunnerFolderChecksResponse>('POST', FOLDER_CHECKS_PATH)\r\n for (const check of checks) {\r\n const report = await tools.inspect(check.path).catch((error: unknown) => problem(error instanceof Error ? error.message : String(error)))\r\n await tools.api.request('POST', folderCheckPath(check.id), report)\r\n tools.print(reportLine(check.path, report))\r\n }\r\n complained = false\r\n }\r\n catch (error) {\r\n if (error instanceof ApiError && error.status === 404) {\r\n return\r\n }\r\n if (!complained && !stop.aborted) {\r\n tools.print(`Could not check the folders for TAMsense: ${error instanceof Error ? error.message : String(error)}`)\r\n complained = true\r\n }\r\n }\r\n await tools.sleep(CHECKS_INTERVAL_MS, stop)\r\n }\r\n}\r\n","import { Buffer } from 'node:buffer'\r\nimport { spawn } from 'node:child_process'\r\nimport process from 'node:process'\r\n\r\nexport interface GitResult {\r\n code: number\r\n stdout: string\r\n stderr: string\r\n}\r\n\r\nexport interface GitOptions {\r\n cwd: string\r\n // ключ GitHub уходит только этому процессу git через окружение: в настройки репозитория и к Claude он не попадает\r\n token?: string\r\n env?: Record<string, string>\r\n // git по сети без ответа не держит работу: процесс останавливается через это время\r\n timeoutMs?: number\r\n}\r\n\r\nconst GITHUB_ORIGIN = 'https://github.com/'\r\n\r\nexport function githubAuthEnv(token: string): Record<string, string> {\r\n const credentials = Buffer.from(`x-access-token:${token}`).toString('base64')\r\n return {\r\n GIT_CONFIG_COUNT: '1',\r\n GIT_CONFIG_KEY_0: `http.${GITHUB_ORIGIN}.extraheader`,\r\n GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${credentials}`,\r\n GIT_TERMINAL_PROMPT: '0',\r\n }\r\n}\r\n\r\n// Вход человека в его удалённый репозиторий: git берёт сохранённые пароли и ключи, но ничего не спрашивает,\r\n// ведь у компьютера никто не сидит. Свою команду ssh человека программа не подменяет\r\nexport function personalGitEnv(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\r\n return { GIT_TERMINAL_PROMPT: '0', ...(env.GIT_SSH_COMMAND ? {} : { GIT_SSH_COMMAND: 'ssh -o BatchMode=yes' }) }\r\n}\r\n\r\nexport function runGit(args: string[], options: GitOptions): Promise<GitResult> {\r\n const env = { ...process.env, ...options.env, ...(options.token ? githubAuthEnv(options.token) : {}) }\r\n return new Promise((resolve, reject) => {\r\n const child = spawn('git', args, { cwd: options.cwd, env, stdio: ['ignore', 'pipe', 'pipe'], ...(options.timeoutMs ? { timeout: options.timeoutMs } : {}) })\r\n let stdout = ''\r\n let stderr = ''\r\n child.stdout.on('data', (chunk: Buffer) => {\r\n stdout += chunk.toString()\r\n })\r\n child.stderr.on('data', (chunk: Buffer) => {\r\n stderr += chunk.toString()\r\n })\r\n child.on('error', reject)\r\n child.on('close', code => resolve({ code: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() }))\r\n })\r\n}\r\n\r\n// Ошибка git с его собственным текстом: человек увидит её в отчёте задачи\r\nexport async function git(args: string[], options: GitOptions): Promise<string> {\r\n const result = await runGit(args, options)\r\n if (result.code !== 0) {\r\n throw new Error(`git ${args[0]} failed: ${result.stderr || result.stdout || `exit code ${result.code}`}`)\r\n }\r\n return result.stdout\r\n}\r\n","export interface KeyedLock {\r\n run: <T>(key: string, work: () => Promise<T>) => Promise<T>\r\n}\r\n\r\n// Работа с одним ключом идёт по очереди, с разными ключами параллельно\r\nexport function keyedLock(): KeyedLock {\r\n const tails = new Map<string, Promise<void>>()\r\n return {\r\n async run<T>(key: string, work: () => Promise<T>): Promise<T> {\r\n const previous = tails.get(key) ?? Promise.resolve()\r\n let release = () => {}\r\n const done = new Promise<void>((resolve) => {\r\n release = resolve\r\n })\r\n const tail = previous.then(() => done)\r\n tails.set(key, tail)\r\n await previous\r\n try {\r\n return await work()\r\n }\r\n finally {\r\n release()\r\n if (tails.get(key) === tail) {\r\n tails.delete(key)\r\n }\r\n }\r\n },\r\n }\r\n}\r\n","import type { RunnerJob, RunnerPushedBranch, RunnerRepository } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { rm } from 'node:fs/promises'\r\nimport { join, resolve } from 'node:path'\r\nimport { git, runGit } from './git'\r\nimport { keyedLock } from './lock'\r\n\r\n// local: репозиторий взят из папки человека на этом компьютере\r\nexport interface WorkspaceRepository {\r\n fullName: string\r\n folder: string\r\n path: string\r\n baseSha: string\r\n local?: LocalSource\r\n}\r\n\r\n// source: папка человека, remote: её удалённый репозиторий (null: его нет), baseBranch: основная ветка, как её видит git\r\nexport interface LocalSource {\r\n source: string\r\n remote: string | null\r\n baseBranch: string\r\n}\r\n\r\nexport interface Workspace {\r\n root: string\r\n repositories: WorkspaceRepository[]\r\n}\r\n\r\nexport interface WorkspaceTools {\r\n home: string\r\n remoteUrl: (fullName: string) => string\r\n}\r\n\r\nconst FALLBACK_IDENTITY = { name: 'TAMsense', email: 'noreply@tamsense.com' }\r\n\r\n// Копия репозитория в ~/.tamsense/repos общая для всех задач: git с ней работает по очереди\r\nconst cacheLock = keyedLock()\r\n\r\nexport function githubRemoteUrl(fullName: string): string {\r\n return `https://github.com/${fullName}.git`\r\n}\r\n\r\nexport function tamsenseFolder(home: string, ...parts: string[]): string {\r\n return join(home, '.tamsense', ...parts)\r\n}\r\n\r\n// Папка репозитория называется его именем; два репозитория с одинаковым именем получают имя владельца в начале\r\nexport function folderNames(repositories: RunnerRepository[]): string[] {\r\n const shortName = (fullName: string) => fullName.split('/').pop() ?? fullName\r\n const counts = new Map<string, number>()\r\n for (const repository of repositories) {\r\n counts.set(shortName(repository.fullName), (counts.get(shortName(repository.fullName)) ?? 0) + 1)\r\n }\r\n return repositories.map(repository => counts.get(shortName(repository.fullName)) === 1\r\n ? shortName(repository.fullName)\r\n : repository.fullName.replace('/', '__'))\r\n}\r\n\r\nasync function refreshCache(cache: string, repository: RunnerRepository, token: string, tools: WorkspaceTools): Promise<void> {\r\n if (existsSync(join(cache, '.git'))) {\r\n await git(['fetch', '--prune', 'origin'], { cwd: cache, token })\r\n return\r\n }\r\n await git(['clone', '--no-checkout', tools.remoteUrl(repository.fullName), cache], { cwd: tools.home, token })\r\n}\r\n\r\nexport async function removeWorktree(cache: string, path: string): Promise<void> {\r\n await runGit(['worktree', 'remove', '--force', path], { cwd: cache })\r\n await rm(path, { recursive: true, force: true })\r\n await git(['worktree', 'prune'], { cwd: cache })\r\n}\r\n\r\n// Своя папка у каждого шага задачи: две проверки одной задачи на одном компьютере идут одновременно и не мешают друг другу.\r\n// Старый сайт шага не называет: тогда папка одна на задачу, как раньше\r\nexport function stepFolder(job: Pick<RunnerJob, 'taskId' | 'stage'>): string[] {\r\n const task = `task-${job.taskId}`\r\n return typeof job.stage.columnId === 'number' ? [task, `step-${job.stage.columnId}`] : [task]\r\n}\r\n\r\n// Проверка смотрит работу, но ничего не отправляет: она берёт последний коммит ветки без самой ветки,\r\n// поэтому несколько проверок и шаг кода не спорят за одну ветку\r\nexport function isReadOnlyCheck(job: RunnerJob): boolean {\r\n return job.stage.kind === 'review' || job.stage.kind === 'test'\r\n}\r\n\r\n// Папки, в которых стоит ветка, по выводу git worktree list --porcelain\r\nexport function branchHolders(porcelain: string, branch: string): string[] {\r\n const holders: string[] = []\r\n let path: string | null = null\r\n for (const line of porcelain.split('\\n').map(text => text.trim())) {\r\n if (line.startsWith('worktree ')) {\r\n path = line.slice('worktree '.length)\r\n }\r\n else if (path && line === `branch refs/heads/${branch}`) {\r\n holders.push(path)\r\n }\r\n }\r\n return holders\r\n}\r\n\r\n// Ветку задачи держит одна папка: шаг, который пишет в неё код, забирает её у папки прошлого шага этой задачи\r\nasync function releaseBranch(cache: string, branch: string, keep: string): Promise<void> {\r\n const listed = await runGit(['worktree', 'list', '--porcelain'], { cwd: cache })\r\n for (const path of branchHolders(listed.stdout, branch).filter(holder => resolve(holder) !== resolve(keep))) {\r\n await removeWorktree(cache, path)\r\n }\r\n}\r\n\r\n// Доработка продолжает уже отправленную ветку задачи, первый запуск начинает её с основной ветки\r\nasync function baseRef(cache: string, job: RunnerJob, repository: RunnerRepository): Promise<string> {\r\n const pushed = await runGit(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${job.branch}`], { cwd: cache })\r\n return pushed.code === 0 ? `origin/${job.branch}` : `origin/${repository.defaultBranch}`\r\n}\r\n\r\n// Продолжение после ответа человека берёт папку задачи как есть: незакоммиченная работа агента остаётся. Базой остаётся ветка на GitHub\r\nasync function keptBase(cache: string, path: string, job: RunnerJob, repository: RunnerRepository): Promise<string | null> {\r\n if (!job.keepWorkspace || !existsSync(join(path, '.git'))) {\r\n return null\r\n }\r\n const branch = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: path })\r\n if (branch.code !== 0 || branch.stdout !== (isReadOnlyCheck(job) ? 'HEAD' : job.branch)) {\r\n return null\r\n }\r\n return git(['rev-parse', await baseRef(cache, job, repository)], { cwd: cache })\r\n}\r\n\r\n// Папка шага задачи на компьютере: в ней репозитории или, на доске без кода, работа и результат задачи\r\nexport function taskFolder(home: string, job: Pick<RunnerJob, 'taskId' | 'stage'>): string {\r\n return tamsenseFolder(home, 'work', ...stepFolder(job))\r\n}\r\n\r\nexport async function prepareWorkspace(job: RunnerJob, token: string, tools: WorkspaceTools): Promise<Workspace> {\r\n const root = taskFolder(tools.home, job)\r\n const folders = folderNames(job.repositories)\r\n const repositories: WorkspaceRepository[] = []\r\n for (const [index, repository] of job.repositories.entries()) {\r\n const cache = tamsenseFolder(tools.home, 'repos', repository.fullName.replace('/', '__'))\r\n const folder = folders[index] ?? repository.fullName\r\n const path = join(root, folder)\r\n const baseSha = await cacheLock.run(repository.fullName, async () => {\r\n await refreshCache(cache, repository, token, tools)\r\n const kept = await keptBase(cache, path, job, repository)\r\n if (kept) {\r\n return kept\r\n }\r\n await removeWorktree(cache, path)\r\n const base = await baseRef(cache, job, repository)\r\n if (isReadOnlyCheck(job)) {\r\n await git(['worktree', 'add', '--force', '--detach', path, base], { cwd: cache })\r\n }\r\n else {\r\n await releaseBranch(cache, job.branch, path)\r\n await git(['worktree', 'add', '--force', '-B', job.branch, path, base], { cwd: cache })\r\n }\r\n return git(['rev-parse', 'HEAD'], { cwd: path })\r\n })\r\n repositories.push({ fullName: repository.fullName, folder, path, baseSha })\r\n }\r\n return { root, repositories }\r\n}\r\n\r\n// Коммиты без автора git не создаёт: если человек его не задал, коммиты подписываются именем TAMsense\r\nexport async function commitIdentityEnv(home: string): Promise<Record<string, string>> {\r\n const configured = await runGit(['config', '--get', 'user.email'], { cwd: home })\r\n if (configured.code === 0 && configured.stdout) {\r\n return {}\r\n }\r\n return {\r\n GIT_AUTHOR_NAME: FALLBACK_IDENTITY.name,\r\n GIT_AUTHOR_EMAIL: FALLBACK_IDENTITY.email,\r\n GIT_COMMITTER_NAME: FALLBACK_IDENTITY.name,\r\n GIT_COMMITTER_EMAIL: FALLBACK_IDENTITY.email,\r\n }\r\n}\r\n\r\n// Незакоммиченное Claude досохраняется одним коммитом; ветка отправляется, только если в ней есть новое\r\nexport async function publishWorkspace(workspace: Workspace, job: RunnerJob, token: string, identity: Record<string, string>): Promise<RunnerPushedBranch[]> {\r\n const pushed: RunnerPushedBranch[] = []\r\n for (const repository of workspace.repositories) {\r\n const options = { cwd: repository.path, env: identity }\r\n const sha = await cacheLock.run(repository.fullName, async () => {\r\n await git(['add', '--all'], options)\r\n const hasStaged = (await runGit(['diff', '--cached', '--quiet'], options)).code === 1\r\n if (hasStaged) {\r\n await git(['commit', '--message', `Task #${job.taskId}: ${job.title}`], options)\r\n }\r\n const newCommits = Number(await git(['rev-list', '--count', `${repository.baseSha}..HEAD`], options))\r\n if (newCommits === 0) {\r\n return null\r\n }\r\n await git(['push', 'origin', `HEAD:refs/heads/${job.branch}`], { ...options, token })\r\n return git(['rev-parse', 'HEAD'], options)\r\n })\r\n if (sha) {\r\n pushed.push({ repository: repository.fullName, branch: job.branch, sha })\r\n }\r\n }\r\n return pushed\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { RunnerBoardTarget, RunnerHandOverBody, RunnerJob, RunnerResult, RunnerResultBody, RunnerResultFile, RunnerResultFileBody, RunnerToolReply } from './protocol'\r\nimport type { Workspace } from './workspace'\r\nimport { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises'\r\nimport { basename, extname, isAbsolute, join, resolve } from 'node:path'\r\nimport { tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { MAX_RESULT_FILE_BYTES, resultFilePath, runPath } from './protocol'\r\n\r\nexport const SAVE_DOCUMENT_TOOL_NAME = 'save_result_document'\r\nexport const SAVE_TABLE_TOOL_NAME = 'save_result_table'\r\nexport const ATTACH_FILE_TOOL_NAME = 'attach_result_file'\r\nexport const ADD_LINK_TOOL_NAME = 'add_result_link'\r\nexport const HAND_OVER_TOOL_NAME = 'hand_over'\r\nexport const RESULTS_FOLDER = 'results'\r\n\r\nconst MAX_NAME_LENGTH = 60\r\nconst TYPES_BY_EXTENSION: Record<string, string> = {\r\n '.pdf': 'application/pdf',\r\n '.png': 'image/png',\r\n '.jpg': 'image/jpeg',\r\n '.jpeg': 'image/jpeg',\r\n '.webp': 'image/webp',\r\n '.gif': 'image/gif',\r\n '.csv': 'text/csv',\r\n '.txt': 'text/plain',\r\n '.md': 'text/markdown',\r\n '.json': 'application/json',\r\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\r\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\r\n '.zip': 'application/zip',\r\n}\r\nconst EXTENSIONS: Record<RunnerResult['kind'], string> = { document: '.md', table: '.csv', file: '', link: '' }\r\n\r\n// Результат кладётся на задачу запросами к сайту; root: папка задачи, относительные пути файлов считаются от неё\r\nexport interface ResultTools {\r\n root: string\r\n save: (body: RunnerResultBody) => Promise<RunnerToolReply>\r\n attach: (body: RunnerResultFileBody) => Promise<RunnerToolReply>\r\n}\r\n\r\n// Доски, на которые агент может передать задачу по правилу шага, и сама передача\r\nexport interface HandOverTools {\r\n boards: RunnerBoardTarget[]\r\n send: (body: RunnerHandOverBody) => Promise<RunnerToolReply>\r\n}\r\n\r\nexport function isFolderJob(job: Pick<RunnerJob, 'workspace'>): boolean {\r\n return job.workspace === 'folder'\r\n}\r\n\r\nexport function resultTools(job: RunnerJob, api: ApiClient, root: string): ResultTools | null {\r\n if (!isFolderJob(job) || job.stage.kind !== 'code') {\r\n return null\r\n }\r\n return {\r\n root,\r\n save: body => api.request<RunnerToolReply>('POST', runPath(job.runId, 'results'), body),\r\n attach: body => api.request<RunnerToolReply>('POST', runPath(job.runId, 'result-files'), body),\r\n }\r\n}\r\n\r\nexport function handOverTools(job: RunnerJob, api: ApiClient): HandOverTools | null {\r\n const boards = job.handOverBoards ?? []\r\n if (boards.length === 0 || job.stage.kind !== 'code') {\r\n return null\r\n }\r\n return { boards, send: body => api.request<RunnerToolReply>('POST', runPath(job.runId, 'hand-over'), body) }\r\n}\r\n\r\n// Имя файла из названия результата: буквы любого языка, цифры и дефисы; одинаковые названия получают номер\r\nexport function resultFileName(result: Pick<RunnerResult, 'kind' | 'title' | 'fileName'>, taken: Set<string>): string {\r\n const stem = (result.kind === 'file' && result.fileName ? result.fileName.replace(/\\.[^.]*$/, '') : result.title)\r\n .normalize('NFKC')\r\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\r\n .replace(/^-+|-+$/g, '')\r\n .slice(0, MAX_NAME_LENGTH) || 'result'\r\n const extension = result.kind === 'file' ? extname(result.fileName ?? '') : EXTENSIONS[result.kind]\r\n let name = `${stem}${extension}`\r\n for (let index = 2; taken.has(name.toLowerCase()); index += 1) {\r\n name = `${stem}-${index}${extension}`\r\n }\r\n taken.add(name.toLowerCase())\r\n return name\r\n}\r\n\r\nfunction linksText(links: RunnerResult[]): string {\r\n return links.map(link => `- ${link.title}: ${link.url ?? ''}`).join('\\n')\r\n}\r\n\r\n// Результат задачи в папке results: последние версии документов (.md) и таблиц (.csv), файлы и ссылки (links.md).\r\n// Файл, который не скачался, назван в notes.md: агент знает, что его нет. Возвращает строки для подсказки агенту\r\nexport async function writeResults(folder: string, job: RunnerJob, api: ApiClient): Promise<string[]> {\r\n const results = job.results ?? []\r\n await rm(folder, { recursive: true, force: true })\r\n if (results.length === 0) {\r\n return []\r\n }\r\n await mkdir(folder, { recursive: true })\r\n const taken = new Set<string>()\r\n const lines: string[] = []\r\n const missing: string[] = []\r\n for (const result of results.filter(item => item.kind !== 'link')) {\r\n const name = resultFileName(result, taken)\r\n if (result.kind === 'file') {\r\n try {\r\n const file = await api.request<RunnerResultFile>('GET', resultFilePath(job.runId, result.id))\r\n await writeFile(join(folder, name), Buffer.from(file.data, 'base64'))\r\n }\r\n catch (error) {\r\n missing.push(`${result.title}: ${error instanceof Error ? error.message : String(error)}`)\r\n continue\r\n }\r\n }\r\n else {\r\n await writeFile(join(folder, name), result.content ?? '')\r\n }\r\n lines.push(`${RESULTS_FOLDER}/${name}: ${result.title}${result.version ? `, version ${result.version}` : ''}`)\r\n }\r\n const links = results.filter(item => item.kind === 'link')\r\n if (links.length > 0) {\r\n await writeFile(join(folder, 'links.md'), `${linksText(links)}\\n`)\r\n lines.push(`${RESULTS_FOLDER}/links.md: ${links.length} link(s)`)\r\n }\r\n if (missing.length > 0) {\r\n await writeFile(join(folder, 'notes.md'), `These files could not be downloaded:\\n${missing.map(line => `- ${line}`).join('\\n')}\\n`)\r\n }\r\n return lines\r\n}\r\n\r\n// Папка задачи без репозиториев: в ней агент работает, в results лежит то, что задача уже сделала.\r\n// Продолжение после ответа человека берёт папку как есть\r\nexport async function prepareFolder(job: RunnerJob, root: string, api: ApiClient): Promise<Workspace & { results: string[] }> {\r\n if (!job.keepWorkspace) {\r\n await rm(root, { recursive: true, force: true })\r\n }\r\n await mkdir(root, { recursive: true })\r\n return { root, repositories: [], results: await writeResults(join(root, RESULTS_FOLDER), job, api) }\r\n}\r\n\r\n// Файл, который агент прикладывает как результат: путь от папки задачи или полный, не больше 10 МБ.\r\n// Ответ Claude, если файл не годится: он поправит путь или уменьшит файл\r\nexport async function readResultFile(root: string, path: string, title: string): Promise<RunnerResultFileBody | string> {\r\n const full = isAbsolute(path) ? path : resolve(root, path)\r\n const info = await stat(full).catch(() => null)\r\n if (!info?.isFile()) {\r\n return `There is no file at ${full}.`\r\n }\r\n if (info.size > MAX_RESULT_FILE_BYTES) {\r\n return `The file is larger than ${MAX_RESULT_FILE_BYTES / 1024 / 1024} MB. Attach a smaller one or split it.`\r\n }\r\n const name = basename(full)\r\n return { name, title: title.trim() || name, contentType: TYPES_BY_EXTENSION[extname(full).toLowerCase()] ?? 'application/octet-stream', data: (await readFile(full)).toString('base64') }\r\n}\r\n\r\nfunction reply(text: string, isError = false) {\r\n return { content: [{ type: 'text' as const, text }], ...(isError ? { isError: true } : {}) }\r\n}\r\n\r\nasync function sent(send: () => Promise<RunnerToolReply>): Promise<ReturnType<typeof reply>> {\r\n try {\r\n return reply((await send()).text)\r\n }\r\n catch (error) {\r\n return reply(`Could not save it on TAMsense: ${error instanceof Error ? error.message : String(error)}. Try again once; if it fails again, say so in your report.`, true)\r\n }\r\n}\r\n\r\n// Инструменты результата на доске без кода: документ и таблица с версиями, файл, ссылка\r\nexport function resultToolset(tools: ResultTools) {\r\n return [\r\n tool(\r\n SAVE_DOCUMENT_TOOL_NAME,\r\n 'Save a document as the result of this step on the TAMsense task: Markdown text such as a letter, a reply, a brief or a report. Saving again with the same title makes a new version of that document; every version is kept and people compare them.',\r\n {\r\n title: z.string().describe('short name of the document, the same for every version of it'),\r\n content: z.string().describe('the full text in Markdown, never a diff or a part'),\r\n note: z.string().describe('one line on what this version is or what changed: \"First draft\", \"Fixed the prices after the check\"'),\r\n },\r\n async args => await sent(() => tools.save({ kind: 'document', ...args })),\r\n ),\r\n tool(\r\n SAVE_TABLE_TOOL_NAME,\r\n 'Save a table as the result of this step, as CSV with a header row. Saving again with the same title makes a new version of that table.',\r\n {\r\n title: z.string().describe('short name of the table, the same for every version of it'),\r\n csv: z.string().describe('the full table as CSV, the first row holds the column names'),\r\n note: z.string().describe('one line on what this version is or what changed'),\r\n },\r\n async args => await sent(() => tools.save({ kind: 'table', title: args.title, content: args.csv, note: args.note })),\r\n ),\r\n tool(\r\n ATTACH_FILE_TOOL_NAME,\r\n `Attach a file you made in the task folder to the TAMsense task as part of the result: a PDF, a spreadsheet, an image. Up to ${MAX_RESULT_FILE_BYTES / 1024 / 1024} MB.`,\r\n {\r\n path: z.string().describe('path of the file, from the task folder or absolute'),\r\n title: z.string().describe('what the file is, in a few words'),\r\n },\r\n async (args) => {\r\n const body = await readResultFile(tools.root, args.path, args.title)\r\n return typeof body === 'string' ? reply(body, true) : await sent(() => tools.attach(body))\r\n },\r\n ),\r\n tool(\r\n ADD_LINK_TOOL_NAME,\r\n 'Attach a link to the TAMsense task as part of the result: a page or a file the result relies on.',\r\n {\r\n title: z.string().describe('what the link is, in a few words'),\r\n url: z.string().describe('the full address starting with https://'),\r\n },\r\n async args => await sent(() => tools.save({ kind: 'link', ...args })),\r\n ),\r\n ]\r\n}\r\n\r\nexport function handOverTool(tools: HandOverTools) {\r\n return tool(\r\n HAND_OVER_TOOL_NAME,\r\n `Hand the task to another board of the project as a linked task, when part of the work belongs to that board. Boards you may hand to: ${tools.boards.map(board => board.name).join(', ')}. Without autopilot a person confirms it first.`,\r\n {\r\n board: z.string().describe('name of the board'),\r\n title: z.string().describe('title of the new task on that board, one plain sentence'),\r\n note: z.string().describe('what you found and what that board should do; never names, emails or other personal data of clients'),\r\n wait: z.boolean().describe('true when this task should wait on its step until the linked task is done'),\r\n },\r\n async args => await sent(() => tools.send(args)),\r\n )\r\n}\r\n","// ТЗ задачи компактным текстом для Claude: те же разделы, что видит человек на сайте.\r\n// Повторяет specPromptText сайта (apps/web/shared/utils/taskSpecView.ts): программа ставится на компьютер отдельно\r\n// и пакетов сайта не видит. Старые ТЗ (критерии и допущения строками, без новых разделов) читаются так же\r\n\r\ntype Fields = Record<string, unknown>\r\n\r\nfunction isRecord(value: unknown): value is Fields {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\nfunction text(value: unknown): string {\r\n return typeof value === 'string' ? value.trim() : ''\r\n}\r\n\r\nfunction texts(value: unknown): string[] {\r\n return Array.isArray(value) ? value.map(text).filter(Boolean) : []\r\n}\r\n\r\nfunction records(value: unknown): Fields[] {\r\n return Array.isArray(value) ? value.filter(isRecord) : []\r\n}\r\n\r\nfunction section(title: string, lines: string[]): string | null {\r\n return lines.length > 0 ? `${title}\\n${lines.join('\\n')}` : null\r\n}\r\n\r\nfunction criterionLine(value: unknown): string | null {\r\n if (typeof value === 'string') {\r\n return value.trim() ? `- ${value.trim()}` : null\r\n }\r\n if (!isRecord(value) || !text(value.text)) {\r\n return null\r\n }\r\n const kind = ['main', 'edge', 'keep'].includes(value.kind as string) ? `(${value.kind as string})` : ''\r\n const head = [text(value.id), kind].filter(Boolean).join(' ')\r\n return `- ${head ? `${head}: ` : ''}${text(value.text)}`\r\n}\r\n\r\nfunction assumptionLine(value: unknown): string | null {\r\n if (typeof value === 'string') {\r\n return value.trim() ? `- ${value.trim()}` : null\r\n }\r\n if (!isRecord(value) || !text(value.text)) {\r\n return null\r\n }\r\n return `- ${text(value.text)}${text(value.reason) ? ` (${text(value.reason)})` : ''}`\r\n}\r\n\r\nfunction changeLines(change: Fields, index: number): string[] {\r\n const action = ['create', 'modify', 'delete'].includes(change.action as string) ? ` (${change.action as string})` : ''\r\n const [produces, consumes, acceptance] = [texts(change.produces), texts(change.consumes), texts(change.acceptance)]\r\n return [\r\n `${index + 1}. ${text(change.repo) ? `${text(change.repo)} ` : ''}${text(change.file)}${action}: ${text(change.description)}`,\r\n ...(produces.length > 0 ? [` Produces: ${produces.join('; ')}`] : []),\r\n ...(consumes.length > 0 ? [` Consumes: ${consumes.join('; ')}`] : []),\r\n ...(acceptance.length > 0 ? [` Makes true: ${acceptance.join(', ')}`] : []),\r\n ]\r\n}\r\n\r\nconst CODE_USE_TEXT: Record<string, string> = { reuse: 'reuse', change: 'change', keep: 'do not touch' }\r\nconst RISK_LEVELS = ['low', 'medium', 'high']\r\n\r\nfunction present(lines: Array<string | null>): string[] {\r\n return lines.filter((line): line is string => line !== null)\r\n}\r\n\r\nfunction decisionLines(spec: Fields): string[] {\r\n return records(spec.decisions)\r\n .filter(item => text(item.question) && text(item.answer))\r\n .map(item => `- ${text(item.question)} → ${text(item.answer)} (${item.by === 'agent' ? 'agent\\'s call' : 'owner'})`)\r\n}\r\n\r\n// Бриф результата доски без кода: что должно быть в результате и критерии, по которым его судят проверки\r\nfunction briefPromptText(brief: Fields): string {\r\n return [\r\n text(brief.goal) && `Goal: ${text(brief.goal)}`,\r\n text(brief.context) && `Summary: ${text(brief.context)}`,\r\n section('The result must contain:', texts(brief.contains).map(item => `- ${item}`)),\r\n section('Acceptance criteria:', present(Array.isArray(brief.acceptance) ? brief.acceptance.map(criterionLine) : [])),\r\n section('Constraints:', texts(brief.constraints).map(item => `- ${item}`)),\r\n section('Out of scope (do not make):', texts(brief.outOfScope).map(item => `- ${item}`)),\r\n section('Sources to use:', records(brief.sources).filter(item => text(item.source)).map(item => `- ${text(item.source)}${text(item.use) ? `: ${text(item.use)}` : ''}`)),\r\n text(brief.approver) && `Approved by: ${text(brief.approver)}`,\r\n section('Decisions:', decisionLines(brief)),\r\n section('Assumptions:', present(Array.isArray(brief.assumptions) ? brief.assumptions.map(assumptionLine) : [])),\r\n section('Open questions (not answered yet):', records(brief.openQuestions).filter(item => text(item.question)).map(item => `- ${text(item.question)}`)),\r\n ].filter(Boolean).join('\\n\\n')\r\n}\r\n\r\nexport function specPromptText(spec: unknown): string | null {\r\n if (!isRecord(spec)) {\r\n return null\r\n }\r\n if (spec.format === 'brief') {\r\n return briefPromptText(spec)\r\n }\r\n const structured = Array.isArray(spec.outOfScope) || isRecord(spec.testPlan)\r\n const plan = isRecord(spec.testPlan) ? spec.testPlan : {}\r\n const risks = records(spec.risks).filter(item => text(item.risk))\r\n const riskLevel = RISK_LEVELS.includes(spec.riskLevel as string) ? spec.riskLevel as string : null\r\n return [\r\n text(spec.goal) && `Goal: ${text(spec.goal)}`,\r\n text(spec.context) && `${structured ? 'Summary' : 'Context'}: ${text(spec.context)}`,\r\n section('In scope:', texts(spec.inScope).map(item => `- ${item}`)),\r\n section('Out of scope (do not build):', texts(spec.outOfScope).map(item => `- ${item}`)),\r\n section('Decisions:', decisionLines(spec)),\r\n section('Code map:', records(spec.codeMap)\r\n .filter(item => text(item.file))\r\n .map(item => `- ${text(item.repo) ? `${text(item.repo)} ` : ''}${text(item.file)}: ${text(item.role)}${CODE_USE_TEXT[item.use as string] ? ` [${CODE_USE_TEXT[item.use as string]}]` : ''}`)),\r\n section('Changes, in order:', records(spec.changes).filter(item => text(item.file)).flatMap(changeLines)),\r\n section('Acceptance criteria:', present(Array.isArray(spec.acceptance) ? spec.acceptance.map(criterionLine) : [])),\r\n section('Test plan:', [\r\n ...records(plan.automated).filter(item => text(item.command)).map(item => `- Run in ${text(item.repo) || 'the repository'}: ${text(item.command)}${text(item.expect) ? `. Passing: ${text(item.expect)}` : ''}`),\r\n ...records(plan.newTests).filter(item => text(item.seam)).map(item => `- New test at ${text(item.seam)}${texts(item.covers).length > 0 ? `, covers ${texts(item.covers).join(', ')}` : ''}`),\r\n ...records(plan.manual).filter(item => text(item.steps)).map(item => `- By hand${text(item.page) ? ` on ${text(item.page)}` : ''}: ${text(item.steps)}`),\r\n ]),\r\n risks.length > 0\r\n ? section(`Risks${riskLevel ? ` (risk level ${riskLevel})` : ''}:`, risks.map(item => `- ${text(item.risk)}${text(item.mitigation) ? `. Mitigation: ${text(item.mitigation)}` : ''}`))\r\n : riskLevel && `Risk level: ${riskLevel}`,\r\n section('Assumptions:', present(Array.isArray(spec.assumptions) ? spec.assumptions.map(assumptionLine) : [])),\r\n section('Open questions (not answered yet):', records(spec.openQuestions).filter(item => text(item.question)).map(item => `- ${text(item.question)}`)),\r\n ].filter(Boolean).join('\\n\\n')\r\n}\r\n","import type { RunnerJob, RunnerPerson, RunnerSpecWork } from './protocol'\r\nimport type { Workspace } from './workspace'\r\nimport { personLine } from './askPerson'\r\nimport { handoffTargetLine } from './handoff'\r\nimport { knowledgeText, workplaceText } from './knowledge'\r\nimport { isLocalJob } from './localWorkspace'\r\nimport { ADD_LINK_TOOL_NAME, ATTACH_FILE_TOOL_NAME, HAND_OVER_TOOL_NAME, isFolderJob, RESULTS_FOLDER, SAVE_DOCUMENT_TOOL_NAME, SAVE_TABLE_TOOL_NAME } from './results'\r\nimport { specPromptText } from './specText'\r\nimport { stepFolder } from './workspace'\r\n\r\nexport interface TaskPrompt {\r\n // первое сообщение: сама задача\r\n prompt: string\r\n // дописывается к стандартным инструкциям Claude Code: как работать в этом запуске\r\n instructions: string\r\n}\r\n\r\nexport const VERDICT_TOOL_NAME = 'submit_verdict'\r\nexport const SCREENSHOT_TOOL_NAME = 'attach_screenshot'\r\nexport const REPLAY_TOOL_NAME = 'attach_replay'\r\nexport const VIDEO_TOOL_NAME = 'attach_video'\r\nexport const HANDOFF_TOOL_NAME = 'hand_off'\r\nexport const SUBMIT_QUESTIONS_TOOL_NAME = 'submit_questions'\r\nexport const SUBMIT_SPEC_TOOL_NAME = 'submit_spec'\r\nexport const ASK_PERSON_TOOL_NAME = 'ask_person'\r\n\r\nconst MESSAGES_RULE = 'You are working on a TAMsense task on the user\\'s own computer. People on the task may send you messages during this run, each starting with \"Message from <name>:\". Follow them: they take priority over the original task where the two disagree.'\r\n\r\nconst UNATTENDED_RULE = `${MESSAGES_RULE} Nobody answers questions during the run, so never stop to wait for a reply: decide yourself, keep working, and explain your decisions in your report.`\r\n\r\nconst ASKING_RULE = `${MESSAGES_RULE} Decide yourself when you reasonably can, keep working, and explain your decisions in your report. When you are stuck, or the decision belongs to a person (intent, design, access, how the team runs something locally), ask one of the people on the task with the ${ASK_PERSON_TOOL_NAME} tool instead of guessing, and say what you tried. Never ask what the code or the project knowledge already answers. Nobody watches your messages, so ask only with the tool.`\r\n\r\n// Действия, о которых Claude Code спросил бы человека, решают правила проекта или люди задачи на сайте\r\nconst APPROVAL_RULE = 'Some actions need a person\\'s approval. When Claude Code would ask for one, TAMsense checks the project safety rules: a Never action is refused at once, an Allowed one goes ahead, and anything else goes to a person on the task while your tool call waits up to 15 minutes. Before such an action, write one short line: why you need it and what you will do instead if it is denied. Never get around a refused action with another command that has the same effect.'\r\n\r\nconst MACHINE_RULE = 'This is the person\\'s own computer, and their other programs may be running on it. Stop only the processes you started yourself, by their process id, and stop them before you finish. Never kill processes by name or pattern (taskkill /IM, pkill, killall, Stop-Process -Name) and never touch programs you did not start.'\r\n\r\nconst CODE_RULES = [\r\n 'Make the changes the task asks for, and keep them focused on it.',\r\n 'When there is an approved spec, follow it: make the changes in its order with the names and data shapes it gives, build nothing from its out of scope list, and add the tests and run the commands of its test plan.',\r\n 'Check your work with the project\\'s own tools: run the tests, type checks and linters that exist, and fix what you broke.',\r\n 'Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch and opens the pull request.',\r\n]\r\n\r\n// Репозитории из папок человека: ветку задачи отправляет TAMsense, если у папки есть удалённый, а сливает человек\r\nconst LOCAL_COMMIT_RULE = 'Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch when the folder has a remote, and the person merges it.'\r\n\r\nconst LOCAL_FOLDERS_RULE = 'Each repository here is a git worktree of the person\\'s own folder on this computer. Work only here: never open, change or check out branches in the person\\'s original folder.'\r\n\r\nconst REWORK_RULE = 'This is a rework. The branch already has the earlier work and maybe the reviewer\\'s own commits: treat the current branch as the source of truth, keep the reviewer\\'s changes unless a comment asks otherwise, and address every comment.'\r\n\r\nconst CODE_REPORT_RULE = 'End with a short report for the reviewer: what you changed, how you checked it, and anything left open.'\r\n\r\n// Роль только для чтения на шаге кода: работа это исследование и отчёт, ничего не коммитится и не отправляется\r\nconst READ_ONLY_CODE_RULES = [\r\n 'Your role on this step only reads. Do the work the task asks for by researching and running what helps, then report. Do not change, commit or push product code: nothing from this run is pushed. You may create throwaway files outside the repositories.',\r\n 'Check what you report with the project\\'s own tools where they exist, and say how you checked it.',\r\n]\r\n\r\nconst READ_ONLY_REPORT_RULE = 'End with a short report: what you found, how you checked it, and what is left open.'\r\n\r\nconst CHECK_RULES = [\r\n 'The work to check is on the current branch. See it with git diff against the base branch named for each repository below, and read the code around it.',\r\n 'Do not change, commit or push product code. You may run anything and create throwaway files outside the repositories.',\r\n 'Judge against the task, the spec and its acceptance criteria, checking each criterion by its id (AC-1 and so on) when the spec has ids. Be specific: every problem gets a file, a line and what to do about it.',\r\n `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`,\r\n]\r\n\r\nconst SCREENSHOT_RULE = `If you render screens (screenshots of the interface), attach the ones that show the result with the ${SCREENSHOT_TOOL_NAME} tool. People see them on the task without opening the code.`\r\n\r\nconst NAMING_RULE = 'Name everything you attach (screenshots, replays, videos) by what it shows and why it was made: a short title such as \"Design option 1\", \"Feature testing\" or \"Feature testing, attempt 2\", and a one-line purpose such as \"Comparing two layouts for the empty orders state\". Number design variants, name test runs by their goal and attempt, and never use generic names like \"screenshot1\".'\r\n\r\nconst RECORDING_RULE = `Every browser you open with Playwright from Node is recorded for the task as a replay people can watch, requests from the tested site to the TAMsense tracker are blocked, so your runs never reach analytics, and requests other than GET, HEAD and OPTIONS to addresses outside this computer are aborted. In every browser script, load the recorder from the TAMSENSE_RECORDER environment variable (require(process.env.TAMSENSE_RECORDER), or createRequire(import.meta.url)(process.env.TAMSENSE_RECORDER) in an .mjs script), open each context with await recorder.newRecordedContext(browser, { title, purpose, context: { viewport } }), call await recorder.mark(page, 'what just happened') at every meaningful step, and end with await recorder.closeRecorded(context). Add video: true when the page has a canvas, a map, a video player or iframes from another site (a replay cannot show them), or when someone asks for a video. Recordings are attached to the task when the run ends; ${REPLAY_TOOL_NAME} and ${VIDEO_TOOL_NAME} attach one earlier with a better title.`\r\n\r\nconst BROWSER_RULE = 'A headless browser is installed for interface checks. If the project has no Playwright of its own, load playwright-core from the folder in the TAMSENSE_PLAYWRIGHT environment variable, with PLAYWRIGHT_BROWSERS_PATH set to the folder in TAMSENSE_PLAYWRIGHT_BROWSERS.'\r\n\r\n// Доска без кода: репозиториев нет, работа идёт в папке задачи, а результат кладётся на задачу инструментами TAMsense\r\nconst FOLDER_RULE = 'This board works without code: there are no repositories. The current folder belongs to this task: work in it and keep your throwaway files here.'\r\n\r\nconst RESULT_HOME_RULE = 'The result lives only on the TAMsense task: never save it in outside services such as Google Drive or a shared folder. Use connected services only to read. Sending anything to real people, such as an email or a message, is an action that needs approval like any other.'\r\n\r\nconst FOLDER_DO_RULES = [\r\n `Save what you make on the task with the TAMsense tools: ${SAVE_DOCUMENT_TOOL_NAME} for text in Markdown, ${SAVE_TABLE_TOOL_NAME} for rows of data as CSV, ${ATTACH_FILE_TOOL_NAME} for files you made in this folder, ${ADD_LINK_TOOL_NAME} for pages the result relies on. Keep one title per document and save every change as a new version of it.`,\r\n 'Use only facts from the task, the notes, the earlier results and what you read yourself; never invent numbers, names or dates.',\r\n RESULT_HOME_RULE,\r\n]\r\n\r\nconst FOLDER_REWORK_RULE = 'The task came back with notes. Read the earlier results in the results folder, fix what the notes ask and save new versions of the same documents.'\r\n\r\nconst FOLDER_REPORT_RULE = 'End with a short report for the people on the task: what you made, what you decided yourself and what is still open.'\r\n\r\nconst FOLDER_CHECK_RULES = [\r\n `The result to check is in the ${RESULTS_FOLDER} folder: the latest version of each document (.md) and table (.csv), the files and links.md. Read all of it.`,\r\n `Do not change the result. You may create throwaway files outside the ${RESULTS_FOLDER} folder.`,\r\n 'Judge it against the task, the notes of earlier steps and the instructions of this step. Be specific: every problem names the document, the place in it and what to change.',\r\n `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`,\r\n]\r\n\r\nconst DEPENDENCIES_RULE = 'The checkout may have no dependencies installed. To run the app or its tests, install the project\\'s own dependencies from its lockfile with its package manager (pnpm install --frozen-lockfile, npm ci, yarn install --immutable and similar). Never add packages, change a lockfile or download browsers.'\r\n\r\n// Свой compose-проект у каждого шага задачи: проверки одной задачи поднимают приложение одновременно и не трогают контейнеры друг друга\r\nexport function composeProjectName(job: Pick<RunnerJob, 'taskId' | 'stage'>): string {\r\n return ['tamsense', ...stepFolder(job)].join('-')\r\n}\r\n\r\n// Приложение поднимается только локально из папки задачи; не поднялось из-за компьютера: проверка ждёт человека, а не проходит и не уходит назад.\r\n// Как команда готовит локальную копию, агент спрашивает у человека, если может\r\nfunction localAppRule(job: Pick<RunnerJob, 'taskId' | 'stage'>, isCheck: boolean, canAsk: boolean): string {\r\n const project = composeProjectName(job)\r\n const cannotRun = isCheck\r\n ? `call ${VERDICT_TOOL_NAME} with passed false and couldNotRun true, write the exact reason and what a person should do in the report, and do not hand off: the task waits on this step for a person.`\r\n : 'say so in your report with the exact reason and what a person should do.'\r\n return [\r\n 'When you need the running app, start a local copy from this task folder only.',\r\n 'Find how the project runs locally yourself: the README, compose files named local or dev, .env.example and local env files, package scripts.',\r\n canAsk ? `If you still cannot tell how the team prepares it, for example how the local database gets its tables, ask with ${ASK_PERSON_TOOL_NAME} instead of guessing.` : null,\r\n 'Use only settings meant for a local run that are in this checkout. Never read or copy .env files from elsewhere on this computer, including the person\\'s own checkout, and never use staging, shared dev or production addresses.',\r\n 'Before starting, check every address in the settings you use: databases, caches, queues and APIs must point at localhost or at containers you started, and keys of outside services stay empty.',\r\n `Start containers as a separate compose project named ${project} on free host ports (an override file in a temp folder), never stop or change containers you did not start, and remove yours with their volumes at the end (docker compose -p ${project} down -v).`,\r\n `If Docker is not running, there is no local setup, or the settings point outside this computer, do not start anything: ${cannotRun}`,\r\n ].filter(Boolean).join(' ')\r\n}\r\n\r\n// browser: на компьютере стоит браузер TAMsense для проверок интерфейса; recorder: браузер агента записывается;\r\n// results: что лежит в папке results на доске без кода\r\nexport interface TaskPromptOptions {\r\n browser?: boolean\r\n recorder?: boolean\r\n results?: string[] | null\r\n}\r\n\r\nconst STAGE_GOALS = {\r\n code: 'Implement',\r\n review: 'Review the code for',\r\n test: 'Test',\r\n} as const\r\n\r\nconst READ_ONLY_GOAL = 'Work on'\r\n\r\n// Шаг кода с ролью только для чтения ведёт себя как проверка без вердикта: ничего не меняет и не отправляет\r\nexport function isReadOnlyCode(stage: Pick<RunnerJob['stage'], 'kind' | 'role'>): boolean {\r\n return stage.kind === 'code' && stage.role?.access === 'read'\r\n}\r\n\r\nconst SPEC_UNATTENDED_RULE = 'You are preparing a TAMsense task on the user\\'s own computer. Nobody answers you during this run: the owner answers your questions and reviews the spec on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with \"Message from <name>:\". Follow them.'\r\n\r\nconst READ_ONLY_RULE = 'Read the code to research the task. Do not change, commit or push anything: nothing from this run is pushed, and the code step starts from a clean branch.'\r\n\r\nconst RESEARCH_RULE = 'Start by loading the tamsense:task-interview skill with the Skill tool and follow it: it holds the question test, the corner sweep and the rules for rounds. Research before asking: read the code in the repositories here and the evidence of the linked action, if there is one. Finding facts is your job; the decisions about intent are the owner\\'s.'\r\n\r\nconst BRIEF_RULE = 'Write the brief as you go: findings (what the code does today that matters, each with owner/repo path:line), dataFacts (numbers and facts from the linked action, each with its source; empty when none), approach (2 to 4 sentences), codeMap (the files the change will touch or must reuse, each with its role), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none).'\r\n\r\nconst QUESTION_SHAPE_RULE = 'Each question has a snake_case id, one plain sentence the owner can answer without reading code, a whyItMatters line, and 2 to 4 concrete options grounded in what you saw, each saying what happens if it is chosen. Put the option you recommend first and set recommended on it (in a multiSelect question, on every option you recommend). multiSelect is true only when the options are additive. Never add an \"Other\" option: the page adds a free-text field. An unanswered question takes your recommended option, so recommend the one you would defend.'\r\n\r\nconst QUESTIONS_REPLY_RULE = 'Then reply in one or two plain sentences: what you understood from the code and why you ask these questions, or that no questions are needed. Do not repeat the questions: the page shows them.'\r\n\r\nconst SPEC_RULES = [\r\n 'Start by loading the tamsense:spec-writing skill with the Skill tool and follow it: it holds the shape of the spec, the rules for acceptance criteria, the size budget, the lint list and the builder check.',\r\n 'Start from the brief and the conversation below. Verify in the code what the spec depends on and research only what is missing; without a brief, research first as the tamsense task-interview skill describes.',\r\n 'Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked \"Recommended option applied, not answered\" is an assumption with the reason \"not answered, the recommended option applies\". If an answer contradicts the code or another answer, check the code before following it and say so in your reply.',\r\n 'Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the scope or the behaviour becomes an open question; keep them to 3 at most.',\r\n 'Before you hand the spec in, run the builder check of the skill once. It is required, also for a small task: start one fresh general-purpose subagent with the Agent tool, give it only the spec as text inside the builder check prompt of the skill, and settle each point it reports as the skill says.',\r\n `Hand in the FULL spec with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it and checks the files, and sends it back once if no fresh reader has checked it; if it returns feedback, the spec was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of lint feedback the spec is saved as it is.`,\r\n 'When the task already has a saved spec, the conversation holds the owner\\'s feedback or answers to its open questions: apply them and hand in the full updated spec. An answered open question becomes a decision; a corrected or confirmed assumption becomes a decision and leaves the assumptions.',\r\n 'Then reply with a short plain summary: the approach, the risk level and the open questions, if any. Do not paste the spec: the page shows it.',\r\n]\r\n\r\nconst SPEC_WORK_MISSING = 'TAMsense sent a spec step without its round and brief.'\r\n\r\n// Шаг Clarify на доске без кода: вопросы и бриф результата вместо ТЗ разработки; кода и навыков разработки здесь нет\r\nconst BRIEF_UNATTENDED_RULE = 'You are preparing a TAMsense task on the user\\'s own computer. Nobody answers you during this run: the owner answers your questions and reviews the brief on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with \"Message from <name>:\". Follow them.'\r\n\r\nconst BRIEF_RESEARCH_RULE = 'Research before asking: read what the task, the project knowledge, the files in this folder and the connected services give you. Finding facts is your job; the decisions about intent are the owner\\'s.'\r\n\r\nconst RESEARCH_BRIEF_RULE = 'Write the research brief as you go: findings (what the sources say that matters, each with where: the source), dataFacts (numbers and facts, each with its source; empty when none), approach (2 to 4 sentences), codeMap (always empty: this board has no code), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none).'\r\n\r\nconst RESULT_BRIEF_RULES = [\r\n 'Write the brief of the result: goal, context (the summary), contains (what the result must contain), acceptance (criteria with ids AC-1, AC-2 and so on in order, each one observable property a checker confirms by reading the result), constraints, outOfScope, sources (where the facts come from: knowledge, linked items, files of the task, connected services that are read only), approver (who approves the result), decisions, assumptions and openQuestions. Set format to \"brief\". Never write a code map, files to touch or repositories.',\r\n 'Start from the research brief and the conversation below. Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked \"Recommended option applied, not answered\" is an assumption with the reason \"not answered, the recommended option applies\".',\r\n 'Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the result becomes an open question; keep them to 3 at most. Never invent numbers, names or dates.',\r\n `Hand in the FULL brief with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it; if it returns feedback, the brief was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of feedback the brief is saved as it is.`,\r\n 'When the task already has a saved brief, the conversation holds the owner\\'s feedback or answers to its open questions: apply them and hand in the full updated brief. An answered open question becomes a decision.',\r\n 'Then reply with a short plain summary: what the result is and the open questions, if any. Do not paste the brief: the page shows it.',\r\n]\r\n\r\nconst BRIEF_STYLE_RULE = 'Write the research brief, the questions, the brief and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real names, numbers and sources; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.'\r\n\r\nconst SPEC_STYLE_RULE = 'Write the brief, the questions, the spec and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real file paths, function and table names; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.'\r\n\r\nfunction specText(spec: unknown): string | null {\r\n const text = specPromptText(spec)\r\n return text ? `Approved spec:\\n${text}` : null\r\n}\r\n\r\n// Утверждённый бриф доски без кода: по его критериям AC-1 и так далее судят проверки\r\nfunction briefText(spec: unknown): string | null {\r\n const text = specPromptText(spec)\r\n return text ? `Approved brief:\\n${text}` : null\r\n}\r\n\r\n// Контекст проекта пишет команда в настройках доски: Claude следует ему, как правилам проекта\r\nfunction contextText(context: string): string | null {\r\n return context.trim() ? `Project context from the team (follow it like the project's own rules):\\n${context.trim()}` : null\r\n}\r\n\r\n// Правила безопасности проекта от команды идут после постоянных правил: агент знает, что можно, о чём спросить и чего никогда\r\nfunction safetyText(job: RunnerJob): string | null {\r\n return job.safetyRules?.trim() || null\r\n}\r\n\r\nfunction bulletList(lines: string[]): string {\r\n return lines.map(line => `- ${line}`).join('\\n')\r\n}\r\n\r\nfunction repositoryLines(job: RunnerJob, workspace: Workspace): string[] {\r\n const sourceOf = new Map(job.repositories.map(repository => [repository.fullName, repository]))\r\n return workspace.repositories.map((repository) => {\r\n const source = sourceOf.get(repository.fullName)\r\n const pull = source?.pullRequestUrl ? `, pull request ${source.pullRequestUrl}` : ''\r\n const base = repository.local ? repository.local.baseBranch : `origin/${source?.defaultBranch ?? 'main'}`\r\n return `${repository.folder} (${repository.fullName}) at commit ${repository.baseSha.slice(0, 7)}, base branch ${base}${pull}`\r\n })\r\n}\r\n\r\nfunction codeRules(job: RunnerJob): string[] {\r\n return isLocalJob(job) ? [...CODE_RULES.slice(0, -1), LOCAL_COMMIT_RULE] : CODE_RULES\r\n}\r\n\r\nfunction stepLine(stage: RunnerJob['stage']): string {\r\n return `This step of the board is \"${stage.name}\"${stage.helper ? `, and you work as the ${stage.helper}` : ''}.`\r\n}\r\n\r\n// Сообщения людей, которые ждали этого запуска, идут вместе с задачей\r\nfunction teamMessagesText(messages: RunnerJob['messages']): string | null {\r\n return messages.length > 0 ? `Messages from the team before this run:\\n${messages.map(message => `${message.authorName}: ${message.text}`).join('\\n')}` : null\r\n}\r\n\r\n// Кого из людей задачи агент может спросить и как выбрать: исполнителя первым, вопрос по теме тому, чья роль подходит\r\nfunction peopleText(people: RunnerPerson[]): string | null {\r\n if (people.length === 0) {\r\n return null\r\n }\r\n return [\r\n `People on this task you can ask with ${ASK_PERSON_TOOL_NAME} (id: name, relation, access, then their team roles with what each owns):`,\r\n bulletList(people.map(personLine)),\r\n 'Ask the assignee first. A question on a topic goes to the person whose team role fits it, and then why names that role and what it owns, for example \"Designer, owns empty states\". Set blocking only when you cannot go on without the answer: the run then pauses and the computer is freed for other tasks. Otherwise give the assumption you continue with.',\r\n ].join('\\n')\r\n}\r\n\r\n// Заметку из ответа человека предлагает агент, который спрашивает людей; сайт прежней версии знаний не принимает\r\nexport function canProposeKnowledge(job: RunnerJob): boolean {\r\n return job.knowledge !== undefined && (job.people ?? []).length > 0 && job.stage.kind !== 'interview' && job.stage.kind !== 'spec'\r\n}\r\n\r\n// Что задача уже сделала: файлы папки results; на доске без кода вместо списка репозиториев\r\nfunction folderText(root: string, results: string[] | null | undefined): string {\r\n const lines = results ?? []\r\n return lines.length > 0\r\n ? `The task folder is ${root}. The results of the task so far, latest versions:\\n${bulletList(lines)}`\r\n : `The task folder is ${root}. The task has no result yet.`\r\n}\r\n\r\n// Другая доска проекта, на которую шаг может передать задачу; агент сам решает, нужна ли передача\r\nfunction handOverText(boards: RunnerJob['handOverBoards']): string | null {\r\n const names = (boards ?? []).map(board => board.name)\r\n if (names.length === 0) {\r\n return null\r\n }\r\n return `When part of the work belongs to another board, hand it over with the ${HAND_OVER_TOOL_NAME} tool: ${names.join(', ')}. Write a plain title and a note on what you found and what that board should do; never put names, emails or other personal data of clients in the note. With wait true this task waits on its step until the linked task is done, then it goes on and you get its result note.`\r\n}\r\n\r\n// Куда агент может передать задачу по правилам доски; на шаге проверки вердикт всё равно нужен\r\nfunction handoffText(targets: RunnerJob['allowedHandoffs'], isCheck: boolean): string | null {\r\n if (targets.length === 0) {\r\n return null\r\n }\r\n return [\r\n `When you finish, hand the task on with the ${HANDOFF_TOOL_NAME} tool. Write a clear note for the next agent and the task owner: what was done, what to check and what is still open. The steps this task can go to from here:`,\r\n bulletList(targets.map(handoffTargetLine)),\r\n `If none of them fits, just finish without handing off.${isCheck ? ` Hand in your verdict with ${VERDICT_TOOL_NAME} as well.` : ''}`,\r\n ].join('\\n')\r\n}\r\n\r\nfunction threadText(thread: RunnerSpecWork['thread']): string | null {\r\n if (thread.length === 0) {\r\n return null\r\n }\r\n const lines = thread.map(message => `${message.role === 'user' ? 'Owner' : 'You, earlier'}: ${message.text}`)\r\n return `The conversation on the task so far (answers to the questions and the owner's feedback):\\n${lines.join('\\n\\n')}`\r\n}\r\n\r\nfunction questionRules(work: RunnerSpecWork): string[] {\r\n const handIn = `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`\r\n if (work.round === 1) {\r\n return [\r\n RESEARCH_RULE,\r\n BRIEF_RULE,\r\n `Ask 0 to ${work.maxQuestions} questions, ideally 2 or 3, the most important first, and only the ones that pass the three tests of the skill. Zero questions is a good result: the spec is then written right away. Ask only questions that do not depend on another question of this round; put a dependent decision into pendingDecisions.`,\r\n QUESTION_SHAPE_RULE,\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n }\r\n const taken = work.takenIds.length > 0 ? ` These ids are taken by round 1: ${work.takenIds.join(', ')}.` : ''\r\n return [\r\n `This is the follow-up round ${work.round} of 2, and there is no round after it. Follow the tamsense:task-interview skill (load it with the Skill tool if this conversation has not loaded it yet). Read the answers in the conversation below. Do not research again what your brief covers; check only what the answers changed.`,\r\n `Ask at most ${work.maxQuestions} more questions, and only when an answer is vague (\"not sure\", \"depends\", \"maybe\"), contradicts another answer or the code, or unlocked a decision from pendingDecisions that passes the tests of the tamsense task-interview skill. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,\r\n QUESTION_SHAPE_RULE,\r\n 'Update the brief with what the answers changed and leave pendingDecisions empty.',\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n}\r\n\r\nfunction specGoal(job: RunnerJob, work: RunnerSpecWork): string {\r\n if (work.round === null) {\r\n return `Write the spec for TAMsense task #${job.taskId}: ${job.title}`\r\n }\r\n return work.round > 1 ? `Follow-up questions for TAMsense task #${job.taskId}: ${job.title}` : `Prepare TAMsense task #${job.taskId} for its spec: ${job.title}`\r\n}\r\n\r\nfunction briefQuestionRules(work: RunnerSpecWork): string[] {\r\n const handIn = `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the research brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`\r\n if (work.round === 1) {\r\n return [\r\n BRIEF_RESEARCH_RULE,\r\n RESEARCH_BRIEF_RULE,\r\n `Ask 0 to ${work.maxQuestions} questions, ideally 2 or 3, the most important first, and only the ones research cannot settle, whose answers change the result, and where a wrong default is costly. Zero questions is a good result: the brief is then written right away.`,\r\n QUESTION_SHAPE_RULE,\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n }\r\n const taken = work.takenIds.length > 0 ? ` These ids are taken by round 1: ${work.takenIds.join(', ')}.` : ''\r\n return [\r\n `This is the follow-up round ${work.round} of 2, and there is no round after it. Read the answers in the conversation below; do not research again what your research brief covers.`,\r\n `Ask at most ${work.maxQuestions} more questions, and only when an answer is vague, contradicts another answer or the facts, or unlocked a decision from pendingDecisions. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,\r\n QUESTION_SHAPE_RULE,\r\n 'Update the research brief with what the answers changed and leave pendingDecisions empty.',\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n}\r\n\r\nfunction briefGoal(job: RunnerJob, work: RunnerSpecWork): string {\r\n if (work.round === null) {\r\n return `Write the brief for TAMsense task #${job.taskId}: ${job.title}`\r\n }\r\n return work.round > 1 ? `Follow-up questions for TAMsense task #${job.taskId}: ${job.title}` : `Prepare TAMsense task #${job.taskId} for its brief: ${job.title}`\r\n}\r\n\r\n// Шаг Clarify на доске без кода: Claude изучает источники задачи и сдаёт вопросы или бриф результата инструментами TAMsense\r\nfunction buildBriefPrompt(job: RunnerJob, work: RunnerSpecWork, workspace: Workspace): TaskPrompt {\r\n const prompt = [\r\n briefGoal(job, work),\r\n job.description,\r\n work.linkedAction,\r\n work.brief ? `Research brief from the clarification phase (verify only what you rely on):\\n${JSON.stringify(work.brief)}` : null,\r\n work.round === null && job.spec ? `Current saved brief (hand in the full updated version):\\n${JSON.stringify(job.spec)}` : null,\r\n job.handoffNote && `Note from another step: ${job.handoffNote}`,\r\n threadText(work.thread),\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const instructions = [\r\n BRIEF_UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n FOLDER_RULE,\r\n RESULT_HOME_RULE,\r\n `How to work:\\n${bulletList(work.round === null ? RESULT_BRIEF_RULES : briefQuestionRules(work))}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, false),\r\n BRIEF_STYLE_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n\r\n// Шаг ТЗ: Claude изучает код и сдаёт вопросы с брифом или ТЗ инструментами TAMsense; метод лежит в навыках task-interview и spec-writing\r\nfunction buildSpecPrompt(job: RunnerJob, work: RunnerSpecWork, workspace: Workspace): TaskPrompt {\r\n const prompt = [\r\n specGoal(job, work),\r\n job.description,\r\n work.linkedAction,\r\n work.brief ? `Brief from the clarification phase (research already done, verify only what you rely on):\\n${JSON.stringify(work.brief)}` : null,\r\n work.round === null && job.spec ? `Current saved spec (hand in the full updated version):\\n${JSON.stringify(job.spec)}` : null,\r\n job.handoffNote && `Note from another step: ${job.handoffNote}`,\r\n threadText(work.thread),\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const instructions = [\r\n SPEC_UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n `The repositories for this task are checked out in the current folder, one subfolder each:\\n${bulletList(repositoryLines(job, workspace))}`,\r\n isLocalJob(job) && LOCAL_FOLDERS_RULE,\r\n READ_ONLY_RULE,\r\n `How to work:\\n${bulletList(work.round === null ? SPEC_RULES : questionRules(work))}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, false),\r\n SPEC_STYLE_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n\r\nconst FOLDER_GOALS = {\r\n code: 'Work on',\r\n review: 'Check the result of',\r\n test: 'Check the result of',\r\n} as const\r\n\r\n// Доска без кода: шаг Do кладёт результат на задачу, проверка читает его из папки results\r\nfunction buildFolderPrompt(job: RunnerJob, workspace: Workspace, options: TaskPromptOptions): TaskPrompt {\r\n const kind = job.stage.kind === 'review' || job.stage.kind === 'test' ? job.stage.kind : 'code'\r\n const isCheck = kind !== 'code'\r\n const people = job.people ?? []\r\n const canAsk = people.length > 0\r\n const prompt = [\r\n `${FOLDER_GOALS[kind]} TAMsense task #${job.taskId}: ${job.title}`,\r\n job.description,\r\n briefText(job.spec),\r\n job.handoffNote && `Note from the previous step: ${job.handoffNote}`,\r\n job.review && `Notes to address:\\n${job.review}`,\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const rules = isCheck\r\n ? FOLDER_CHECK_RULES\r\n : [...FOLDER_DO_RULES, ...(job.review ? [FOLDER_REWORK_RULE] : []), FOLDER_REPORT_RULE]\r\n const instructions = [\r\n canAsk ? ASKING_RULE : UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n `${FOLDER_RULE} ${folderText(workspace.root, options.results)}`,\r\n `How to work:\\n${bulletList(rules)}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job)),\r\n APPROVAL_RULE,\r\n peopleText(people),\r\n isCheck ? null : handOverText(job.handOverBoards),\r\n handoffText(job.allowedHandoffs, isCheck),\r\n options.browser && BROWSER_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n\r\nexport function buildTaskPrompt(job: RunnerJob, workspace: Workspace, options: TaskPromptOptions = {}): TaskPrompt {\r\n const { kind } = job.stage\r\n if (kind === 'interview' || kind === 'spec') {\r\n if (!job.specWork) {\r\n throw new Error(SPEC_WORK_MISSING)\r\n }\r\n return isFolderJob(job) ? buildBriefPrompt(job, job.specWork, workspace) : buildSpecPrompt(job, job.specWork, workspace)\r\n }\r\n if (isFolderJob(job)) {\r\n return buildFolderPrompt(job, workspace, options)\r\n }\r\n const isCheck = kind !== 'code'\r\n const readOnly = isReadOnlyCode(job.stage)\r\n const people = job.people ?? []\r\n const canAsk = people.length > 0\r\n const prompt = [\r\n `${readOnly ? READ_ONLY_GOAL : STAGE_GOALS[kind]} TAMsense task #${job.taskId}: ${job.title}`,\r\n job.description,\r\n specText(job.spec),\r\n job.handoffNote && `Note from the previous step: ${job.handoffNote}`,\r\n job.review && `Review comments to address:\\n${job.review}`,\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const evidence = [SCREENSHOT_RULE, ...(options.recorder ? [RECORDING_RULE] : []), NAMING_RULE]\r\n const localApp = localAppRule(job, isCheck, canAsk)\r\n const rules = isCheck\r\n ? [...CHECK_RULES, DEPENDENCIES_RULE, localApp, ...evidence]\r\n : readOnly\r\n ? [...READ_ONLY_CODE_RULES, DEPENDENCIES_RULE, localApp, ...evidence, READ_ONLY_REPORT_RULE]\r\n : [...codeRules(job), ...(job.review ? [REWORK_RULE] : []), DEPENDENCIES_RULE, localApp, ...evidence, CODE_REPORT_RULE]\r\n const instructions = [\r\n canAsk ? ASKING_RULE : UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n `The repositories for this task are checked out in the current folder, one subfolder each, ${isCheck ? 'at the latest commit of branch' : 'on branch'} ${job.branch}:\\n${bulletList(repositoryLines(job, workspace))}`,\r\n isLocalJob(job) && LOCAL_FOLDERS_RULE,\r\n `How to work:\\n${bulletList(rules)}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job)),\r\n APPROVAL_RULE,\r\n peopleText(people),\r\n handoffText(job.allowedHandoffs, isCheck),\r\n options.browser && BROWSER_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n","import type { RunnerQuestions, RunnerReviewBody, RunnerReviewReply, RunnerSpecResult } from './protocol'\r\nimport { tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { SUBMIT_QUESTIONS_TOOL_NAME, SUBMIT_SPEC_TOOL_NAME } from './prompt'\r\n\r\n// Схемы повторяют questionsPayloadSchema и specSchema агентов облака (packages/agents) слово в слово:\r\n// программа ставится на компьютер отдельно и пакетов сайта не видит. Тест следит, чтобы они не разошлись\r\n\r\nconst MAX_FIRST_ROUND = 4\r\n\r\nexport const questionSchema = z.object({\r\n id: z.string().describe('Stable snake_case id, unique in the task, for example \"empty_state\"'),\r\n question: z.string().describe('One plain sentence the owner can answer without reading code'),\r\n whyItMatters: z.string().describe('One line: what changes in the result depending on the answer'),\r\n options: z.array(z.object({\r\n label: z.string().describe('Short, concrete answer (1-6 words), without \"(Recommended)\": use the recommended flag'),\r\n description: z.string().describe('One sentence: what happens if this option is chosen'),\r\n recommended: z.boolean().describe('true on the option you recommend (in a multiSelect question, on every option you recommend). Recommended options come first'),\r\n })).min(2).max(4).describe('2-4 mutually exclusive options grounded in the code and data. The UI always adds a free-text \"Other\" field, so never add \"Other\" yourself'),\r\n multiSelect: z.boolean().describe('true only when the options are additive and several can apply together'),\r\n})\r\n\r\nexport const briefSchema = z.object({\r\n findings: z.array(z.object({\r\n fact: z.string().describe('What the code does today that matters for this task'),\r\n where: z.string().describe('Where it is: owner/repo path:line'),\r\n })).describe('Findings from the code'),\r\n dataFacts: z.array(z.string()).describe('Numbers and facts from the events or the business data, each with its source. Empty when the task needs no data'),\r\n approach: z.string().describe('The approach you would take, in 2-4 sentences'),\r\n codeMap: z.array(z.object({\r\n repo: z.string().describe('Repository full name (owner/name)'),\r\n file: z.string().describe('Path relative to the repository root'),\r\n role: z.string().describe('Why it matters: what to change or reuse there'),\r\n })).describe('The files the change will touch or must reuse'),\r\n assumptions: z.array(z.object({\r\n text: z.string().describe('The default you chose'),\r\n reason: z.string().describe('Why it is safe or why it was chosen'),\r\n })).describe('Corners settled with a default instead of a question'),\r\n pendingDecisions: z.array(z.string()).describe('Decisions that depend on an answer to this round and cannot be asked yet. Empty when none'),\r\n})\r\n\r\nexport const questionsPayloadSchema = z.object({\r\n brief: briefSchema,\r\n questions: z.array(questionSchema).max(MAX_FIRST_ROUND).describe(`0-${MAX_FIRST_ROUND} questions that pass the question test, most important first. An empty list when research settled everything`),\r\n})\r\n\r\nexport const specSchema = z.object({\r\n goal: z.string().describe('The problem solved, for whom, and the outcome a user or the business will notice'),\r\n context: z.string().describe('Summary: the problem in 1-2 sentences, then the chosen approach in 1-2 sentences'),\r\n inScope: z.array(z.string()).describe('What this task delivers, one item each'),\r\n outOfScope: z.array(z.string()).describe('What this task does not do: nearby additions a coder might be tempted to make and approaches that are ruled out. Never empty'),\r\n decisions: z.array(z.object({\r\n question: z.string(),\r\n answer: z.string(),\r\n by: z.enum(['owner', 'agent']).describe('owner: answered by a person; agent: your own call'),\r\n })).describe('Every answered question with by: owner, and your own calls that matter with by: agent'),\r\n codeMap: z.array(z.object({\r\n repo: z.string().describe('Repository full name (owner/name)'),\r\n file: z.string().describe('Path relative to the repository root'),\r\n role: z.string().describe('What the coder needs to know about this file'),\r\n use: z.enum(['reuse', 'change', 'keep']).describe('reuse: build on it; change: it is in changes; keep: do not touch'),\r\n })).describe('The files a coder must read before starting'),\r\n changes: z.array(z.object({\r\n repo: z.string().describe('Repository full name (owner/name) where the change happens'),\r\n file: z.string().describe('Path to the file, relative to the repository root'),\r\n action: z.enum(['create', 'modify', 'delete']),\r\n description: z.string().describe('The behaviour the file has after the change and which existing functions or patterns to extend. No code snippets or diffs'),\r\n produces: z.array(z.string()).describe('Names with signatures or data shapes this change creates or changes for other changes. Empty when none'),\r\n consumes: z.array(z.string()).describe('Names with signatures or data shapes this change uses from earlier changes. Empty when none'),\r\n acceptance: z.array(z.string()).describe('Ids of the criteria this change makes true, like AC-1'),\r\n })).min(1).describe('File changes in dependency order: each uses only what earlier changes or the code already provide'),\r\n acceptance: z.array(z.object({\r\n id: z.string().describe('AC-1, AC-2 and so on, in order'),\r\n kind: z.enum(['main', 'edge', 'keep']).describe('main: the main path; edge: an error, empty or limit case; keep: existing behaviour that must keep working'),\r\n text: z.string().describe('\"Given <state>, when <action>, then <observable result>\", saying where to look. No file paths or function names'),\r\n })).min(1).describe('Acceptance criteria: main path first, then edge cases, then what must keep working'),\r\n testPlan: z.object({\r\n automated: z.array(z.object({\r\n repo: z.string(),\r\n command: z.string().describe('The exact command to run'),\r\n expect: z.string().describe('What passing looks like'),\r\n })).describe('At least one command'),\r\n newTests: z.array(z.object({\r\n seam: z.string().describe('Where the test goes: the highest seam that covers the behaviour (an endpoint, a page, a public function)'),\r\n covers: z.array(z.string()).describe('Ids of the criteria it covers'),\r\n })),\r\n manual: z.array(z.object({\r\n page: z.string().describe('The page or screen'),\r\n steps: z.string().describe('What a person does and sees'),\r\n })),\r\n }),\r\n risks: z.array(z.object({\r\n risk: z.string(),\r\n mitigation: z.string(),\r\n })).describe('What could go wrong: migrations, existing data, access, performance, compatibility'),\r\n riskLevel: z.enum(['low', 'medium', 'high']).describe('How closely a person should review the result'),\r\n assumptions: z.array(z.object({\r\n text: z.string().describe('The default you chose'),\r\n reason: z.string().describe('Why, so the owner can confirm or correct it quickly'),\r\n })).describe('Defaults chosen where the task was silent. Empty when nothing was assumed'),\r\n openQuestions: z.array(questionSchema).describe('Questions only the owner can settle; the spec cannot be approved until each is answered or dismissed. Empty when none'),\r\n split: z.array(z.string()).describe('When the task holds more than one shippable goal or more than about 8 files: the separate tasks you propose, one line each. Empty otherwise'),\r\n})\r\n\r\n// Бриф результата доски без кода: что сделать и как проверить, без карты кода, файлов и репозиториев\r\nexport const resultBriefSchema = z.object({\r\n format: z.literal('brief').describe('Always \"brief\"'),\r\n goal: z.string().describe('What the work is for, for whom, and the outcome people will notice'),\r\n context: z.string().describe('Summary: the situation in 1-2 sentences, then how the work will be done in 1-2 sentences'),\r\n contains: z.array(z.string()).min(1).describe('What the result must contain, one item each: the parts, facts and attachments a reader expects'),\r\n acceptance: z.array(z.object({\r\n id: z.string().describe('AC-1, AC-2 and so on, in order'),\r\n text: z.string().describe('One observable property of the result that a checker can confirm by reading it'),\r\n })).min(1).describe('Acceptance criteria the checks judge the result by, the most important first'),\r\n constraints: z.array(z.string()).describe('Rules the result must keep: length, tone, language, deadlines, what must not be promised. Empty when none'),\r\n outOfScope: z.array(z.string()).describe('What this work does not do: nearby things someone might be tempted to add. Empty when nothing is tempting'),\r\n sources: z.array(z.object({\r\n source: z.string().describe('Where the facts come from: a project knowledge note, a linked item, a file of the task, a connected service read only'),\r\n use: z.string().describe('What to take from it'),\r\n })).describe('Sources the work must use. Empty when the task itself is enough'),\r\n approver: z.string().describe('Who approves the result, as the task or the board names them'),\r\n decisions: z.array(z.object({\r\n question: z.string(),\r\n answer: z.string(),\r\n by: z.enum(['owner', 'agent']).describe('owner: answered by a person; agent: your own call'),\r\n })).describe('Every answered question with by: owner, and your own calls that matter with by: agent'),\r\n assumptions: z.array(z.object({\r\n text: z.string().describe('The default you chose'),\r\n reason: z.string().describe('Why, so the owner can confirm or correct it quickly'),\r\n })).describe('Defaults chosen where the task was silent. Empty when nothing was assumed'),\r\n openQuestions: z.array(questionSchema).describe('Questions only the owner can settle; the brief cannot be approved until each is answered or dismissed. Empty when none'),\r\n})\r\n\r\n// Проверка на сайте теми же правилами, что в облаке; ответ уходит Claude как результат инструмента\r\nexport type ReviewRequest = (body: RunnerReviewBody) => Promise<RunnerReviewReply>\r\n\r\nfunction reply(text: string, isError = false) {\r\n return { content: [{ type: 'text' as const, text }], ...(isError ? { isError: true } : {}) }\r\n}\r\n\r\nfunction unreachable(error: unknown, toolName: string) {\r\n const message = error instanceof Error ? error.message : String(error)\r\n return reply(`Could not reach TAMsense to check this: ${message}. Call ${toolName} again in a moment.`, true)\r\n}\r\n\r\n// Принятые вопросы заменяют прежние; отклонённые не сохраняются, Claude получает замечания и присылает заново\r\nexport function questionsTool(review: ReviewRequest, onSubmit: (questions: RunnerQuestions) => void) {\r\n return tool(\r\n SUBMIT_QUESTIONS_TOOL_NAME,\r\n 'Hand in the brief and the clarifying questions of this round. Call it once, after the research. Pass an empty questions list when research settled everything: the spec is then written next. If it returns problems, the questions were not saved: fix them and call it again.',\r\n questionsPayloadSchema.shape,\r\n async (args) => {\r\n let verdict: RunnerReviewReply\r\n try {\r\n verdict = await review({ questions: args })\r\n }\r\n catch (error) {\r\n return unreachable(error, SUBMIT_QUESTIONS_TOOL_NAME)\r\n }\r\n if (!verdict.accepted) {\r\n return reply(verdict.feedback, true)\r\n }\r\n onSubmit(args)\r\n return reply(args.questions.length > 0\r\n ? 'Questions saved. The owner answers them on the task page after this run. Finish now with a reply of one or two sentences.'\r\n : 'Saved with no questions: the spec is written next. Finish now with a reply of one or two sentences.')\r\n },\r\n )\r\n}\r\n\r\nconst BUILDER_CHECK_FIRST = 'The spec passed the lint but was not saved yet: the builder check of the tamsense:spec-writing skill has not run in this session. Start one fresh general-purpose subagent with the Agent tool, give it only the spec as text inside the builder check prompt of the skill, settle each point it reports as the skill says, then call submit_spec again with the full spec.'\r\n\r\n// Сколько раз ТЗ уже возвращали, считает сам инструмент: сайт после двух доработок принимает ТЗ как есть.\r\n// Как в облаке, ТЗ без проверки свежим читателем один раз возвращается: builderCheckRan говорит, звал ли Claude помощника\r\nexport function specTool(review: ReviewRequest, onSubmit: (result: RunnerSpecResult) => void, builderCheckRan: () => boolean) {\r\n let repairs = 0\r\n let reminded = false\r\n return tool(\r\n SUBMIT_SPEC_TOOL_NAME,\r\n 'Hand in the implementation spec. It replaces the previous version entirely, so always send the FULL spec. TAMsense lints it and checks the files first; if it returns feedback, the spec was not saved: fix it and call submit_spec again.',\r\n specSchema.shape,\r\n async (args) => {\r\n let verdict: RunnerReviewReply\r\n try {\r\n verdict = await review({ spec: args, repairs })\r\n }\r\n catch (error) {\r\n return unreachable(error, SUBMIT_SPEC_TOOL_NAME)\r\n }\r\n if (!verdict.accepted) {\r\n repairs += 1\r\n return reply(verdict.feedback, true)\r\n }\r\n if (!builderCheckRan() && !reminded) {\r\n reminded = true\r\n return reply(BUILDER_CHECK_FIRST, true)\r\n }\r\n onSubmit({ spec: args, problems: verdict.problems })\r\n return reply(verdict.problems.length > 0\r\n ? `Spec saved with these problems left: ${verdict.problems.join('; ')}. Mention them in your reply.`\r\n : 'Spec saved.')\r\n },\r\n )\r\n}\r\n\r\n// На доске без кода тем же инструментом сдаётся бриф результата: его проверяет линт сайта, свежего читателя у брифа нет\r\nexport function briefTool(review: ReviewRequest, onSubmit: (result: RunnerSpecResult) => void) {\r\n let repairs = 0\r\n return tool(\r\n SUBMIT_SPEC_TOOL_NAME,\r\n 'Hand in the brief of the result. It replaces the previous version entirely, so always send the FULL brief. TAMsense lints it first; if it returns feedback, the brief was not saved: fix it and call submit_spec again.',\r\n resultBriefSchema.shape,\r\n async (args) => {\r\n let verdict: RunnerReviewReply\r\n try {\r\n verdict = await review({ spec: args, repairs })\r\n }\r\n catch (error) {\r\n return unreachable(error, SUBMIT_SPEC_TOOL_NAME)\r\n }\r\n if (!verdict.accepted) {\r\n repairs += 1\r\n return reply(verdict.feedback, true)\r\n }\r\n onSubmit({ spec: args, problems: verdict.problems })\r\n return reply(verdict.problems.length > 0\r\n ? `Brief saved with these problems left: ${verdict.problems.join('; ')}. Mention them in your reply.`\r\n : 'Brief saved.')\r\n },\r\n )\r\n}\r\n","import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'\r\n\r\nexport const USAGE_LIMIT_REPORT = 'Claude hit its usage limit'\r\n\r\nconst DEFAULT_PAUSE_MS = 30 * 60_000\r\nconst DAY_MS = 24 * 60 * 60_000\r\n// время сброса в секундах Unix, если число меньше этого; иначе в миллисекундах\r\nconst SECONDS_BELOW = 1e11\r\n\r\n// Тексты Claude Code про исчерпанный лимит (USAGE_LIMIT_ERROR_PREFIXES в SDK) и старый вид «usage limit reached|<время>»\r\nconst LIMIT_TEXT = /You['’]ve (?:hit|reached) your|You['’]re out of (?:usage credits|extra usage)|usage limit/i\r\nconst EPOCH_RESET = /\\|(\\d{10,13})\\b/\r\n// «resets 3pm», «resets 5:30am (Europe/Berlin)», «resets Oct 25, 3pm», «resets at 15:00»\r\nconst CLOCK_RESET = /resets?\\s+(?:at\\s+)?(?:([a-z]{3})[a-z]*\\.?\\s+(\\d{1,2}),?\\s+(?:at\\s+)?)?(\\d{1,2})(?::(\\d{2}))?\\s*([ap]m)?/i\r\nconst MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']\r\n\r\nfunction fromEpoch(value: number): Date {\r\n return new Date(value < SECONDS_BELOW ? value * 1000 : value)\r\n}\r\n\r\nfunction hourOf(hour: number, meridiem: string | undefined): number | null {\r\n if (!meridiem) {\r\n return hour < 24 ? hour : null\r\n }\r\n if (hour < 1 || hour > 12) {\r\n return null\r\n }\r\n return (hour % 12) + (meridiem.toLowerCase() === 'pm' ? 12 : 0)\r\n}\r\n\r\n// Время сброса из текста Claude Code, в часовом поясе этого компьютера; null, если его нет или оно уже прошло\r\nexport function resetTimeOf(text: string, now: Date): Date | null {\r\n const epoch = EPOCH_RESET.exec(text)\r\n if (epoch) {\r\n const reset = fromEpoch(Number(epoch[1]))\r\n return reset > now ? reset : null\r\n }\r\n const clock = CLOCK_RESET.exec(text)\r\n if (!clock) {\r\n return null\r\n }\r\n const [, monthName, day, hourText, minuteText, meridiem] = clock\r\n const hour = hourOf(Number(hourText), meridiem)\r\n const minute = Number(minuteText ?? 0)\r\n const month = monthName ? MONTHS.indexOf(monthName.toLowerCase()) : now.getMonth()\r\n if (hour === null || minute > 59 || month < 0) {\r\n return null\r\n }\r\n const reset = new Date(now.getFullYear(), month, day ? Number(day) : now.getDate(), hour, minute)\r\n if (reset > now) {\r\n return reset\r\n }\r\n if (!monthName) {\r\n return new Date(reset.getTime() + DAY_MS)\r\n }\r\n // «Jan 2» в конце декабря: сброс в следующем году\r\n const nextYear = new Date(now.getFullYear() + 1, month, Number(day), hour, minute)\r\n return nextYear.getTime() - now.getTime() < 31 * DAY_MS ? nextYear : null\r\n}\r\n\r\nfunction assistantText(message: Extract<SDKMessage, { type: 'assistant' }>): string {\r\n return message.message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\\n')\r\n}\r\n\r\nexport interface UsageLimitWatch {\r\n observe: (message: SDKMessage) => void\r\n // когда можно продолжить (ISO), если запуск не закончен из-за лимита; null, если лимит ни при чём\r\n pausedUntil: (completed: boolean, now?: Date) => string | null\r\n}\r\n\r\n// Следит за сообщениями Claude Code: отказ по лимиту подписки, ошибка rate_limit или текст про исчерпанный лимит.\r\n// Удачный итог хода стирает прошлые признаки: значит, Claude продолжил работу\r\nexport function usageLimitWatch(): UsageLimitWatch {\r\n let limitedByText = false\r\n let limitedByEvent = false\r\n let eventReset: Date | null = null\r\n let texts: string[] = []\r\n\r\n function hitText(text: string): void {\r\n limitedByText = true\r\n texts.push(text)\r\n }\r\n\r\n return {\r\n observe(message) {\r\n if (message.type === 'rate_limit_event') {\r\n const info = message.rate_limit_info\r\n limitedByEvent = info.status === 'rejected' && !info.isUsingOverage\r\n eventReset = limitedByEvent && info.resetsAt ? fromEpoch(info.resetsAt) : null\r\n }\r\n else if (message.type === 'assistant' && message.error) {\r\n const text = assistantText(message)\r\n if (message.error === 'rate_limit' || LIMIT_TEXT.test(text)) {\r\n hitText(text)\r\n }\r\n }\r\n else if (message.type === 'result') {\r\n if (message.subtype === 'success' && !message.is_error) {\r\n limitedByText = false\r\n texts = []\r\n return\r\n }\r\n const text = message.subtype === 'success' ? message.result : message.errors.join('\\n')\r\n if (LIMIT_TEXT.test(text)) {\r\n hitText(text)\r\n }\r\n }\r\n },\r\n pausedUntil(completed, now = new Date()) {\r\n if (completed || !(limitedByText || limitedByEvent)) {\r\n return null\r\n }\r\n const fromText = texts.map(text => resetTimeOf(text, now)).find(reset => reset !== null)\r\n const reset = eventReset && eventReset > now ? eventReset : fromText ?? new Date(now.getTime() + DEFAULT_PAUSE_MS)\r\n return reset.toISOString()\r\n },\r\n }\r\n}\r\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\r\nimport { homedir } from 'node:os'\r\nimport { dirname, join } from 'node:path'\r\n\r\nexport interface RunnerConfig {\r\n server: string\r\n token: string\r\n deviceId: number\r\n deviceName: string\r\n}\r\n\r\nconst OWNER_ONLY_FILE = 0o600\r\nconst OWNER_ONLY_FOLDER = 0o700\r\n\r\nexport function configPath(home: string = homedir()): string {\r\n return join(home, '.tamsense', 'runner.json')\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\nfunction parseConfig(value: unknown): RunnerConfig | null {\r\n if (!isRecord(value)) {\r\n return null\r\n }\r\n const { server, token, deviceId, deviceName } = value\r\n const isValid = typeof server === 'string' && typeof token === 'string' && typeof deviceId === 'number' && typeof deviceName === 'string'\r\n return isValid ? { server, token, deviceId, deviceName } : null\r\n}\r\n\r\nfunction isMissingFile(error: unknown): boolean {\r\n return error instanceof Error && 'code' in error && error.code === 'ENOENT'\r\n}\r\n\r\n// Испорченный файл считается отсутствующим: программа предложит подключиться заново\r\nexport async function readConfig(path: string = configPath()): Promise<RunnerConfig | null> {\r\n try {\r\n return parseConfig(JSON.parse(await readFile(path, 'utf8')))\r\n }\r\n catch (error) {\r\n if (isMissingFile(error) || error instanceof SyntaxError) {\r\n return null\r\n }\r\n throw error\r\n }\r\n}\r\n\r\n// Ключ компьютера читает только его владелец\r\nexport async function writeConfig(config: RunnerConfig, path: string = configPath()): Promise<void> {\r\n await mkdir(dirname(path), { recursive: true, mode: OWNER_ONLY_FOLDER })\r\n await writeFile(path, `${JSON.stringify(config, null, 4)}\\n`, { mode: OWNER_ONLY_FILE })\r\n}\r\n\r\nexport async function removeConfig(path: string = configPath()): Promise<void> {\r\n await rm(path, { force: true })\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { ConnectedDevice, PairingStartResponse, PairingTokenResponse } from './protocol'\r\nimport { LOGIN_COMMAND } from './commands'\r\nimport { PAIRING_TOKEN_PATH, PAIRINGS_PATH } from './protocol'\r\n\r\nexport interface LoginRequest {\r\n name: string\r\n platform: string\r\n}\r\n\r\nexport interface LoginTools {\r\n api: ApiClient\r\n openBrowser: (url: string) => void\r\n print: (line: string) => void\r\n sleep: (ms: number) => Promise<void>\r\n now: () => number\r\n}\r\n\r\nexport interface LoginResult {\r\n token: string\r\n device: ConnectedDevice\r\n}\r\n\r\nconst MS_PER_SECOND = 1000\r\nconst EXPIRED_TEXT = `The code expired before it was confirmed. Run ${LOGIN_COMMAND} again.`\r\nconst DENIED_TEXT = 'The connection was cancelled in the browser. This computer was not connected.'\r\n\r\n// Как вход в gh: программа показывает код и ссылку, человек подтверждает их на сайте, программа ждёт ключ\r\nexport async function login(tools: LoginTools, request: LoginRequest): Promise<LoginResult> {\r\n const pairing = await tools.api.request<PairingStartResponse>('POST', PAIRINGS_PATH, request)\r\n const pageUrl = new URL(pairing.verificationPath, tools.api.server).toString()\r\n const expiresAt = Date.parse(pairing.expiresAt)\r\n\r\n tools.print(`Your code: ${pairing.userCode}`)\r\n tools.print(`Confirm it in the browser: ${pageUrl}`)\r\n tools.print('Waiting for confirmation…')\r\n tools.openBrowser(pageUrl)\r\n\r\n while (tools.now() < expiresAt) {\r\n await tools.sleep(pairing.intervalSeconds * MS_PER_SECOND)\r\n const reply = await tools.api.request<PairingTokenResponse>('POST', PAIRING_TOKEN_PATH, { deviceCode: pairing.deviceCode })\r\n if (reply.status === 'connected') {\r\n return { token: reply.token, device: reply.device }\r\n }\r\n if (reply.status === 'denied') {\r\n throw new Error(DENIED_TEXT)\r\n }\r\n if (reply.status === 'expired') {\r\n break\r\n }\r\n }\r\n throw new Error(EXPIRED_TEXT)\r\n}\r\n","import type { RunnerJob } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { join } from 'node:path'\r\nimport { fileURLToPath } from 'node:url'\r\nimport { DEFAULT_PURPOSE_ENV, DEFAULT_TITLE_ENV, MAX_PURPOSE_LENGTH, MAX_TITLE_LENGTH, RECORDINGS_DIR_ENV, SERVER_ENV } from './recorder'\r\n\r\nexport const RECORDER_ENV = 'TAMSENSE_RECORDER'\r\n\r\n// Модуль записи собирается рядом с программой; в исходниках его нет, и тогда браузер агента не записывается\r\nexport function bundledRecorder(): string | null {\r\n const path = fileURLToPath(new URL('./recorder.cjs', import.meta.url))\r\n return existsSync(path) ? path : null\r\n}\r\n\r\n// Своя папка у каждого запуска: что не ушло на сайт, удаляется вместе с ней\r\nexport function recordingsFolder(home: string, runId: number): string {\r\n return join(home, '.tamsense', 'recordings', `run-${runId}`)\r\n}\r\n\r\nexport interface RecorderEnvInput {\r\n recorder: string\r\n folder: string\r\n server: string\r\n job: Pick<RunnerJob, 'taskId' | 'stage'>\r\n nodeOptions: string | undefined\r\n}\r\n\r\n// Внутри NODE_OPTIONS обратная косая черта в кавычках считается экранированием: путь идёт с прямыми\r\nexport function nodeOptionsWithRecorder(recorder: string, nodeOptions: string | undefined): string {\r\n return [nodeOptions?.trim(), `--require \"${recorder.replaceAll('\\\\', '/')}\"`].filter(Boolean).join(' ')\r\n}\r\n\r\n// Каждый процесс Node агента подгружает запись, и любой браузер Playwright записывается сам, с названием шага по умолчанию\r\nexport function recorderEnv(input: RecorderEnvInput): Record<string, string> {\r\n const { job } = input\r\n return {\r\n [RECORDER_ENV]: input.recorder,\r\n [RECORDINGS_DIR_ENV]: input.folder,\r\n [SERVER_ENV]: input.server,\r\n [DEFAULT_TITLE_ENV]: `${job.stage.name} browser session`.slice(0, MAX_TITLE_LENGTH),\r\n [DEFAULT_PURPOSE_ENV]: `Recorded automatically in the ${job.stage.name} step of task #${job.taskId}`.slice(0, MAX_PURPOSE_LENGTH),\r\n NODE_OPTIONS: nodeOptionsWithRecorder(input.recorder, input.nodeOptions),\r\n }\r\n}\r\n","import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\r\nimport Module, { createRequire } from 'node:module'\r\nimport { basename, dirname, join } from 'node:path'\r\nimport process from 'node:process'\r\n\r\n// Запись браузера агента: rrweb пишет каждую страницу в свой файл NDJSON в папке запуска, по желанию ещё и видео.\r\n// Модуль самодостаточный: программа подключает его ко всем процессам Node агента через NODE_OPTIONS --require,\r\n// и тогда любой браузер Playwright записывается сам; скрипты агента импортируют его же, чтобы дать записи название и отметки\r\n\r\nexport const RECORDINGS_DIR_ENV = 'TAMSENSE_RECORDINGS_DIR'\r\nexport const SERVER_ENV = 'TAMSENSE_SERVER'\r\nexport const DEFAULT_TITLE_ENV = 'TAMSENSE_RECORDING_TITLE'\r\nexport const DEFAULT_PURPOSE_ENV = 'TAMSENSE_RECORDING_PURPOSE'\r\nexport const MARK_TAG = 'tamsense.mark'\r\nexport const REPLAY_SUFFIX = '.rrweb.ndjson'\r\nexport const META_SUFFIX = '.json'\r\nexport const MAX_TITLE_LENGTH = 60\r\nexport const MAX_PURPOSE_LENGTH = 160\r\n// трекер TAMsense по умолчанию шлёт сюда; адрес сервера из настроек программы добавляется к нему\r\nexport const DEFAULT_TRACKER_ORIGINS = ['https://my.tamsense.com']\r\n\r\n// эти методы только читают; остальные к адресам вне компьютера отменяются\r\nexport const READ_METHODS = ['GET', 'HEAD', 'OPTIONS']\r\n\r\nconst BINDING = '__tamsenseRecord'\r\nconst STATE_KEY = Symbol.for('tamsense.recorder')\r\nconst FLUSH_MS = 100\r\nconst MAX_SESSION_BYTES = 300 * 1024 * 1024\r\nconst GAP_CHECK_MS = 1500\r\nconst MAX_LABEL_LENGTH = 120\r\nconst RRWEB_META = 4\r\n\r\nexport interface RecordingCaption {\r\n title: string\r\n purpose: string\r\n}\r\n\r\nexport interface RecordedContextOptions extends RecordingCaption {\r\n // видео в придачу к записи: для canvas, карт, видео на странице, чужих iframe или когда его попросили\r\n video?: boolean\r\n // обычные настройки browser.newContext: viewport, storageState и другие\r\n context?: Record<string, unknown>\r\n}\r\n\r\nexport interface SessionMark {\r\n ts: number\r\n label: string\r\n}\r\n\r\n// Что знает программа о записи одной страницы; лежит рядом с файлом записи\r\nexport interface SessionMeta {\r\n version: 1\r\n id: string\r\n title: string\r\n purpose: string\r\n startedAt: number\r\n endedAt: number | null\r\n url: string\r\n marks: SessionMark[]\r\n video: string | null\r\n gaps: string[]\r\n blockedTrackerRequests: number\r\n blockedExternalWrites: number\r\n truncated: boolean\r\n // записано само, без названия от агента\r\n automatic: boolean\r\n}\r\n\r\nexport interface RecordingSummary {\r\n title: string\r\n purpose: string\r\n replay: string\r\n video: string | null\r\n marks: number\r\n gaps: string[]\r\n blockedTrackerRequests: number\r\n blockedExternalWrites: number\r\n}\r\n\r\ninterface PlaywrightVideo {\r\n path: () => Promise<string>\r\n}\r\n\r\ninterface PlaywrightPage {\r\n url: () => string\r\n isClosed: () => boolean\r\n on: (event: 'close', listener: () => void) => unknown\r\n evaluate: (fn: string | ((arg: never) => unknown), arg?: unknown) => Promise<unknown>\r\n video: () => PlaywrightVideo | null\r\n}\r\n\r\ninterface PlaywrightRoute {\r\n fulfill: (response: { status: number, contentType: string, body: string }) => Promise<void>\r\n abort: (errorCode?: string) => Promise<void>\r\n continue: () => Promise<void>\r\n request: () => { method: () => string, url: () => string, frame: () => { page: () => PlaywrightPage } }\r\n}\r\n\r\nexport interface PlaywrightContext {\r\n pages: () => PlaywrightPage[]\r\n on: (event: 'page' | 'close', listener: (page: PlaywrightPage) => void) => unknown\r\n exposeBinding: (name: string, callback: (source: { page: PlaywrightPage }, payload: string) => unknown) => Promise<void>\r\n addInitScript: (script: { content: string }) => Promise<void>\r\n route: (url: (url: URL) => boolean, handler: (route: PlaywrightRoute) => Promise<void>) => Promise<void>\r\n close: (options?: unknown) => Promise<void>\r\n}\r\n\r\nexport interface PlaywrightBrowser {\r\n newContext: (options?: Record<string, unknown>) => Promise<PlaywrightContext>\r\n}\r\n\r\ninterface Instrumentation {\r\n addListener: (listener: { runAfterCreateBrowserContext: (context: PlaywrightContext) => Promise<void> }) => void\r\n}\r\n\r\ninterface ContextRecording {\r\n caption: RecordingCaption\r\n automatic: boolean\r\n sessions: Session[]\r\n}\r\n\r\ninterface RecorderState {\r\n contexts: WeakMap<PlaywrightContext, ContextRecording>\r\n pages: WeakMap<PlaywrightPage, Session>\r\n instrumented: WeakSet<object>\r\n script: string | null\r\n hooked: boolean\r\n}\r\n\r\nfunction state(): RecorderState {\r\n const holder = globalThis as { [STATE_KEY]?: RecorderState }\r\n holder[STATE_KEY] ??= { contexts: new WeakMap(), pages: new WeakMap(), instrumented: new WeakSet(), script: null, hooked: false }\r\n return holder[STATE_KEY]\r\n}\r\n\r\nfunction recordingsDir(): string {\r\n const dir = process.env[RECORDINGS_DIR_ENV]\r\n if (!dir) {\r\n throw new Error(`${RECORDINGS_DIR_ENV} is not set: recordings work only inside a TAMsense run`)\r\n }\r\n return dir\r\n}\r\n\r\nfunction warn(text: string): void {\r\n process.stderr.write(`[tamsense recorder] ${text}\\n`)\r\n}\r\n\r\n// Правило блокировки трекера TAMsense: запрос на адрес, который кончается на /api/events, с ключом проекта в ?key=\r\n// или к серверу TAMsense. Так записи агента не попадают в аналитику и недельный разбор\r\nexport function isTrackerIngest(url: URL, tamsenseOrigins: readonly string[]): boolean {\r\n if (!/\\/api\\/events\\/?$/.test(url.pathname)) {\r\n return false\r\n }\r\n return url.searchParams.has('key') || tamsenseOrigins.includes(url.origin)\r\n}\r\n\r\n// Своим считается только сам компьютер: localhost, *.localhost, адреса loopback и 0.0.0.0. Имена контейнеров браузер\r\n// на компьютере не видит, поэтому имя без точки тоже чужое\r\nexport function isLocalHost(hostname: string): boolean {\r\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '').replace(/\\.$/, '')\r\n return host === 'localhost' || host.endsWith('.localhost') || host === '::1' || host === '0.0.0.0' || /^127(?:\\.\\d{1,3}){3}$/.test(host)\r\n}\r\n\r\n// Запрос, который может что-то изменить у чужого сервиса: метод не только читает, а адрес вне этого компьютера\r\nexport function isExternalWrite(method: string, url: URL): boolean {\r\n return !READ_METHODS.includes(method.toUpperCase()) && !isLocalHost(url.hostname)\r\n}\r\n\r\nexport function trackerOrigins(env: Record<string, string | undefined> = process.env): string[] {\r\n const origins = new Set(DEFAULT_TRACKER_ORIGINS)\r\n try {\r\n if (env[SERVER_ENV]) {\r\n origins.add(new URL(env[SERVER_ENV]).origin)\r\n }\r\n }\r\n catch {}\r\n return [...origins]\r\n}\r\n\r\nexport function checkCaption(caption: Partial<RecordingCaption> | undefined): RecordingCaption {\r\n const title = typeof caption?.title === 'string' ? caption.title.trim() : ''\r\n const purpose = typeof caption?.purpose === 'string' ? caption.purpose.trim() : ''\r\n if (!title || !purpose) {\r\n throw new Error('Give it a title (what it shows, for example \"Design option 1\" or \"Feature testing, attempt 2\") and a purpose (one line on why it was made).')\r\n }\r\n if (title.length > MAX_TITLE_LENGTH || purpose.length > MAX_PURPOSE_LENGTH) {\r\n throw new Error(`Keep the title within ${MAX_TITLE_LENGTH} characters and the purpose within ${MAX_PURPOSE_LENGTH}.`)\r\n }\r\n return { title, purpose }\r\n}\r\n\r\nfunction clip(text: string, max: number): string {\r\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\r\n}\r\n\r\n// Название записи, которую никто не назвал: из настроек запуска и имени скрипта, который открыл браузер\r\nexport function automaticCaption(env: Record<string, string | undefined> = process.env, script: string | undefined = process.argv[1]): RecordingCaption {\r\n const from = script ? `, opened by ${basename(script)}` : ''\r\n return {\r\n title: clip(env[DEFAULT_TITLE_ENV]?.trim() || 'Browser session', MAX_TITLE_LENGTH),\r\n purpose: clip(`${env[DEFAULT_PURPOSE_ENV]?.trim() || 'Recorded automatically while the agent used a browser'}${from}`, MAX_PURPOSE_LENGTH),\r\n }\r\n}\r\n\r\n// Сборка rrweb без модулей: UMD кладёт её в локальный module.exports, страница своего rrweb не видит и не теряет\r\nfunction rrwebSource(): string {\r\n const entry = createRequire(import.meta.url).resolve('rrweb')\r\n return readFileSync(join(dirname(entry), 'rrweb.umd.min.cjs'), 'utf8')\r\n}\r\n\r\n// Скрипт для каждой страницы: пишет только верхний документ, iframe того же сайта rrweb снимает сам.\r\n// События уходят в программу пачками через exposeBinding, пароли скрыты, как в rrweb по умолчанию\r\nexport function pageScript(rrweb: string): string {\r\n return `(() => {\r\nif (window.top !== window || window.__tamsenseRecording) return;\r\nwindow.__tamsenseRecording = true;\r\nconst rrweb = (() => { const module = { exports: {} }; const exports = module.exports; ${rrweb}\r\n; return module.exports; })();\r\nlet buffer = [];\r\nlet timer = null;\r\nconst send = (extra) => {\r\n clearTimeout(timer);\r\n timer = null;\r\n if (!buffer.length && !extra) return Promise.resolve();\r\n const events = buffer;\r\n buffer = [];\r\n return window.${BINDING}(JSON.stringify({ events, ...extra })).catch(() => {});\r\n};\r\nrrweb.record({\r\n emit(event) {\r\n buffer.push(event);\r\n if (!timer) timer = setTimeout(send, ${FLUSH_MS});\r\n },\r\n maskInputOptions: { password: true },\r\n});\r\nconst crossOrigin = (frame) => { try { return new URL(frame.src, location.href).origin !== location.origin; } catch { return false; } };\r\nconst checkGaps = () => {\r\n const gaps = [];\r\n if (document.querySelector('canvas')) gaps.push('canvas');\r\n if (document.querySelector('video')) gaps.push('video');\r\n if ([...document.querySelectorAll('iframe')].some(crossOrigin)) gaps.push('cross-origin iframe');\r\n if (gaps.length) send({ gaps });\r\n};\r\nwindow.__tamsenseFlush = () => send();\r\nwindow.__tamsenseMark = (label) => { rrweb.record.addCustomEvent('${MARK_TAG}', { label }); return send(); };\r\naddEventListener('pagehide', () => { send(); });\r\naddEventListener('load', () => setTimeout(checkGaps, ${GAP_CHECK_MS}));\r\n})();`\r\n}\r\n\r\nfunction script(): string {\r\n const current = state()\r\n current.script ??= pageScript(rrwebSource())\r\n return current.script\r\n}\r\n\r\nclass Session {\r\n readonly file: string\r\n readonly metaFile: string\r\n readonly meta: SessionMeta\r\n private bytes = 0\r\n\r\n constructor(dir: string, recording: ContextRecording, url: string) {\r\n const id = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`\r\n this.file = join(dir, `${id}${REPLAY_SUFFIX}`)\r\n this.metaFile = join(dir, `${id}${META_SUFFIX}`)\r\n this.meta = { version: 1, id, ...recording.caption, startedAt: Date.now(), endedAt: null, url, marks: [], video: null, gaps: [], blockedTrackerRequests: 0, blockedExternalWrites: 0, truncated: false, automatic: recording.automatic }\r\n writeFileSync(this.file, '')\r\n this.save()\r\n }\r\n\r\n save(): void {\r\n writeFileSync(this.metaFile, `${JSON.stringify(this.meta, null, 2)}\\n`)\r\n }\r\n\r\n receive(payload: string): void {\r\n let batch: { events?: unknown[], gaps?: string[] }\r\n try {\r\n batch = JSON.parse(payload) as typeof batch\r\n }\r\n catch {\r\n return\r\n }\r\n if (batch.gaps?.length) {\r\n this.meta.gaps = [...new Set([...this.meta.gaps, ...batch.gaps])]\r\n this.save()\r\n }\r\n const page = (batch.events as Array<{ type?: unknown, data?: { href?: unknown } }> | undefined)?.find(event => event.type === RRWEB_META && typeof event.data?.href === 'string' && event.data.href !== 'about:blank')\r\n if (page && this.meta.url === 'about:blank') {\r\n this.meta.url = String(page.data!.href)\r\n this.save()\r\n }\r\n const lines = (batch.events ?? []).map(event => JSON.stringify(event)).join('\\n')\r\n if (!lines || this.meta.truncated) {\r\n return\r\n }\r\n if (this.bytes + lines.length > MAX_SESSION_BYTES) {\r\n this.meta.truncated = true\r\n this.save()\r\n return\r\n }\r\n this.bytes += lines.length + 1\r\n appendFileSync(this.file, `${lines}\\n`)\r\n }\r\n\r\n end(): void {\r\n if (this.meta.endedAt === null) {\r\n this.meta.endedAt = Date.now()\r\n this.save()\r\n }\r\n }\r\n\r\n summary(): RecordingSummary {\r\n const { title, purpose, video, marks, gaps, blockedTrackerRequests, blockedExternalWrites } = this.meta\r\n return { title, purpose, replay: this.file, video, marks: marks.length, gaps, blockedTrackerRequests, blockedExternalWrites }\r\n }\r\n}\r\n\r\nfunction countBlocked(route: PlaywrightRoute, counter: 'blockedTrackerRequests' | 'blockedExternalWrites'): void {\r\n try {\r\n const session = state().pages.get(route.request().frame().page())\r\n if (session) {\r\n session.meta[counter] += 1\r\n session.save()\r\n }\r\n }\r\n catch {}\r\n}\r\n\r\nasync function startSession(recording: ContextRecording, page: PlaywrightPage, injectNow: boolean): Promise<void> {\r\n const current = state()\r\n if (current.pages.has(page)) {\r\n return\r\n }\r\n const session = new Session(recordingsDir(), recording, page.url())\r\n current.pages.set(page, session)\r\n recording.sessions.push(session)\r\n page.on('close', () => session.end())\r\n const video = page.video()\r\n if (video) {\r\n session.meta.video = await video.path().catch(() => null)\r\n session.save()\r\n }\r\n // страница уже была открыта до записи: скрипт для будущих страниц её не коснётся\r\n if (injectNow) {\r\n await page.evaluate(script()).catch(() => {})\r\n }\r\n}\r\n\r\nasync function flush(context: PlaywrightContext): Promise<void> {\r\n await Promise.all(context.pages().filter(page => !page.isClosed()).map(page =>\r\n page.evaluate(() => (window as unknown as { __tamsenseFlush?: () => Promise<void> }).__tamsenseFlush?.(), undefined).catch(() => {}),\r\n ))\r\n}\r\n\r\n// Записывает контекст Playwright; второй вызов для того же контекста только меняет название и назначение записи.\r\n// automatic: контекст записан сам, название придумала программа\r\nexport async function recordContext(context: PlaywrightContext, caption: RecordingCaption, automatic = false): Promise<void> {\r\n const checked = checkCaption(caption)\r\n const current = state()\r\n const known = current.contexts.get(context)\r\n if (known) {\r\n Object.assign(known, { caption: checked, automatic })\r\n for (const session of known.sessions) {\r\n Object.assign(session.meta, checked, { automatic })\r\n session.save()\r\n }\r\n return\r\n }\r\n mkdirSync(recordingsDir(), { recursive: true })\r\n const recording: ContextRecording = { caption: checked, automatic, sessions: [] }\r\n current.contexts.set(context, recording)\r\n const origins = trackerOrigins()\r\n // маршруты Playwright идут от последнего к первому: трекер отвечает раньше, запись наружу отменяется после\r\n await context.route(url => !isLocalHost(url.hostname), async (route) => {\r\n const request = route.request()\r\n if (!isExternalWrite(request.method(), new URL(request.url()))) {\r\n // страница могла закрыться, пока запрос шёл\r\n await route.continue().catch(() => {})\r\n return\r\n }\r\n countBlocked(route, 'blockedExternalWrites')\r\n await route.abort('blockedbyclient').catch(() => {})\r\n })\r\n await context.route(url => isTrackerIngest(url, origins), async (route) => {\r\n countBlocked(route, 'blockedTrackerRequests')\r\n await route.fulfill({ status: 200, contentType: 'application/json', body: '{\"ok\":true}' })\r\n })\r\n await context.exposeBinding(BINDING, ({ page }, payload) => current.pages.get(page)?.receive(payload))\r\n await context.addInitScript({ content: script() })\r\n context.on('page', page => void startSession(recording, page, false))\r\n context.on('close', () => recording.sessions.forEach(session => session.end()))\r\n for (const page of context.pages()) {\r\n await startSession(recording, page, true)\r\n }\r\n const close = context.close.bind(context)\r\n context.close = async (options?: unknown) => {\r\n await flush(context)\r\n return close(options)\r\n }\r\n}\r\n\r\n// Новый записанный контекст: название и назначение обязательны, video: true добавляет видео Playwright\r\nexport async function newRecordedContext(browser: PlaywrightBrowser, options: RecordedContextOptions): Promise<PlaywrightContext> {\r\n const caption = checkCaption(options)\r\n const extra = options.video ? { recordVideo: { dir: join(recordingsDir(), 'video') } } : {}\r\n const context = await browser.newContext({ ...options.context, ...extra })\r\n await recordContext(context, caption)\r\n return context\r\n}\r\n\r\n// Отметка шага: видна на шкале плеера и в списке отметок, клик по ней перематывает запись к этому месту\r\nexport async function mark(page: PlaywrightPage, label: string): Promise<void> {\r\n const text = clip(String(label ?? '').trim(), MAX_LABEL_LENGTH)\r\n const session = state().pages.get(page)\r\n if (!text || !session) {\r\n warn(text ? 'mark() was called for a page that is not recorded' : 'mark() needs a label')\r\n return\r\n }\r\n session.meta.marks.push({ ts: Date.now(), label: text })\r\n session.save()\r\n await page.evaluate(value => (window as unknown as { __tamsenseMark?: (label: string) => Promise<void> }).__tamsenseMark?.(value), text).catch(() => {})\r\n}\r\n\r\n// Закрывает записанный контекст и говорит, что записано; подсказывает видео, если записи не хватит\r\nexport async function closeRecorded(context: PlaywrightContext): Promise<RecordingSummary[]> {\r\n await context.close()\r\n const sessions = state().contexts.get(context)?.sessions ?? []\r\n const summaries = sessions.map(session => session.summary())\r\n for (const summary of summaries) {\r\n if (summary.gaps.length > 0 && !summary.video) {\r\n warn(`\"${summary.title}\" has ${summary.gaps.join(', ')}: the replay may not show it. Record it again with video: true.`)\r\n }\r\n }\r\n return summaries\r\n}\r\n\r\nfunction instrument(exports: unknown): void {\r\n const chromium = (exports as { chromium?: { launch?: unknown, _instrumentation?: Instrumentation } } | null)?.chromium\r\n const instrumentation = (exports as { _instrumentation?: Instrumentation } | null)?._instrumentation ?? chromium?._instrumentation\r\n const current = state()\r\n if (typeof chromium?.launch !== 'function' || typeof instrumentation?.addListener !== 'function' || current.instrumented.has(instrumentation)) {\r\n return\r\n }\r\n current.instrumented.add(instrumentation)\r\n instrumentation.addListener({\r\n runAfterCreateBrowserContext: async (context) => {\r\n try {\r\n if (!current.contexts.has(context)) {\r\n await recordContext(context, automaticCaption(), true)\r\n }\r\n }\r\n catch (error) {\r\n warn(`could not record this browser: ${error instanceof Error ? error.message : String(error)}`)\r\n }\r\n },\r\n })\r\n}\r\n\r\n// Сам цепляется к Playwright, как только его загрузят: агент не может забыть включить запись\r\nexport function installAutoRecording(): void {\r\n const current = state()\r\n if (current.hooked) {\r\n return\r\n }\r\n current.hooked = true\r\n const loader = Module as unknown as { _load: (request: string, parent: unknown, isMain: boolean) => unknown }\r\n const load = loader._load\r\n loader._load = function (this: unknown, ...args: [string, unknown, boolean]) {\r\n const exports = load.apply(this, args)\r\n try {\r\n instrument(exports)\r\n }\r\n catch {}\r\n return exports\r\n }\r\n}\r\n\r\nif (process.env[RECORDINGS_DIR_ENV]) {\r\n installAutoRecording()\r\n}\r\n","import type { RunnerIdentityResponse } from './protocol'\r\n\r\nconst TEAM_KEY_MISSING_TEXT = 'This is a team computer: Claude must run on the Anthropic API key of your company, not on a personal Claude subscription. Set ANTHROPIC_API_KEY on this computer and start again.'\r\n\r\n// Через общий компьютер работает вся команда, а подписку Claude правила Anthropic разрешают только её владельцу\r\nexport function teamKeyProblem(isTeam: boolean, env: Record<string, string | undefined>): string | null {\r\n return isTeam && !env.ANTHROPIC_API_KEY?.trim() ? TEAM_KEY_MISSING_TEXT : null\r\n}\r\n\r\nexport function describeDevice(identity: RunnerIdentityResponse): string {\r\n return identity.device.team\r\n ? `a team computer of ${identity.projectName}`\r\n : `the personal computer of ${identity.email ?? 'its owner'}`\r\n}\r\n","import { spawn } from 'node:child_process'\r\nimport { hostname } from 'node:os'\r\nimport { delimiter } from 'node:path'\r\nimport { setTimeout as wait } from 'node:timers/promises'\r\n\r\nconst PLATFORM_LABELS: Partial<Record<NodeJS.Platform, string>> = {\r\n win32: 'Windows',\r\n darwin: 'macOS',\r\n linux: 'Linux',\r\n}\r\n\r\nconst MAX_NAME_LENGTH = 80\r\n\r\nexport function platformLabel(platform: NodeJS.Platform = process.platform): string {\r\n return PLATFORM_LABELS[platform] ?? platform\r\n}\r\n\r\nexport function defaultDeviceName(): string {\r\n return hostname().slice(0, MAX_NAME_LENGTH) || 'My computer'\r\n}\r\n\r\n// На Windows ссылку открывает rundll32: через cmd знак & в адресе разрезал бы команду\r\nfunction browserCommand(url: string, platform: NodeJS.Platform): [string, string[]] {\r\n if (platform === 'win32') {\r\n return ['rundll32', ['url.dll,FileProtocolHandler', url]]\r\n }\r\n return [platform === 'darwin' ? 'open' : 'xdg-open', [url]]\r\n}\r\n\r\n// Браузер может не открыться (сервер без экрана): ссылка уже напечатана, человек откроет её сам\r\nexport function openBrowser(url: string, platform: NodeJS.Platform = process.platform): void {\r\n const [command, args] = browserCommand(url, platform)\r\n const child = spawn(command, args, { detached: true, stdio: 'ignore' })\r\n child.on('error', () => {})\r\n child.unref()\r\n}\r\n\r\n// Claude и проекты зовут node и npm: если программу запустили так, что её папки Node нет в PATH, папка добавляется в конец\r\nexport function withNodeOnPath(env: NodeJS.ProcessEnv, nodeDir: string, separator: string = delimiter): NodeJS.ProcessEnv {\r\n const key = Object.keys(env).find(name => name.toUpperCase() === 'PATH') ?? 'PATH'\r\n const entries = (env[key] ?? '').split(separator).filter(Boolean)\r\n const normalize = (dir: string) => dir.replace(/[\\\\/]+$/, '').toLowerCase()\r\n if (entries.some(entry => normalize(entry) === normalize(nodeDir))) {\r\n return env\r\n }\r\n return { ...env, [key]: [...entries, nodeDir].join(separator) }\r\n}\r\n\r\n// Остановка будит ожидание сразу: Ctrl+C не ждёт конца паузы между опросами\r\nexport async function sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n await wait(ms, undefined, { signal }).catch(() => {})\r\n}\r\n","import { spawn } from 'node:child_process'\r\nimport { readFileSync } from 'node:fs'\r\nimport process from 'node:process'\r\n\r\nexport const PACKAGE_NAME = '@tamsensedev/runner'\r\nexport const RELAUNCHED_ENV = 'TAMSENSE_RELAUNCHED'\r\nexport const NO_UPDATE_ENV = 'TAMSENSE_NO_UPDATE'\r\n\r\nconst REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME.replace('/', '%2f')}/latest`\r\nconst REGISTRY_TIMEOUT_MS = 3000\r\nconst RELEASE_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)$/\r\n\r\nexport interface UpdateCheck {\r\n current: string\r\n latest: string\r\n}\r\n\r\ntype FetchLatest = (url: string, signal: AbortSignal) => Promise<unknown>\r\n\r\nfunction releaseParts(version: string): number[] | null {\r\n const match = RELEASE_PATTERN.exec(version.trim())\r\n return match ? match.slice(1).map(Number) : null\r\n}\r\n\r\n// Сравниваются только выпуски вида 1.2.3: предварительные версии и сборки из исходников не обновляются\r\nexport function isNewer(latest: string, current: string): boolean {\r\n const next = releaseParts(latest)\r\n const now = releaseParts(current)\r\n if (!next || !now) {\r\n return false\r\n }\r\n const index = next.findIndex((part, position) => part !== now[position])\r\n return index !== -1 && next[index]! > now[index]!\r\n}\r\n\r\nexport function currentVersion(): string | null {\r\n try {\r\n const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { name?: unknown, version?: unknown }\r\n return manifest.name === PACKAGE_NAME && typeof manifest.version === 'string' ? manifest.version : null\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\nasync function fetchLatest(url: string, signal: AbortSignal): Promise<unknown> {\r\n const response = await fetch(url, { signal, headers: { accept: 'application/json' } })\r\n return response.ok ? await response.json() : null\r\n}\r\n\r\n// Последняя версия на npm; без сети или при ответе с ошибкой обновления нет, программа работает как есть\r\nexport async function latestVersion(fetchJson: FetchLatest = fetchLatest, timeoutMs = REGISTRY_TIMEOUT_MS): Promise<string | null> {\r\n try {\r\n const body = await fetchJson(REGISTRY_URL, AbortSignal.timeout(timeoutMs))\r\n const version = body && typeof body === 'object' ? (body as { version?: unknown }).version : null\r\n return typeof version === 'string' ? version : null\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\n// После перезапуска и при TAMSENSE_NO_UPDATE обновление не проверяется: так перезапуск не зацикливается\r\nexport async function updateToRun(env: NodeJS.ProcessEnv, current: string | null, latest: () => Promise<string | null>): Promise<UpdateCheck | null> {\r\n if (!current || env[RELAUNCHED_ENV] || env[NO_UPDATE_ENV]) {\r\n return null\r\n }\r\n const found = await latest()\r\n return found && isNewer(found, current) ? { current, latest: found } : null\r\n}\r\n\r\nexport function relaunchArgs(argv: string[]): string[] {\r\n return ['--yes', `${PACKAGE_NAME}@latest`, ...argv]\r\n}\r\n\r\n// Новая версия запускается в этом же окне; Ctrl+C достаётся ей, а эта ждёт и выходит с её кодом\r\nexport function relaunch(argv: string[], env: NodeJS.ProcessEnv): Promise<number> {\r\n const isWindows = process.platform === 'win32'\r\n return new Promise((resolve, reject) => {\r\n const child = spawn(isWindows ? 'npx.cmd' : 'npx', relaunchArgs(argv), { stdio: 'inherit', shell: isWindows, env: { ...env, [RELAUNCHED_ENV]: '1' } })\r\n const ignoreInterrupt = () => {}\r\n process.on('SIGINT', ignoreInterrupt)\r\n child.on('error', (error) => {\r\n process.off('SIGINT', ignoreInterrupt)\r\n reject(error)\r\n })\r\n child.on('close', (code) => {\r\n process.off('SIGINT', ignoreInterrupt)\r\n resolve(code ?? 1)\r\n })\r\n })\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { InstalledBrowser } from './browser'\r\nimport type { AskingTools, RunAgent, SpecWorkTools } from './claude'\r\nimport type { FeedDraft } from './feed'\r\nimport type { FeedQueue } from './feedQueue'\r\nimport type { FolderTools } from './folders'\r\nimport type { InputChannel } from './input'\r\nimport type { KnowledgeTools } from './knowledge'\r\nimport type { PermissionTools } from './permissions'\r\nimport type { RunnerAskReply, RunnerAttachmentResponse, RunnerCapabilities, RunnerFinishBody, RunnerGitTokenResponse, RunnerHelperStop, RunnerInbox, RunnerInboxBody, RunnerInboxMessage, RunnerJob, RunnerKnowledgeEntry, RunnerKnowledgeReply, RunnerNextResponse, RunnerPermissionReply, RunnerReviewReply, RunnerSession } from './protocol'\r\nimport type { AttachFields } from './screenshot'\r\nimport type { Workspace, WorkspaceTools } from './workspace'\r\nimport { rm } from 'node:fs/promises'\r\nimport { dirname } from 'node:path'\r\nimport process from 'node:process'\r\nimport { ApiError } from './api'\r\nimport { browserEnv } from './browser'\r\nimport { capabilitiesLine } from './capabilities'\r\nimport { consoleLine } from './feed'\r\nimport { feedQueue } from './feedQueue'\r\nimport { checkFolders } from './folders'\r\nimport { helperStoppedNote, helperStopRequest } from './helpers'\r\nimport { inputChannel, switchLine } from './input'\r\nimport { knowledgeIds } from './knowledge'\r\nimport { allowedSkills, prepareLibrary, roleLibraryFolder } from './library'\r\nimport { isLocalJob, localBranchLine, prepareLocalWorkspace, publishLocalWorkspace } from './localWorkspace'\r\nimport { keyedLock } from './lock'\r\nimport { buildTaskPrompt, canProposeKnowledge, isReadOnlyCode } from './prompt'\r\nimport { CAPABILITIES_PATH, knowledgePath, MAX_ATTACHMENTS_PER_RUN, NEXT_RUN_PATH, permissionPath, runPath } from './protocol'\r\nimport { recorderEnv, recordingsFolder } from './recorderEnv'\r\nimport { attachRecording, uploadPendingRecordings } from './recordings'\r\nimport { handOverTools, isFolderJob, prepareFolder, resultTools } from './results'\r\nimport { readScreenshot } from './screenshot'\r\nimport { withNodeOnPath } from './system'\r\nimport { USAGE_LIMIT_REPORT } from './usageLimit'\r\nimport { commitIdentityEnv, prepareWorkspace, publishWorkspace, stepFolder, taskFolder } from './workspace'\r\n\r\nexport interface WorkerTools extends WorkspaceTools {\r\n api: ApiClient\r\n runAgent: RunAgent\r\n print: (line: string) => void\r\n sleep: (ms: number, signal?: AbortSignal) => Promise<void>\r\n // браузер для проверок интерфейса, если человек согласился его поставить\r\n browser?: InstalledBrowser | null\r\n // что умеет Claude на компьютере, без траты токенов; без него сайт этого не узнаёт\r\n capabilities?: (stop: AbortSignal) => Promise<RunnerCapabilities>\r\n // модуль записи браузера агента (recorder.cjs рядом с программой); без него браузер не записывается\r\n recorder?: string | null\r\n // проверка папки, которую человек добавил на доску; без неё программа папки не проверяет\r\n inspectFolder?: FolderTools['inspect']\r\n}\r\n\r\nconst POLL_INTERVAL_MS = 5_000\r\nconst RETRY_AFTER_ERROR_MS = 15_000\r\n// заодно с сообщениями людей сайт узнаёт, что компьютер ещё работает над задачей\r\nconst INBOX_INTERVAL_MS = 2_000\r\nconst MAX_MESSAGE_LINE = 200\r\nconst CAPABILITIES_INTERVAL_MS = 30 * 60_000\r\n// журнал уходит на сайт почти сразу: человек видит работу Claude вживую\r\nconst FEED_INTERVAL_MS = 1_500\r\nconst FATAL_STATUSES = new Set([401, 403])\r\n\r\nconst STOPPED_HERE = 'stopped_here'\r\nconst CLOSED_ON_SITE = 'closed_on_site'\r\n\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error)\r\n}\r\n\r\n// Отказ в доступе не лечится повтором: компьютер отключили или у человека забрали задачи\r\nfunction isFatal(error: unknown): boolean {\r\n return error instanceof ApiError && FATAL_STATUSES.has(error.status)\r\n}\r\n\r\nasync function gitToken(api: ApiClient, job: RunnerJob): Promise<string> {\r\n return (await api.request<RunnerGitTokenResponse>('POST', runPath(job.runId, 'git-token'))).token\r\n}\r\n\r\n// Ответ уходит Claude: приложено, или почему нет, чтобы он поправил путь или формат\r\nasync function attachScreenshot(api: ApiClient, job: RunnerJob, screenshot: AttachFields, attached: { count: number }): Promise<string> {\r\n if (attached.count >= MAX_ATTACHMENTS_PER_RUN) {\r\n return `This run already attached ${MAX_ATTACHMENTS_PER_RUN} screenshots. Mention the rest in your report.`\r\n }\r\n const body = await readScreenshot(screenshot)\r\n if (typeof body === 'string') {\r\n return body\r\n }\r\n try {\r\n await api.request<RunnerAttachmentResponse>('POST', runPath(job.runId, 'attachments'), body)\r\n }\r\n catch (error) {\r\n return `Could not attach the screenshot: ${messageOf(error)}`\r\n }\r\n attached.count += 1\r\n return 'Attached to the task.'\r\n}\r\n\r\n// Сайт показывает, какая модель и чей вход взялись за задачу; сбой не мешает работе\r\nfunction sessionReporter(api: ApiClient, job: RunnerJob, print: (line: string) => void): { report: (session: RunnerSession) => void, sent: () => Promise<void> } {\r\n let sending: Promise<void> | null = null\r\n return {\r\n report: (session) => {\r\n sending ??= api.request('POST', runPath(job.runId, 'session'), session)\r\n .then(() => {}, error => print(`Could not tell TAMsense which Claude took the task: ${messageOf(error)}`))\r\n },\r\n sent: async () => {\r\n await sending\r\n },\r\n }\r\n}\r\n\r\n// Запись журнала уходит на сайт, а действия Claude ещё и печатаются здесь\r\nfunction journal(feed: FeedQueue, print: (line: string) => void): (draft: FeedDraft) => void {\r\n return (draft) => {\r\n feed.push(draft)\r\n const line = consoleLine(draft)\r\n if (line) {\r\n print(` ${line}`)\r\n }\r\n }\r\n}\r\n\r\nfunction messageLine(message: Pick<RunnerInboxMessage, 'authorName' | 'text'>): string {\r\n const line = `Message from ${message.authorName}: ${message.text}`.replace(/\\s+/g, ' ').trim()\r\n return line.length > MAX_MESSAGE_LINE ? `${line.slice(0, MAX_MESSAGE_LINE - 1)}…` : line\r\n}\r\n\r\n// Помощника останавливает Claude Code, и Claude узнаёт, кто его остановил; если Claude Code о нём не сообщил, Claude просят остановить его самого\r\nasync function stopHelper(input: InputChannel, stop: RunnerHelperStop, print: (line: string) => void): Promise<void> {\r\n try {\r\n const result = await input.stopHelper(stop.ref)\r\n if (result === 'stopped') {\r\n input.note(helperStoppedNote(stop), 'queue')\r\n print(` ${stop.byName} stopped the helper ${stop.name}`)\r\n }\r\n else if (result === 'unknown' && input.note(helperStopRequest(stop), 'steer')) {\r\n print(` ${stop.byName} asked Claude to stop the helper ${stop.name}`)\r\n }\r\n }\r\n catch (error) {\r\n print(`Could not stop the helper ${stop.name}: ${messageOf(error)}`)\r\n }\r\n}\r\n\r\n// input: что уходит Claude; после его хода входящие проверяются сразу, не дожидаясь очередной проверки\r\ninterface Inbox {\r\n input: InputChannel\r\n poll: () => Promise<void>\r\n settled: () => Promise<void>\r\n}\r\n\r\n// Входящие запуска: жив ли он на сайте, новые сообщения людей и смена модели; в ответ сайт узнаёт, что уже передано Claude.\r\n// Сбой не останавливает работу: следующая проверка повторит, а строка печатается одна на всю полосу сбоев\r\nfunction inbox(job: RunnerJob, tools: WorkerTools, record: (draft: FeedDraft) => void, closedOnSite: () => void): Inbox {\r\n const input = inputChannel({ onIdle: () => void poll() })\r\n // сайт присылает остановки за весь запуск: каждая выполняется один раз\r\n const stopsSeen = new Set<string>()\r\n let running: Promise<void> | null = null\r\n let again = false\r\n let failing = false\r\n\r\n async function check(): Promise<void> {\r\n const ack = input.takeDelivered()\r\n const body: RunnerInboxBody = { ack }\r\n let reply: RunnerInbox\r\n try {\r\n reply = await tools.api.request<RunnerInbox>('POST', runPath(job.runId, 'inbox'), body)\r\n }\r\n catch (error) {\r\n input.restoreDelivered(ack)\r\n if (!failing) {\r\n tools.print(`Could not check for new messages on TAMsense, trying again: ${messageOf(error)}`)\r\n }\r\n failing = true\r\n return\r\n }\r\n failing = false\r\n if (!reply.keepGoing) {\r\n closedOnSite()\r\n return\r\n }\r\n for (const message of reply.messages) {\r\n if (input.push(message)) {\r\n tools.print(` ${messageLine(message)}`)\r\n }\r\n }\r\n for (const stop of reply.helperStops ?? []) {\r\n if (!stopsSeen.has(stop.ref)) {\r\n stopsSeen.add(stop.ref)\r\n void stopHelper(input, stop, tools.print)\r\n }\r\n }\r\n if (reply.model !== null || reply.effort !== null) {\r\n const change = { model: reply.model, effort: reply.effort }\r\n void input.switchModel(change).then(\r\n (applied) => {\r\n if (applied) {\r\n record({ kind: 'tool', text: switchLine(change), tool: 'model', ref: null, parent: null, failed: false })\r\n }\r\n },\r\n error => tools.print(`Could not switch the model: ${messageOf(error)}`),\r\n )\r\n }\r\n }\r\n\r\n // проверка, попросившая слова во время другой, идёт сразу после неё: Claude мог как раз закончить ход\r\n function poll(): Promise<void> {\r\n if (running) {\r\n again = true\r\n return running\r\n }\r\n running = input.checking(check).finally(() => {\r\n running = null\r\n if (again) {\r\n again = false\r\n void poll()\r\n }\r\n })\r\n return running\r\n }\r\n\r\n return {\r\n input,\r\n poll,\r\n settled: async () => {\r\n for (let current = running; current; current = running) {\r\n await current\r\n }\r\n },\r\n }\r\n}\r\n\r\n// Шаг ТЗ: вопросы и ТЗ сдаются инструментами, а сайт проверяет их теми же правилами, что в облаке\r\nfunction specWorkTools(job: RunnerJob, api: ApiClient): SpecWorkTools | null {\r\n const { kind } = job.stage\r\n if (kind !== 'interview' && kind !== 'spec') {\r\n return null\r\n }\r\n return { stage: kind, review: body => api.request<RunnerReviewReply>('POST', runPath(job.runId, 'review'), body), brief: isFolderJob(job) }\r\n}\r\n\r\n// Спросить можно людей задачи на шагах кода и проверки; у шага ТЗ свои раунды вопросов\r\nfunction askingTools(job: RunnerJob, api: ApiClient): AskingTools | null {\r\n const people = job.people ?? []\r\n if (people.length === 0 || job.stage.kind === 'interview' || job.stage.kind === 'spec') {\r\n return null\r\n }\r\n return { people, send: body => api.request<RunnerAskReply>('POST', runPath(job.runId, 'questions'), body) }\r\n}\r\n\r\n// Действия, о которых Claude Code спросил бы человека, решают правила проекта или люди задачи на сайте; у шага ТЗ таких запросов нет.\r\n// Пока решения нет, здесь печатается, чего ждёт вызов Claude\r\nfunction permissionTools(job: RunnerJob, tools: WorkerTools): PermissionTools | null {\r\n if (job.stage.kind === 'interview' || job.stage.kind === 'spec') {\r\n return null\r\n }\r\n return {\r\n send: async (body) => {\r\n const reply = await tools.api.request<RunnerPermissionReply>('POST', runPath(job.runId, 'permissions'), body)\r\n if (reply.decision === 'wait') {\r\n tools.print(` Waiting for a person on TAMsense to allow: ${body.title || body.action}`)\r\n }\r\n return reply\r\n },\r\n check: id => tools.api.request<RunnerPermissionReply>('GET', permissionPath(job.runId, id)),\r\n sleep: tools.sleep,\r\n }\r\n}\r\n\r\n// Знания приходят только с нового сайта: прочесть, проверить и поправить можно заметки этого запуска, предложить заметку тот, кто спрашивает людей\r\nfunction knowledgeTools(job: RunnerJob, api: ApiClient): KnowledgeTools | null {\r\n if (!job.knowledge) {\r\n return null\r\n }\r\n return {\r\n entryIds: knowledgeIds(job.knowledge),\r\n moreIds: job.knowledge.more.map(entry => entry.id),\r\n canPropose: canProposeKnowledge(job),\r\n propose: body => api.request<RunnerKnowledgeReply>('POST', runPath(job.runId, 'knowledge'), body),\r\n update: (entryId, body) => api.request<RunnerKnowledgeReply>('POST', knowledgePath(job.runId, entryId), body),\r\n read: entryId => api.request<RunnerKnowledgeEntry>('GET', knowledgePath(job.runId, entryId)),\r\n }\r\n}\r\n\r\n// Роль с узким списком навыков: человек у компьютера видит, какие навыки Claude дано\r\nfunction skillsLine(skills: string[]): string {\r\n return `This role uses ${skills.length} skill(s) from the library: ${skills.join(', ')}.`\r\n}\r\n\r\n// Доска с кодом: репозитории задачи на её ветке. Доска без кода: папка задачи с её результатом, git не нужен\r\nasync function prepare(job: RunnerJob, tools: WorkerTools): Promise<{ workspace: Workspace, results: string[] | null, identity: Record<string, string> }> {\r\n if (isFolderJob(job)) {\r\n tools.print('Preparing the task folder…')\r\n const { results, ...workspace } = await prepareFolder(job, taskFolder(tools.home, job), tools.api)\r\n return { workspace, results, identity: {} }\r\n }\r\n if (isLocalJob(job)) {\r\n tools.print('Preparing the code from the folders on this computer…')\r\n const workspace = await prepareLocalWorkspace(job, tools)\r\n return { workspace, results: null, identity: await commitIdentityEnv(job.repositories[0]?.folder ?? tools.home) }\r\n }\r\n tools.print('Preparing the code…')\r\n const workspace = await prepareWorkspace(job, await gitToken(tools.api, job), tools)\r\n return { workspace, results: null, identity: await commitIdentityEnv(tools.home) }\r\n}\r\n\r\n// Ветка из папки на компьютере уходит входом человека, ветка с GitHub ключом TAMsense\r\nasync function publish(workspace: Workspace, job: RunnerJob, api: ApiClient, identity: Record<string, string>): Promise<RunnerFinishBody['pushed']> {\r\n return isLocalJob(job) ? await publishLocalWorkspace(workspace, job, identity) : await publishWorkspace(workspace, job, await gitToken(api, job), identity)\r\n}\r\n\r\nasync function executeJob(job: RunnerJob, tools: WorkerTools, signal: AbortSignal, input: InputChannel, record: (draft: FeedDraft) => void): Promise<RunnerFinishBody> {\r\n const isCheck = job.stage.kind === 'review' || job.stage.kind === 'test'\r\n const { workspace, results, identity } = await prepare(job, tools)\r\n const skills = allowedSkills(job.stage)\r\n if (skills) {\r\n tools.print(skillsLine(skills))\r\n }\r\n const library = await prepareLibrary(roleLibraryFolder(tools.home, job.runId), skills)\r\n signal.throwIfAborted()\r\n\r\n tools.print('Claude is working…')\r\n const attached = { count: 0 }\r\n const session = sessionReporter(tools.api, job, tools.print)\r\n const recordings = recordingsFolder(tools.home, job.runId)\r\n const baseEnv = withNodeOnPath(process.env, dirname(process.execPath))\r\n const recording = tools.recorder ? recorderEnv({ recorder: tools.recorder, folder: recordings, server: tools.api.server, job, nodeOptions: baseEnv.NODE_OPTIONS }) : {}\r\n const outcome = await tools.runAgent({\r\n cwd: workspace.root,\r\n library,\r\n prompt: buildTaskPrompt(job, workspace, { browser: Boolean(tools.browser), recorder: Boolean(tools.recorder), results }),\r\n input,\r\n env: { ...baseEnv, ...(tools.browser ? browserEnv(tools.browser) : {}), ...recording, ...identity },\r\n signal,\r\n wantsVerdict: isCheck,\r\n specWork: specWorkTools(job, tools.api),\r\n asking: askingTools(job, tools.api),\r\n permissions: permissionTools(job, tools),\r\n knowledge: knowledgeTools(job, tools.api),\r\n handoffs: job.allowedHandoffs,\r\n resumeSessionId: job.resumeSessionId,\r\n model: job.model,\r\n effort: job.effort,\r\n onFeed: record,\r\n onSession: session.report,\r\n attach: request => request.kind === 'screenshot'\r\n ? attachScreenshot(tools.api, job, request, attached)\r\n : attachRecording(tools.api, job.runId, recordings, request),\r\n results: resultTools(job, tools.api, workspace.root),\r\n handOver: handOverTools(job, tools.api),\r\n })\r\n await session.sent()\r\n signal.throwIfAborted()\r\n\r\n // ключ живёт час, а работа бывает дольше: для отправки ветки берётся свежий\r\n // проверка и шаг ТЗ ничего не отправляют: их итог это вердикт, вопросы или ТЗ; роль только для чтения на шаге кода тоже.\r\n // Код отправляется, только если Claude закончил работу, а не ждёт ответа человека: тогда работа остаётся в папке задачи до продолжения.\r\n // На доске без кода отправлять нечего: результат уже на задаче\r\n const waitingFor = outcome.waitingFor ?? null\r\n const shouldPublish = job.stage.kind === 'code' && !isFolderJob(job) && !isReadOnlyCode(job.stage) && outcome.completed && waitingFor === null\r\n const pushed = shouldPublish ? await publish(workspace, job, tools.api, identity) : []\r\n return {\r\n outcome: outcome.completed ? 'succeeded' : 'failed',\r\n report: outcome.report,\r\n pushed,\r\n model: outcome.model,\r\n usage: outcome.usage,\r\n verdict: outcome.verdict,\r\n handoff: outcome.handoff ?? null,\r\n sessionId: outcome.sessionId ?? null,\r\n pausedUntil: outcome.pausedUntil ?? null,\r\n questions: outcome.questions ?? null,\r\n spec: outcome.spec ?? null,\r\n waitingFor,\r\n }\r\n}\r\n\r\nfunction clockOf(iso: string): string {\r\n const time = new Date(iso)\r\n return `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`\r\n}\r\n\r\n// folder: доска без кода, результат уже лежит на задаче; local: ветки в папках этого компьютера\r\nexport function resultLine(finish: RunnerFinishBody, workspace: RunnerJob['workspace'] = 'repositories'): string {\r\n if (finish.pausedUntil) {\r\n return `Paused: ${USAGE_LIMIT_REPORT}, resumes at ${clockOf(finish.pausedUntil)}`\r\n }\r\n if (finish.waitingFor !== null) {\r\n return 'Waiting for an answer on TAMsense. This computer is free for other tasks; the run continues here after the answer.'\r\n }\r\n if (finish.verdict?.couldNotRun) {\r\n return `Check could not run: ${finish.verdict.report}`\r\n }\r\n if (finish.verdict) {\r\n return finish.verdict.passed ? 'Check passed.' : `Check did not pass: ${finish.verdict.report}`\r\n }\r\n if (finish.questions) {\r\n return finish.questions.questions.length > 0 ? `Done. ${finish.questions.questions.length} question(s) wait for the owner on TAMsense.` : 'Done. No questions needed, the spec is written next.'\r\n }\r\n if (finish.spec) {\r\n return 'Done. The spec waits for the owner on TAMsense.'\r\n }\r\n if (finish.outcome !== 'succeeded') {\r\n return `Failed: ${finish.report}`\r\n }\r\n if (workspace === 'folder') {\r\n return 'Done. The result is on the task on TAMsense.'\r\n }\r\n if (workspace === 'local') {\r\n return finish.pushed.length > 0 ? `Done. ${finish.pushed.map(localBranchLine).join(' ')}` : 'Done. Nothing new to commit.'\r\n }\r\n return `Done. Pushed ${finish.pushed.length} branch(es), the pull requests open on TAMsense.`\r\n}\r\n\r\n// Всё, что браузер агента записал и агент не приложил сам, уходит в задачу до итога запуска: потом сайт закроет запуск\r\nasync function sendRecordings(job: RunnerJob, tools: WorkerTools): Promise<void> {\r\n const folder = recordingsFolder(tools.home, job.runId)\r\n try {\r\n const report = await uploadPendingRecordings(tools.api, job.runId, folder)\r\n if (report.uploaded > 0) {\r\n tools.print(`Attached ${report.uploaded} browser recording(s) to the task.`)\r\n }\r\n if (report.skipped > 0) {\r\n tools.print(`${report.skipped} more recording(s) were not attached: the run reached its limit.`)\r\n }\r\n for (const line of report.failed) {\r\n tools.print(`Could not attach a recording: ${line}`)\r\n }\r\n }\r\n finally {\r\n await rm(folder, { recursive: true, force: true })\r\n }\r\n}\r\n\r\nfunction failureReport(error: unknown, signal: AbortSignal): string {\r\n return signal.reason === STOPPED_HERE ? 'The run was stopped on the computer.' : `The run stopped on the computer: ${messageOf(error)}`\r\n}\r\n\r\nexport async function runJob(job: RunnerJob, tools: WorkerTools, stop: AbortSignal): Promise<void> {\r\n tools.print(`Task #${job.taskId}: ${job.title} (${job.stage.name})`)\r\n const run = new AbortController()\r\n const stopRun = () => run.abort(STOPPED_HERE)\r\n if (stop.aborted) {\r\n stopRun()\r\n }\r\n stop.addEventListener('abort', stopRun, { once: true })\r\n const feed = feedQueue(entries => tools.api.request('POST', runPath(job.runId, 'feed'), { entries }))\r\n const feeding = setInterval(() => void feed.flush(), FEED_INTERVAL_MS)\r\n const record = journal(feed, tools.print)\r\n const messages = inbox(job, tools, record, () => run.abort(CLOSED_ON_SITE))\r\n const { input } = messages\r\n run.signal.addEventListener('abort', input.close, { once: true })\r\n void messages.poll()\r\n const polling = setInterval(() => void messages.poll(), INBOX_INTERVAL_MS)\r\n\r\n let finish: RunnerFinishBody\r\n try {\r\n finish = await executeJob(job, tools, run.signal, input, record)\r\n }\r\n catch (error) {\r\n finish = { outcome: 'failed', report: failureReport(error, run.signal), pushed: [], model: null, usage: null, verdict: null, handoff: null, sessionId: null, pausedUntil: null, questions: null, spec: null, waitingFor: null }\r\n }\r\n finally {\r\n await rm(roleLibraryFolder(tools.home, job.runId), { recursive: true, force: true })\r\n clearInterval(polling)\r\n clearInterval(feeding)\r\n stop.removeEventListener('abort', stopRun)\r\n input.close()\r\n }\r\n // последние переданные Claude сообщения подтверждаются до итога\r\n await messages.settled()\r\n if (input.hasDelivered()) {\r\n await messages.poll()\r\n }\r\n await feed.flush()\r\n\r\n if (run.signal.reason === CLOSED_ON_SITE) {\r\n await rm(recordingsFolder(tools.home, job.runId), { recursive: true, force: true })\r\n tools.print('TAMsense closed this run while it was in progress. The result was not sent.')\r\n return\r\n }\r\n await sendRecordings(job, tools).catch(error => tools.print(`Could not attach the browser recordings: ${messageOf(error)}`))\r\n try {\r\n await tools.api.request('POST', runPath(job.runId, 'finish'), finish)\r\n tools.print(resultLine(finish, job.workspace))\r\n }\r\n catch (error) {\r\n tools.print(`Could not send the result to TAMsense: ${messageOf(error)}`)\r\n }\r\n}\r\n\r\n// При запуске и потом раз в полчаса: вход и модели могли смениться. Ошибка печатается один раз и ничего не останавливает\r\nasync function reportCapabilities(tools: WorkerTools, stop: AbortSignal): Promise<void> {\r\n if (!tools.capabilities) {\r\n return\r\n }\r\n let printed = false\r\n let complained = false\r\n while (!stop.aborted) {\r\n try {\r\n const capabilities = await tools.capabilities(stop)\r\n await tools.api.request('PUT', CAPABILITIES_PATH, capabilities)\r\n if (!printed) {\r\n tools.print(capabilitiesLine(capabilities))\r\n printed = true\r\n }\r\n }\r\n catch (error) {\r\n if (!complained && !stop.aborted) {\r\n tools.print(`Could not tell TAMsense which Claude models this computer has: ${messageOf(error)}`)\r\n complained = true\r\n }\r\n }\r\n await tools.sleep(CAPABILITIES_INTERVAL_MS, stop)\r\n }\r\n}\r\n\r\n// Несколько запусков сразу: строки каждого подписаны номером задачи и шагом, иначе их не различить\r\nfunction jobTools(job: RunnerJob, tools: WorkerTools, parallel: number): WorkerTools {\r\n return parallel > 1 ? { ...tools, print: line => tools.print(`[#${job.taskId} ${job.stage.name}] ${line}`) } : tools\r\n}\r\n\r\n// Берёт не больше parallel запусков сразу; два запуска одного шага задачи идут по очереди, у них одна рабочая папка,\r\n// а проверки одной задачи на разных шагах идут одновременно\r\nexport async function work(tools: WorkerTools, stop: AbortSignal, parallel: number = 1): Promise<void> {\r\n tools.print(parallel > 1 ? `Waiting for tasks, up to ${parallel} at once. Press Ctrl+C to stop.` : 'Waiting for tasks. Press Ctrl+C to stop.')\r\n // отказ в доступе тоже останавливает все идущие задачи, каждая отчитывается\r\n const halt = new AbortController()\r\n const stopAll = AbortSignal.any([stop, halt.signal])\r\n const running = new Set<Promise<void>>()\r\n const folderLock = keyedLock()\r\n const reporting = reportCapabilities(tools, stopAll)\r\n const checking = tools.inspectFolder ? checkFolders({ ...tools, inspect: tools.inspectFolder }, stopAll) : Promise.resolve()\r\n const start = (job: RunnerJob) => {\r\n const runTools = jobTools(job, tools, parallel)\r\n const task: Promise<void> = folderLock.run(stepFolder(job).join('/'), () => runJob(job, runTools, stopAll))\r\n .catch(error => runTools.print(`The run stopped: ${messageOf(error)}`))\r\n .finally(() => running.delete(task))\r\n running.add(task)\r\n }\r\n try {\r\n while (!stopAll.aborted) {\r\n if (running.size >= parallel) {\r\n await Promise.race(running)\r\n continue\r\n }\r\n let reply: RunnerNextResponse\r\n try {\r\n reply = await tools.api.request<RunnerNextResponse>('POST', NEXT_RUN_PATH)\r\n }\r\n catch (error) {\r\n if (isFatal(error)) {\r\n halt.abort()\r\n throw error\r\n }\r\n tools.print(`Could not reach TAMsense: ${messageOf(error)}. Trying again soon.`)\r\n await tools.sleep(RETRY_AFTER_ERROR_MS, stopAll)\r\n continue\r\n }\r\n if (reply.job) {\r\n start(reply.job)\r\n }\r\n else {\r\n await tools.sleep(POLL_INTERVAL_MS, stopAll)\r\n }\r\n }\r\n }\r\n finally {\r\n await Promise.allSettled([...running, reporting, checking])\r\n }\r\n}\r\n","import type { FeedDraft } from './feed'\r\nimport type { RunnerFeedEntry } from './protocol'\r\n\r\nexport interface FeedQueue {\r\n push: (draft: FeedDraft) => RunnerFeedEntry\r\n flush: () => Promise<void>\r\n}\r\n\r\nconst MAX_BATCH = 200\r\nconst MAX_WAITING = 2_000\r\n\r\n// Журнал копится и уходит на сайт пачками. Неотправленное ждёт следующей попытки;\r\n// если сайт долго недоступен, теряются самые старые записи, а не работа Claude\r\nexport function feedQueue(send: (entries: RunnerFeedEntry[]) => Promise<void>): FeedQueue {\r\n let seq = 0\r\n let waiting: RunnerFeedEntry[] = []\r\n let sending: Promise<void> = Promise.resolve()\r\n\r\n async function drain(): Promise<void> {\r\n while (waiting.length > 0) {\r\n const batch = waiting.slice(0, MAX_BATCH)\r\n try {\r\n await send(batch)\r\n }\r\n catch {\r\n return\r\n }\r\n const lastSent = batch.at(-1)!.seq\r\n waiting = waiting.filter(entry => entry.seq > lastSent)\r\n }\r\n }\r\n\r\n return {\r\n push(draft) {\r\n seq += 1\r\n const entry = { ...draft, seq }\r\n waiting.push(entry)\r\n if (waiting.length > MAX_WAITING) {\r\n waiting = waiting.slice(-MAX_WAITING)\r\n }\r\n return entry\r\n },\r\n flush() {\r\n sending = sending.then(drain)\r\n return sending\r\n },\r\n }\r\n}\r\n","import type { RunnerStage } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { cp, mkdir, rm } from 'node:fs/promises'\r\nimport { join } from 'node:path'\r\nimport { fileURLToPath } from 'node:url'\r\n\r\n// Библиотека помощников и навыков TAMsense лежит рядом с программой и подключается к Claude Code плагином на время задачи\r\nexport const LIBRARY_PATH = fileURLToPath(new URL('../library', import.meta.url))\r\n\r\nconst SKILL_NAME = /^[a-z0-9-]+$/\r\n\r\n// Навыки, которые постоянные правила шага велят загрузить: они остаются и у роли с узким списком\r\nconst STAGE_SKILLS: Partial<Record<RunnerStage['kind'], string[]>> = {\r\n interview: ['task-interview'],\r\n spec: ['spec-writing', 'task-interview'],\r\n}\r\n\r\n// Навыки запуска: null, если роль разрешает всю библиотеку (или сайт роли не прислал). Имя навыка только из букв, цифр и дефисов\r\nexport function allowedSkills(stage: Pick<RunnerStage, 'kind' | 'role'>): string[] | null {\r\n const skills = stage.role?.skills ?? null\r\n return skills && [...new Set([...skills, ...STAGE_SKILLS[stage.kind] ?? []])].filter(name => SKILL_NAME.test(name))\r\n}\r\n\r\nexport function roleLibraryFolder(home: string, runId: number): string {\r\n return join(home, '.tamsense', 'libraries', `run-${runId}`)\r\n}\r\n\r\n// Роль с «Only these»: своя копия плагина, где из навыков лежат только разрешённые. Остальные навыки библиотеки Claude Code\r\n// в этом запуске не находит вовсе, а свои навыки человека и проекта видны как обычно. Помощники библиотеки остаются\r\nexport async function prepareLibrary(folder: string, skills: string[] | null, source: string = LIBRARY_PATH): Promise<string> {\r\n if (skills === null) {\r\n return source\r\n }\r\n await rm(folder, { recursive: true, force: true })\r\n await mkdir(join(folder, 'skills'), { recursive: true })\r\n await cp(join(source, '.claude-plugin'), join(folder, '.claude-plugin'), { recursive: true })\r\n await cp(join(source, 'agents'), join(folder, 'agents'), { recursive: true })\r\n // навыка, которого нет в этой версии программы, просто нет и в копии\r\n for (const skill of skills.filter(name => SKILL_NAME.test(name) && existsSync(join(source, 'skills', name)))) {\r\n await cp(join(source, 'skills', skill), join(folder, 'skills', skill), { recursive: true })\r\n }\r\n return folder\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { RunnerAttachmentResponse, RunnerRecordingBody, RunnerRecordingMark, RunnerVideoType } from './protocol'\r\nimport type { RecordingCaption, SessionMeta } from './recorder'\r\nimport type { AttachFields } from './screenshot'\r\nimport { randomUUID } from 'node:crypto'\r\nimport { createReadStream } from 'node:fs'\r\nimport { readdir, readFile, stat, writeFile } from 'node:fs/promises'\r\nimport { basename, dirname, extname, isAbsolute, join } from 'node:path'\r\nimport { createInterface } from 'node:readline'\r\nimport { Readable } from 'node:stream'\r\nimport { promisify } from 'node:util'\r\nimport { gzip } from 'node:zlib'\r\nimport { ApiError } from './api'\r\nimport { MAX_VIDEO_BYTES, recordingUploadPath, runPath } from './protocol'\r\nimport { checkCaption, MARK_TAG, MAX_TITLE_LENGTH, META_SUFFIX, REPLAY_SUFFIX } from './recorder'\r\n\r\nconst gzipAsync = promisify(gzip)\r\n\r\n// кусок записи до сжатия; сайт принимает до 4 МБ сжатого и 32 МБ несжатого\r\nexport const CHUNK_TARGET_BYTES = 2 * 1024 * 1024\r\nconst MAX_LINE_BYTES = 30 * 1024 * 1024\r\nconst LEDGER_FILE = 'uploaded.json'\r\nconst RRWEB_FULL_SNAPSHOT = 2\r\nconst RRWEB_META = 4\r\nconst RRWEB_CUSTOM = 5\r\n\r\nconst VIDEO_TYPES_BY_EXTENSION: Record<string, RunnerVideoType> = {\r\n '.webm': 'video/webm',\r\n '.mp4': 'video/mp4',\r\n}\r\n\r\nexport type RecordingKind = 'replay' | 'video'\r\n\r\n// Запись или видео в папке запуска, которые ещё не ушли на сайт\r\nexport interface PendingRecording {\r\n kind: RecordingKind\r\n file: string\r\n meta: SessionMeta | null\r\n}\r\n\r\ninterface ReplayEvent {\r\n type: number\r\n timestamp: number\r\n data?: { tag?: unknown, payload?: { label?: unknown } }\r\n}\r\n\r\ninterface ReplayScan {\r\n startTs: number\r\n snapshot: boolean\r\n marks: Array<{ ts: number, label: string }>\r\n}\r\n\r\nfunction eventOf(line: string): ReplayEvent | null {\r\n try {\r\n const event = JSON.parse(line) as ReplayEvent\r\n return typeof event?.type === 'number' && typeof event.timestamp === 'number' ? event : null\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\n// Новый кусок начинается там, где rrweb снимает страницу заново (переход на другую страницу), или когда кусок вырос:\r\n// так же режет записи трекер, и плеер перематывает от ближайшего снимка\r\nexport async function* replayChunks(lines: AsyncIterable<string>, scan: ReplayScan, targetBytes: number = CHUNK_TARGET_BYTES): AsyncGenerator<string> {\r\n let chunk: string[] = []\r\n let bytes = 0\r\n let previous: ReplayEvent | null = null\r\n for await (const line of lines) {\r\n const event = line.trim() ? eventOf(line) : null\r\n if (!event) {\r\n continue\r\n }\r\n if (line.length > MAX_LINE_BYTES) {\r\n throw new Error('One event of this replay is larger than 30 MB, so it cannot be uploaded.')\r\n }\r\n const startsGroup = event.type === RRWEB_META || (event.type === RRWEB_FULL_SNAPSHOT && previous?.type !== RRWEB_META)\r\n if (chunk.length > 0 && (startsGroup || bytes + line.length > targetBytes)) {\r\n yield `${chunk.join('\\n')}\\n`\r\n chunk = []\r\n bytes = 0\r\n }\r\n chunk.push(line)\r\n bytes += line.length + 1\r\n previous = event\r\n scan.startTs = Math.min(scan.startTs, event.timestamp)\r\n scan.snapshot ||= event.type === RRWEB_FULL_SNAPSHOT\r\n if (event.type === RRWEB_CUSTOM && event.data?.tag === MARK_TAG && typeof event.data.payload?.label === 'string') {\r\n scan.marks.push({ ts: event.timestamp, label: event.data.payload.label })\r\n }\r\n }\r\n if (chunk.length > 0) {\r\n yield `${chunk.join('\\n')}\\n`\r\n }\r\n}\r\n\r\nexport function marksFrom(marks: Array<{ ts: number, label: string }>, startTs: number): RunnerRecordingMark[] {\r\n return marks.map(mark => ({ at: Math.max(0, mark.ts - startTs), label: mark.label })).sort((a, b) => a.at - b.at)\r\n}\r\n\r\nfunction sessionIdOf(file: string): string {\r\n return basename(file).replace(REPLAY_SUFFIX, '')\r\n}\r\n\r\nasync function readMeta(file: string): Promise<SessionMeta | null> {\r\n try {\r\n return JSON.parse(await readFile(file, 'utf8')) as SessionMeta\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\n// Подпись рядом с записью: файл .json того же сеанса; у видео из записи сеанс находится по пути видео\r\nasync function metaFor(kind: RecordingKind, file: string): Promise<SessionMeta | null> {\r\n if (kind === 'replay') {\r\n return readMeta(join(dirname(file), `${sessionIdOf(file)}${META_SUFFIX}`))\r\n }\r\n const folder = basename(dirname(file)) === 'video' ? dirname(dirname(file)) : dirname(file)\r\n for (const name of await readdir(folder).catch(() => [])) {\r\n if (name.endsWith(META_SUFFIX) && name !== LEDGER_FILE) {\r\n const meta = await readMeta(join(folder, name))\r\n if (meta?.video === file) {\r\n return meta\r\n }\r\n }\r\n }\r\n return null\r\n}\r\n\r\nasync function ledger(dir: string): Promise<Set<string>> {\r\n try {\r\n return new Set(JSON.parse(await readFile(join(dir, LEDGER_FILE), 'utf8')) as string[])\r\n }\r\n catch {\r\n return new Set()\r\n }\r\n}\r\n\r\nasync function remember(dir: string, file: string): Promise<void> {\r\n const done = await ledger(dir)\r\n done.add(file)\r\n await writeFile(join(dir, LEDGER_FILE), JSON.stringify([...done]))\r\n}\r\n\r\nasync function nonEmpty(file: string | null): Promise<boolean> {\r\n return file !== null && ((await stat(file).catch(() => null))?.size ?? 0) > 0\r\n}\r\n\r\nexport async function pendingRecordings(dir: string): Promise<PendingRecording[]> {\r\n const done = await ledger(dir)\r\n const names = (await readdir(dir).catch(() => [] as string[])).sort()\r\n const pending: PendingRecording[] = []\r\n for (const name of names) {\r\n if (!name.endsWith(META_SUFFIX) || name === LEDGER_FILE) {\r\n continue\r\n }\r\n const meta = await readMeta(join(dir, name))\r\n const replay = join(dir, `${name.slice(0, -META_SUFFIX.length)}${REPLAY_SUFFIX}`)\r\n if (!done.has(replay) && await nonEmpty(replay)) {\r\n pending.push({ kind: 'replay', file: replay, meta })\r\n }\r\n if (meta?.video && !done.has(meta.video) && await nonEmpty(meta.video)) {\r\n pending.push({ kind: 'video', file: meta.video, meta })\r\n }\r\n }\r\n return pending\r\n}\r\n\r\ninterface Upload {\r\n api: ApiClient\r\n runId: number\r\n}\r\n\r\nasync function uploadReplay(upload: Upload, file: string, caption: RecordingCaption): Promise<number> {\r\n const id = randomUUID()\r\n const scan: ReplayScan = { startTs: Number.POSITIVE_INFINITY, snapshot: false, marks: [] }\r\n const lines = createInterface({ input: createReadStream(file, 'utf8'), crlfDelay: Infinity })\r\n for await (const chunk of replayChunks(lines, scan)) {\r\n await upload.api.upload('POST', recordingUploadPath(upload.runId, id, 'chunks'), new Uint8Array(await gzipAsync(chunk)), 'application/gzip')\r\n }\r\n if (!scan.snapshot) {\r\n throw new Error('This replay has no page snapshot, so it cannot be played. Was the page ever opened?')\r\n }\r\n return finish(upload, { kind: 'replay', upload: id, name: basename(file), ...caption, contentType: 'application/x-ndjson', durationMs: null, marks: marksFrom(scan.marks, scan.startTs) })\r\n}\r\n\r\nasync function uploadVideo(upload: Upload, file: string, caption: RecordingCaption, meta: SessionMeta | null): Promise<number> {\r\n const contentType = VIDEO_TYPES_BY_EXTENSION[extname(file).toLowerCase()]!\r\n const info = await stat(file)\r\n if (info.size > MAX_VIDEO_BYTES) {\r\n throw new Error(`The video is larger than ${MAX_VIDEO_BYTES / 1024 / 1024} MB. Record a shorter one.`)\r\n }\r\n const id = randomUUID()\r\n const body = Readable.toWeb(createReadStream(file)) as ReadableStream<Uint8Array>\r\n await upload.api.upload('PUT', recordingUploadPath(upload.runId, id, 'video'), body, contentType)\r\n const startedAt = meta?.startedAt ?? info.birthtimeMs\r\n const endedAt = meta?.endedAt ?? info.mtimeMs\r\n const durationMs = Math.max(0, Math.round(endedAt - startedAt))\r\n const marks = marksFrom(meta?.marks ?? [], startedAt).filter(mark => mark.at <= durationMs)\r\n return finish(upload, { kind: 'video', upload: id, name: basename(file), ...caption, contentType, durationMs, marks })\r\n}\r\n\r\nasync function finish(upload: Upload, body: RunnerRecordingBody): Promise<number> {\r\n return (await upload.api.request<RunnerAttachmentResponse>('POST', runPath(upload.runId, 'recordings'), body)).id\r\n}\r\n\r\n// Ответ Claude на attach_replay и attach_video: приложено, или почему нет, чтобы он поправил путь или подпись\r\nexport async function attachRecording(api: ApiClient, runId: number, recordingsDir: string, request: AttachFields & { kind: RecordingKind }): Promise<string> {\r\n if (!isAbsolute(request.path)) {\r\n return `Give the absolute path of the ${request.kind === 'replay' ? 'replay (.rrweb.ndjson)' : 'video (.webm or .mp4)'} file.`\r\n }\r\n const fitsKind = request.kind === 'replay' ? request.path.endsWith(REPLAY_SUFFIX) : extname(request.path).toLowerCase() in VIDEO_TYPES_BY_EXTENSION\r\n if (!fitsKind) {\r\n return request.kind === 'replay' ? `A replay is the ${REPLAY_SUFFIX} file the TAMsense recorder wrote.` : 'Only WebM and MP4 videos can be attached.'\r\n }\r\n if (!await nonEmpty(request.path)) {\r\n return `There is no recording at ${request.path}. Close the browser context first (closeRecorded) so the file is complete.`\r\n }\r\n let caption: RecordingCaption\r\n try {\r\n caption = checkCaption(request)\r\n }\r\n catch (error) {\r\n return error instanceof Error ? error.message : String(error)\r\n }\r\n try {\r\n const meta = await metaFor(request.kind, request.path)\r\n const upload = { api, runId }\r\n await (request.kind === 'replay' ? uploadReplay(upload, request.path, caption) : uploadVideo(upload, request.path, caption, meta))\r\n await remember(recordingsDir, request.path)\r\n }\r\n catch (error) {\r\n return `Could not attach the ${request.kind}: ${error instanceof Error ? error.message : String(error)}`\r\n }\r\n return 'Attached to the task.'\r\n}\r\n\r\nfunction numbered(key: string, title: string, used: Map<string, number>): string {\r\n const count = (used.get(key) ?? 0) + 1\r\n used.set(key, count)\r\n const suffix = count > 1 ? ` (${count})` : ''\r\n return `${title.slice(0, MAX_TITLE_LENGTH - suffix.length)}${suffix}`\r\n}\r\n\r\n// сайт отказал, потому что запуск уже приложил сколько можно записей этого вида\r\nfunction overLimit(error: unknown): boolean {\r\n return error instanceof ApiError && error.status === 422 && /at most/i.test(error.message)\r\n}\r\n\r\nexport interface UploadReport {\r\n uploaded: number\r\n failed: string[]\r\n // не ушли из-за лимита запуска\r\n skipped: number\r\n}\r\n\r\n// В конце запуска на сайт уходит всё записанное, что агент не приложил сам: сначала то, что агент назвал сам, потом\r\n// записанное автоматически. Одинаковые названия получают номер\r\nexport async function uploadPendingRecordings(api: ApiClient, runId: number, dir: string): Promise<UploadReport> {\r\n const report: UploadReport = { uploaded: 0, failed: [], skipped: 0 }\r\n const used = new Map<string, number>()\r\n const full = new Set<RecordingKind>()\r\n const upload = { api, runId }\r\n const pending = (await pendingRecordings(dir)).sort((a, b) => Number(a.meta?.automatic ?? true) - Number(b.meta?.automatic ?? true))\r\n for (const recording of pending) {\r\n if (full.has(recording.kind)) {\r\n report.skipped += 1\r\n continue\r\n }\r\n const title = recording.meta?.title || 'Browser session'\r\n const caption = checkCaption({\r\n title: numbered(`${recording.kind}:${title}`, title, used),\r\n purpose: recording.meta?.purpose || 'Recorded automatically while the agent used a browser',\r\n })\r\n try {\r\n await (recording.kind === 'replay' ? uploadReplay(upload, recording.file, caption) : uploadVideo(upload, recording.file, caption, recording.meta))\r\n await remember(dir, recording.file)\r\n report.uploaded += 1\r\n }\r\n catch (error) {\r\n if (overLimit(error)) {\r\n full.add(recording.kind)\r\n report.skipped += 1\r\n continue\r\n }\r\n report.failed.push(`${caption.title} (${recording.kind}): ${error instanceof Error ? error.message : String(error)}`)\r\n }\r\n }\r\n return report\r\n}\r\n","import type { RunnerAttachmentBody, RunnerAttachmentType } from './protocol'\r\nimport { readFile, stat } from 'node:fs/promises'\r\nimport { basename, extname, isAbsolute } from 'node:path'\r\nimport { MAX_ATTACHMENT_BYTES } from './protocol'\r\nimport { checkCaption } from './recorder'\r\n\r\nconst TYPES_BY_EXTENSION: Record<string, RunnerAttachmentType> = {\r\n '.png': 'image/png',\r\n '.jpg': 'image/jpeg',\r\n '.jpeg': 'image/jpeg',\r\n '.webp': 'image/webp',\r\n}\r\n\r\n// Файл, который Claude прикладывает к задаче: путь, что на нём (title) и зачем он сделан (purpose)\r\nexport interface AttachFields {\r\n path: string\r\n title: string\r\n purpose: string\r\n}\r\n\r\nexport type AttachRequest = AttachFields & ({ kind: 'screenshot' } | { kind: 'replay' | 'video' })\r\n\r\n// Ответ Claude, если файл не годится: он поправит путь, формат или подпись и попробует снова\r\nexport async function readScreenshot(request: AttachFields): Promise<RunnerAttachmentBody | string> {\r\n if (!isAbsolute(request.path)) {\r\n return 'Give the absolute path of the image file.'\r\n }\r\n const contentType = TYPES_BY_EXTENSION[extname(request.path).toLowerCase()]\r\n if (!contentType) {\r\n return 'Only PNG, JPEG and WebP images can be attached.'\r\n }\r\n let caption: { title: string, purpose: string }\r\n try {\r\n caption = checkCaption(request)\r\n }\r\n catch (error) {\r\n return error instanceof Error ? error.message : String(error)\r\n }\r\n const info = await stat(request.path).catch(() => null)\r\n if (!info?.isFile()) {\r\n return `There is no file at ${request.path}.`\r\n }\r\n if (info.size > MAX_ATTACHMENT_BYTES) {\r\n return 'The image is larger than 5 MB. Take a smaller screenshot (viewport instead of full page, or JPEG).'\r\n }\r\n const data = (await readFile(request.path)).toString('base64')\r\n return { name: basename(request.path), ...caption, contentType, data }\r\n}\r\n"],"mappings":"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","names":["existsSync","homedir","process","reply","resolve","text","present","resolve","query","tool","z","text","tool","HELPER_TOOLS","text","tool","ref","task","text","applySwitch","resolve","work","text","STOPPED_TEXT","messageOf","reply","existsSync","join","resolve","existsSync","join","process","Buffer","spawn","process","resolve","text","join","process","existsSync","work","done","resolve","existsSync","rm","join","resolve","join","existsSync","rm","text","resolve","print","releaseBranch","work","resolve","keptBase","existsSync","join","sent","mkdir","readFile","rm","writeFile","basename","isAbsolute","join","resolve","tool","z","rm","mkdir","writeFile","join","isAbsolute","resolve","basename","readFile","text","tool","z","text","sourceOf","work","tool","z","z","reply","text","tool","text","reset","texts","z","tool","reply","query","mkdir","readFile","rm","writeFile","homedir","dirname","join","isRecord","reply","existsSync","join","basename","dirname","join","process","process","text","hostname","clip","script","basename","join","dirname","process","existsSync","join","spawn","delimiter","MAX_NAME_LENGTH","spawn","readFileSync","process","resolve","rm","dirname","process","existsSync","mkdir","rm","join","fileURLToPath","readFile","stat","writeFile","basename","dirname","extname","isAbsolute","join","createInterface","RRWEB_META","basename","readFile","join","dirname","done","writeFile","stat","createInterface","extname","recordingsDir","isAbsolute","readFile","stat","basename","extname","isAbsolute","TYPES_BY_EXTENSION","isAbsolute","extname","stat","readFile","basename","messageOf","print","reply","specWorkTools","knowledgeTools","workspace","process","dirname","finish","rm","run","process","existsSync","problem","homedir"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/api.ts","../src/connection.ts","../src/browser.ts","../src/commands.ts","../src/capabilities.ts","../src/claude.ts","../src/askPerson.ts","../src/claudeResult.ts","../src/protocol.ts","../src/feed.ts","../src/handoff.ts","../src/helpers.ts","../src/input.ts","../src/knowledge.ts","../src/permissions.ts","../src/localWorkspace.ts","../src/folders.ts","../src/git.ts","../src/lock.ts","../src/workspace.ts","../src/results.ts","../src/schedules.ts","../src/specText.ts","../src/prompt.ts","../src/specTools.ts","../src/usageLimit.ts","../src/config.ts","../src/login.ts","../src/recorderEnv.ts","../src/recorder.ts","../src/scope.ts","../src/system.ts","../src/update.ts","../src/worker.ts","../src/feedQueue.ts","../src/library.ts","../src/recordings.ts","../src/screenshot.ts"],"sourcesContent":["import type { InstalledBrowser, InstallTools } from './browser'\r\nimport type { Command } from './commands'\r\nimport type { RunnerConfig } from './config'\r\nimport type { RunnerIdentityResponse } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { homedir } from 'node:os'\r\nimport process from 'node:process'\r\nimport { ApiError, createApi, DEFAULT_SERVER, serverOrigin } from './api'\r\nimport { browserStatusText, ensureBrowser, installBrowser, installedBrowser, removeBrowser, runProcess } from './browser'\r\nimport { probeCapabilities } from './capabilities'\r\nimport { runClaude } from './claude'\r\nimport { HELP_TEXT, LOGIN_COMMAND, parseCommand, START_COMMAND } from './commands'\r\nimport { readConfig, removeConfig, writeConfig } from './config'\r\nimport { connectionWatch } from './connection'\r\nimport { inspectFolder } from './folders'\r\nimport { login } from './login'\r\nimport { IDENTITY_PATH } from './protocol'\r\nimport { bundledRecorder } from './recorderEnv'\r\nimport { runClaudeCheck } from './schedules'\r\nimport { describeDevice, teamKeyProblem } from './scope'\r\nimport { defaultDeviceName, openBrowser, platformLabel, sleep } from './system'\r\nimport { currentVersion, latestVersion, relaunch, updateToRun } from './update'\r\nimport { work } from './worker'\r\nimport { githubRemoteUrl } from './workspace'\r\n\r\nconst NOT_CONNECTED_TEXT = `This computer is not connected. Run ${LOGIN_COMMAND}`\r\nconst DISCONNECTED_TEXT = `This computer was disconnected on the website. Run ${LOGIN_COMMAND}`\r\nconst NO_TASKS_ACCESS_TEXT = 'Your account no longer has access to Tasks. Ask the project owner.'\r\nconst ACCESS_PROBLEMS: Record<number, string> = { 401: DISCONNECTED_TEXT, 403: NO_TASKS_ACCESS_TEXT }\r\nconst INTERRUPTED_EXIT_CODE = 130\r\n\r\nfunction print(line: string): void {\r\n process.stdout.write(`${line}\\n`)\r\n}\r\n\r\nfunction serverFor(flag: string | null, saved: RunnerConfig | null): string {\r\n return serverOrigin(flag ?? process.env.TAMSENSE_URL ?? saved?.server ?? DEFAULT_SERVER)\r\n}\r\n\r\n// Отказ в доступе объясняется человеку словами, остальные ошибки идут как есть\r\nfunction accessProblem(error: unknown): string | null {\r\n return error instanceof ApiError ? ACCESS_PROBLEMS[error.status] ?? null : null\r\n}\r\n\r\n// Прежний ключ этого компьютера отзывается: в списке на сайте не остаётся мёртвых записей\r\nasync function forgetDevice(config: RunnerConfig): Promise<void> {\r\n await createApi(config.server, config.token).request('DELETE', IDENTITY_PATH).catch(() => {})\r\n await removeConfig()\r\n}\r\n\r\nasync function runLogin(server: string | null, deviceName: string | null): Promise<void> {\r\n const saved = await readConfig()\r\n const api = createApi(serverFor(server, saved))\r\n if (saved) {\r\n await forgetDevice(saved)\r\n }\r\n const name = deviceName?.trim() || defaultDeviceName()\r\n const result = await login(\r\n { api, openBrowser, print, sleep, now: Date.now },\r\n { name, platform: platformLabel() },\r\n )\r\n await writeConfig({ server: api.server, token: result.token, deviceId: result.device.id, deviceName: result.device.name })\r\n print(`Connected: ${result.device.name}. To take your tasks, go to your code folder and run ${START_COMMAND}`)\r\n}\r\n\r\n// Первый Ctrl+C останавливает текущую задачу и отчитывается о ней, второй закрывает программу сразу\r\nfunction stopOnInterrupt(): AbortSignal {\r\n const stop = new AbortController()\r\n process.once('SIGINT', () => {\r\n print('Stopping… Press Ctrl+C again to quit at once.')\r\n stop.abort()\r\n process.once('SIGINT', () => process.exit(INTERRUPTED_EXIT_CODE))\r\n })\r\n return stop.signal\r\n}\r\n\r\nfunction installTools(home: string): InstallTools {\r\n return { home, nodePath: process.execPath, env: process.env, print, run: runProcess, exists: existsSync }\r\n}\r\n\r\n// Браузер для проверок интерфейса ставится сам; --no-browser только не ставит его, уже поставленный работает\r\nasync function browserForStart(home: string, install: boolean): Promise<InstalledBrowser | null> {\r\n const installed = await installedBrowser(home)\r\n if (installed || !install) {\r\n return installed\r\n }\r\n return ensureBrowser({ print, install: () => installBrowser(installTools(home)) })\r\n}\r\n\r\n// Вышла новая версия: она запускается вместо этой, и компьютер всегда работает на последней\r\nasync function startedNewVersion(): Promise<boolean> {\r\n const update = await updateToRun(process.env, currentVersion(), () => latestVersion())\r\n if (!update) {\r\n return false\r\n }\r\n print(`TAMsense runner ${update.latest} is out, this is ${update.current}. Starting the new version...`)\r\n try {\r\n process.exitCode = await relaunch(process.argv.slice(2), process.env)\r\n return true\r\n }\r\n catch {\r\n print('Could not start the new version. Going on with this one.')\r\n return false\r\n }\r\n}\r\n\r\nasync function runStart(parallel: number, installsBrowser: boolean): Promise<void> {\r\n if (await startedNewVersion()) {\r\n return\r\n }\r\n const saved = await readConfig()\r\n if (!saved) {\r\n print(NOT_CONNECTED_TEXT)\r\n return\r\n }\r\n const identity = await createApi(saved.server, saved.token).request<RunnerIdentityResponse>('GET', IDENTITY_PATH)\r\n const problem = teamKeyProblem(identity.device.team, process.env)\r\n if (problem) {\r\n throw new Error(problem)\r\n }\r\n print(`${identity.device.name} is connected to ${saved.server} as ${describeDevice(identity)}.`)\r\n const home = homedir()\r\n const browser = await browserForStart(home, installsBrowser)\r\n // дальше программа работает часами: о пропаже связи она говорит сама и ждёт её возвращения\r\n const api = createApi(saved.server, saved.token, { watch: connectionWatch(print) })\r\n await work({ api, runAgent: runClaude, runCheck: runClaudeCheck, version: currentVersion() ?? undefined, capabilities: probeCapabilities, print, sleep, home, remoteUrl: githubRemoteUrl, browser, recorder: bundledRecorder(), inspectFolder: path => inspectFolder(path, home), startFolder: process.cwd() }, stopOnInterrupt(), parallel)\r\n}\r\n\r\nasync function runBrowser(remove: boolean): Promise<void> {\r\n const home = homedir()\r\n if (remove) {\r\n print(await removeBrowser(home) ? 'Removed the headless browser. The next start installs it again, unless you add --no-browser.' : 'No headless browser is installed.')\r\n return\r\n }\r\n const installed = await installedBrowser(home)\r\n if (installed) {\r\n print(browserStatusText(installed))\r\n return\r\n }\r\n await installBrowser(installTools(home))\r\n print('Claude uses it for interface checks from the next start.')\r\n}\r\n\r\nasync function runStatus(): Promise<void> {\r\n const saved = await readConfig()\r\n if (!saved) {\r\n print(NOT_CONNECTED_TEXT)\r\n return\r\n }\r\n const identity = await createApi(saved.server, saved.token).request<RunnerIdentityResponse>('GET', IDENTITY_PATH)\r\n print(`Connected: ${identity.device.name}, ${describeDevice(identity)}`)\r\n print(`Account: ${identity.email ?? 'unknown'}, project ${identity.projectName}`)\r\n print(`Server: ${saved.server}`)\r\n}\r\n\r\nasync function runLogout(): Promise<void> {\r\n const saved = await readConfig()\r\n if (!saved) {\r\n print(NOT_CONNECTED_TEXT)\r\n return\r\n }\r\n await forgetDevice(saved)\r\n print('Disconnected. This computer no longer takes your tasks.')\r\n}\r\n\r\nasync function run(command: Command): Promise<void> {\r\n if (command.name === 'login') {\r\n await runLogin(command.server, command.deviceName)\r\n }\r\n else if (command.name === 'start') {\r\n await runStart(command.parallel, command.browser)\r\n }\r\n else if (command.name === 'browser') {\r\n await runBrowser(command.remove)\r\n }\r\n else if (command.name === 'status') {\r\n await runStatus()\r\n }\r\n else if (command.name === 'logout') {\r\n await runLogout()\r\n }\r\n else {\r\n print(HELP_TEXT)\r\n }\r\n}\r\n\r\ntry {\r\n await run(parseCommand(process.argv.slice(2), process.env))\r\n}\r\ncatch (error) {\r\n process.stderr.write(`${accessProblem(error) ?? (error instanceof Error ? error.message : String(error))}\\n`)\r\n process.exitCode = 1\r\n}\r\n","import type { ConnectionWatch, Failure, FailureKind } from './connection'\r\nimport { setTimeout as wait } from 'node:timers/promises'\r\nimport { backoffMs, BUSY_STATUSES, busyFailure, networkFailure } from './connection'\r\n\r\nexport const DEFAULT_SERVER = 'https://my.tamsense.com'\r\n\r\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'\r\n\r\nexport type UploadBody = Uint8Array<ArrayBuffer> | ReadableStream<Uint8Array>\r\n\r\nexport interface RequestOptions {\r\n // повтор после потерянного ответа ничего не портит: опрос, журнал с номерами записей\r\n safeToRepeat?: boolean\r\n}\r\n\r\n// request: JSON туда и обратно; upload: файл как есть, без base64 (куски записи, видео), ответ JSON\r\nexport interface ApiClient {\r\n readonly server: string\r\n request: <Reply>(method: HttpMethod, path: string, body?: object, options?: RequestOptions) => Promise<Reply>\r\n upload: <Reply>(method: HttpMethod, path: string, body: UploadBody, contentType: string) => Promise<Reply>\r\n // сработает, когда связь с сайтом вернётся; есть только у клиента, который следит за связью\r\n nextReconnect?: () => AbortSignal\r\n}\r\n\r\nexport interface ApiOptions {\r\n fetch?: typeof fetch\r\n watch?: ConnectionWatch\r\n timeoutMs?: number\r\n actionTimeoutMs?: number\r\n sleep?: (ms: number) => Promise<void>\r\n}\r\n\r\n// Каждая попытка ждёт ответа не дольше этого: зависшее соединение закрывается, повтор идёт по новому.\r\n// Повторяемый запрос короткий и сдаётся раньше; действие (итог, проверка ТЗ) может работать на сайте дольше\r\nconst REPEATABLE_TIMEOUT_MS = 30_000\r\nconst ACTION_TIMEOUT_MS = 2 * 60_000\r\n// файл записи на медленной сети идёт долго\r\nconst UPLOAD_TIMEOUT_MS = 15 * 60_000\r\nconst ATTEMPTS = 3\r\nconst RETRY_BASE_MS = 1_000\r\nconst RETRY_CAP_MS = 10_000\r\n// GET, PUT и DELETE по смыслу HTTP можно повторять; POST только с safeToRepeat\r\nconst REPEATABLE_METHODS = new Set<HttpMethod>(['GET', 'PUT', 'DELETE'])\r\n\r\n// Сайт недоступен или занят и после повторов. reported: строка о сбое уже напечатана, второй раз её печатать не нужно\r\nexport class ConnectionError extends Error {\r\n readonly kind: FailureKind\r\n readonly reported: boolean\r\n\r\n constructor(failure: Failure, reported: boolean) {\r\n super(failure.text)\r\n this.name = 'ConnectionError'\r\n this.kind = failure.kind\r\n this.reported = reported\r\n }\r\n}\r\n\r\nexport function alreadyReported(error: unknown): boolean {\r\n return error instanceof ConnectionError && error.reported\r\n}\r\n\r\nexport class ApiError extends Error {\r\n readonly status: number\r\n readonly code: string | null\r\n\r\n constructor(status: number, code: string | null, message: string) {\r\n super(message)\r\n this.name = 'ApiError'\r\n this.status = status\r\n this.code = code\r\n }\r\n}\r\n\r\ninterface ErrorReply {\r\n message?: unknown\r\n statusMessage?: unknown\r\n data?: { code?: unknown }\r\n}\r\n\r\nexport function serverOrigin(address: string): string {\r\n let url: URL\r\n try {\r\n url = new URL(address)\r\n }\r\n catch {\r\n throw new Error(`\"${address}\" is not a valid address. Use a full address like ${DEFAULT_SERVER}`)\r\n }\r\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\r\n throw new Error(`\"${address}\" is not a web address. Use a full address like ${DEFAULT_SERVER}`)\r\n }\r\n return url.origin\r\n}\r\n\r\nfunction textOf(value: unknown): string | null {\r\n return typeof value === 'string' && value.trim() ? value : null\r\n}\r\n\r\nasync function errorOf(response: Response): Promise<ApiError> {\r\n const reply = await response.json().catch(() => ({})) as ErrorReply\r\n const code = textOf(reply.data?.code)\r\n const message = textOf(reply.message) ?? textOf(reply.statusMessage) ?? `TAMsense answered with status ${response.status}`\r\n return new ApiError(response.status, code, message)\r\n}\r\n\r\ninterface Sending {\r\n method: HttpMethod\r\n path: string\r\n headers: Record<string, string>\r\n body: BodyInit | undefined\r\n attempts: number\r\n timeoutMs: number\r\n safeToRepeat: boolean\r\n}\r\n\r\nfunction isFailure(outcome: object): outcome is Failure {\r\n return 'kind' in outcome\r\n}\r\n\r\nexport function createApi(server: string, token: string | null = null, options: ApiOptions = {}): ApiClient {\r\n const { fetch: fetchImpl = fetch, watch, timeoutMs = REPEATABLE_TIMEOUT_MS, actionTimeoutMs = ACTION_TIMEOUT_MS, sleep = (ms: number) => wait(ms) } = options\r\n\r\n // Сбой сети и занятый сайт возвращаются как Failure, отказ сайта бросается как ApiError\r\n async function once<Reply>(sending: Sending): Promise<{ reply: Reply } | Failure> {\r\n const { method, path, headers, body } = sending\r\n let response: Response\r\n try {\r\n // поток уходит по мере чтения файла: fetch в Node просит для этого duplex\r\n response = await fetchImpl(new URL(path, server), { method, headers, body, signal: AbortSignal.timeout(sending.timeoutMs), ...(body instanceof ReadableStream ? { duplex: 'half' } : {}) } as RequestInit)\r\n }\r\n catch (error) {\r\n return networkFailure(error, server, sending.timeoutMs)\r\n }\r\n if (BUSY_STATUSES.has(response.status)) {\r\n await response.body?.cancel().catch(() => {})\r\n return busyFailure(response)\r\n }\r\n watch?.reached()\r\n if (!response.ok) {\r\n throw await errorOf(response)\r\n }\r\n try {\r\n return { reply: await response.json() as Reply }\r\n }\r\n catch (error) {\r\n if (error instanceof SyntaxError) {\r\n throw error\r\n }\r\n return networkFailure(error, server, sending.timeoutMs)\r\n }\r\n }\r\n\r\n async function send<Reply>(sending: Sending): Promise<Reply> {\r\n const headers: Record<string, string> = { accept: 'application/json', ...sending.headers }\r\n if (token) {\r\n headers.authorization = `Bearer ${token}`\r\n }\r\n for (let attempt = 1; ; attempt += 1) {\r\n const outcome = await once<Reply>({ ...sending, headers })\r\n if (!isFailure(outcome)) {\r\n return outcome.reply\r\n }\r\n if (attempt >= sending.attempts || !(outcome.unsent || sending.safeToRepeat)) {\r\n watch?.failed(outcome)\r\n throw new ConnectionError(outcome, Boolean(watch))\r\n }\r\n await sleep(outcome.waitMs === null ? backoffMs(attempt, RETRY_BASE_MS, RETRY_CAP_MS) : Math.min(outcome.waitMs, RETRY_CAP_MS))\r\n }\r\n }\r\n\r\n return {\r\n server,\r\n request: <Reply>(method: HttpMethod, path: string, body?: object, requestOptions: RequestOptions = {}) => {\r\n const safeToRepeat = REPEATABLE_METHODS.has(method) || Boolean(requestOptions.safeToRepeat)\r\n return send<Reply>({\r\n method,\r\n path,\r\n headers: body ? { 'content-type': 'application/json' } : {},\r\n body: body && JSON.stringify(body),\r\n attempts: ATTEMPTS,\r\n timeoutMs: safeToRepeat ? timeoutMs : actionTimeoutMs,\r\n safeToRepeat,\r\n })\r\n },\r\n // поток файла не перечитать, поэтому отправка файла не повторяется\r\n upload: <Reply>(method: HttpMethod, path: string, body: UploadBody, contentType: string) => send<Reply>({\r\n method,\r\n path,\r\n headers: { 'content-type': contentType },\r\n body,\r\n attempts: 1,\r\n timeoutMs: UPLOAD_TIMEOUT_MS,\r\n safeToRepeat: false,\r\n }),\r\n ...(watch ? { nextReconnect: watch.nextReconnect } : {}),\r\n }\r\n}\r\n","// Почему запрос к TAMsense не прошёл, простыми словами и с кодом ошибки: по нему видно настоящую причину\r\nexport type FailureKind = 'timeout' | 'closed' | 'no_address' | 'no_connection' | 'certificate' | 'busy' | 'other'\r\n\r\nexport interface Failure {\r\n kind: FailureKind\r\n text: string\r\n // запрос точно не дошёл до сайта или сайт отказал, не начав работу: повтор безопасен для любого запроса\r\n unsent: boolean\r\n // сколько сайт просит подождать (Retry-After), если просит\r\n waitMs: number | null\r\n}\r\n\r\nexport const BUSY_STATUSES = new Set([429, 502, 503, 504])\r\nconst REFUSED_STATUSES = new Set([429, 503])\r\n\r\nconst NOT_FOUND_CODES = new Set(['ENOTFOUND', 'EAI_AGAIN'])\r\nconst NO_CONNECTION_CODES = new Set(['ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'ENETDOWN', 'UND_ERR_CONNECT_TIMEOUT'])\r\nconst CLOSED_CODES = new Set(['ECONNRESET', 'EPIPE', 'ECONNABORTED', 'UND_ERR_SOCKET', 'UND_ERR_CLOSED'])\r\nconst TIMEOUT_CODES = new Set(['ETIMEDOUT', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT'])\r\nconst CERTIFICATE_CODE = /CERT|SELF_SIGNED|UNABLE_TO_VERIFY|ERR_TLS/\r\n\r\ninterface ErrorFields {\r\n name?: unknown\r\n code?: unknown\r\n cause?: unknown\r\n errors?: unknown\r\n}\r\n\r\n// fetch прячет причину в cause, а при нескольких адресах сайта ещё и в errors\r\nfunction codeOf(error: unknown): string | null {\r\n for (let current = error, depth = 0; current && typeof current === 'object' && depth < 5; depth += 1) {\r\n const fields = current as ErrorFields\r\n if (typeof fields.code === 'string') {\r\n return fields.code\r\n }\r\n current = fields.cause ?? (Array.isArray(fields.errors) ? fields.errors[0] : null)\r\n }\r\n return null\r\n}\r\n\r\nfunction isTimeout(error: unknown): boolean {\r\n return error instanceof Error && error.name === 'TimeoutError'\r\n}\r\n\r\nexport function networkFailure(error: unknown, server: string, timeoutMs: number): Failure {\r\n const host = new URL(server).host\r\n const code = codeOf(error)\r\n if (isTimeout(error)) {\r\n return { kind: 'timeout', text: `TAMsense did not answer in ${Math.round(timeoutMs / 1000)} seconds`, unsent: false, waitMs: null }\r\n }\r\n if (code && TIMEOUT_CODES.has(code)) {\r\n return { kind: 'timeout', text: `TAMsense did not answer in time (${code})`, unsent: false, waitMs: null }\r\n }\r\n if (code && NOT_FOUND_CODES.has(code)) {\r\n return { kind: 'no_address', text: `Could not find ${host} (${code}). Check the internet connection`, unsent: true, waitMs: null }\r\n }\r\n if (code && NO_CONNECTION_CODES.has(code)) {\r\n return { kind: 'no_connection', text: `Could not connect to ${host} (${code})`, unsent: true, waitMs: null }\r\n }\r\n if (code && CLOSED_CODES.has(code)) {\r\n return { kind: 'closed', text: `The network closed the connection to TAMsense (${code})`, unsent: false, waitMs: null }\r\n }\r\n if (code && CERTIFICATE_CODE.test(code)) {\r\n return { kind: 'certificate', text: `The secure connection to ${host} failed (${code}). A proxy or antivirus may be in the way`, unsent: true, waitMs: null }\r\n }\r\n return { kind: 'other', text: `Could not reach ${server}${code ? ` (${code})` : ''}. Check your internet connection`, unsent: false, waitMs: null }\r\n}\r\n\r\n// Retry-After: секунды или дата\r\nfunction retryAfterMs(header: string | null, now: number): number | null {\r\n if (!header) {\r\n return null\r\n }\r\n const seconds = Number(header)\r\n const at = Number.isFinite(seconds) ? now + seconds * 1000 : Date.parse(header)\r\n return Number.isFinite(at) ? Math.max(0, at - now) : null\r\n}\r\n\r\nexport function busyFailure(response: Response, now: number = Date.now()): Failure {\r\n return {\r\n kind: 'busy',\r\n text: `TAMsense is busy (${response.status})`,\r\n unsent: REFUSED_STATUSES.has(response.status),\r\n waitMs: retryAfterMs(response.headers.get('retry-after'), now),\r\n }\r\n}\r\n\r\n// Пауза растёт вдвое с каждой неудачей, но не выше потолка; половина паузы случайна, чтобы запуски не стучались разом\r\nexport function backoffMs(failures: number, baseMs: number, capMs: number, random: () => number = Math.random): number {\r\n const ceiling = Math.min(capMs, baseMs * 2 ** Math.max(0, failures - 1))\r\n return Math.round(ceiling / 2 + random() * ceiling / 2)\r\n}\r\n\r\nfunction durationText(ms: number): string {\r\n const seconds = Math.round(ms / 1000)\r\n if (seconds < 60) {\r\n return `${seconds} s`\r\n }\r\n const minutes = Math.round(seconds / 60)\r\n return minutes < 60 ? `${minutes} min` : `${Math.floor(minutes / 60)} h ${minutes % 60} min`\r\n}\r\n\r\nexport interface ConnectionWatch {\r\n failed: (failure: Failure) => void\r\n reached: () => void\r\n // сработает, когда связь вернётся: ожидание после сбоя кончается сразу\r\n nextReconnect: () => AbortSignal\r\n}\r\n\r\n// Связь с сайтом одна на всю программу: на сбой одна строка на каждую причину, на возврат одна строка с итогом\r\nexport function connectionWatch(print: (line: string) => void, now: () => number = Date.now): ConnectionWatch {\r\n let lostAt: number | null = null\r\n let failures = 0\r\n let told = new Set<FailureKind>()\r\n let reconnect = new AbortController()\r\n\r\n return {\r\n failed(failure) {\r\n lostAt ??= now()\r\n failures += 1\r\n if (!told.has(failure.kind)) {\r\n told.add(failure.kind)\r\n print(`${failure.text}. Trying again.`)\r\n }\r\n },\r\n reached() {\r\n if (lostAt === null) {\r\n return\r\n }\r\n print(`Connected to TAMsense again after ${durationText(now() - lostAt)}, ${failures} request(s) failed meanwhile.`)\r\n lostAt = null\r\n failures = 0\r\n told = new Set()\r\n reconnect.abort()\r\n reconnect = new AbortController()\r\n },\r\n nextReconnect: () => reconnect.signal,\r\n }\r\n}\r\n","import { spawn } from 'node:child_process'\r\nimport { existsSync } from 'node:fs'\r\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\r\nimport { delimiter, dirname, join } from 'node:path'\r\nimport { createInterface } from 'node:readline'\r\nimport { BROWSER_COMMAND } from './commands'\r\n\r\nexport const PLAYWRIGHT_VERSION = '1.64.1'\r\n\r\n// Сначала облегчённый headless Chromium; если для этой системы его нет, обычный\r\nconst BROWSER_NAMES = ['chromium-headless-shell', 'chromium']\r\nconst MARKER_FILE = 'tamsense-browser.json'\r\nconst OUTPUT_TAIL_LINES = 40\r\nconst ERROR_TAIL_LINES = 3\r\n// eslint-disable-next-line no-control-regex\r\nconst ANSI_PATTERN = /\\u001B\\[[\\d;]*m/g\r\nconst OFFLINE_PATTERN = /ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNREFUSED|ECONNRESET|ENETUNREACH|getaddrinfo/i\r\n\r\nexport const INSTALLING_TEXT = 'Interface checks need a headless browser. Installing it once into ~/.tamsense, about 150 MB. To start without it, add --no-browser.'\r\nconst OFFLINE_TEXT = `No connection to download it. Check your internet connection and run ${BROWSER_COMMAND} again.`\r\n\r\nexport interface BrowserFolders {\r\n prefix: string\r\n packageDir: string\r\n browsers: string\r\n marker: string\r\n}\r\n\r\nexport interface InstalledBrowser {\r\n packageDir: string\r\n browsers: string\r\n version: string\r\n browser: string\r\n}\r\n\r\nexport interface ProcessResult {\r\n code: number\r\n output: string\r\n}\r\n\r\nexport interface ProcessOptions {\r\n cwd: string\r\n env: NodeJS.ProcessEnv\r\n onLine: (line: string) => void\r\n}\r\n\r\nexport type RunProcess = (command: string, args: string[], options: ProcessOptions) => Promise<ProcessResult>\r\n\r\nexport interface InstallTools {\r\n home: string\r\n nodePath: string\r\n env: NodeJS.ProcessEnv\r\n print: (line: string) => void\r\n run: RunProcess\r\n exists: (path: string) => boolean\r\n}\r\n\r\nexport interface EnsureTools {\r\n print: (line: string) => void\r\n install: () => Promise<InstalledBrowser>\r\n}\r\n\r\nexport function browserFolders(home: string): BrowserFolders {\r\n const prefix = join(home, '.tamsense', 'playwright')\r\n return {\r\n prefix,\r\n packageDir: join(prefix, 'node_modules', 'playwright-core'),\r\n browsers: join(home, '.tamsense', 'browsers'),\r\n marker: join(prefix, MARKER_FILE),\r\n }\r\n}\r\n\r\n// Установленным считается только браузер, который докачался до конца и той же версии, что ждёт программа\r\nexport async function installedBrowser(home: string): Promise<InstalledBrowser | null> {\r\n const folders = browserFolders(home)\r\n let saved: { version?: unknown, browser?: unknown }\r\n try {\r\n saved = JSON.parse(await readFile(folders.marker, 'utf8')) as typeof saved\r\n }\r\n catch {\r\n return null\r\n }\r\n const complete = saved.version === PLAYWRIGHT_VERSION && typeof saved.browser === 'string'\r\n && existsSync(join(folders.packageDir, 'package.json')) && existsSync(folders.browsers)\r\n return complete ? { packageDir: folders.packageDir, browsers: folders.browsers, version: PLAYWRIGHT_VERSION, browser: String(saved.browser) } : null\r\n}\r\n\r\n// Свой браузер не подсовывается Playwright проекта: путь к браузерам скрипт проверки ставит сам, когда грузит эту копию\r\nexport function browserEnv(browser: InstalledBrowser): Record<string, string> {\r\n return { TAMSENSE_PLAYWRIGHT: browser.packageDir, TAMSENSE_PLAYWRIGHT_BROWSERS: browser.browsers }\r\n}\r\n\r\nexport function browserStatusText(browser: InstalledBrowser): string {\r\n return `The headless browser is installed: ${browser.browser} with playwright-core ${browser.version}, in ${dirname(browser.browsers)}. To remove it, run ${BROWSER_COMMAND} remove`\r\n}\r\n\r\n// npm запускается тем же node, что и программа: так он находится, даже если npm нет в PATH, и обходится без оболочки\r\nexport function findNpm(nodePath: string, env: NodeJS.ProcessEnv, exists: (path: string) => boolean): string | null {\r\n const pathKey = Object.keys(env).find(name => name.toUpperCase() === 'PATH') ?? 'PATH'\r\n const folders = [dirname(nodePath), ...(env[pathKey] ?? '').split(delimiter).filter(Boolean)]\r\n for (const folder of folders) {\r\n for (const cli of [join(folder, 'node_modules', 'npm', 'bin', 'npm-cli.js'), join(folder, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js')]) {\r\n if (exists(cli)) {\r\n return cli\r\n }\r\n }\r\n }\r\n return null\r\n}\r\n\r\nexport const runProcess: RunProcess = (command, args, options) => new Promise((resolve, reject) => {\r\n const child = spawn(command, args, { cwd: options.cwd, env: options.env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true })\r\n const tail: string[] = []\r\n for (const stream of [child.stdout, child.stderr]) {\r\n createInterface({ input: stream }).on('line', (line) => {\r\n const text = line.replace(ANSI_PATTERN, '').trim()\r\n if (text) {\r\n tail.push(text)\r\n tail.splice(0, Math.max(0, tail.length - OUTPUT_TAIL_LINES))\r\n options.onLine(text)\r\n }\r\n })\r\n }\r\n child.on('error', reject)\r\n child.on('close', code => resolve({ code: code ?? 1, output: tail.join('\\n') }))\r\n})\r\n\r\nfunction failureText(what: string, result: ProcessResult): string {\r\n if (OFFLINE_PATTERN.test(result.output)) {\r\n return `${what}: ${OFFLINE_TEXT}`\r\n }\r\n const lastLines = result.output.split('\\n').slice(-ERROR_TAIL_LINES).join(' ')\r\n return `${what} (exit code ${result.code})${lastLines ? `: ${lastLines}` : '.'}`\r\n}\r\n\r\n// Ставится в ~/.tamsense, не в проект: зависимости проектов и этой программы не меняются\r\nexport async function installBrowser(tools: InstallTools): Promise<InstalledBrowser> {\r\n const folders = browserFolders(tools.home)\r\n const npmCli = findNpm(tools.nodePath, tools.env, tools.exists)\r\n if (!npmCli) {\r\n throw new Error(`npm was not found next to ${tools.nodePath} or on PATH. Install Node.js with npm and run ${BROWSER_COMMAND} again.`)\r\n }\r\n const showLine = (line: string) => tools.print(` ${line}`)\r\n await rm(folders.marker, { force: true })\r\n await mkdir(folders.prefix, { recursive: true })\r\n\r\n tools.print(`Step 1 of 2: downloading playwright-core ${PLAYWRIGHT_VERSION}…`)\r\n const npmArgs = [npmCli, 'install', '--no-save', '--no-audit', '--no-fund', '--prefix', folders.prefix, `playwright-core@${PLAYWRIGHT_VERSION}`]\r\n const npm = await tools.run(tools.nodePath, npmArgs, { cwd: folders.prefix, env: tools.env, onLine: showLine })\r\n if (npm.code !== 0) {\r\n throw new Error(failureText('npm could not download playwright-core', npm))\r\n }\r\n\r\n tools.print('Step 2 of 2: downloading the browser, about 150 MB…')\r\n const env = { ...tools.env, PLAYWRIGHT_BROWSERS_PATH: folders.browsers }\r\n let result: ProcessResult = { code: 1, output: '' }\r\n for (const browser of BROWSER_NAMES) {\r\n result = await tools.run(tools.nodePath, [join(folders.packageDir, 'cli.js'), 'install', browser], { cwd: folders.prefix, env, onLine: showLine })\r\n if (result.code === 0) {\r\n await writeFile(folders.marker, `${JSON.stringify({ version: PLAYWRIGHT_VERSION, browser }, null, 4)}\\n`)\r\n tools.print(`Installed ${browser} into ${folders.browsers}.`)\r\n return { packageDir: folders.packageDir, browsers: folders.browsers, version: PLAYWRIGHT_VERSION, browser }\r\n }\r\n if (OFFLINE_PATTERN.test(result.output)) {\r\n break\r\n }\r\n }\r\n throw new Error(failureText('Could not download the browser', result))\r\n}\r\n\r\nexport async function removeBrowser(home: string): Promise<boolean> {\r\n const folders = browserFolders(home)\r\n const present = existsSync(folders.prefix) || existsSync(folders.browsers)\r\n await rm(folders.prefix, { recursive: true, force: true })\r\n await rm(folders.browsers, { recursive: true, force: true })\r\n return present\r\n}\r\n\r\n// Ставится сам, без вопроса и с терминалом или без; сбой установки не мешает брать задачи\r\nexport async function ensureBrowser(tools: EnsureTools): Promise<InstalledBrowser | null> {\r\n tools.print(INSTALLING_TEXT)\r\n try {\r\n return await tools.install()\r\n }\r\n catch (error) {\r\n tools.print(`Could not install the browser: ${error instanceof Error ? error.message : String(error)}`)\r\n tools.print(`Tasks still run without it. To try again, run ${BROWSER_COMMAND}`)\r\n return null\r\n }\r\n}\r\n","import { parseArgs } from 'node:util'\r\nimport { DEFAULT_SERVER } from './api'\r\n\r\nexport const LOGIN_COMMAND = 'npx @tamsensedev/runner@latest login'\r\nexport const START_COMMAND = 'npx @tamsensedev/runner@latest start'\r\nexport const BROWSER_COMMAND = 'npx @tamsensedev/runner@latest browser'\r\n\r\nexport const DEFAULT_PARALLEL = 2\r\nexport const MAX_PARALLEL = 4\r\n\r\nexport type Command\r\n = | { name: 'login', server: string | null, deviceName: string | null }\r\n | { name: 'start', parallel: number, browser: boolean }\r\n | { name: 'browser', remove: boolean }\r\n | { name: 'status' }\r\n | { name: 'logout' }\r\n | { name: 'help' }\r\n\r\nexport const HELP_TEXT = `Connects this computer to TAMsense so it can work on your tasks.\r\n\r\nUsage:\r\n ${LOGIN_COMMAND} connect this computer\r\n ${START_COMMAND} take your tasks and work on them until you press Ctrl+C;\r\n run it from your code folder (or the folder above it) and it finds the code there\r\n ${BROWSER_COMMAND} install the headless browser for interface checks now, or show it\r\n ${BROWSER_COMMAND} remove delete that browser\r\n npx @tamsensedev/runner@latest status show the connection\r\n npx @tamsensedev/runner@latest logout disconnect this computer\r\n\r\nLogin options:\r\n --name <name> how this computer is called in TAMsense (default: its network name)\r\n --server <url> TAMsense address (default: ${DEFAULT_SERVER})\r\n\r\nStart options:\r\n --parallel <n> how many tasks to work on at once, 1 to ${MAX_PARALLEL} (default: ${DEFAULT_PARALLEL}, or TAMSENSE_PARALLEL)\r\n --no-browser do not install the headless browser for interface checks (start installs it when it is missing)`\r\n\r\n// Флаг важнее переменной окружения; неверное число останавливает запуск, а не тихо меняется на другое\r\nexport function parallelFor(flag: string | undefined, env: string | undefined): number {\r\n const [source, value] = flag !== undefined ? ['--parallel', flag] : ['TAMSENSE_PARALLEL', env?.trim() || undefined]\r\n if (value === undefined) {\r\n return DEFAULT_PARALLEL\r\n }\r\n const count = Number(value)\r\n if (!/^\\d+$/.test(value) || count < 1 || count > MAX_PARALLEL) {\r\n throw new Error(`${source} must be a whole number from 1 to ${MAX_PARALLEL}, got \"${value}\".`)\r\n }\r\n return count\r\n}\r\n\r\nexport function parseCommand(argv: string[], env: Record<string, string | undefined> = {}): Command {\r\n const { positionals, values } = parseArgs({\r\n args: argv,\r\n allowPositionals: true,\r\n strict: true,\r\n options: {\r\n 'server': { type: 'string' },\r\n 'name': { type: 'string' },\r\n 'parallel': { type: 'string' },\r\n 'no-browser': { type: 'boolean' },\r\n 'help': { type: 'boolean' },\r\n },\r\n })\r\n const [name, detail] = positionals\r\n if (values.help) {\r\n return { name: 'help' }\r\n }\r\n if (name === 'browser' && (detail === undefined || detail === 'remove') && positionals.length <= 2) {\r\n return { name: 'browser', remove: detail === 'remove' }\r\n }\r\n if (positionals.length !== 1) {\r\n return { name: 'help' }\r\n }\r\n if (name === 'login') {\r\n return { name: 'login', server: values.server ?? null, deviceName: values.name ?? null }\r\n }\r\n if (name === 'start') {\r\n return { name: 'start', parallel: parallelFor(values.parallel, env.TAMSENSE_PARALLEL), browser: !values['no-browser'] }\r\n }\r\n if (name === 'status' || name === 'logout') {\r\n return { name }\r\n }\r\n return { name: 'help' }\r\n}\r\n","import type { AccountInfo, ApiKeySource, SDKControlInitializeResponse, SDKSystemMessage, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerAccount, RunnerCapabilities, RunnerSession } from './protocol'\r\nimport { homedir } from 'node:os'\r\nimport { query } from '@anthropic-ai/claude-agent-sdk'\r\n\r\nconst PROBE_TIMEOUT_MS = 60_000\r\nconst NO_ANSWER_TEXT = 'Claude Code did not start in time.'\r\nconst STOPPED_TEXT = 'Stopped before Claude Code answered.'\r\n\r\n// apiKeySource по SDK: ключ из окружения, из apiKeyHelper или ключ Console после /login (user, project, org, temporary из старых версий);\r\n// 'none' (и старое 'oauth') значит без ключа, то есть вход через claude.ai\r\nconst API_KEY_SOURCES = new Set<string>(['ANTHROPIC_API_KEY', 'apiKeyHelper', '/login managed key', 'user', 'project', 'org', 'temporary'] satisfies ApiKeySource[])\r\nconst LOGIN_SOURCES = new Set<string>(['none', 'oauth'] satisfies ApiKeySource[])\r\n\r\nconst ACCOUNT_WORDS: Record<RunnerAccount, string> = {\r\n subscription: 'on your subscription',\r\n api_key: 'on an API key',\r\n}\r\n\r\nexport function accountOfKeySource(source: string | undefined): RunnerAccount | null {\r\n if (source && API_KEY_SOURCES.has(source)) {\r\n return 'api_key'\r\n }\r\n return source && LOGIN_SOURCES.has(source) ? 'subscription' : null\r\n}\r\n\r\n// Bedrock, Vertex и другие облака не подписка и не ключ Anthropic\r\nexport function accountOfInfo(info: AccountInfo): RunnerAccount | null {\r\n if (info.apiProvider && info.apiProvider !== 'firstParty') {\r\n return null\r\n }\r\n if (info.apiKeySource && API_KEY_SOURCES.has(info.apiKeySource)) {\r\n return 'api_key'\r\n }\r\n return info.subscriptionType ? 'subscription' : null\r\n}\r\n\r\nexport function sessionOf(init: SDKSystemMessage): RunnerSession {\r\n return {\r\n model: init.model || null,\r\n account: accountOfKeySource(init.apiKeySource),\r\n claudeVersion: init.claude_code_version || null,\r\n }\r\n}\r\n\r\nexport function capabilitiesOf(init: SDKControlInitializeResponse): RunnerCapabilities {\r\n return {\r\n account: accountOfInfo(init.account),\r\n claudeVersion: init.claude_code_version || null,\r\n models: init.models.map(model => ({ value: model.value, displayName: model.displayName, description: model.description })),\r\n }\r\n}\r\n\r\n// В строке только короткие имена (opus, sonnet, haiku): точные версии и «по умолчанию» уходят на сайт, но здесь лишние\r\nfunction isAlias(value: string): boolean {\r\n return value !== 'default' && !value.startsWith('claude-')\r\n}\r\n\r\nexport function capabilitiesLine(capabilities: RunnerCapabilities): string {\r\n const version = capabilities.claudeVersion ? `Claude Code ${capabilities.claudeVersion}` : 'Claude Code'\r\n const account = capabilities.account ? ` ${ACCOUNT_WORDS[capabilities.account]}` : ''\r\n const names = capabilities.models.filter(model => isAlias(model.value)).map(model => model.displayName)\r\n return names.length > 0 ? `${version}${account}: ${names.join(', ')}` : `${version}${account}`\r\n}\r\n\r\n// Claude Code запускается без единого сообщения: он отвечает на знакомство (вход, версия, модели) и закрывается, модель не вызывается\r\nexport async function probeCapabilities(stop?: AbortSignal): Promise<RunnerCapabilities> {\r\n stop?.throwIfAborted()\r\n let release = () => {}\r\n const released = new Promise<void>((resolve) => {\r\n release = resolve\r\n })\r\n const nothing: AsyncIterable<SDKUserMessage> = {\r\n [Symbol.asyncIterator]: () => ({\r\n next: async () => {\r\n await released\r\n return { done: true, value: undefined }\r\n },\r\n }),\r\n }\r\n const session = query({ prompt: nothing, options: { cwd: homedir(), settingSources: ['user'], persistSession: false, strictMcpConfig: true } })\r\n const timeout = AbortSignal.timeout(PROBE_TIMEOUT_MS)\r\n const giveUp = stop ? AbortSignal.any([stop, timeout]) : timeout\r\n const gaveUp = new Promise<never>((_, reject) => {\r\n giveUp.addEventListener('abort', () => reject(new Error(stop?.aborted ? STOPPED_TEXT : NO_ANSWER_TEXT)), { once: true })\r\n })\r\n try {\r\n return capabilitiesOf(await Promise.race([session.initializationResult(), gaveUp]))\r\n }\r\n finally {\r\n release()\r\n session.close()\r\n }\r\n}\r\n","import type { McpSdkServerConfigWithInstance, Options, Query } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { AgentOutcome } from './claudeResult'\r\nimport type { FeedDraft } from './feed'\r\nimport type { AgentInput, ModelSwitch } from './input'\r\nimport type { KnowledgeTools } from './knowledge'\r\nimport type { PermissionTools } from './permissions'\r\nimport type { TaskPrompt } from './prompt'\r\nimport type { RunnerAskBody, RunnerAskReply, RunnerEffort, RunnerHandoff, RunnerHandoffTarget, RunnerPerson, RunnerQuestions, RunnerSession, RunnerSpecResult, RunnerVerdict } from './protocol'\r\nimport type { HandOverTools, ResultTools } from './results'\r\nimport type { ScheduleTools } from './schedules'\r\nimport type { AttachRequest } from './screenshot'\r\nimport type { ReviewRequest } from './specTools'\r\nimport type { UsageLimitWatch } from './usageLimit'\r\nimport { createSdkMcpServer, query, tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { ALREADY_WAITING_TEXT, askedText, checkAsk, MAX_ASK_OPTIONS, MAX_ASK_TRIES } from './askPerson'\r\nimport { sessionOf } from './capabilities'\r\nimport { outcomeOf } from './claudeResult'\r\nimport { feedOf, hiddenStepsFilter } from './feed'\r\nimport { chooseHandoff, HANDOFF_OUTCOMES } from './handoff'\r\nimport { helperTasks } from './helpers'\r\nimport { DEFAULT_MODEL } from './input'\r\nimport { proposeKnowledgeTool, readKnowledgeTool, updateKnowledgeTool } from './knowledge'\r\nimport { askPermission, denyWhenAsked } from './permissions'\r\nimport { ASK_PERSON_TOOL_NAME, HANDOFF_TOOL_NAME, REPLAY_TOOL_NAME, SCREENSHOT_TOOL_NAME, VERDICT_TOOL_NAME, VIDEO_TOOL_NAME } from './prompt'\r\nimport { handOverTool, resultToolset } from './results'\r\nimport { proposeScheduleTool } from './schedules'\r\nimport { briefTool, questionsTool, specTool } from './specTools'\r\nimport { USAGE_LIMIT_REPORT, usageLimitWatch } from './usageLimit'\r\n\r\nexport type { AgentOutcome }\r\n\r\n// wantsVerdict: шаг проверки, Claude сдаёт вердикт инструментом, а не текстом\r\n// handoffs: куда Claude может передать задачу инструментом hand_off; пусто: инструмента нет\r\n// resumeSessionId: сеанс Claude Code прошлого запуска шага, разговор продолжается\r\n// onFeed: каждая запись журнала, attach: скриншот, запись браузера или видео к задаче, ответ уходит Claude как результат инструмента\r\n// model и effort: null значит как настроено в Claude Code; onSession: какая модель и чей вход на самом деле начали работу\r\n// input: первое сообщение, сообщения людей по ходу работы и смена модели\r\n// specWork: шаг ТЗ, Claude сдаёт вопросы или ТЗ своими инструментами, а сайт их проверяет; null на шагах кода и проверки\r\n// asking: люди задачи и отправка вопроса одному из них; null: спрашивать некого\r\n// permissions: действия, о которых Claude Code спросил бы человека, уходят на сайт; null: такие действия отклоняются сразу\r\n// knowledge: проверка и правка знаний проекта и предложение новой заметки; null: сайт знаний не прислал\r\n// library: папка плагина TAMsense для этого запуска, вся библиотека или копия с навыками роли\r\n// results: доска без кода, шаг Do кладёт результат на задачу своими инструментами; null на доске с кодом и на других шагах\r\n// handOver: правило шага разрешает передать задачу на другую доску; null: инструмента нет\r\n// schedules: агент может предложить расписание проверки; null: сайт прежней версии, инструмента нет\r\nexport interface AgentRequest {\r\n cwd: string\r\n library: string\r\n prompt: TaskPrompt\r\n input: AgentInput\r\n env: Record<string, string | undefined>\r\n signal: AbortSignal\r\n wantsVerdict: boolean\r\n specWork: SpecWorkTools | null\r\n asking: AskingTools | null\r\n permissions: PermissionTools | null\r\n knowledge: KnowledgeTools | null\r\n handoffs: RunnerHandoffTarget[]\r\n resumeSessionId: string | null\r\n model: string | null\r\n effort: RunnerEffort | null\r\n onFeed: (draft: FeedDraft) => void\r\n onSession: (session: RunnerSession) => void\r\n attach: (request: AttachRequest) => Promise<string>\r\n results?: ResultTools | null\r\n handOver?: HandOverTools | null\r\n schedules?: ScheduleTools | null\r\n}\r\n\r\n// brief: доска без кода, шаг ТЗ сдаёт бриф результата вместо ТЗ разработки\r\nexport interface SpecWorkTools {\r\n stage: 'interview' | 'spec'\r\n review: ReviewRequest\r\n brief?: boolean\r\n}\r\n\r\nexport interface AskingTools {\r\n people: RunnerPerson[]\r\n send: (body: RunnerAskBody) => Promise<RunnerAskReply>\r\n}\r\n\r\nexport type RunAgent = (request: AgentRequest) => Promise<AgentOutcome>\r\n\r\nexport type StartQuery = typeof query\r\n\r\nconst NO_RESULT_TEXT = 'Claude finished without a result.'\r\nconst SERVER_NAME = 'tamsense'\r\n\r\nconst CAPTION_FIELDS = {\r\n title: z.string().describe('short name of what it shows, at most 60 characters: \"Design option 1\", \"Feature testing\", \"Feature testing, attempt 2\". Never generic names like \"screenshot1\"'),\r\n purpose: z.string().describe('one line, at most 160 characters, on why it was made: \"Comparing two layouts for the empty orders state\"'),\r\n}\r\n\r\nconst NOTES_FIELD = {\r\n notes: z.string().optional().describe('what you tested in it and what happened, at most 600 characters: what you opened, what you clicked or typed, what you expected, and whether it passed or failed. \"Opened the disposed requests on the warehouse page, filtered by December, expected 3 rows: got 3, passed. Clicked Export: the file was empty, failed.\"'),\r\n}\r\n\r\nconst ATTACH_TOOLS = {\r\n screenshot: {\r\n name: SCREENSHOT_TOOL_NAME,\r\n description: 'Attach a screenshot to the TAMsense task so people can see the result without opening the code. Attach the final look of each changed screen and anything a reviewer must see. PNG, JPEG or WebP up to 5 MB, at most 20 per run.',\r\n path: 'absolute path of the image file',\r\n },\r\n replay: {\r\n name: REPLAY_TOOL_NAME,\r\n description: 'Attach a browser replay (the .rrweb.ndjson file the TAMsense recorder wrote) to the task now, with your own title, purpose and notes on what you tested and what happened. Every recording is attached at the end of the run anyway; use this to attach one earlier or to name it better. Close the browser context first so the file is complete.',\r\n path: 'absolute path of the .rrweb.ndjson replay file',\r\n },\r\n video: {\r\n name: VIDEO_TOOL_NAME,\r\n description: 'Attach a browser video (.webm or .mp4, up to 200 MB) to the task now, with your own title, purpose and notes on what you tested and what happened. Videos recorded with video: true are attached at the end of the run anyway. Close the browser context first so the file is complete.',\r\n path: 'absolute path of the video file',\r\n },\r\n} as const\r\n\r\nfunction attachTool(kind: AttachRequest['kind'], attach: AgentRequest['attach']) {\r\n const spec = ATTACH_TOOLS[kind]\r\n return tool(\r\n spec.name,\r\n spec.description,\r\n { path: z.string().describe(spec.path), ...CAPTION_FIELDS, ...(kind === 'screenshot' ? {} : NOTES_FIELD) },\r\n async args => ({ content: [{ type: 'text', text: await attach({ kind, ...args }) }] }),\r\n )\r\n}\r\n\r\n// Проверка, которая не смогла запуститься из-за этого компьютера, никогда не считается пройденной\r\nexport function verdictTool(onVerdict: (verdict: RunnerVerdict) => void) {\r\n return tool(\r\n VERDICT_TOOL_NAME,\r\n 'Hand in the result of this check. Call it exactly once, at the very end.',\r\n {\r\n passed: z.boolean().describe('true only if nothing must be fixed'),\r\n report: z.string().describe('what you checked and, if it failed, exactly what to fix with file and line'),\r\n couldNotRun: z.boolean().optional().describe('true when the check could not run at all because of this computer: Docker not running, no local setup, or settings that point outside this computer; passed must be false and the report gives the exact reason and what a person should do'),\r\n },\r\n async (args) => {\r\n onVerdict(args.couldNotRun ? { passed: false, report: args.report, couldNotRun: true } : { passed: args.passed, report: args.report })\r\n return { content: [{ type: 'text', text: 'Verdict received. You can stop now.' }] }\r\n },\r\n )\r\n}\r\n\r\n// Передача проверяется здесь же: ошибка с перечнем допустимых шагов уходит Claude, принятая заменяет прежнюю\r\nexport function handoffTool(targets: RunnerHandoffTarget[], onHandoff: (handoff: RunnerHandoff) => void) {\r\n return tool(\r\n HANDOFF_TOOL_NAME,\r\n 'Hand the task to the next step of the TAMsense board when you finish. The next agent and the task owner read your note. Only one handoff counts: calling it again replaces the earlier one.',\r\n {\r\n to: z.string().describe('name or id of the step to hand the task to'),\r\n outcome: z.enum(HANDOFF_OUTCOMES).describe('pass: the work is done; problems: something must be fixed; ask: a question for that step'),\r\n note: z.string().describe('for the next agent and the owner: what was done, what to check, what is open'),\r\n },\r\n async (args) => {\r\n const choice = chooseHandoff(targets, args)\r\n if ('error' in choice) {\r\n return { content: [{ type: 'text', text: choice.error }], isError: true }\r\n }\r\n onHandoff(choice.handoff)\r\n return { content: [{ type: 'text', text: 'Handoff recorded. It happens when you finish.' }] }\r\n },\r\n )\r\n}\r\n\r\n// Вопрос человеку уходит на сайт сразу; если без ответа агент не может продолжить, запуск ждёт этого вопроса\r\nexport function askPersonTool(asking: AskingTools, waiting: { waitingFor: number | null }) {\r\n return tool(\r\n ASK_PERSON_TOOL_NAME,\r\n 'Ask a person on this TAMsense task when you are stuck or the decision belongs to a person: intent, design, access, or how the team runs something locally. Never ask what the code or the project knowledge already answers. With blocking true the run pauses and frees the computer until the answer comes; with blocking false you keep working on your assumption.',\r\n {\r\n to: z.union([z.number(), z.string()]).describe('id of the person from the people list in your instructions: the assignee first; a topic question goes to the person whose role fits'),\r\n why: z.string().describe('one line on why this person: \"assignee, and the run is on her computer\", \"Designer, owns empty states\"'),\r\n question: z.string().describe('one plain question the person can answer without reading the code'),\r\n context: z.string().optional().describe('one line on what you saw that led to the question'),\r\n tried: z.array(z.object({\r\n did: z.string().describe('the command or step'),\r\n error: z.string().optional().describe('the exact error line, if there was one'),\r\n result: z.string().describe('what it showed, one line'),\r\n })).max(MAX_ASK_TRIES).optional().describe('what you already tried, the most telling first'),\r\n options: z.array(z.object({\r\n label: z.string().describe('a short answer the person can pick'),\r\n detail: z.string().describe('one line on what happens if it is picked'),\r\n })).max(MAX_ASK_OPTIONS).optional().describe('2 to 4 answers you suggest, the one you would pick first'),\r\n blocking: z.boolean().describe('true when you cannot go on without the answer'),\r\n assumption: z.string().optional().describe('when blocking is false: what you continue with until the answer comes'),\r\n },\r\n async (args) => {\r\n if (waiting.waitingFor !== null) {\r\n return { content: [{ type: 'text', text: ALREADY_WAITING_TEXT }], isError: true }\r\n }\r\n const choice = checkAsk(asking.people, args)\r\n if ('error' in choice) {\r\n return { content: [{ type: 'text', text: choice.error }], isError: true }\r\n }\r\n let reply: RunnerAskReply\r\n try {\r\n reply = await asking.send(choice.body)\r\n }\r\n catch (error) {\r\n return { content: [{ type: 'text', text: `Could not send the question: ${error instanceof Error ? error.message : String(error)}. Decide yourself and say so in your report.` }], isError: true }\r\n }\r\n if (choice.body.blocking) {\r\n waiting.waitingFor = reply.id\r\n }\r\n return { content: [{ type: 'text', text: askedText(reply.name, choice.body) }] }\r\n },\r\n )\r\n}\r\n\r\n// helperStarted: Claude уже звал свежего помощника в этом запуске; по нему инструмент ТЗ знает, была ли проверка свежим читателем;\r\n// waitingFor: вопрос человеку, ответа на который запуск ждёт; waitingForSchedule: расписание, итога которого он ждёт\r\ninterface Submitted {\r\n verdict: RunnerVerdict | null\r\n handoff: RunnerHandoff | null\r\n questions: RunnerQuestions | null\r\n spec: RunnerSpecResult | null\r\n helperStarted: boolean\r\n waitingFor: number | null\r\n waitingForSchedule: number | null\r\n}\r\n\r\nfunction specWorkTools(specWork: SpecWorkTools | null, submitted: Submitted) {\r\n if (!specWork) {\r\n return []\r\n }\r\n if (specWork.stage === 'interview') {\r\n return [questionsTool(specWork.review, (questions) => { submitted.questions = questions })]\r\n }\r\n return specWork.brief\r\n ? [briefTool(specWork.review, (brief) => { submitted.spec = brief })]\r\n : [specTool(specWork.review, (spec) => { submitted.spec = spec }, () => submitted.helperStarted)]\r\n}\r\n\r\n// Знания: правка, если запуск получил заметки, чтение тех, что пришли по названиям, предложение, если агент спрашивает людей\r\nfunction knowledgeTools(knowledge: KnowledgeTools | null) {\r\n if (!knowledge) {\r\n return []\r\n }\r\n return [\r\n ...(knowledge.entryIds.length > 0 ? [updateKnowledgeTool(knowledge)] : []),\r\n ...(knowledge.moreIds.length > 0 ? [readKnowledgeTool(knowledge)] : []),\r\n ...(knowledge.canPropose ? [proposeKnowledgeTool(knowledge)] : []),\r\n ]\r\n}\r\n\r\n// Свои инструменты TAMsense: скриншоты, записи браузера и видео всегда, вердикт только на шаге проверки, вопросы или ТЗ на шаге ТЗ,\r\n// вопрос человеку, если есть кого спросить, знания проекта, передача, если доска её разрешает, расписание проверки;\r\n// на доске без кода результат задачи и передача на другую доску\r\nfunction tamsenseServer(request: AgentRequest, submitted: Submitted): { server: McpSdkServerConfigWithInstance, allowed: string[] } {\r\n const tools = [\r\n attachTool('screenshot', request.attach),\r\n attachTool('replay', request.attach),\r\n attachTool('video', request.attach),\r\n ...(request.wantsVerdict ? [verdictTool((verdict) => { submitted.verdict = verdict })] : []),\r\n ...specWorkTools(request.specWork, submitted),\r\n ...(request.asking ? [askPersonTool(request.asking, submitted)] : []),\r\n ...knowledgeTools(request.knowledge),\r\n ...(request.handoffs.length > 0 ? [handoffTool(request.handoffs, (handoff) => { submitted.handoff = handoff })] : []),\r\n ...(request.results ? resultToolset(request.results) : []),\r\n ...(request.handOver ? [handOverTool(request.handOver)] : []),\r\n ...(request.schedules ? [proposeScheduleTool(request.schedules, submitted)] : []),\r\n ]\r\n return {\r\n server: createSdkMcpServer({ name: SERVER_NAME, version: '1.0.0', tools }),\r\n allowed: tools.map(item => `mcp__${SERVER_NAME}__${item.name}`),\r\n }\r\n}\r\n\r\nexport function modelOptions(request: Pick<AgentRequest, 'model' | 'effort'>): Pick<Options, 'model' | 'effort'> {\r\n return {\r\n ...(request.model === null ? {} : { model: request.model }),\r\n ...(request.effort === null ? {} : { effort: request.effort }),\r\n }\r\n}\r\n\r\nexport async function applySwitch(target: Pick<Query, 'setModel' | 'applyFlagSettings'>, change: ModelSwitch): Promise<void> {\r\n if (change.model !== null) {\r\n await target.setModel(change.model === DEFAULT_MODEL ? undefined : change.model)\r\n }\r\n if (change.effort !== null) {\r\n await target.applyFlagSettings({ effortLevel: change.effort })\r\n }\r\n}\r\n\r\n// resumeFailed: Claude Code не смог продолжить прошлый сеанс и не начал работу\r\ninterface Attempt {\r\n outcome: AgentOutcome\r\n sessionId: string | null\r\n resumeFailed: boolean\r\n}\r\n\r\ninterface AttemptContext {\r\n startQuery: StartQuery\r\n abortController: AbortController\r\n submitted: Submitted\r\n limit: UsageLimitWatch\r\n // последнее, что Claude написал: им запрос на действие объясняет, зачем оно\r\n lastWords: { text: string }\r\n}\r\n\r\nasync function attempt(request: AgentRequest, context: AttemptContext, resume: string | null): Promise<Attempt> {\r\n let lastText = ''\r\n let sessionId: string | null = null\r\n let outcome: AgentOutcome = { completed: false, report: NO_RESULT_TEXT, model: null, usage: null, verdict: null }\r\n const tamsense = tamsenseServer(request, context.submitted)\r\n const shown = hiddenStepsFilter()\r\n const helpers = helperTasks()\r\n const messages = context.startQuery({\r\n prompt: request.input.start(request.prompt.prompt),\r\n options: {\r\n cwd: request.cwd,\r\n systemPrompt: { type: 'preset', preset: 'claude_code', append: request.prompt.instructions },\r\n settingSources: ['user', 'project', 'local'],\r\n permissionMode: 'auto',\r\n // краткий пересказ размышлений Claude идёт в журнал задачи, как в Paseo\r\n thinking: { type: 'adaptive', display: 'summarized' },\r\n // режим auto сам решает, что можно; о чём он спросил бы человека, решают правила проекта или люди задачи\r\n canUseTool: request.permissions ? askPermission(request.permissions, () => context.lastWords.text, request.cwd) : denyWhenAsked,\r\n plugins: [{ type: 'local', path: request.library }],\r\n mcpServers: { [SERVER_NAME]: tamsense.server },\r\n allowedTools: tamsense.allowed,\r\n abortController: context.abortController,\r\n env: request.env,\r\n ...modelOptions(request),\r\n ...(resume === null ? {} : { resume }),\r\n },\r\n })\r\n request.input.onSwitch(change => applySwitch(messages, change))\r\n request.input.onStopHelper(ref => helpers.stop(messages, ref))\r\n const failedToResume = () => resume !== null && sessionId === null && !request.signal.aborted\r\n try {\r\n for await (const message of messages) {\r\n if (message.type === 'system' && message.subtype === 'init') {\r\n sessionId = message.session_id\r\n request.onSession(sessionOf(message))\r\n }\r\n if (message.type === 'result' && (message.is_error || message.subtype !== 'success') && failedToResume()) {\r\n messages.close()\r\n return { outcome, sessionId, resumeFailed: true }\r\n }\r\n for (const draft of [...helpers.observe(message), ...feedOf(message).filter(shown).flatMap(helpers.adjust)]) {\r\n if (draft.kind === 'agent' && draft.parent === null) {\r\n context.submitted.helperStarted = true\r\n }\r\n request.onFeed(draft)\r\n if (draft.kind === 'text' && draft.parent === null) {\r\n lastText = draft.text\r\n context.lastWords.text = draft.text\r\n }\r\n }\r\n if (message.type === 'result') {\r\n outcome = outcomeOf(message, lastText)\r\n }\r\n context.limit.observe(message)\r\n request.input.observe(message)\r\n }\r\n }\r\n catch (error) {\r\n if (failedToResume()) {\r\n return { outcome, sessionId, resumeFailed: true }\r\n }\r\n throw error\r\n }\r\n return { outcome, sessionId, resumeFailed: false }\r\n}\r\n\r\n// Обычный Claude Code: его стандартные инструкции, настройки и навыки человека, вход человека в Claude.\r\n// Прошлый сеанс шага продолжается; если Claude Code не может его открыть, разговор начинается заново один раз\r\nexport function claudeRunner(startQuery: StartQuery): RunAgent {\r\n return async (request) => {\r\n request.signal.throwIfAborted()\r\n const abortController = new AbortController()\r\n request.signal.addEventListener('abort', () => {\r\n request.input.close()\r\n abortController.abort()\r\n }, { once: true })\r\n const submitted: Submitted = { verdict: null, handoff: null, questions: null, spec: null, helperStarted: false, waitingFor: null, waitingForSchedule: null }\r\n const limit = usageLimitWatch()\r\n const context: AttemptContext = { startQuery, abortController, submitted, limit, lastWords: { text: '' } }\r\n try {\r\n let result = await attempt(request, context, request.resumeSessionId)\r\n if (result.resumeFailed) {\r\n result = await attempt(request, context, null)\r\n }\r\n const pausedUntil = limit.pausedUntil(result.outcome.completed)\r\n const outcome = pausedUntil ? { ...result.outcome, completed: false, report: USAGE_LIMIT_REPORT } : result.outcome\r\n return { ...outcome, verdict: submitted.verdict, handoff: submitted.handoff, questions: submitted.questions, spec: submitted.spec, sessionId: result.sessionId, pausedUntil, waitingFor: submitted.waitingFor, waitingForSchedule: submitted.waitingForSchedule }\r\n }\r\n finally {\r\n request.input.close()\r\n }\r\n }\r\n}\r\n\r\nexport const runClaude: RunAgent = claudeRunner(query)\r\n","import type { RunnerAskBody, RunnerAskOption, RunnerAskTry, RunnerPerson } from './protocol'\r\n\r\nexport const MAX_ASK_TRIES = 5\r\nexport const MAX_ASK_OPTIONS = 4\r\n\r\n// Как Claude присылает вопрос: человека можно назвать номером из списка или по имени\r\nexport interface AskRequest {\r\n to: number | string\r\n why: string\r\n question: string\r\n context?: string\r\n tried?: Array<{ did: string, error?: string, result: string }>\r\n options?: RunnerAskOption[]\r\n blocking: boolean\r\n assumption?: string\r\n}\r\n\r\nexport type AskChoice = { body: RunnerAskBody, person: RunnerPerson } | { error: string }\r\n\r\n// Роли команды приходят строками «название: описание»\r\nexport function personLine(person: RunnerPerson): string {\r\n const roles = person.roles.length > 0 ? `. Team roles: ${person.roles.join('; ')}` : ''\r\n return `${person.id}: ${person.name} (${person.relation}, ${person.access} access)${roles}`\r\n}\r\n\r\nfunction findPerson(people: RunnerPerson[], to: number | string): RunnerPerson | null {\r\n const key = String(to).trim().toLowerCase()\r\n return people.find(person => String(person.id) === key || person.name.toLowerCase() === key) ?? null\r\n}\r\n\r\nfunction cleanTry(item: NonNullable<AskRequest['tried']>[number]): RunnerAskTry {\r\n return { did: item.did.trim(), error: item.error?.trim() || null, result: item.result.trim() }\r\n}\r\n\r\n// Вопрос проверяется здесь же: ошибка с подсказкой уходит Claude, и он спрашивает заново\r\nexport function checkAsk(people: RunnerPerson[], request: AskRequest): AskChoice {\r\n const question = request.question.trim()\r\n const why = request.why.trim()\r\n const assumption = request.assumption?.trim() ?? ''\r\n if (!question) {\r\n return { error: 'Write the question: one plain sentence the person can answer without reading the code.' }\r\n }\r\n const person = findPerson(people, request.to)\r\n if (!person) {\r\n return { error: `\"${request.to}\" is not a person on this task. Ask one of them by id:\\n${people.map(personLine).join('\\n')}` }\r\n }\r\n if (!why) {\r\n return { error: `Say in one line why ${person.name} is the right person to ask.` }\r\n }\r\n if (!request.blocking && !assumption) {\r\n return { error: 'A question you do not wait for needs the assumption you continue with until the answer comes.' }\r\n }\r\n const tried = (request.tried ?? []).map(cleanTry).filter(item => item.did)\r\n const options = (request.options ?? []).map(option => ({ label: option.label.trim(), detail: option.detail.trim() })).filter(option => option.label)\r\n if (tried.length > MAX_ASK_TRIES || options.length > MAX_ASK_OPTIONS) {\r\n return { error: `Keep it short: at most ${MAX_ASK_TRIES} things you tried and at most ${MAX_ASK_OPTIONS} suggested answers.` }\r\n }\r\n return {\r\n person,\r\n body: { to: person.id, why, question, context: request.context?.trim() ?? '', tried, options, blocking: request.blocking, assumption: request.blocking ? null : assumption },\r\n }\r\n}\r\n\r\nexport function askedText(name: string, body: RunnerAskBody): string {\r\n if (body.blocking) {\r\n return `Sent to ${name}. This run now waits for the answer and frees the computer. Write one short line saying what you wait for, then end your turn without doing more work. When ${name} answers, this conversation continues here with the answer.`\r\n }\r\n return `Sent to ${name}. Keep working on your assumption: ${body.assumption}. The answer comes as a message from ${name} while you work, or with the next run if this one has ended. If it differs, change what you did.`\r\n}\r\n\r\nexport const ALREADY_WAITING_TEXT = 'You already asked a question this run waits for. End your turn now: the conversation continues when the answer comes.'\r\n","import type { ModelUsage, SDKResultMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerHandoff, RunnerQuestions, RunnerSpecResult, RunnerUsage, RunnerVerdict } from './protocol'\r\n\r\n// handoff: куда Claude передал задачу инструментом hand_off; sessionId: сеанс Claude Code для продолжения;\r\n// pausedUntil: Claude упёрся в лимит, когда можно повторить (ISO); questions и spec: что Claude сдал на шаге ТЗ;\r\n// waitingFor: вопрос человеку, ответа на который запуск ждёт; waitingForSchedule: расписание, итога которого он ждёт\r\nexport interface AgentOutcome {\r\n completed: boolean\r\n report: string\r\n model: string | null\r\n usage: RunnerUsage | null\r\n verdict: RunnerVerdict | null\r\n handoff?: RunnerHandoff | null\r\n sessionId?: string | null\r\n pausedUntil?: string | null\r\n questions?: RunnerQuestions | null\r\n spec?: RunnerSpecResult | null\r\n waitingFor?: number | null\r\n waitingForSchedule?: number | null\r\n}\r\n\r\nconst MAX_ACTIVITY_LENGTH = 120\r\nconst FILE_TOOLS = new Set(['Read', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit'])\r\n// помощник Claude Code в разных версиях называется Task или Agent\r\nconst HELPER_TOOLS = new Set(['Task', 'Agent'])\r\n\r\nfunction shorten(text: string): string {\r\n const line = text.replace(/\\s+/g, ' ').trim()\r\n return line.length > MAX_ACTIVITY_LENGTH ? `${line.slice(0, MAX_ACTIVITY_LENGTH - 1)}…` : line\r\n}\r\n\r\n// Строка для терминала: человек видит, чем Claude занят прямо сейчас\r\nexport function activityLine(tool: string, input: unknown): string {\r\n const fields = typeof input === 'object' && input !== null ? input as Record<string, unknown> : {}\r\n if (tool === 'Bash' && typeof fields.command === 'string') {\r\n return `$ ${shorten(fields.command)}`\r\n }\r\n if (FILE_TOOLS.has(tool) && typeof fields.file_path === 'string') {\r\n return `${tool} ${shorten(fields.file_path)}`\r\n }\r\n if (tool === 'Skill' && typeof fields.skill === 'string') {\r\n return `Skill ${shorten(fields.skill)}`\r\n }\r\n if (HELPER_TOOLS.has(tool) && typeof fields.subagent_type === 'string') {\r\n return `Agent ${shorten(fields.subagent_type)}`\r\n }\r\n return tool\r\n}\r\n\r\n// Расход по всем моделям запуска, включая помощников, которых Claude звал сам\r\nexport function usageOf(modelUsage: Record<string, ModelUsage>): RunnerUsage | null {\r\n const entries = Object.values(modelUsage)\r\n if (entries.length === 0) {\r\n return null\r\n }\r\n const sum = (pick: (usage: ModelUsage) => number) => entries.reduce((total, usage) => total + pick(usage), 0)\r\n const inputTokens = sum(usage => usage.inputTokens + usage.cacheReadInputTokens + usage.cacheCreationInputTokens)\r\n const outputTokens = sum(usage => usage.outputTokens)\r\n return { inputTokens, cachedInputTokens: sum(usage => usage.cacheReadInputTokens), outputTokens, totalTokens: inputTokens + outputTokens }\r\n}\r\n\r\n// Основной моделью считается та, что написала больше всего\r\nexport function mainModel(modelUsage: Record<string, ModelUsage>): string | null {\r\n const ranked = Object.entries(modelUsage).sort(([, first], [, second]) => second.outputTokens - first.outputTokens)\r\n return ranked[0]?.[0] ?? null\r\n}\r\n\r\nexport function outcomeOf(result: SDKResultMessage, lastText: string): AgentOutcome {\r\n const measured = { model: mainModel(result.modelUsage), usage: usageOf(result.modelUsage), verdict: null }\r\n if (result.subtype === 'success') {\r\n return { completed: !result.is_error, report: result.result || lastText, ...measured }\r\n }\r\n return { completed: false, report: result.errors.join('\\n') || `Claude stopped early: ${result.subtype}`, ...measured }\r\n}\r\n","export interface ConnectedDevice {\r\n id: number\r\n name: string\r\n platform: string\r\n team: boolean\r\n createdAt: string\r\n lastSeenAt: string | null\r\n}\r\n\r\nexport interface PairingStartResponse {\r\n deviceCode: string\r\n userCode: string\r\n verificationPath: string\r\n expiresAt: string\r\n intervalSeconds: number\r\n}\r\n\r\nexport type PairingTokenResponse\r\n = | { status: 'pending' }\r\n | { status: 'denied' }\r\n | { status: 'expired' }\r\n | { status: 'connected', token: string, device: ConnectedDevice }\r\n\r\nexport interface RunnerIdentityResponse {\r\n device: ConnectedDevice\r\n email: string | null\r\n projectName: string\r\n}\r\n\r\n// pullRequestUrl: открытый PR задачи в этом репозитории, если он уже есть;\r\n// folder: папка репозитория на этом компьютере, когда код доски берётся из папок, а не с GitHub\r\nexport interface RunnerRepository {\r\n fullName: string\r\n defaultBranch: string\r\n pullRequestUrl?: string\r\n folder?: string\r\n}\r\n\r\n// Шаг доски, ради которого компьютер получил задание: вопросы или ТЗ, код, проверка кода или тесты, и помощник, который его делает\r\n// columnId: шаг доски или проверка группы; у каждого шага своя папка задачи на компьютере (у старого сайта его нет)\r\nexport interface RunnerStage {\r\n kind: 'interview' | 'spec' | 'code' | 'review' | 'test'\r\n name: string\r\n helper: string | null\r\n instructions: string\r\n columnId?: number | null\r\n // роль шага; старый сайт её не присылает, тогда видна вся библиотека и доступ по виду шага\r\n role?: RunnerRole | null\r\n}\r\n\r\n// Роль запуска. version: версия своей роли, у встроенной null; skills: навыки библиотеки, которые роль разрешает, null: вся библиотека;\r\n// access read: роль только читает, на шаге кода ничего не коммитит и не отправляет\r\nexport interface RunnerRole {\r\n key: string\r\n version: number | null\r\n skills: string[] | null\r\n access: 'read' | 'edit'\r\n}\r\n\r\nexport interface RunnerThreadMessage {\r\n role: 'user' | 'assistant'\r\n text: string\r\n}\r\n\r\n// Работа шага ТЗ. round: 1 или 2 у вопросов, null у ТЗ; maxQuestions и takenIds: сколько вопросов можно задать и чьи id заняты;\r\n// brief: бриф раунда вопросов; linkedAction: находка аналитики, из которой создана задача; thread: ответы и замечания владельца\r\nexport interface RunnerSpecWork {\r\n round: number | null\r\n maxQuestions: number\r\n takenIds: string[]\r\n brief: unknown\r\n linkedAction: string | null\r\n thread: RunnerThreadMessage[]\r\n}\r\n\r\nexport interface RunnerJob {\r\n runId: number\r\n taskId: number\r\n title: string\r\n description: string\r\n spec: unknown\r\n branch: string\r\n repositories: RunnerRepository[]\r\n review: string | null\r\n stage: RunnerStage\r\n // контекст проекта от команды из настроек доски, пустая строка, если его нет\r\n projectContext: string\r\n // модель Claude (opus, sonnet, haiku) и усилие для этого запуска; null: как настроено в Claude Code на компьютере\r\n model: string | null\r\n effort: RunnerEffort | null\r\n // сообщения людей, которые ждали следующего запуска этого шага\r\n messages: RunnerQueuedMessage[]\r\n // куда агент может передать задачу с этого шага по правилам доски\r\n allowedHandoffs: RunnerHandoffTarget[]\r\n // записка от предыдущего шага: что сделано и что проверить\r\n handoffNote: string | null\r\n // сеанс Claude Code прошлого запуска этого шага на этом компьютере: разговор продолжается\r\n resumeSessionId: string | null\r\n // вопросы или ТЗ на шаге ТЗ; null на шагах кода и проверки\r\n specWork: RunnerSpecWork | null\r\n // люди задачи, которых агент может спросить инструментом ask_person; пусто на шаге ТЗ, старый сайт их не присылает\r\n people?: RunnerPerson[]\r\n // запуск продолжает тот, что ждал ответа человека: папка задачи остаётся как была\r\n keepWorkspace?: boolean\r\n // правила безопасности проекта текстом: что можно самому, о чём спросить, чего никогда; старый сайт их не присылает\r\n safetyRules?: string\r\n // компьютер запуска и его хозяин, знания проекта и этого компьютера; старый сайт их не присылает\r\n computer?: RunnerComputer | null\r\n knowledge?: RunnerKnowledge\r\n // folder: доска без кода, репозиториев нет: работа идёт в папке задачи, результат кладётся на задачу инструментами.\r\n // local: репозитории лежат в папках этого компьютера, ветка задачи создаётся в них самих, GitHub не нужен.\r\n // Старый сайт поля не присылает: тогда репозитории\r\n workspace?: 'repositories' | 'folder' | 'local'\r\n // результат задачи, который читают шаг Do и проверка; старый сайт его не присылает\r\n results?: RunnerResult[]\r\n // доски, на которые агент может передать задачу по правилу шага\r\n handOverBoards?: RunnerBoardTarget[]\r\n // запуск продолжает тот, что ждал расписание: чем оно кончилось и что нашла последняя проверка\r\n scheduleNote?: string | null\r\n // агент может предложить расписание проверки; старый сайт поля не присылает, тогда инструмента нет\r\n canSchedule?: boolean\r\n}\r\n\r\n// Результат задачи на доске без кода для агента: последняя версия документа или таблицы, файл или ссылка.\r\n// fileName у файла: его можно скачать запросом resultFilePath\r\nexport interface RunnerResult {\r\n id: number\r\n kind: 'document' | 'table' | 'file' | 'link'\r\n title: string\r\n version: number | null\r\n content: string | null\r\n url: string | null\r\n fileName: string | null\r\n}\r\n\r\n// Другая доска проекта, на которую агент может передать задачу по правилу шага\r\nexport interface RunnerBoardTarget {\r\n id: number\r\n name: string\r\n}\r\n\r\n// Агент кладёт результат на задачу: документ (Markdown) или таблицу (CSV) новой версией, или ссылку\r\nexport type RunnerResultBody\r\n = | { kind: 'document' | 'table', title: string, content: string, note: string }\r\n | { kind: 'link', title: string, url: string }\r\n\r\n// Файл результата; data в base64\r\nexport interface RunnerResultFileBody {\r\n name: string\r\n title: string\r\n contentType: string\r\n data: string\r\n}\r\n\r\n// Агент передаёт задачу на другую доску; wait: эта задача ждёт связанную на своём шаге\r\nexport interface RunnerHandOverBody {\r\n board: string\r\n title: string\r\n note: string\r\n wait: boolean\r\n}\r\n\r\n// Ответ уходит агенту как результат инструмента\r\nexport interface RunnerToolReply {\r\n text: string\r\n}\r\n\r\nexport const MAX_RESULT_FILE_BYTES = 10 * 1024 * 1024\r\n\r\n// Файл результата задачи для проверки; data в base64\r\nexport interface RunnerResultFile {\r\n name: string\r\n contentType: string\r\n data: string\r\n}\r\n\r\nexport interface RunnerComputer {\r\n name: string\r\n ownerName: string\r\n platform: string\r\n}\r\n\r\n// checkedAt: когда агент последний раз нашёл заметку верной (ISO), null: ещё не проверяли\r\nexport interface RunnerKnowledgeEntry {\r\n id: number\r\n title: string\r\n text: string\r\n checkedAt: string | null\r\n}\r\n\r\nexport interface RunnerKnowledgeTitle {\r\n id: number\r\n title: string\r\n scope: 'project' | 'computer'\r\n}\r\n\r\n// Знания проекта и этого компьютера, самые свежие первыми; что не вошло по размеру, идёт одними названиями\r\nexport interface RunnerKnowledge {\r\n project: RunnerKnowledgeEntry[]\r\n computer: RunnerKnowledgeEntry[]\r\n more: RunnerKnowledgeTitle[]\r\n}\r\n\r\n// Заметка, которую агент предлагает сохранить; question: вопрос, из ответа на который она взята, null: агент нашёл её сам\r\nexport interface RunnerKnowledgeProposal {\r\n title: string\r\n text: string\r\n scope: 'project' | 'computer'\r\n question: number | null\r\n}\r\n\r\n// stillTrue: агент опирался на заметку и она верна; иначе text: новый текст, evidence: что показал код\r\nexport interface RunnerKnowledgeUpdate {\r\n stillTrue: boolean\r\n text: string\r\n evidence: string\r\n}\r\n\r\nexport interface RunnerKnowledgeReply {\r\n id: number\r\n}\r\n\r\n// Человек задачи: relation (assignee, owner, step owner, watcher), access (роль доступа в проекте),\r\n// roles: роли в команде строками «название: описание», по ним агент выбирает, кого спросить о теме\r\nexport interface RunnerPerson {\r\n id: number\r\n name: string\r\n relation: string\r\n access: string\r\n roles: string[]\r\n}\r\n\r\n// Что агент пробовал перед вопросом: шаг или команда, точная ошибка и что вышло\r\nexport interface RunnerAskTry {\r\n did: string\r\n error: string | null\r\n result: string\r\n}\r\n\r\nexport interface RunnerAskOption {\r\n label: string\r\n detail: string\r\n}\r\n\r\n// Вопрос человеку задачи. blocking: запуск ждёт ответа и освобождает компьютер; иначе агент идёт дальше на допущении assumption\r\nexport interface RunnerAskBody {\r\n to: number\r\n why: string\r\n question: string\r\n context: string\r\n tried: RunnerAskTry[]\r\n options: RunnerAskOption[]\r\n blocking: boolean\r\n assumption: string | null\r\n}\r\n\r\nexport interface RunnerAskReply {\r\n id: number\r\n name: string\r\n}\r\n\r\n// Действие, о котором Claude Code спросил бы человека: инструмент, точная команда или цель, что агент сказал о нём перед этим.\r\n// folder: папка задачи на компьютере; outsideFolder: Claude Code сообщил путь за ней\r\nexport interface RunnerPermissionBody {\r\n tool: string\r\n action: string\r\n title: string\r\n reason: string\r\n folder: string\r\n outsideFolder: boolean\r\n}\r\n\r\n// allow и deny: решено правилами проекта или человеком, message уходит Claude; wait: спрашивать снова, но не дольше waitUntil\r\nexport interface RunnerPermissionReply {\r\n id: number\r\n decision: 'allow' | 'deny' | 'wait'\r\n message: string\r\n waitUntil: string\r\n}\r\n\r\n// outcome: pass (готово), problems (нашлись проблемы), ask (спросить другой шаг)\r\nexport type RunnerHandoffOutcome = 'pass' | 'problems' | 'ask'\r\n\r\nexport interface RunnerHandoffTarget {\r\n columnId: number\r\n name: string\r\n role: string | null\r\n outcome: RunnerHandoffOutcome\r\n label: string\r\n}\r\n\r\n// Передача, которую выбрал агент: куда, с каким итогом и с какой запиской для следующего шага и владельца\r\nexport interface RunnerHandoff {\r\n toColumnId: number | null\r\n outcome: RunnerHandoffOutcome\r\n note: string\r\n}\r\n\r\nexport interface RunnerQueuedMessage {\r\n id: number\r\n text: string\r\n authorName: string\r\n}\r\n\r\n// Сообщение человека во время работы: steer сразу в идущий ход, queue когда ход закончится\r\nexport interface RunnerInboxMessage extends RunnerQueuedMessage {\r\n mode: 'steer' | 'queue'\r\n}\r\n\r\n// ack: сообщения, которые программа уже передала Claude\r\nexport interface RunnerInboxBody {\r\n ack: number[]\r\n}\r\n\r\n// Человек остановил одного помощника: ref его вызова в журнале, name: вид помощника, byName: кто остановил\r\nexport interface RunnerHelperStop {\r\n ref: string\r\n name: string\r\n byName: string\r\n}\r\n\r\n// keepGoing: false, если запуск закрыт на сайте (остановили или сочли пропавшим);\r\n// model и effort: смена модели, которую человек выбрал по ходу работы;\r\n// helperStops: помощники, которых люди остановили за весь запуск; старый сайт их не присылает\r\nexport interface RunnerInbox {\r\n keepGoing: boolean\r\n messages: RunnerInboxMessage[]\r\n model: string | null\r\n effort: RunnerEffort | null\r\n helperStops?: RunnerHelperStop[]\r\n}\r\n\r\nexport const RUNNER_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'] as const\r\nexport type RunnerEffort = typeof RUNNER_EFFORTS[number]\r\n\r\n// Чей Claude: подписка человека или ключ API; null, если Claude Code не сказал\r\nexport type RunnerAccount = 'subscription' | 'api_key'\r\n\r\nexport interface RunnerModel {\r\n value: string\r\n displayName: string\r\n description: string\r\n}\r\n\r\n// Что умеет Claude на этом компьютере: программа сообщает при запуске и потом раз в полчаса\r\nexport interface RunnerCapabilities {\r\n account: RunnerAccount | null\r\n claudeVersion: string | null\r\n models: RunnerModel[]\r\n}\r\n\r\n// Какая модель на самом деле начала работу: приходит, как только Claude Code запустился\r\nexport interface RunnerSession {\r\n model: string | null\r\n account: RunnerAccount | null\r\n claudeVersion: string | null\r\n}\r\n\r\n// Запись журнала: что Claude думает, пишет и делает. ref связывает инструмент с его результатом,\r\n// parent: запись сделал помощник, которого Claude позвал этим инструментом.\r\n// У итога остановленного помощника tool равен HELPER_STOPPED, у остальных результатов null\r\nexport const HELPER_STOPPED = 'stopped'\r\n\r\nexport type RunnerFeedKind = 'thinking' | 'text' | 'tool' | 'result' | 'skill' | 'agent'\r\n\r\nexport interface RunnerFeedEntry {\r\n seq: number\r\n kind: RunnerFeedKind\r\n text: string\r\n tool: string | null\r\n ref: string | null\r\n parent: string | null\r\n failed: boolean\r\n}\r\n\r\nexport interface RunnerFeedBody {\r\n entries: RunnerFeedEntry[]\r\n}\r\n\r\nexport const ATTACHMENT_TYPES = ['image/png', 'image/jpeg', 'image/webp'] as const\r\nexport type RunnerAttachmentType = typeof ATTACHMENT_TYPES[number]\r\nexport const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024\r\nexport const MAX_ATTACHMENTS_PER_RUN = 20\r\n\r\n// Скриншот, который Claude прикладывает к задаче; data в base64. title: что на нём, purpose: зачем он сделан\r\nexport interface RunnerAttachmentBody {\r\n name: string\r\n title: string\r\n purpose: string\r\n contentType: RunnerAttachmentType\r\n data: string\r\n}\r\n\r\nexport const VIDEO_TYPES = ['video/webm', 'video/mp4'] as const\r\nexport type RunnerVideoType = typeof VIDEO_TYPES[number]\r\n// сайт принимает видео до 200 МБ, если у него не настроено другое\r\nexport const MAX_VIDEO_BYTES = 200 * 1024 * 1024\r\n\r\nexport interface RunnerRecordingMark {\r\n at: number\r\n label: string\r\n}\r\n\r\n// Запись браузера или видео уже лежит на сайте по своему upload: этим запросом она появляется в задаче\r\nexport interface RunnerRecordingBody {\r\n kind: 'replay' | 'video'\r\n upload: string\r\n name: string\r\n title: string\r\n purpose: string\r\n // что агент проверял в записи и чем кончилось; пусто, если он ничего не написал и не ставил отметок\r\n notes: string\r\n contentType: string\r\n durationMs: number | null\r\n marks: RunnerRecordingMark[]\r\n}\r\n\r\nexport interface RunnerAttachmentResponse {\r\n id: number\r\n}\r\n\r\n// check: проверка расписания; её получает только программа, которая назвала свою версию\r\nexport interface RunnerNextResponse {\r\n job: RunnerJob | null\r\n check?: RunnerCheckJob | null\r\n}\r\n\r\nexport interface RunnerGitTokenResponse {\r\n token: string\r\n expiresAt: string\r\n}\r\n\r\nexport interface RunnerHeartbeatResponse {\r\n keepGoing: boolean\r\n}\r\n\r\n// local: ветка в папке на компьютере; у веток, отправленных на GitHub ключом TAMsense, его нет\r\nexport interface RunnerPushedBranch {\r\n repository: string\r\n branch: string\r\n sha: string\r\n local?: RunnerLocalBranch\r\n}\r\n\r\n// Ветка задачи в папке: сколько в ней коммитов поверх основной ветки, куда она отправлена своим входом человека\r\n// (remoteUrl null: осталась только в папке) и почему не отправилась\r\nexport interface RunnerLocalBranch {\r\n folder: string\r\n baseBranch: string\r\n commits: number\r\n remoteUrl: string | null\r\n pushProblem: string | null\r\n}\r\n\r\n// Папка, которую человек добавил на доску для этого компьютера: программа проверяет, что это репозиторий git\r\nexport interface RunnerFolderCheck {\r\n id: number\r\n path: string\r\n}\r\n\r\nexport interface RunnerFolderChecksResponse {\r\n checks: RunnerFolderCheck[]\r\n}\r\n\r\n// Что программа нашла в папке; адрес удалённого репозитория без логина и пароля\r\nexport type RunnerFolderReport\r\n = | { ok: true, repository: string, currentBranch: string | null, defaultBranch: string, remoteUrl: string | null }\r\n | { ok: false, problem: string }\r\n\r\n// Репозиторий, который программа нашла при запуске: папка, откуда её запустили, или папка на уровень ниже.\r\n// repository: владелец/имя удалённого репозитория, null без него\r\nexport interface RunnerFoundFolder {\r\n path: string\r\n name: string\r\n repository: string | null\r\n currentBranch: string | null\r\n defaultBranch: string\r\n remoteUrl: string | null\r\n}\r\n\r\nexport interface RunnerFoundFoldersBody {\r\n folders: RunnerFoundFolder[]\r\n}\r\n\r\n// Агент предлагает расписание, а на автопилоте заводит его сам: что проверять каждый раз, как часто, до каких пор, где идти и когда звать человека.\r\n// everyMinutes или dailyAt (ЧЧ:ММ, days: дни недели, 0 воскресенье); until: условие, которое проверяет сам агент, пусто: без него;\r\n// forMinutes: сколько идти, null: самое долгое, что разрешает проект; where computer: компьютер шага, cloud: облако за кредиты;\r\n// model: null значит дешёвая; wait: этот запуск ждёт итога расписания, кончает ход и продолжится, когда расписание кончится\r\nexport interface RunnerScheduleBody {\r\n title: string\r\n instruction: string\r\n everyMinutes: number | null\r\n dailyAt: string | null\r\n days: number[]\r\n until: string\r\n forMinutes: number | null\r\n maxRuns: number | null\r\n where: 'computer' | 'cloud'\r\n model: string | null\r\n callWhen: string\r\n callOnTimeout: boolean\r\n keepAfterStep: boolean\r\n wait: boolean\r\n}\r\n\r\n// id: null, если расписание не заведено, text тогда говорит почему; started: уже идёт (автопилот), иначе ждёт человека;\r\n// waits: запуск ждёт его итога\r\nexport interface RunnerScheduleReply {\r\n id: number | null\r\n text: string\r\n started: boolean\r\n waits: boolean\r\n}\r\n\r\n// Что прошлые запуски расписания написали: по ним новый запуск видит, что изменилось\r\nexport interface RunnerCheckNote {\r\n at: string\r\n outcome: string\r\n summary: string\r\n note: string | null\r\n}\r\n\r\n// Проверка расписания на этом компьютере: короткий запуск дешёвой модели. stepColumnId: папка шага задачи, в которой идёт проверка\r\nexport interface RunnerCheckJob {\r\n checkId: number\r\n scheduleId: number\r\n taskId: number | null\r\n taskTitle: string\r\n stepColumnId: number | null\r\n title: string\r\n instruction: string\r\n until: string\r\n callWhen: string\r\n model: string | null\r\n runNumber: number\r\n earlier: RunnerCheckNote[]\r\n safetyRules: string\r\n computer: RunnerComputer | null\r\n}\r\n\r\n// Итог проверки: изменилось ли что-то с прошлого раза, одна строка для чата, полная заметка, наступило ли условие, звать ли человека\r\nexport interface RunnerCheckResult {\r\n changed: boolean\r\n summary: string\r\n note: string | null\r\n conditionMet: boolean\r\n callPerson: boolean\r\n}\r\n\r\nexport interface RunnerCheckFinishBody {\r\n outcome: 'succeeded' | 'failed'\r\n report: string\r\n model: string | null\r\n usage: RunnerUsage | null\r\n result: RunnerCheckResult | null\r\n}\r\n\r\n// Опрос программы с 0.13.0: её версия и может ли она взять ещё задание шага и ещё проверку расписания\r\nexport interface RunnerNextBody {\r\n version: string\r\n jobs: boolean\r\n checks: boolean\r\n}\r\n\r\nexport interface RunnerUsage {\r\n inputTokens: number\r\n cachedInputTokens: number\r\n outputTokens: number\r\n totalTokens: number\r\n}\r\n\r\n// Вердикт сдаёт Claude на шагах проверки: прошло или нет и почему.\r\n// couldNotRun: проверка не запустилась из-за компьютера, задача ждёт человека на этом шаге\r\nexport interface RunnerVerdict {\r\n passed: boolean\r\n report: string\r\n couldNotRun?: boolean\r\n}\r\n\r\n// Вопросы с брифом, которые Claude сдал инструментом submit_questions; форму проверяет сайт\r\nexport interface RunnerQuestions {\r\n brief: unknown\r\n questions: unknown[]\r\n}\r\n\r\n// ТЗ, которое Claude сдал инструментом submit_spec, и замечания проверки, которые в нём остались\r\nexport interface RunnerSpecResult {\r\n spec: unknown\r\n problems: string[]\r\n}\r\n\r\n// Проверка на сайте теми же правилами, что в облаке: вопросы раунда или ТЗ; repairs: сколько раз ТЗ уже возвращали\r\nexport type RunnerReviewBody = { questions: RunnerQuestions } | { spec: unknown, repairs: number }\r\n\r\n// accepted: можно сдавать; иначе feedback уходит Claude, и он присылает исправленное\r\nexport interface RunnerReviewReply {\r\n accepted: boolean\r\n feedback: string\r\n problems: string[]\r\n}\r\n\r\nexport interface RunnerFinishBody {\r\n outcome: 'succeeded' | 'failed'\r\n report: string\r\n pushed: RunnerPushedBranch[]\r\n model: string | null\r\n usage: RunnerUsage | null\r\n verdict: RunnerVerdict | null\r\n // передача, которую выбрал агент инструментом hand_off; null: по правилам доски\r\n handoff: RunnerHandoff | null\r\n // сеанс Claude Code, чтобы следующий запуск шага продолжил разговор\r\n sessionId: string | null\r\n // Claude упёрся в лимит подписки: когда можно повторить (ISO); null, если не упёрся\r\n pausedUntil: string | null\r\n // итог шага ТЗ: вопросы раунда или ТЗ; null, если Claude их не сдал\r\n questions: RunnerQuestions | null\r\n spec: RunnerSpecResult | null\r\n // вопрос, без ответа на который агент не может продолжить: запуск ждёт человека; null, если не ждёт\r\n waitingFor: number | null\r\n // расписание, итога которого ждёт запуск: он кончил ход и продолжится, когда расписание кончится; старая программа поля не шлёт\r\n waitingForSchedule?: number | null\r\n}\r\n\r\nexport const PAIRINGS_PATH = '/api/runner/pairings'\r\nexport const PAIRING_TOKEN_PATH = '/api/runner/pairings/token'\r\nexport const IDENTITY_PATH = '/api/runner/me'\r\nexport const NEXT_RUN_PATH = '/api/runner/runs/next'\r\n\r\nexport const CAPABILITIES_PATH = '/api/runner/me/capabilities'\r\nexport const FOUND_FOLDERS_PATH = '/api/runner/me/folders'\r\n\r\nexport const FOLDER_CHECKS_PATH = '/api/runner/folders/checks'\r\n\r\nexport function folderCheckPath(folderId: number): string {\r\n return `/api/runner/folders/${folderId}`\r\n}\r\n\r\nexport function runPath(runId: number, action: 'git-token' | 'heartbeat' | 'finish' | 'feed' | 'attachments' | 'recordings' | 'session' | 'inbox' | 'review' | 'questions' | 'permissions' | 'knowledge' | 'results' | 'result-files' | 'hand-over' | 'schedules'): string {\r\n return `/api/runner/runs/${runId}/${action}`\r\n}\r\n\r\n// Файл результата задачи: проверка кладёт его в папку results\r\nexport function resultFilePath(runId: number, resultId: number): string {\r\n return `/api/runner/runs/${runId}/results/${resultId}`\r\n}\r\n\r\n// Проверка или правка одной заметки знаний\r\nexport function knowledgePath(runId: number, entryId: number): string {\r\n return `/api/runner/runs/${runId}/knowledge/${entryId}`\r\n}\r\n\r\n// Решён ли запрос на действие, которого ждёт вызов Claude\r\nexport function permissionPath(runId: number, permissionId: number): string {\r\n return `/api/runner/runs/${runId}/permissions/${permissionId}`\r\n}\r\n\r\n// Куски записи и тело видео идут отдельными запросами под одним upload, потом запись появляется в задаче через recordings\r\nexport function recordingUploadPath(runId: number, upload: string, part: 'chunks' | 'video'): string {\r\n return `/api/runner/runs/${runId}/recordings/${upload}/${part}`\r\n}\r\n\r\n// Сигнал и итог проверки расписания\r\nexport function checkPath(checkId: number, action: 'heartbeat' | 'finish'): string {\r\n return `/api/runner/checks/${checkId}/${action}`\r\n}\r\n","import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerFeedEntry } from './protocol'\r\nimport { activityLine } from './claudeResult'\r\nimport { HELPER_STOPPED } from './protocol'\r\n\r\nexport type FeedDraft = Omit<RunnerFeedEntry, 'seq'>\r\n\r\nconst MAX_TEXT_LENGTH = 8_000\r\nconst MAX_RESULT_LENGTH = 2_000\r\nconst MAX_SUMMARY_LENGTH = 500\r\nconst HELPER_TOOLS = new Set(['Task', 'Agent'])\r\nconst MCP_PREFIX = /^mcp__[^_]+(?:_[^_]+)*__/\r\nconst ATTACH_LABELS: Record<string, string> = { attach_screenshot: 'Screenshot', attach_replay: 'Replay', attach_video: 'Video' }\r\n\r\ninterface ContentBlock {\r\n type: string\r\n text?: string\r\n thinking?: string\r\n id?: string\r\n name?: string\r\n input?: unknown\r\n tool_use_id?: string\r\n content?: unknown\r\n is_error?: boolean\r\n}\r\n\r\nfunction clip(text: string, max: number): string {\r\n const trimmed = text.trim()\r\n return trimmed.length > max ? `${trimmed.slice(0, max - 1)}…` : trimmed\r\n}\r\n\r\nfunction fieldsOf(input: unknown): Record<string, unknown> {\r\n return typeof input === 'object' && input !== null ? input as Record<string, unknown> : {}\r\n}\r\n\r\nfunction textField(fields: Record<string, unknown>, name: string): string | null {\r\n const value = fields[name]\r\n return typeof value === 'string' && value.trim() ? value : null\r\n}\r\n\r\n// Вызов инструмента одной строкой: команда, файл, навык, помощник или свой инструмент TAMsense\r\nfunction toolDraft(block: ContentBlock, parent: string | null): FeedDraft {\r\n const tool = (block.name ?? 'tool').replace(MCP_PREFIX, '')\r\n const fields = fieldsOf(block.input)\r\n const base = { tool, ref: block.id ?? null, parent, failed: false }\r\n if (tool === 'Skill' && textField(fields, 'skill')) {\r\n return { ...base, kind: 'skill', text: clip(textField(fields, 'skill')!, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (HELPER_TOOLS.has(tool)) {\r\n const who = textField(fields, 'subagent_type') ?? 'general-purpose'\r\n const what = textField(fields, 'description')\r\n return { ...base, kind: 'agent', text: clip(what ? `${who}: ${what}` : who, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (tool === 'submit_verdict') {\r\n const verdict = fields.couldNotRun === true ? 'Could not run' : fields.passed === true ? 'Passed' : 'Did not pass'\r\n return { ...base, kind: 'tool', text: clip(`${verdict}. ${textField(fields, 'report') ?? ''}`, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (tool === 'hand_off') {\r\n return { ...base, kind: 'tool', text: clip(`Hand off to ${textField(fields, 'to') ?? '?'}: ${textField(fields, 'note') ?? ''}`, MAX_SUMMARY_LENGTH) }\r\n }\r\n if (tool in ATTACH_LABELS) {\r\n return { ...base, kind: 'tool', text: clip(`${ATTACH_LABELS[tool]}: ${textField(fields, 'title') ?? textField(fields, 'path') ?? ''}`, MAX_SUMMARY_LENGTH) }\r\n }\r\n const command = textField(fields, 'command')\r\n const summary = tool === 'Bash' && command ? `$ ${command}` : activityLine(tool, block.input)\r\n return { ...base, kind: 'tool', text: clip(summary, MAX_SUMMARY_LENGTH) }\r\n}\r\n\r\nfunction resultText(content: unknown): string {\r\n if (typeof content === 'string') {\r\n return content\r\n }\r\n if (!Array.isArray(content)) {\r\n return ''\r\n }\r\n return content\r\n .map((part: ContentBlock) => part.type === 'text' ? part.text ?? '' : part.type === 'image' ? '[image]' : '')\r\n .filter(Boolean)\r\n .join('\\n')\r\n}\r\n\r\nexport type HelperEnd = 'completed' | 'failed' | 'stopped'\r\n\r\n// Итог помощника по его концу: ответ, ошибка или остановка; у остановленного ответа нет, есть пометка\r\nexport function helperEndDraft(ref: string, parent: string | null, end: HelperEnd, text: string): FeedDraft {\r\n const stopped = end === 'stopped'\r\n return { kind: 'result', text: stopped ? '' : clip(text, MAX_RESULT_LENGTH), tool: stopped ? HELPER_STOPPED : null, ref, parent, failed: end === 'failed' }\r\n}\r\n\r\n// Сообщение Claude Code превращается в записи журнала; служебные сообщения и итог запуска в журнал не идут\r\nexport function feedOf(message: SDKMessage): FeedDraft[] {\r\n if (message.type !== 'assistant' && message.type !== 'user') {\r\n return []\r\n }\r\n const parent = message.parent_tool_use_id ?? null\r\n const content = message.message.content\r\n if (!Array.isArray(content)) {\r\n return []\r\n }\r\n const drafts: FeedDraft[] = []\r\n for (const block of content as ContentBlock[]) {\r\n if (message.type === 'assistant' && block.type === 'thinking' && block.thinking?.trim()) {\r\n drafts.push({ kind: 'thinking', text: clip(block.thinking, MAX_TEXT_LENGTH), tool: null, ref: null, parent, failed: false })\r\n }\r\n else if (message.type === 'assistant' && block.type === 'text' && block.text?.trim()) {\r\n drafts.push({ kind: 'text', text: clip(block.text, MAX_TEXT_LENGTH), tool: null, ref: null, parent, failed: false })\r\n }\r\n else if (message.type === 'assistant' && block.type === 'tool_use') {\r\n drafts.push(toolDraft(block, parent))\r\n }\r\n else if (message.type === 'user' && block.type === 'tool_result') {\r\n drafts.push({ kind: 'result', text: clip(resultText(block.content), MAX_RESULT_LENGTH), tool: null, ref: block.tool_use_id ?? null, parent, failed: block.is_error === true })\r\n }\r\n }\r\n return drafts\r\n}\r\n\r\nconst CONSOLE_LABELS: Partial<Record<FeedDraft['kind'], string>> = { tool: '', skill: 'Skill ', agent: 'Agent ' }\r\nconst MAX_CONSOLE_LENGTH = 120\r\n\r\n// В окне программы только действия Claude, его размышления и ответы видны на сайте\r\nexport function consoleLine(draft: FeedDraft): string | null {\r\n const label = CONSOLE_LABELS[draft.kind]\r\n return label === undefined ? null : clip(`${label}${draft.text}`.replace(/\\s+/g, ' '), MAX_CONSOLE_LENGTH)\r\n}\r\n\r\n// Служебные вызовы Claude Code человеку ничего не говорят: ни они, ни их результаты в журнал не идут\r\nconst HIDDEN_TOOLS = new Set(['ToolSearch', 'SubagentHandback'])\r\n\r\nexport function hiddenStepsFilter(): (draft: FeedDraft) => boolean {\r\n const hidden = new Set<string>()\r\n return (draft) => {\r\n if (draft.kind === 'tool' && draft.tool && HIDDEN_TOOLS.has(draft.tool)) {\r\n if (draft.ref) {\r\n hidden.add(draft.ref)\r\n }\r\n return false\r\n }\r\n return !(draft.kind === 'result' && draft.ref && hidden.has(draft.ref))\r\n }\r\n}\r\n","import type { RunnerHandoff, RunnerHandoffOutcome, RunnerHandoffTarget } from './protocol'\r\n\r\nexport const HANDOFF_OUTCOMES = ['pass', 'problems', 'ask'] as const satisfies readonly RunnerHandoffOutcome[]\r\n\r\nexport interface HandoffRequest {\r\n to: string\r\n outcome: RunnerHandoffOutcome\r\n note: string\r\n}\r\n\r\nexport type HandoffChoice = { handoff: RunnerHandoff } | { error: string }\r\n\r\nexport function handoffTargetLine(target: RunnerHandoffTarget): string {\r\n return target.label ? `${target.name} (${target.outcome}: ${target.label})` : `${target.name} (${target.outcome})`\r\n}\r\n\r\nfunction validTargets(targets: RunnerHandoffTarget[]): string {\r\n return `Valid targets: ${targets.map(handoffTargetLine).join(', ')}.`\r\n}\r\n\r\n// Шаг ищется по имени без учёта регистра или по номеру; передать можно только по правилу доски с этим итогом\r\nexport function chooseHandoff(targets: RunnerHandoffTarget[], request: HandoffRequest): HandoffChoice {\r\n const to = request.to.trim()\r\n const note = request.note.trim()\r\n if (!note) {\r\n return { error: 'Write a note for the next agent and the owner: what was done, what to check and what is open.' }\r\n }\r\n const matching = targets.filter(target => target.name.toLowerCase() === to.toLowerCase() || String(target.columnId) === to)\r\n if (matching.length === 0) {\r\n return { error: `\"${to}\" is not a step this task can be handed to. ${validTargets(targets)}` }\r\n }\r\n const edge = matching.find(target => target.outcome === request.outcome)\r\n if (!edge) {\r\n return { error: `${matching[0]!.name} does not take the outcome \"${request.outcome}\". ${validTargets(targets)}` }\r\n }\r\n return { handoff: { toColumnId: edge.columnId, outcome: edge.outcome, note } }\r\n}\r\n","import type { Query, SDKMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { FeedDraft } from './feed'\r\nimport type { RunnerHelperStop } from './protocol'\r\nimport { helperEndDraft } from './feed'\r\n\r\n// Помощник глазами Claude Code: номер задачи для остановки, работает ли он в фоне, остановлен ли и записан ли его итог\r\ninterface HelperTask {\r\n taskId: string\r\n background: boolean\r\n stopped: boolean\r\n over: boolean\r\n}\r\n\r\n// stopped: помощник остановлен; finished: он уже кончил работу; unknown: Claude Code не сообщил о нём, остановить его можно только просьбой\r\nexport type HelperStopResult = 'stopped' | 'finished' | 'unknown'\r\n\r\nexport interface HelperTasks {\r\n // записи журнала по служебному сообщению: итог помощника, который работал в фоне\r\n observe: (message: SDKMessage) => FeedDraft[]\r\n // запись журнала по ходу Claude: заглушка фонового помощника пропадает, итог остановленного помечается остановкой\r\n adjust: (draft: FeedDraft) => FeedDraft[]\r\n stop: (target: Pick<Query, 'stopTask'>, ref: string) => Promise<HelperStopResult>\r\n}\r\n\r\n// Claude Code сообщает о помощниках задачами: начало с номером вызова, уход в фон и конец.\r\n// Помощник в фоне сразу отвечает заглушкой «запущен», а настоящий итог приходит уведомлением о конце задачи.\r\n// Остановленный помощник отвечает «прервано»: такой итог в журнале остановка, а не ошибка\r\nexport function helperTasks(): HelperTasks {\r\n const byRef = new Map<string, HelperTask>()\r\n const refByTask = new Map<string, string>()\r\n const parents = new Map<string, string | null>()\r\n\r\n function observe(message: SDKMessage): FeedDraft[] {\r\n if (message.type !== 'system') {\r\n return []\r\n }\r\n if (message.subtype === 'task_started' && message.task_type === 'local_agent' && message.tool_use_id) {\r\n byRef.set(message.tool_use_id, { taskId: message.task_id, background: message.is_backgrounded === true, stopped: false, over: false })\r\n refByTask.set(message.task_id, message.tool_use_id)\r\n return []\r\n }\r\n if (message.subtype === 'task_updated' && message.patch.is_backgrounded !== undefined) {\r\n const ref = refByTask.get(message.task_id)\r\n const task = ref ? byRef.get(ref) : undefined\r\n if (task) {\r\n task.background = message.patch.is_backgrounded\r\n }\r\n return []\r\n }\r\n if (message.subtype !== 'task_notification') {\r\n return []\r\n }\r\n const ref = message.tool_use_id ?? refByTask.get(message.task_id)\r\n const task = ref ? byRef.get(ref) : undefined\r\n if (!ref || !task || task.over) {\r\n return []\r\n }\r\n task.stopped = message.status === 'stopped'\r\n if (!task.background) {\r\n return []\r\n }\r\n task.over = true\r\n return [helperEndDraft(ref, parents.get(ref) ?? null, message.status, message.summary)]\r\n }\r\n\r\n function adjust(draft: FeedDraft): FeedDraft[] {\r\n if (draft.kind === 'agent' && draft.ref) {\r\n parents.set(draft.ref, draft.parent)\r\n }\r\n const task = draft.kind === 'result' && draft.ref ? byRef.get(draft.ref) : undefined\r\n if (!task || !draft.ref) {\r\n return [draft]\r\n }\r\n if (task.background) {\r\n return []\r\n }\r\n task.over = true\r\n return task.stopped ? [helperEndDraft(draft.ref, draft.parent, 'stopped', '')] : [draft]\r\n }\r\n\r\n async function stop(target: Pick<Query, 'stopTask'>, ref: string): Promise<HelperStopResult> {\r\n const task = byRef.get(ref)\r\n if (!task) {\r\n return 'unknown'\r\n }\r\n if (task.over || task.stopped) {\r\n return 'finished'\r\n }\r\n await target.stopTask(task.taskId)\r\n return 'stopped'\r\n }\r\n\r\n return { observe, adjust, stop }\r\n}\r\n\r\n// Claude узнаёт, кто остановил помощника, и идёт дальше без него\r\nexport function helperStoppedNote(stop: RunnerHelperStop): string {\r\n return `${stop.byName} stopped your helper ${stop.name} on TAMsense. Go on without it and say in your result what it did not finish. Start it again only if a person asks.`\r\n}\r\n\r\n// Claude Code не сообщил о помощнике как о задаче: остановить его может только сам Claude\r\nexport function helperStopRequest(stop: RunnerHelperStop): string {\r\n return `${stop.byName} asked on TAMsense to stop your helper ${stop.name}. Stop it now, go on without it and say in your result what it did not finish. Start it again only if a person asks.`\r\n}\r\n","import type { SDKMessage, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { HelperStopResult } from './helpers'\r\nimport type { RunnerEffort, RunnerInboxMessage } from './protocol'\r\n\r\n// Смена модели по ходу работы; model 'default' значит вернуться к модели Claude Code на компьютере\r\nexport interface ModelSwitch {\r\n model: string | null\r\n effort: RunnerEffort | null\r\n}\r\n\r\nexport const DEFAULT_MODEL = 'default'\r\n\r\n// Что видит запуск Claude: первое сообщение, ходы Claude, смена модели и остановка одного помощника\r\nexport interface AgentInput {\r\n start: (prompt: string) => AsyncIterable<SDKUserMessage>\r\n observe: (message: SDKMessage) => void\r\n onSwitch: (apply: (change: ModelSwitch) => Promise<void>) => void\r\n onStopHelper: (stop: (ref: string) => Promise<HelperStopResult>) => void\r\n close: () => void\r\n}\r\n\r\n// Что видит программа: сообщения людей, проверка входящих и что уже передано Claude.\r\n// note: сообщение от самой программы, сайт о нём не знает; stopHelper: unknown, пока Claude не начал работу\r\nexport interface InputChannel extends AgentInput {\r\n readonly closed: boolean\r\n push: (message: RunnerInboxMessage) => boolean\r\n note: (text: string, mode: RunnerInboxMessage['mode']) => boolean\r\n stopHelper: (ref: string) => Promise<HelperStopResult>\r\n checking: (work: () => Promise<void>) => Promise<void>\r\n takeDelivered: () => number[]\r\n restoreDelivered: (ids: number[]) => void\r\n hasDelivered: () => boolean\r\n switchModel: (change: ModelSwitch) => Promise<boolean>\r\n}\r\n\r\n// onIdle: Claude закончил ход, пора проверить, не написали ли ему ещё;\r\n// quietMs: сколько после хода ждать, не начнёт ли Claude Code следующий сам;\r\n// idleLimitMs: сколько ждать проверки после хода, прежде чем закрыть вход в любом случае\r\nexport interface InputOptions {\r\n onIdle?: () => void\r\n quietMs?: number\r\n idleLimitMs?: number\r\n}\r\n\r\nconst QUIET_MS = 1_000\r\nconst IDLE_LIMIT_MS = 30_000\r\n\r\ninterface Queued {\r\n ids: number[]\r\n message: SDKUserMessage\r\n}\r\n\r\nfunction userMessage(text: string): SDKUserMessage {\r\n return { type: 'user', message: { role: 'user', content: text }, parent_tool_use_id: null }\r\n}\r\n\r\nfunction humanText(message: RunnerInboxMessage): string {\r\n return `Message from ${message.authorName}: ${message.text}`\r\n}\r\n\r\nfunction priorityOf(mode: RunnerInboxMessage['mode']): SDKUserMessage['priority'] {\r\n return mode === 'steer' ? 'now' : 'next'\r\n}\r\n\r\n// steer: now прерывает текущий шаг Claude и начинает новый ход с сообщением; queue: next доходит на ближайшей границе шагов\r\nexport function humanMessage(message: RunnerInboxMessage): SDKUserMessage {\r\n return { ...userMessage(humanText(message)), priority: priorityOf(message.mode), client_composed: true }\r\n}\r\n\r\nexport function switchLine(change: ModelSwitch): string {\r\n const model = change.model === DEFAULT_MODEL ? 'the default model' : change.model\r\n const effort = change.effort && `${change.effort} effort`\r\n return `Switched to ${[model, effort].filter(Boolean).join(', ')}`\r\n}\r\n\r\n// Признаки того, что Claude Code работает над ходом: после итога они значат, что начался следующий\r\nfunction isActivity(message: SDKMessage): boolean {\r\n return message.type === 'assistant' || message.type === 'user' || (message.type === 'system' && message.subtype === 'init')\r\n}\r\n\r\n// Вход закрывается, когда Claude закончил ход, ждать нечего и проверка после хода прошла:\r\n// тогда запуск кончается как раньше, а сообщение, пришедшее под конец, ещё успевает дойти\r\nexport function inputChannel(options: InputOptions = {}): InputChannel {\r\n const queue: Queued[] = []\r\n const seen = new Set<number>()\r\n let delivered: number[] = []\r\n let started = false\r\n // первое сообщение запуска и номер потока, который сейчас читает Claude Code\r\n let opening = ''\r\n let generation = 0\r\n let closed = false\r\n // ход кончился, и с тех пор ничего не началось\r\n let idle = false\r\n // Claude Code уже отозвался на текущий ход\r\n let active = false\r\n // сколько итогов прерванных ходов ещё придёт: steer прерывает ход, и тот кончается своим итогом\r\n let interrupted = 0\r\n let drained = false\r\n let quiet = false\r\n let idleEpoch = 0\r\n let inFlight = 0\r\n let statesSeen = false\r\n let wake: (() => void) | null = null\r\n let timers: Array<ReturnType<typeof setTimeout>> = []\r\n let applySwitch: ((change: ModelSwitch) => Promise<void>) | null = null\r\n let applyStop: ((ref: string) => Promise<HelperStopResult>) | null = null\r\n let waitingSwitch: { change: ModelSwitch, resolve: (applied: boolean) => void } | null = null\r\n\r\n function wakeUp(): void {\r\n wake?.()\r\n wake = null\r\n }\r\n\r\n function clearTimers(): void {\r\n timers.forEach(clearTimeout)\r\n timers = []\r\n }\r\n\r\n function close(): void {\r\n if (closed) {\r\n return\r\n }\r\n closed = true\r\n clearTimers()\r\n waitingSwitch?.resolve(false)\r\n waitingSwitch = null\r\n wakeUp()\r\n }\r\n\r\n function maybeClose(): void {\r\n if (started && idle && drained && quiet && queue.length === 0 && inFlight === 0) {\r\n close()\r\n }\r\n }\r\n\r\n function turnStarted(): void {\r\n idle = false\r\n clearTimers()\r\n }\r\n\r\n function turnEnded(): void {\r\n if (closed || idle) {\r\n return\r\n }\r\n idle = true\r\n idleEpoch += 1\r\n drained = !options.onIdle\r\n quiet = false\r\n timers = [\r\n setTimeout(() => {\r\n quiet = true\r\n maybeClose()\r\n }, options.quietMs ?? QUIET_MS),\r\n setTimeout(close, options.idleLimitMs ?? IDLE_LIMIT_MS),\r\n ]\r\n options.onIdle?.()\r\n }\r\n\r\n async function* stream(own: number): AsyncGenerator<SDKUserMessage> {\r\n for (;;) {\r\n if (closed || own !== generation) {\r\n return\r\n }\r\n const next = queue.shift()\r\n if (!next) {\r\n await new Promise<void>((resolve) => {\r\n wake = resolve\r\n })\r\n continue\r\n }\r\n if (active && !idle && next.message.priority === 'now') {\r\n interrupted += 1\r\n }\r\n turnStarted()\r\n delivered.push(...next.ids)\r\n yield next.message\r\n }\r\n }\r\n\r\n return {\r\n get closed() {\r\n return closed\r\n },\r\n // Сообщения, пришедшие, пока готовился код, идут вместе с задачей.\r\n // Повторный start для нового Claude Code (прошлый сеанс не открылся) шлёт то же первое сообщение снова, а прежний поток кончается\r\n start(prompt) {\r\n if (started) {\r\n queue.unshift({ ids: [], message: userMessage(opening) })\r\n }\r\n else {\r\n started = true\r\n const early = queue.splice(0)\r\n opening = [prompt, ...early.map(item => item.message.message.content as string)].join('\\n\\n')\r\n queue.push({ ids: early.flatMap(item => item.ids), message: userMessage(opening) })\r\n }\r\n generation += 1\r\n wakeUp()\r\n return stream(generation)\r\n },\r\n // Если Claude Code сообщает состояние сессии, верим только ему: idle приходит после фоновых задач\r\n observe(message) {\r\n if (message.type === 'system' && message.subtype === 'session_state_changed') {\r\n statesSeen = true\r\n if (message.state === 'idle') {\r\n turnEnded()\r\n }\r\n else {\r\n turnStarted()\r\n }\r\n return\r\n }\r\n if (statesSeen) {\r\n return\r\n }\r\n if (message.type === 'result') {\r\n active = false\r\n if (interrupted > 0) {\r\n interrupted -= 1\r\n return\r\n }\r\n turnEnded()\r\n }\r\n else if (isActivity(message)) {\r\n active = true\r\n turnStarted()\r\n }\r\n },\r\n onSwitch(apply) {\r\n applySwitch = apply\r\n if (waitingSwitch) {\r\n const { change, resolve } = waitingSwitch\r\n waitingSwitch = null\r\n void apply(change).then(() => resolve(true), () => resolve(false))\r\n }\r\n },\r\n onStopHelper(stop) {\r\n applyStop = stop\r\n },\r\n close,\r\n push(message) {\r\n if (closed || seen.has(message.id)) {\r\n return false\r\n }\r\n seen.add(message.id)\r\n queue.push({ ids: [message.id], message: humanMessage(message) })\r\n wakeUp()\r\n return true\r\n },\r\n note(text, mode) {\r\n if (closed) {\r\n return false\r\n }\r\n queue.push({ ids: [], message: { ...userMessage(text), priority: priorityOf(mode) } })\r\n wakeUp()\r\n return true\r\n },\r\n async stopHelper(ref) {\r\n return closed || !applyStop ? 'unknown' : await applyStop(ref)\r\n },\r\n async checking(work) {\r\n const epoch = idle ? idleEpoch : null\r\n inFlight += 1\r\n try {\r\n await work()\r\n }\r\n finally {\r\n inFlight -= 1\r\n if (idle && epoch === idleEpoch) {\r\n drained = true\r\n }\r\n maybeClose()\r\n }\r\n },\r\n takeDelivered() {\r\n const ids = delivered\r\n delivered = []\r\n return ids\r\n },\r\n restoreDelivered(ids) {\r\n delivered = [...ids, ...delivered]\r\n },\r\n hasDelivered: () => delivered.length > 0,\r\n // До запуска Claude смена ждёт его; после конца работы менять уже нечего\r\n async switchModel(change) {\r\n if (closed) {\r\n return false\r\n }\r\n if (applySwitch) {\r\n await applySwitch(change)\r\n return true\r\n }\r\n const earlier = waitingSwitch\r\n earlier?.resolve(false)\r\n const merged = { model: change.model ?? earlier?.change.model ?? null, effort: change.effort ?? earlier?.change.effort ?? null }\r\n return new Promise<boolean>((resolve) => {\r\n waitingSwitch = { change: merged, resolve }\r\n })\r\n },\r\n }\r\n}\r\n","import type { RunnerComputer, RunnerKnowledge, RunnerKnowledgeEntry, RunnerKnowledgeProposal, RunnerKnowledgeReply, RunnerKnowledgeUpdate } from './protocol'\r\nimport { tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\n\r\nexport const PROPOSE_KNOWLEDGE_TOOL_NAME = 'propose_knowledge'\r\nexport const UPDATE_KNOWLEDGE_TOOL_NAME = 'update_knowledge'\r\nexport const READ_KNOWLEDGE_TOOL_NAME = 'read_knowledge'\r\n\r\n// Как на сайте: заметка короткая, иначе её не прочтут ни люди, ни агенты\r\nexport const MAX_KNOWLEDGE_TITLE = 80\r\nexport const MAX_KNOWLEDGE_TEXT = 600\r\n\r\n// entryIds: заметки, которые этот запуск может прочесть, проверить или поправить; moreIds: те из них, что пришли одними названиями;\r\n// canPropose: агент спрашивает людей и может предложить заметку\r\nexport interface KnowledgeTools {\r\n entryIds: number[]\r\n moreIds: number[]\r\n canPropose: boolean\r\n propose: (body: RunnerKnowledgeProposal) => Promise<RunnerKnowledgeReply>\r\n update: (entryId: number, body: RunnerKnowledgeUpdate) => Promise<RunnerKnowledgeReply>\r\n read: (entryId: number) => Promise<RunnerKnowledgeEntry>\r\n}\r\n\r\nconst UPDATE_RULE = `Rely on this knowledge instead of asking people again. When you relied on an entry and found it true, call ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue true. When the code shows an entry is out of date (a file moved, a command or a port changed), fix it yourself at once: ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue false, the full new text and the evidence (the commit, the file or the command output that shows it). The change applies right away and people can revert it. Never ask a person whether an entry is still right, and never move facts of this computer into project knowledge.`\r\n\r\nconst PROPOSE_RULE = `When a person's answer to your question teaches something later runs need (how the team runs the local stand, where something lives, a convention), propose one short note with ${PROPOSE_KNOWLEDGE_TOOL_NAME} and the question number from the answer. Scope computer is for what is true only on this computer (folders, ports, installed tools, local quirks); scope project is for what comes from the repository and works on every computer. Never propose secrets, personal data or decisions about this one task. A person approves every note; keep working while they decide.`\r\n\r\nfunction dateOf(iso: string | null): string {\r\n return iso ? `checked ${iso.slice(0, 10)}` : 'not checked yet'\r\n}\r\n\r\nfunction entryText(entry: RunnerKnowledgeEntry): string {\r\n return `[${entry.id}] ${entry.title}: ${entry.text.replace(/\\s+/g, ' ').trim()} (${dateOf(entry.checkedAt)})`\r\n}\r\n\r\nfunction entryLine(entry: RunnerKnowledgeEntry): string {\r\n return `- ${entryText(entry)}`\r\n}\r\n\r\n// Где идёт работа: чей это компьютер, папка задачи и какие коммиты в ней. Чужие папки и инструменты агент не трогает\r\nexport function workplaceText(computer: RunnerComputer | null | undefined, folder: string): string | null {\r\n if (!computer) {\r\n return null\r\n }\r\n return `You run on ${computer.name}, the ${computer.platform} computer of ${computer.ownerName}. Your task folder is ${folder}; everything outside it belongs to ${computer.ownerName}.`\r\n}\r\n\r\nfunction hasKnowledge(knowledge: RunnerKnowledge | undefined): knowledge is RunnerKnowledge {\r\n return Boolean(knowledge && (knowledge.project.length > 0 || knowledge.computer.length > 0 || knowledge.more.length > 0))\r\n}\r\n\r\n// Знания проекта и этого компьютера отдельно: агент не путает факты одного компьютера с фактами проекта\r\nexport function knowledgeText(knowledge: RunnerKnowledge | undefined, computer: RunnerComputer | null | undefined, canPropose: boolean): string | null {\r\n const rules = canPropose ? [PROPOSE_RULE] : []\r\n if (!hasKnowledge(knowledge)) {\r\n return rules.length > 0 ? `Knowledge of this project is empty so far. ${rules.join(' ')}` : null\r\n }\r\n const here = computer ? computer.name : 'this computer'\r\n const sections = [\r\n knowledge.project.length > 0 ? `Project knowledge (approved by the team, true on every computer):\\n${knowledge.project.map(entryLine).join('\\n')}` : null,\r\n knowledge.computer.length > 0 ? `Knowledge for ${here} only (true here, not on other computers):\\n${knowledge.computer.map(entryLine).join('\\n')}` : null,\r\n knowledge.more.length > 0 ? `More entries, titles only to keep this short: ${knowledge.more.map(entry => `[${entry.id}] ${entry.title} (${entry.scope})`).join('; ')}. When a title touches your work, read the entry with ${READ_KNOWLEDGE_TOOL_NAME} and its number.` : null,\r\n ]\r\n return [...sections, UPDATE_RULE, ...rules].filter(Boolean).join('\\n\\n')\r\n}\r\n\r\n// Все заметки, которые получил запуск: и целиком, и по названиям\r\nexport function knowledgeIds(knowledge: RunnerKnowledge | undefined): number[] {\r\n return knowledge ? [...knowledge.project, ...knowledge.computer, ...knowledge.more].map(entry => entry.id) : []\r\n}\r\n\r\nfunction failed(text: string) {\r\n return { content: [{ type: 'text' as const, text }], isError: true }\r\n}\r\n\r\nfunction done(text: string) {\r\n return { content: [{ type: 'text' as const, text }] }\r\n}\r\n\r\nfunction unknownEntry(tools: KnowledgeTools, entry: number) {\r\n return failed(`There is no entry ${entry} in this run. Use a number in square brackets from your instructions: ${tools.entryIds.join(', ') || 'none'}.`)\r\n}\r\n\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error)\r\n}\r\n\r\nexport function proposeKnowledgeTool(tools: KnowledgeTools) {\r\n return tool(\r\n PROPOSE_KNOWLEDGE_TOOL_NAME,\r\n 'Propose a short note for the TAMsense project knowledge after a person answered your question, so later runs do not ask again. A person on the task approves it: for the whole project, for this computer only, or not at all.',\r\n {\r\n title: z.string().describe(`a few words, at most ${MAX_KNOWLEDGE_TITLE} characters: \"Local stand and database\"`),\r\n text: z.string().describe(`the fact in one to three plain sentences, at most ${MAX_KNOWLEDGE_TEXT} characters, with exact commands and paths in backticks`),\r\n scope: z.enum(['project', 'computer']).describe('computer: true only on this computer (folders, ports, installed tools); project: comes from the repository and works everywhere'),\r\n question: z.number().optional().describe('the number of the question whose answer this comes from, as in \"(question 31)\"'),\r\n },\r\n async (args) => {\r\n const title = args.title.trim()\r\n const text = args.text.trim()\r\n if (!title || !text || title.length > MAX_KNOWLEDGE_TITLE || text.length > MAX_KNOWLEDGE_TEXT) {\r\n return failed(`Write a title of at most ${MAX_KNOWLEDGE_TITLE} characters and a text of at most ${MAX_KNOWLEDGE_TEXT}.`)\r\n }\r\n try {\r\n await tools.propose({ title, text, scope: args.scope, question: args.question ?? null })\r\n }\r\n catch (error) {\r\n return failed(`The note was not suggested: ${messageOf(error)}`)\r\n }\r\n return done('Suggested. A person on the task decides whether to save it; keep working.')\r\n },\r\n )\r\n}\r\n\r\nexport function updateKnowledgeTool(tools: KnowledgeTools) {\r\n return tool(\r\n UPDATE_KNOWLEDGE_TOOL_NAME,\r\n 'Keep the TAMsense knowledge current. stillTrue true: you relied on the entry and it holds. stillTrue false: the code shows it is out of date; the new text applies at once and people see your evidence.',\r\n {\r\n entry: z.number().describe('the entry number in square brackets from your instructions'),\r\n stillTrue: z.boolean(),\r\n text: z.string().optional().describe(`when stillTrue is false: the full new text, at most ${MAX_KNOWLEDGE_TEXT} characters`),\r\n evidence: z.string().optional().describe('when stillTrue is false: one line on what shows the old text is wrong, such as a commit, a file or a command output'),\r\n },\r\n async (args) => {\r\n if (!tools.entryIds.includes(args.entry)) {\r\n return unknownEntry(tools, args.entry)\r\n }\r\n const text = args.text?.trim() ?? ''\r\n const evidence = args.evidence?.trim() ?? ''\r\n if (!args.stillTrue && (!text || !evidence || text.length > MAX_KNOWLEDGE_TEXT)) {\r\n return failed(`An update needs the full new text (at most ${MAX_KNOWLEDGE_TEXT} characters) and the evidence.`)\r\n }\r\n try {\r\n await tools.update(args.entry, { stillTrue: args.stillTrue, text, evidence })\r\n }\r\n catch (error) {\r\n return failed(`The entry was not updated: ${messageOf(error)}`)\r\n }\r\n return done(args.stillTrue ? 'Marked as checked today.' : 'Updated. People see the old text, your evidence and can revert it.')\r\n },\r\n )\r\n}\r\n\r\n// Заметка, которую запуск видел только по названию: сайт отдаёт её текст и записывает, что агент её прочёл\r\nexport function readKnowledgeTool(tools: KnowledgeTools) {\r\n return tool(\r\n READ_KNOWLEDGE_TOOL_NAME,\r\n 'Read a TAMsense knowledge entry that your instructions list by title only.',\r\n {\r\n entry: z.number().describe('the entry number in square brackets from your instructions'),\r\n },\r\n async (args) => {\r\n if (!tools.entryIds.includes(args.entry)) {\r\n return unknownEntry(tools, args.entry)\r\n }\r\n try {\r\n return done(entryText(await tools.read(args.entry)))\r\n }\r\n catch (error) {\r\n return failed(`The entry was not read: ${messageOf(error)}`)\r\n }\r\n },\r\n )\r\n}\r\n","import type { CanUseTool, PermissionResult } from '@anthropic-ai/claude-agent-sdk'\r\nimport type { RunnerPermissionBody, RunnerPermissionReply } from './protocol'\r\n\r\n// send: запрос уходит на сайт; check: решён ли он; sleep: пауза между проверками, прерываемая остановкой запуска\r\nexport interface PermissionTools {\r\n send: (body: RunnerPermissionBody) => Promise<RunnerPermissionReply>\r\n check: (id: number) => Promise<RunnerPermissionReply>\r\n sleep: (ms: number, signal?: AbortSignal) => Promise<void>\r\n now?: () => number\r\n}\r\n\r\nexport const NOBODY_TO_ASK = 'Nobody is at the computer to approve this during a TAMsense run. Do it another way, or explain in your report why it is needed.'\r\nexport const CHECK_INTERVAL_MS = 5_000\r\n// сайт сам отказывает по сроку; запас на случай, если он не отвечает\r\nconst SITE_SILENCE_MS = 60_000\r\nconst MAX_ACTION_LENGTH = 4_000\r\nconst STOPPED_TEXT = 'The run is stopping, so this was not done.'\r\nconst SITE_SILENT_TEXT = 'TAMsense did not answer about this action, so it was not done. Do it another way, or explain in your report why it is needed.'\r\n// вопросы и план Claude задаёт людям не так: для вопроса есть ask_person\r\nconst NEVER_SENT = new Set(['AskUserQuestion', 'ExitPlanMode'])\r\nconst ACTION_FIELDS = ['command', 'file_path', 'notebook_path', 'url', 'query', 'path']\r\n\r\n// Что именно Claude хочет сделать: команда оболочки, путь файла, адрес или входные данные инструмента\r\nexport function actionOf(input: Record<string, unknown>): string {\r\n const field = ACTION_FIELDS.map(key => input[key]).find(value => typeof value === 'string' && value.trim())\r\n return (typeof field === 'string' ? field.trim() : JSON.stringify(input)).slice(0, MAX_ACTION_LENGTH)\r\n}\r\n\r\n// Название действия для людей: пояснение Claude к команде, иначе строка запроса Claude Code\r\nexport function titleOf(input: Record<string, unknown>, options: { title?: string, displayName?: string }): string {\r\n const described = typeof input.description === 'string' ? input.description.trim() : ''\r\n return described || options.title?.trim() || options.displayName?.trim() || ''\r\n}\r\n\r\nfunction deny(message: string): PermissionResult {\r\n return { behavior: 'deny', message }\r\n}\r\n\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error)\r\n}\r\n\r\n// Ждёт решения: сайт отвечает wait, пока решения нет и срок не вышел. Сбой проверки не отказ: следующая проверка повторит\r\nasync function decisionOf(tools: PermissionTools, first: RunnerPermissionReply, signal: AbortSignal): Promise<RunnerPermissionReply | string> {\r\n const now = tools.now ?? Date.now\r\n const giveUpAt = Date.parse(first.waitUntil) + SITE_SILENCE_MS\r\n let reply = first\r\n while (reply.decision === 'wait') {\r\n await tools.sleep(CHECK_INTERVAL_MS, signal)\r\n if (signal.aborted) {\r\n return STOPPED_TEXT\r\n }\r\n if (now() > giveUpAt) {\r\n return SITE_SILENT_TEXT\r\n }\r\n reply = await tools.check(reply.id).catch(() => reply)\r\n }\r\n return reply\r\n}\r\n\r\n// Вместо отказа «спросить некого»: то, о чём Claude Code спросил бы человека, уходит на сайт. Правила проекта решают сразу,\r\n// иначе вызов ждёт решения исполнителя или владельца задачи, а остальная работа идёт. lastWords: последнее, что Claude написал;\r\n// folder: папка задачи, в которой работает Claude\r\nexport function askPermission(tools: PermissionTools, lastWords: () => string, folder: string): CanUseTool {\r\n return async (toolName, input, options) => {\r\n if (NEVER_SENT.has(toolName)) {\r\n return deny(NOBODY_TO_ASK)\r\n }\r\n const body: RunnerPermissionBody = {\r\n tool: toolName,\r\n action: actionOf(input),\r\n title: titleOf(input, options),\r\n reason: lastWords() || options.decisionReason || '',\r\n folder,\r\n outsideFolder: Boolean(options.blockedPath),\r\n }\r\n let first: RunnerPermissionReply\r\n try {\r\n first = await tools.send(body)\r\n }\r\n catch (error) {\r\n return deny(`Could not ask for permission on TAMsense: ${messageOf(error)}. Do it another way, or explain in your report why it is needed.`)\r\n }\r\n const reply = await decisionOf(tools, first, options.signal)\r\n if (typeof reply === 'string') {\r\n return deny(reply)\r\n }\r\n return reply.decision === 'allow' ? { behavior: 'allow', updatedInput: input } : deny(reply.message)\r\n }\r\n}\r\n\r\n// Шаг ТЗ: запросов с него нет, режим auto сам решает, что можно, а остальное отклоняется сразу\r\nexport const denyWhenAsked: CanUseTool = async () => deny(NOBODY_TO_ASK)\r\n","import type { RunnerJob, RunnerPushedBranch, RunnerRepository } from './protocol'\r\nimport type { LocalSource, Workspace, WorkspaceRepository } from './workspace'\r\nimport { existsSync } from 'node:fs'\r\nimport { join, resolve } from 'node:path'\r\nimport { hasRef, isInside, remoteOf, remoteWithoutSecrets } from './folders'\r\nimport { git, personalGitEnv, runGit } from './git'\r\nimport { keyedLock } from './lock'\r\nimport { branchHolders, folderNames, isReadOnlyCheck, removeWorktree, tamsenseFolder, taskFolder } from './workspace'\r\n\r\nexport interface LocalTools {\r\n home: string\r\n print: (line: string) => void\r\n}\r\n\r\n// сеть без ответа не держит работу: без свежих веток задача идёт с тем, что уже есть в папке\r\nconst NETWORK_TIMEOUT_MS = 120_000\r\n\r\n// Папку человека git меняет по очереди: два шага одного репозитория не создают ветки одновременно\r\nconst sourceLock = keyedLock()\r\n\r\nexport function isLocalJob(job: Pick<RunnerJob, 'workspace'>): boolean {\r\n return job.workspace === 'local'\r\n}\r\n\r\n// Где теперь работа шага: ветка в папке человека и, если удалось, на её удалённом репозитории\r\nexport function localBranchLine(branch: RunnerPushedBranch): string {\r\n const local = branch.local\r\n if (!local) {\r\n return `Pushed ${branch.branch} in ${branch.repository}.`\r\n }\r\n const where = `Branch ${branch.branch} has ${local.commits} commit(s) in ${local.folder}`\r\n if (local.pushProblem) {\r\n return `${where}. It was not pushed: ${local.pushProblem}`\r\n }\r\n return local.remoteUrl ? `${where}, pushed to ${local.remoteUrl}.` : `${where}.`\r\n}\r\n\r\n// Вопросы и ТЗ только читают код: ветку задачи они не создают, иначе шаг кода начал бы со старого снимка\r\nfunction readsOnly(job: RunnerJob): boolean {\r\n return isReadOnlyCheck(job) || job.stage.kind === 'interview' || job.stage.kind === 'spec'\r\n}\r\n\r\nasync function isAncestor(folder: string, older: string, newer: string): Promise<boolean> {\r\n return (await runGit(['merge-base', '--is-ancestor', older, newer], { cwd: folder })).code === 0\r\n}\r\n\r\n// Свежие ветки удалённого репозитория папки своим входом человека; без сети или входа работа идёт без них\r\nasync function fetchRemote(source: string, remote: string, print: (line: string) => void): Promise<void> {\r\n const fetched = await runGit(['fetch', '--prune', remote], { cwd: source, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS })\r\n if (fetched.code !== 0) {\r\n print(`Could not fetch from ${remote} in ${source}, going on with what the folder has: ${fetched.stderr || `exit code ${fetched.code}`}`)\r\n }\r\n}\r\n\r\n// Основная ветка: своя ветка папки или та же ветка удалённого, если она новее и своя за ней не ушла вперёд\r\nasync function baseBranchRef(source: string, branch: string, remote: string | null): Promise<string> {\r\n const own = await hasRef(source, `refs/heads/${branch}`) ? branch : null\r\n const fetched = remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null\r\n if (own && fetched) {\r\n return await isAncestor(source, own, fetched) ? fetched : own\r\n }\r\n const found = own ?? fetched\r\n if (!found) {\r\n throw new Error(`The main branch ${branch} is not in ${source}. Check the folder again in the board settings, on the Code tab.`)\r\n }\r\n return found\r\n}\r\n\r\n// Ветка задачи в папке или на её удалённом репозитории; своя ветка без работы поверх основной не в счёт\r\nasync function taskBranchRef(source: string, branch: string, remote: string | null, base: string): Promise<string | null> {\r\n if (await hasRef(source, `refs/heads/${branch}`) && Number(await git(['rev-list', '--count', `${base}..${branch}`], { cwd: source })) > 0) {\r\n return branch\r\n }\r\n return remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null\r\n}\r\n\r\n// Откуда начинается папка шага: ветка задачи, если работа уже есть; проверке без ветки задачи смотреть не на что\r\nasync function startRef(source: string, job: RunnerJob, local: LocalSource): Promise<string> {\r\n const base = await baseBranchRef(source, local.baseBranch, local.remote)\r\n const task = await taskBranchRef(source, job.branch, local.remote, base)\r\n if (task || !isReadOnlyCheck(job)) {\r\n return task ?? base\r\n }\r\n const where = local.remote ? `${source} or on its remote ${local.remote}` : source\r\n throw new Error(`Branch ${job.branch} is not in ${where}. It was made on another computer: push it to a remote this folder can fetch, or run this check on that computer.`)\r\n}\r\n\r\n// Ветку задачи держит одна папка TAMsense; если она открыта в рабочей папке самого человека, её не трогают\r\nasync function releaseBranch(source: string, branch: string, keep: string, home: string): Promise<void> {\r\n const listed = await runGit(['worktree', 'list', '--porcelain'], { cwd: source })\r\n const work = tamsenseFolder(home, 'work')\r\n for (const holder of branchHolders(listed.stdout, branch).filter(path => resolve(path) !== resolve(keep))) {\r\n if (!isInside(holder, work)) {\r\n throw new Error(`Branch ${branch} is checked out in ${resolve(holder)}. Switch that folder to another branch, then start the step again.`)\r\n }\r\n await removeWorktree(source, holder)\r\n }\r\n}\r\n\r\n// Продолжение после ответа человека берёт папку шага как есть, если в ней та же ветка\r\nasync function keptBase(path: string, job: RunnerJob): Promise<string | null> {\r\n if (!job.keepWorkspace || !existsSync(join(path, '.git'))) {\r\n return null\r\n }\r\n const branch = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: path })\r\n if (branch.code !== 0 || branch.stdout !== (readsOnly(job) ? 'HEAD' : job.branch)) {\r\n return null\r\n }\r\n return git(['rev-parse', 'HEAD'], { cwd: path })\r\n}\r\n\r\nfunction sourceOf(repository: RunnerRepository): string {\r\n if (!repository.folder) {\r\n throw new Error(`TAMsense did not say which folder holds ${repository.fullName} on this computer.`)\r\n }\r\n return repository.folder\r\n}\r\n\r\nasync function prepareRepository(job: RunnerJob, repository: RunnerRepository, path: string, tools: LocalTools): Promise<{ baseSha: string, local: LocalSource }> {\r\n const source = sourceOf(repository)\r\n if ((await runGit(['rev-parse', '--show-toplevel'], { cwd: source }).catch(() => null))?.code !== 0) {\r\n throw new Error(`${source} is no longer a git repository on this computer. Check the folder again in the board settings, on the Code tab.`)\r\n }\r\n const local: LocalSource = { source, remote: await remoteOf(source), baseBranch: repository.defaultBranch }\r\n if (local.remote) {\r\n await fetchRemote(source, local.remote, tools.print)\r\n }\r\n const kept = await keptBase(path, job)\r\n if (kept) {\r\n return { baseSha: kept, local }\r\n }\r\n await runGit(['worktree', 'prune'], { cwd: source })\r\n if (existsSync(path)) {\r\n await removeWorktree(source, path)\r\n }\r\n const start = await startRef(source, job, local)\r\n if (readsOnly(job)) {\r\n await git(['worktree', 'add', '--detach', path, start], { cwd: source })\r\n }\r\n else {\r\n await releaseBranch(source, job.branch, path, tools.home)\r\n await git(['worktree', 'add', '-B', job.branch, path, start], { cwd: source })\r\n }\r\n return { baseSha: await git(['rev-parse', 'HEAD'], { cwd: path }), local }\r\n}\r\n\r\n// Папка шага для каждого репозитория: рабочая копия git из папки человека в ~/.tamsense/work. Рабочая папка человека,\r\n// её текущая ветка и остальные ветки не меняются: шаг кода пишет только в ветку задачи\r\nexport async function prepareLocalWorkspace(job: RunnerJob, tools: LocalTools): Promise<Workspace> {\r\n const root = taskFolder(tools.home, job)\r\n const folders = folderNames(job.repositories)\r\n const repositories: WorkspaceRepository[] = []\r\n for (const [index, repository] of job.repositories.entries()) {\r\n const folder = folders[index] ?? repository.fullName\r\n const path = join(root, folder)\r\n const prepared = await sourceLock.run(resolve(sourceOf(repository)), () => prepareRepository(job, repository, path, tools))\r\n repositories.push({ fullName: repository.fullName, folder, path, baseSha: prepared.baseSha, local: prepared.local })\r\n }\r\n return { root, repositories }\r\n}\r\n\r\n// Ветка задачи отправляется на удалённый репозиторий папки входом человека, только сама ветка задачи; основная ветка никогда.\r\n// Не отправилась: работа остаётся в ветке папки, человек видит почему\r\nasync function pushBranch(path: string, local: LocalSource, branch: string): Promise<{ remoteUrl: string | null, pushProblem: string | null }> {\r\n if (!local.remote) {\r\n return { remoteUrl: null, pushProblem: null }\r\n }\r\n if (branch === local.baseBranch) {\r\n return { remoteUrl: null, pushProblem: `TAMsense never pushes the main branch ${branch}.` }\r\n }\r\n const pushed = await runGit(['push', local.remote, `HEAD:refs/heads/${branch}`], { cwd: path, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS })\r\n if (pushed.code !== 0) {\r\n return { remoteUrl: null, pushProblem: pushed.stderr || `exit code ${pushed.code}` }\r\n }\r\n return { remoteUrl: await git(['remote', 'get-url', local.remote], { cwd: path }), pushProblem: null }\r\n}\r\n\r\n// Незакоммиченное Claude досохраняется одним коммитом в ветке задачи; ветка с новой работой отправляется, если у папки есть удалённый\r\nasync function commitAndPush(repository: WorkspaceRepository, local: LocalSource, job: RunnerJob, identity: Record<string, string>) {\r\n const options = { cwd: repository.path, env: identity }\r\n await git(['add', '--all'], options)\r\n if ((await runGit(['diff', '--cached', '--quiet'], options)).code === 1) {\r\n await git(['commit', '--message', `Task #${job.taskId}: ${job.title}`], options)\r\n }\r\n if (Number(await git(['rev-list', '--count', `${repository.baseSha}..HEAD`], options)) === 0) {\r\n return null\r\n }\r\n const base = await baseBranchRef(repository.path, local.baseBranch, local.remote)\r\n const commits = Number(await git(['rev-list', '--count', `${base}..HEAD`], options))\r\n const sent = await pushBranch(repository.path, local, job.branch)\r\n return { sha: await git(['rev-parse', 'HEAD'], options), commits, ...sent }\r\n}\r\n\r\nexport async function publishLocalWorkspace(workspace: Workspace, job: RunnerJob, identity: Record<string, string>): Promise<RunnerPushedBranch[]> {\r\n const pushed: RunnerPushedBranch[] = []\r\n for (const repository of workspace.repositories) {\r\n const local = repository.local\r\n if (!local) {\r\n continue\r\n }\r\n const branch = await sourceLock.run(resolve(local.source), async () => {\r\n const published = await commitAndPush(repository, local, job, identity)\r\n // папка шага отпускает ветку задачи: человек может открыть её у себя, работа осталась в ней\r\n await runGit(['checkout', '--quiet', '--detach'], { cwd: repository.path })\r\n return published\r\n })\r\n if (branch) {\r\n pushed.push({\r\n repository: repository.fullName,\r\n branch: job.branch,\r\n sha: branch.sha,\r\n local: { folder: local.source, baseBranch: local.baseBranch, commits: branch.commits, remoteUrl: branch.remoteUrl && remoteWithoutSecrets(branch.remoteUrl), pushProblem: branch.pushProblem },\r\n })\r\n }\r\n }\r\n return pushed\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { RunnerFolderChecksResponse, RunnerFolderReport, RunnerFoundFolder } from './protocol'\r\nimport { existsSync, realpathSync, statSync } from 'node:fs'\r\nimport { readdir } from 'node:fs/promises'\r\nimport { basename, isAbsolute, join, relative, resolve } from 'node:path'\r\nimport process from 'node:process'\r\nimport { alreadyReported, ApiError } from './api'\r\nimport { runGit } from './git'\r\nimport { FOLDER_CHECKS_PATH, folderCheckPath, FOUND_FOLDERS_PATH } from './protocol'\r\n\r\nconst FIRST_REMOTE = 'origin'\r\nconst USUAL_BASES = ['main', 'master']\r\nconst CHECKS_INTERVAL_MS = 5_000\r\n// адрес по сети (https://, ssh://) или в виде git@host:владелец/имя; у локального пути и file:// имени владельца нет\r\nconst WEB_REMOTE = /^[a-z][\\w+.-]*:\\/\\//i\r\nconst NETWORK_REMOTE = /^(?!file:)[a-z][\\w+.-]*:\\/\\/|^[^/@]+@[^/:]+:/i\r\n\r\nexport interface FolderTools {\r\n api: ApiClient\r\n home: string\r\n print: (line: string) => void\r\n sleep: (ms: number, signal?: AbortSignal) => Promise<void>\r\n inspect: (path: string) => Promise<RunnerFolderReport>\r\n}\r\n\r\nfunction problem(text: string): RunnerFolderReport {\r\n return { ok: false, problem: text }\r\n}\r\n\r\nasync function gitText(args: string[], cwd: string): Promise<string | null> {\r\n const result = await runGit(args, { cwd })\r\n return result.code === 0 && result.stdout ? result.stdout : null\r\n}\r\n\r\n// ~ в начале пути: папка человека\r\nexport function expandHome(path: string, home: string): string {\r\n const trimmed = path.trim()\r\n return trimmed === '~' || trimmed.startsWith('~/') || trimmed.startsWith('~\\\\') ? join(home, trimmed.slice(1)) : trimmed\r\n}\r\n\r\n// Логин и пароль в адресе остаются на компьютере: на сайт уходит адрес без них\r\nexport function remoteWithoutSecrets(url: string): string {\r\n if (!WEB_REMOTE.test(url)) {\r\n return url\r\n }\r\n try {\r\n const parsed = new URL(url)\r\n if (parsed.username || parsed.password) {\r\n parsed.username = ''\r\n parsed.password = ''\r\n }\r\n return parsed.toString().replace(/\\/$/, '')\r\n }\r\n catch {\r\n return url\r\n }\r\n}\r\n\r\n// Владелец/имя удалённого репозитория, как на GitHub; null у локального пути и без удалённого\r\nexport function remoteRepository(remoteUrl: string | null): string | null {\r\n const parts = remoteUrl && NETWORK_REMOTE.test(remoteUrl) ? remoteUrl.replace(/\\/+$/, '').replace(/\\.git$/, '').split(/[/:]/).filter(Boolean) : []\r\n return parts.length >= 3 ? `${parts.at(-2)}/${parts.at(-1)}` : null\r\n}\r\n\r\n// Имя репозитория по адресу удалённого, без него по имени папки\r\nexport function repositoryName(remoteUrl: string | null, folder: string): string {\r\n return remoteRepository(remoteUrl) ?? basename(folder)\r\n}\r\n\r\n// Удалённый репозиторий папки: origin, а если его нет, первый из списка\r\nexport async function remoteOf(folder: string): Promise<string | null> {\r\n const remotes = (await gitText(['remote'], folder))?.split('\\n').map(line => line.trim()).filter(Boolean) ?? []\r\n return remotes.includes(FIRST_REMOTE) ? FIRST_REMOTE : remotes[0] ?? null\r\n}\r\n\r\nexport async function hasRef(folder: string, ref: string): Promise<boolean> {\r\n return (await runGit(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], { cwd: folder })).code === 0\r\n}\r\n\r\n// Основная ветка: та, на которую смотрит HEAD удалённого, иначе main или master, иначе текущая\r\nasync function baseBranchOf(folder: string, remote: string | null, current: string | null): Promise<string | null> {\r\n const remoteHead = remote ? await gitText(['symbolic-ref', '--quiet', '--short', `refs/remotes/${remote}/HEAD`], folder) : null\r\n if (remoteHead?.startsWith(`${remote}/`)) {\r\n return remoteHead.slice(remote!.length + 1)\r\n }\r\n for (const name of USUAL_BASES) {\r\n if (await hasRef(folder, `refs/heads/${name}`) || (remote && await hasRef(folder, `refs/remotes/${remote}/${name}`))) {\r\n return name\r\n }\r\n }\r\n return current\r\n}\r\n\r\n// git называет папку её настоящим путём: ссылки и короткие имена Windows раскрываются до сравнения\r\nfunction samePath(first: string, second: string): boolean {\r\n const [a, b] = [realpathSync.native(first), realpathSync.native(second)]\r\n return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b\r\n}\r\n\r\nexport function isInside(path: string, folder: string): boolean {\r\n const between = relative(resolve(folder), resolve(path))\r\n return between === '' || (!between.startsWith('..') && !isAbsolute(between))\r\n}\r\n\r\n// Проверка только читает git папки: что это верхняя папка репозитория, его ветки и удалённый адрес\r\nexport async function inspectFolder(path: string, home: string): Promise<RunnerFolderReport> {\r\n const folder = expandHome(path, home)\r\n if (!isAbsolute(folder)) {\r\n return problem('Enter the full path of the folder, for example /Users/anna/code/shop or C:\\\\code\\\\shop.')\r\n }\r\n if (!existsSync(folder) || !statSync(folder).isDirectory()) {\r\n return problem(`There is no folder ${folder} on this computer.`)\r\n }\r\n if (isInside(folder, join(home, '.tamsense'))) {\r\n return problem('This is a TAMsense work folder. Choose your own copy of the repository.')\r\n }\r\n const top = await gitText(['rev-parse', '--show-toplevel'], folder)\r\n if (!top) {\r\n return problem(`${folder} is not a git repository. Choose the folder of a cloned repository, or run git init there.`)\r\n }\r\n if (!samePath(top, folder)) {\r\n return problem(`${folder} is inside the repository ${resolve(top)}. Add the top folder of the repository instead.`)\r\n }\r\n if (!(await hasRef(folder, 'HEAD'))) {\r\n return problem('The repository has no commits yet. Make the first commit, then check the folder again.')\r\n }\r\n const remote = await remoteOf(folder)\r\n const remoteUrl = remote ? await gitText(['remote', 'get-url', remote], folder) : null\r\n const currentBranch = await gitText(['symbolic-ref', '--quiet', '--short', 'HEAD'], folder)\r\n const defaultBranch = await baseBranchOf(folder, remote, currentBranch)\r\n if (!defaultBranch) {\r\n return problem('Could not tell the main branch of the repository. Check out the main branch in this folder, then check it again.')\r\n }\r\n const safeUrl = remoteUrl && remoteWithoutSecrets(remoteUrl)\r\n return { ok: true, repository: repositoryName(safeUrl, folder), currentBranch, defaultBranch, remoteUrl: safeUrl }\r\n}\r\n\r\nfunction reportLine(path: string, report: RunnerFolderReport): string {\r\n return report.ok ? `Checked the folder ${path}: ${report.repository}, main branch ${report.defaultBranch}.` : `The folder ${path} cannot be used: ${report.problem}`\r\n}\r\n\r\n// Папки, которые люди добавили на доски для этого компьютера, проверяются здесь же: сайт спрашивает, программа отвечает.\r\n// Старый сайт этих запросов не знает: тогда проверка молча выключается\r\nexport async function checkFolders(tools: FolderTools, stop: AbortSignal): Promise<void> {\r\n let complained = false\r\n while (!stop.aborted) {\r\n try {\r\n const { checks } = await tools.api.request<RunnerFolderChecksResponse>('POST', FOLDER_CHECKS_PATH, undefined, { safeToRepeat: true })\r\n for (const check of checks) {\r\n const report = await tools.inspect(check.path).catch((error: unknown) => problem(error instanceof Error ? error.message : String(error)))\r\n await tools.api.request('POST', folderCheckPath(check.id), report)\r\n tools.print(reportLine(check.path, report))\r\n }\r\n complained = false\r\n }\r\n catch (error) {\r\n if (error instanceof ApiError && error.status === 404) {\r\n return\r\n }\r\n if (!complained && !stop.aborted && !alreadyReported(error)) {\r\n tools.print(`Could not check the folders for TAMsense: ${error instanceof Error ? error.message : String(error)}`)\r\n complained = true\r\n }\r\n }\r\n await tools.sleep(CHECKS_INTERVAL_MS, stop)\r\n }\r\n}\r\n\r\nconst SKIPPED_FOLDER = 'node_modules'\r\nconst MORE_CODE_TEXT = 'To add more code, start the program from your code folder or the folder above it, or add a path in Board settings > Code.'\r\n\r\n// Папки на уровень ниже, где может лежать репозиторий: без скрытых, node_modules и ссылок на другие места\r\nasync function childRepositories(start: string): Promise<string[]> {\r\n const entries = await readdir(start, { withFileTypes: true }).catch(() => [])\r\n return entries\r\n .filter(entry => entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== SKIPPED_FOLDER)\r\n .map(entry => join(start, entry.name))\r\n .filter(folder => existsSync(join(folder, '.git')))\r\n .sort()\r\n}\r\n\r\n// Код, который программа находит при запуске: папка запуска, если это репозиторий, иначе репозитории на уровень ниже.\r\n// Глубже программа не ищет; папки, которые не подходят для работы, пропускаются\r\nexport async function findCodeFolders(start: string, home: string): Promise<RunnerFoundFolder[]> {\r\n const folder = resolve(start)\r\n if (!existsSync(folder)) {\r\n return []\r\n }\r\n const top = await gitText(['rev-parse', '--show-toplevel'], folder)\r\n const candidates = top && samePath(top, folder) ? [folder] : await childRepositories(folder)\r\n const found: RunnerFoundFolder[] = []\r\n for (const candidate of candidates) {\r\n const report = await inspectFolder(candidate, home)\r\n if (report.ok) {\r\n const { currentBranch, defaultBranch, remoteUrl } = report\r\n found.push({ path: candidate, name: basename(candidate), repository: remoteRepository(remoteUrl), currentBranch, defaultBranch, remoteUrl })\r\n }\r\n }\r\n return found\r\n}\r\n\r\nexport function foundCodeLines(start: string, found: RunnerFoundFolder[]): string[] {\r\n const lines = found.length > 0 ? found.map(folder => `Found code: ${folder.name} (${folder.path})`) : [`No code found in ${resolve(start)}.`]\r\n return [...lines, MORE_CODE_TEXT]\r\n}\r\n\r\n// При каждом запуске сайт узнаёт, какой код есть на компьютере. Старый сайт этого адреса не знает: тогда он просто не узнаёт\r\nexport async function reportFoundFolders(tools: Pick<FolderTools, 'api' | 'home' | 'print'>, start: string): Promise<void> {\r\n try {\r\n const found = await findCodeFolders(start, tools.home)\r\n foundCodeLines(start, found).forEach(line => tools.print(line))\r\n await tools.api.request('PUT', FOUND_FOLDERS_PATH, { folders: found })\r\n }\r\n catch (error) {\r\n if (!(error instanceof ApiError && error.status === 404)) {\r\n tools.print(`Could not tell TAMsense about the code on this computer: ${error instanceof Error ? error.message : String(error)}`)\r\n }\r\n }\r\n}\r\n","import { Buffer } from 'node:buffer'\r\nimport { spawn } from 'node:child_process'\r\nimport process from 'node:process'\r\n\r\nexport interface GitResult {\r\n code: number\r\n stdout: string\r\n stderr: string\r\n}\r\n\r\nexport interface GitOptions {\r\n cwd: string\r\n // ключ GitHub уходит только этому процессу git через окружение: в настройки репозитория и к Claude он не попадает\r\n token?: string\r\n env?: Record<string, string>\r\n // git по сети без ответа не держит работу: процесс останавливается через это время\r\n timeoutMs?: number\r\n}\r\n\r\nconst GITHUB_ORIGIN = 'https://github.com/'\r\n\r\nexport function githubAuthEnv(token: string): Record<string, string> {\r\n const credentials = Buffer.from(`x-access-token:${token}`).toString('base64')\r\n return {\r\n GIT_CONFIG_COUNT: '1',\r\n GIT_CONFIG_KEY_0: `http.${GITHUB_ORIGIN}.extraheader`,\r\n GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${credentials}`,\r\n GIT_TERMINAL_PROMPT: '0',\r\n }\r\n}\r\n\r\n// Вход человека в его удалённый репозиторий: git берёт сохранённые пароли и ключи, но ничего не спрашивает,\r\n// ведь у компьютера никто не сидит. Свою команду ssh человека программа не подменяет\r\nexport function personalGitEnv(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\r\n return { GIT_TERMINAL_PROMPT: '0', ...(env.GIT_SSH_COMMAND ? {} : { GIT_SSH_COMMAND: 'ssh -o BatchMode=yes' }) }\r\n}\r\n\r\nexport function runGit(args: string[], options: GitOptions): Promise<GitResult> {\r\n const env = { ...process.env, ...options.env, ...(options.token ? githubAuthEnv(options.token) : {}) }\r\n return new Promise((resolve, reject) => {\r\n const child = spawn('git', args, { cwd: options.cwd, env, stdio: ['ignore', 'pipe', 'pipe'], ...(options.timeoutMs ? { timeout: options.timeoutMs } : {}) })\r\n let stdout = ''\r\n let stderr = ''\r\n child.stdout.on('data', (chunk: Buffer) => {\r\n stdout += chunk.toString()\r\n })\r\n child.stderr.on('data', (chunk: Buffer) => {\r\n stderr += chunk.toString()\r\n })\r\n child.on('error', reject)\r\n child.on('close', code => resolve({ code: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() }))\r\n })\r\n}\r\n\r\n// Ошибка git с его собственным текстом: человек увидит её в отчёте задачи\r\nexport async function git(args: string[], options: GitOptions): Promise<string> {\r\n const result = await runGit(args, options)\r\n if (result.code !== 0) {\r\n throw new Error(`git ${args[0]} failed: ${result.stderr || result.stdout || `exit code ${result.code}`}`)\r\n }\r\n return result.stdout\r\n}\r\n","export interface KeyedLock {\r\n run: <T>(key: string, work: () => Promise<T>) => Promise<T>\r\n}\r\n\r\n// Работа с одним ключом идёт по очереди, с разными ключами параллельно\r\nexport function keyedLock(): KeyedLock {\r\n const tails = new Map<string, Promise<void>>()\r\n return {\r\n async run<T>(key: string, work: () => Promise<T>): Promise<T> {\r\n const previous = tails.get(key) ?? Promise.resolve()\r\n let release = () => {}\r\n const done = new Promise<void>((resolve) => {\r\n release = resolve\r\n })\r\n const tail = previous.then(() => done)\r\n tails.set(key, tail)\r\n await previous\r\n try {\r\n return await work()\r\n }\r\n finally {\r\n release()\r\n if (tails.get(key) === tail) {\r\n tails.delete(key)\r\n }\r\n }\r\n },\r\n }\r\n}\r\n","import type { RunnerJob, RunnerPushedBranch, RunnerRepository, RunnerStage } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { rm } from 'node:fs/promises'\r\nimport { join, resolve } from 'node:path'\r\nimport { git, runGit } from './git'\r\nimport { keyedLock } from './lock'\r\n\r\n// local: репозиторий взят из папки человека на этом компьютере\r\nexport interface WorkspaceRepository {\r\n fullName: string\r\n folder: string\r\n path: string\r\n baseSha: string\r\n local?: LocalSource\r\n}\r\n\r\n// source: папка человека, remote: её удалённый репозиторий (null: его нет), baseBranch: основная ветка, как её видит git\r\nexport interface LocalSource {\r\n source: string\r\n remote: string | null\r\n baseBranch: string\r\n}\r\n\r\nexport interface Workspace {\r\n root: string\r\n repositories: WorkspaceRepository[]\r\n}\r\n\r\nexport interface WorkspaceTools {\r\n home: string\r\n remoteUrl: (fullName: string) => string\r\n}\r\n\r\nconst FALLBACK_IDENTITY = { name: 'TAMsense', email: 'noreply@tamsense.com' }\r\n\r\n// Копия репозитория в ~/.tamsense/repos общая для всех задач: git с ней работает по очереди\r\nconst cacheLock = keyedLock()\r\n\r\nexport function githubRemoteUrl(fullName: string): string {\r\n return `https://github.com/${fullName}.git`\r\n}\r\n\r\nexport function tamsenseFolder(home: string, ...parts: string[]): string {\r\n return join(home, '.tamsense', ...parts)\r\n}\r\n\r\n// Папка репозитория называется его именем; два репозитория с одинаковым именем получают имя владельца в начале\r\nexport function folderNames(repositories: RunnerRepository[]): string[] {\r\n const shortName = (fullName: string) => fullName.split('/').pop() ?? fullName\r\n const counts = new Map<string, number>()\r\n for (const repository of repositories) {\r\n counts.set(shortName(repository.fullName), (counts.get(shortName(repository.fullName)) ?? 0) + 1)\r\n }\r\n return repositories.map(repository => counts.get(shortName(repository.fullName)) === 1\r\n ? shortName(repository.fullName)\r\n : repository.fullName.replace('/', '__'))\r\n}\r\n\r\nasync function refreshCache(cache: string, repository: RunnerRepository, token: string, tools: WorkspaceTools): Promise<void> {\r\n if (existsSync(join(cache, '.git'))) {\r\n await git(['fetch', '--prune', 'origin'], { cwd: cache, token })\r\n return\r\n }\r\n await git(['clone', '--no-checkout', tools.remoteUrl(repository.fullName), cache], { cwd: tools.home, token })\r\n}\r\n\r\nexport async function removeWorktree(cache: string, path: string): Promise<void> {\r\n await runGit(['worktree', 'remove', '--force', path], { cwd: cache })\r\n await rm(path, { recursive: true, force: true })\r\n await git(['worktree', 'prune'], { cwd: cache })\r\n}\r\n\r\n// Своя папка у каждого шага задачи: две проверки одной задачи на одном компьютере идут одновременно и не мешают друг другу.\r\n// Старый сайт шага не называет: тогда папка одна на задачу, как раньше\r\nexport type StepPlace = Pick<RunnerJob, 'taskId'> & { stage: Pick<RunnerStage, 'columnId'> }\r\n\r\nexport function stepFolder(job: StepPlace): string[] {\r\n const task = `task-${job.taskId}`\r\n return typeof job.stage.columnId === 'number' ? [task, `step-${job.stage.columnId}`] : [task]\r\n}\r\n\r\n// Проверка смотрит работу, но ничего не отправляет: она берёт последний коммит ветки без самой ветки,\r\n// поэтому несколько проверок и шаг кода не спорят за одну ветку\r\nexport function isReadOnlyCheck(job: RunnerJob): boolean {\r\n return job.stage.kind === 'review' || job.stage.kind === 'test'\r\n}\r\n\r\n// Папки, в которых стоит ветка, по выводу git worktree list --porcelain\r\nexport function branchHolders(porcelain: string, branch: string): string[] {\r\n const holders: string[] = []\r\n let path: string | null = null\r\n for (const line of porcelain.split('\\n').map(text => text.trim())) {\r\n if (line.startsWith('worktree ')) {\r\n path = line.slice('worktree '.length)\r\n }\r\n else if (path && line === `branch refs/heads/${branch}`) {\r\n holders.push(path)\r\n }\r\n }\r\n return holders\r\n}\r\n\r\n// Ветку задачи держит одна папка: шаг, который пишет в неё код, забирает её у папки прошлого шага этой задачи\r\nasync function releaseBranch(cache: string, branch: string, keep: string): Promise<void> {\r\n const listed = await runGit(['worktree', 'list', '--porcelain'], { cwd: cache })\r\n for (const path of branchHolders(listed.stdout, branch).filter(holder => resolve(holder) !== resolve(keep))) {\r\n await removeWorktree(cache, path)\r\n }\r\n}\r\n\r\n// Доработка продолжает уже отправленную ветку задачи, первый запуск начинает её с основной ветки\r\nasync function baseRef(cache: string, job: RunnerJob, repository: RunnerRepository): Promise<string> {\r\n const pushed = await runGit(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${job.branch}`], { cwd: cache })\r\n return pushed.code === 0 ? `origin/${job.branch}` : `origin/${repository.defaultBranch}`\r\n}\r\n\r\n// Продолжение после ответа человека берёт папку задачи как есть: незакоммиченная работа агента остаётся. Базой остаётся ветка на GitHub\r\nasync function keptBase(cache: string, path: string, job: RunnerJob, repository: RunnerRepository): Promise<string | null> {\r\n if (!job.keepWorkspace || !existsSync(join(path, '.git'))) {\r\n return null\r\n }\r\n const branch = await runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: path })\r\n if (branch.code !== 0 || branch.stdout !== (isReadOnlyCheck(job) ? 'HEAD' : job.branch)) {\r\n return null\r\n }\r\n return git(['rev-parse', await baseRef(cache, job, repository)], { cwd: cache })\r\n}\r\n\r\n// Папка шага задачи на компьютере: в ней репозитории или, на доске без кода, работа и результат задачи\r\nexport function taskFolder(home: string, job: StepPlace): string {\r\n return tamsenseFolder(home, 'work', ...stepFolder(job))\r\n}\r\n\r\nexport async function prepareWorkspace(job: RunnerJob, token: string, tools: WorkspaceTools): Promise<Workspace> {\r\n const root = taskFolder(tools.home, job)\r\n const folders = folderNames(job.repositories)\r\n const repositories: WorkspaceRepository[] = []\r\n for (const [index, repository] of job.repositories.entries()) {\r\n const cache = tamsenseFolder(tools.home, 'repos', repository.fullName.replace('/', '__'))\r\n const folder = folders[index] ?? repository.fullName\r\n const path = join(root, folder)\r\n const baseSha = await cacheLock.run(repository.fullName, async () => {\r\n await refreshCache(cache, repository, token, tools)\r\n const kept = await keptBase(cache, path, job, repository)\r\n if (kept) {\r\n return kept\r\n }\r\n await removeWorktree(cache, path)\r\n const base = await baseRef(cache, job, repository)\r\n if (isReadOnlyCheck(job)) {\r\n await git(['worktree', 'add', '--force', '--detach', path, base], { cwd: cache })\r\n }\r\n else {\r\n await releaseBranch(cache, job.branch, path)\r\n await git(['worktree', 'add', '--force', '-B', job.branch, path, base], { cwd: cache })\r\n }\r\n return git(['rev-parse', 'HEAD'], { cwd: path })\r\n })\r\n repositories.push({ fullName: repository.fullName, folder, path, baseSha })\r\n }\r\n return { root, repositories }\r\n}\r\n\r\n// Коммиты без автора git не создаёт: если человек его не задал, коммиты подписываются именем TAMsense\r\nexport async function commitIdentityEnv(home: string): Promise<Record<string, string>> {\r\n const configured = await runGit(['config', '--get', 'user.email'], { cwd: home })\r\n if (configured.code === 0 && configured.stdout) {\r\n return {}\r\n }\r\n return {\r\n GIT_AUTHOR_NAME: FALLBACK_IDENTITY.name,\r\n GIT_AUTHOR_EMAIL: FALLBACK_IDENTITY.email,\r\n GIT_COMMITTER_NAME: FALLBACK_IDENTITY.name,\r\n GIT_COMMITTER_EMAIL: FALLBACK_IDENTITY.email,\r\n }\r\n}\r\n\r\n// Незакоммиченное Claude досохраняется одним коммитом; ветка отправляется, только если в ней есть новое\r\nexport async function publishWorkspace(workspace: Workspace, job: RunnerJob, token: string, identity: Record<string, string>): Promise<RunnerPushedBranch[]> {\r\n const pushed: RunnerPushedBranch[] = []\r\n for (const repository of workspace.repositories) {\r\n const options = { cwd: repository.path, env: identity }\r\n const sha = await cacheLock.run(repository.fullName, async () => {\r\n await git(['add', '--all'], options)\r\n const hasStaged = (await runGit(['diff', '--cached', '--quiet'], options)).code === 1\r\n if (hasStaged) {\r\n await git(['commit', '--message', `Task #${job.taskId}: ${job.title}`], options)\r\n }\r\n const newCommits = Number(await git(['rev-list', '--count', `${repository.baseSha}..HEAD`], options))\r\n if (newCommits === 0) {\r\n return null\r\n }\r\n await git(['push', 'origin', `HEAD:refs/heads/${job.branch}`], { ...options, token })\r\n return git(['rev-parse', 'HEAD'], options)\r\n })\r\n if (sha) {\r\n pushed.push({ repository: repository.fullName, branch: job.branch, sha })\r\n }\r\n }\r\n return pushed\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { RunnerBoardTarget, RunnerHandOverBody, RunnerJob, RunnerResult, RunnerResultBody, RunnerResultFile, RunnerResultFileBody, RunnerToolReply } from './protocol'\r\nimport type { Workspace } from './workspace'\r\nimport { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises'\r\nimport { basename, extname, isAbsolute, join, resolve } from 'node:path'\r\nimport { tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { MAX_RESULT_FILE_BYTES, resultFilePath, runPath } from './protocol'\r\n\r\nexport const SAVE_DOCUMENT_TOOL_NAME = 'save_result_document'\r\nexport const SAVE_TABLE_TOOL_NAME = 'save_result_table'\r\nexport const ATTACH_FILE_TOOL_NAME = 'attach_result_file'\r\nexport const ADD_LINK_TOOL_NAME = 'add_result_link'\r\nexport const HAND_OVER_TOOL_NAME = 'hand_over'\r\nexport const RESULTS_FOLDER = 'results'\r\n\r\nconst MAX_NAME_LENGTH = 60\r\nconst TYPES_BY_EXTENSION: Record<string, string> = {\r\n '.pdf': 'application/pdf',\r\n '.png': 'image/png',\r\n '.jpg': 'image/jpeg',\r\n '.jpeg': 'image/jpeg',\r\n '.webp': 'image/webp',\r\n '.gif': 'image/gif',\r\n '.csv': 'text/csv',\r\n '.txt': 'text/plain',\r\n '.md': 'text/markdown',\r\n '.json': 'application/json',\r\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\r\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\r\n '.zip': 'application/zip',\r\n}\r\nconst EXTENSIONS: Record<RunnerResult['kind'], string> = { document: '.md', table: '.csv', file: '', link: '' }\r\n\r\n// Результат кладётся на задачу запросами к сайту; root: папка задачи, относительные пути файлов считаются от неё\r\nexport interface ResultTools {\r\n root: string\r\n save: (body: RunnerResultBody) => Promise<RunnerToolReply>\r\n attach: (body: RunnerResultFileBody) => Promise<RunnerToolReply>\r\n}\r\n\r\n// Доски, на которые агент может передать задачу по правилу шага, и сама передача\r\nexport interface HandOverTools {\r\n boards: RunnerBoardTarget[]\r\n send: (body: RunnerHandOverBody) => Promise<RunnerToolReply>\r\n}\r\n\r\nexport function isFolderJob(job: Pick<RunnerJob, 'workspace'>): boolean {\r\n return job.workspace === 'folder'\r\n}\r\n\r\nexport function resultTools(job: RunnerJob, api: ApiClient, root: string): ResultTools | null {\r\n if (!isFolderJob(job) || job.stage.kind !== 'code') {\r\n return null\r\n }\r\n return {\r\n root,\r\n save: body => api.request<RunnerToolReply>('POST', runPath(job.runId, 'results'), body),\r\n attach: body => api.request<RunnerToolReply>('POST', runPath(job.runId, 'result-files'), body),\r\n }\r\n}\r\n\r\nexport function handOverTools(job: RunnerJob, api: ApiClient): HandOverTools | null {\r\n const boards = job.handOverBoards ?? []\r\n if (boards.length === 0 || job.stage.kind !== 'code') {\r\n return null\r\n }\r\n return { boards, send: body => api.request<RunnerToolReply>('POST', runPath(job.runId, 'hand-over'), body) }\r\n}\r\n\r\n// Имя файла из названия результата: буквы любого языка, цифры и дефисы; одинаковые названия получают номер\r\nexport function resultFileName(result: Pick<RunnerResult, 'kind' | 'title' | 'fileName'>, taken: Set<string>): string {\r\n const stem = (result.kind === 'file' && result.fileName ? result.fileName.replace(/\\.[^.]*$/, '') : result.title)\r\n .normalize('NFKC')\r\n .replace(/[^\\p{L}\\p{N}]+/gu, '-')\r\n .replace(/^-+|-+$/g, '')\r\n .slice(0, MAX_NAME_LENGTH) || 'result'\r\n const extension = result.kind === 'file' ? extname(result.fileName ?? '') : EXTENSIONS[result.kind]\r\n let name = `${stem}${extension}`\r\n for (let index = 2; taken.has(name.toLowerCase()); index += 1) {\r\n name = `${stem}-${index}${extension}`\r\n }\r\n taken.add(name.toLowerCase())\r\n return name\r\n}\r\n\r\nfunction linksText(links: RunnerResult[]): string {\r\n return links.map(link => `- ${link.title}: ${link.url ?? ''}`).join('\\n')\r\n}\r\n\r\n// Результат задачи в папке results: последние версии документов (.md) и таблиц (.csv), файлы и ссылки (links.md).\r\n// Файл, который не скачался, назван в notes.md: агент знает, что его нет. Возвращает строки для подсказки агенту\r\nexport async function writeResults(folder: string, job: RunnerJob, api: ApiClient): Promise<string[]> {\r\n const results = job.results ?? []\r\n await rm(folder, { recursive: true, force: true })\r\n if (results.length === 0) {\r\n return []\r\n }\r\n await mkdir(folder, { recursive: true })\r\n const taken = new Set<string>()\r\n const lines: string[] = []\r\n const missing: string[] = []\r\n for (const result of results.filter(item => item.kind !== 'link')) {\r\n const name = resultFileName(result, taken)\r\n if (result.kind === 'file') {\r\n try {\r\n const file = await api.request<RunnerResultFile>('GET', resultFilePath(job.runId, result.id))\r\n await writeFile(join(folder, name), Buffer.from(file.data, 'base64'))\r\n }\r\n catch (error) {\r\n missing.push(`${result.title}: ${error instanceof Error ? error.message : String(error)}`)\r\n continue\r\n }\r\n }\r\n else {\r\n await writeFile(join(folder, name), result.content ?? '')\r\n }\r\n lines.push(`${RESULTS_FOLDER}/${name}: ${result.title}${result.version ? `, version ${result.version}` : ''}`)\r\n }\r\n const links = results.filter(item => item.kind === 'link')\r\n if (links.length > 0) {\r\n await writeFile(join(folder, 'links.md'), `${linksText(links)}\\n`)\r\n lines.push(`${RESULTS_FOLDER}/links.md: ${links.length} link(s)`)\r\n }\r\n if (missing.length > 0) {\r\n await writeFile(join(folder, 'notes.md'), `These files could not be downloaded:\\n${missing.map(line => `- ${line}`).join('\\n')}\\n`)\r\n }\r\n return lines\r\n}\r\n\r\n// Папка задачи без репозиториев: в ней агент работает, в results лежит то, что задача уже сделала.\r\n// Продолжение после ответа человека берёт папку как есть\r\nexport async function prepareFolder(job: RunnerJob, root: string, api: ApiClient): Promise<Workspace & { results: string[] }> {\r\n if (!job.keepWorkspace) {\r\n await rm(root, { recursive: true, force: true })\r\n }\r\n await mkdir(root, { recursive: true })\r\n return { root, repositories: [], results: await writeResults(join(root, RESULTS_FOLDER), job, api) }\r\n}\r\n\r\n// Файл, который агент прикладывает как результат: путь от папки задачи или полный, не больше 10 МБ.\r\n// Ответ Claude, если файл не годится: он поправит путь или уменьшит файл\r\nexport async function readResultFile(root: string, path: string, title: string): Promise<RunnerResultFileBody | string> {\r\n const full = isAbsolute(path) ? path : resolve(root, path)\r\n const info = await stat(full).catch(() => null)\r\n if (!info?.isFile()) {\r\n return `There is no file at ${full}.`\r\n }\r\n if (info.size > MAX_RESULT_FILE_BYTES) {\r\n return `The file is larger than ${MAX_RESULT_FILE_BYTES / 1024 / 1024} MB. Attach a smaller one or split it.`\r\n }\r\n const name = basename(full)\r\n return { name, title: title.trim() || name, contentType: TYPES_BY_EXTENSION[extname(full).toLowerCase()] ?? 'application/octet-stream', data: (await readFile(full)).toString('base64') }\r\n}\r\n\r\nfunction reply(text: string, isError = false) {\r\n return { content: [{ type: 'text' as const, text }], ...(isError ? { isError: true } : {}) }\r\n}\r\n\r\nasync function sent(send: () => Promise<RunnerToolReply>): Promise<ReturnType<typeof reply>> {\r\n try {\r\n return reply((await send()).text)\r\n }\r\n catch (error) {\r\n return reply(`Could not save it on TAMsense: ${error instanceof Error ? error.message : String(error)}. Try again once; if it fails again, say so in your report.`, true)\r\n }\r\n}\r\n\r\n// Инструменты результата на доске без кода: документ и таблица с версиями, файл, ссылка\r\nexport function resultToolset(tools: ResultTools) {\r\n return [\r\n tool(\r\n SAVE_DOCUMENT_TOOL_NAME,\r\n 'Save a document as the result of this step on the TAMsense task: Markdown text such as a letter, a reply, a brief or a report. Saving again with the same title makes a new version of that document; every version is kept and people compare them.',\r\n {\r\n title: z.string().describe('short name of the document, the same for every version of it'),\r\n content: z.string().describe('the full text in Markdown, never a diff or a part'),\r\n note: z.string().describe('one line on what this version is or what changed: \"First draft\", \"Fixed the prices after the check\"'),\r\n },\r\n async args => await sent(() => tools.save({ kind: 'document', ...args })),\r\n ),\r\n tool(\r\n SAVE_TABLE_TOOL_NAME,\r\n 'Save a table as the result of this step, as CSV with a header row. Saving again with the same title makes a new version of that table.',\r\n {\r\n title: z.string().describe('short name of the table, the same for every version of it'),\r\n csv: z.string().describe('the full table as CSV, the first row holds the column names'),\r\n note: z.string().describe('one line on what this version is or what changed'),\r\n },\r\n async args => await sent(() => tools.save({ kind: 'table', title: args.title, content: args.csv, note: args.note })),\r\n ),\r\n tool(\r\n ATTACH_FILE_TOOL_NAME,\r\n `Attach a file you made in the task folder to the TAMsense task as part of the result: a PDF, a spreadsheet, an image. Up to ${MAX_RESULT_FILE_BYTES / 1024 / 1024} MB.`,\r\n {\r\n path: z.string().describe('path of the file, from the task folder or absolute'),\r\n title: z.string().describe('what the file is, in a few words'),\r\n },\r\n async (args) => {\r\n const body = await readResultFile(tools.root, args.path, args.title)\r\n return typeof body === 'string' ? reply(body, true) : await sent(() => tools.attach(body))\r\n },\r\n ),\r\n tool(\r\n ADD_LINK_TOOL_NAME,\r\n 'Attach a link to the TAMsense task as part of the result: a page or a file the result relies on.',\r\n {\r\n title: z.string().describe('what the link is, in a few words'),\r\n url: z.string().describe('the full address starting with https://'),\r\n },\r\n async args => await sent(() => tools.save({ kind: 'link', ...args })),\r\n ),\r\n ]\r\n}\r\n\r\nexport function handOverTool(tools: HandOverTools) {\r\n return tool(\r\n HAND_OVER_TOOL_NAME,\r\n `Hand the task to another board of the project as a linked task, when part of the work belongs to that board. Boards you may hand to: ${tools.boards.map(board => board.name).join(', ')}. Without autopilot a person confirms it first.`,\r\n {\r\n board: z.string().describe('name of the board'),\r\n title: z.string().describe('title of the new task on that board, one plain sentence'),\r\n note: z.string().describe('what you found and what that board should do; never names, emails or other personal data of clients'),\r\n wait: z.boolean().describe('true when this task should wait on its step until the linked task is done'),\r\n },\r\n async args => await sent(() => tools.send(args)),\r\n )\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { TaskPrompt } from './prompt'\r\nimport type { RunnerCheckFinishBody, RunnerCheckJob, RunnerCheckNote, RunnerCheckResult, RunnerHeartbeatResponse, RunnerScheduleBody, RunnerScheduleReply, RunnerUsage } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { mkdir } from 'node:fs/promises'\r\nimport { createSdkMcpServer, query, tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { outcomeOf } from './claudeResult'\r\nimport { workplaceText } from './knowledge'\r\nimport { denyWhenAsked } from './permissions'\r\nimport { checkPath } from './protocol'\r\nimport { tamsenseFolder, taskFolder } from './workspace'\r\n\r\nexport const PROPOSE_SCHEDULE_TOOL_NAME = 'propose_schedule'\r\nexport const REPORT_CHECK_TOOL_NAME = 'report_check'\r\n\r\n// Агент шага отправляет предложение расписания на сайт; ответ сайта уходит ему результатом инструмента\r\nexport interface ScheduleTools {\r\n send: (body: RunnerScheduleBody) => Promise<RunnerScheduleReply>\r\n}\r\n\r\n// waitingFor: вопрос человеку, которого ждёт запуск; waitingForSchedule: расписание, итога которого он ждёт\r\nexport interface ScheduleWaiting {\r\n waitingFor: number | null\r\n waitingForSchedule: number | null\r\n}\r\n\r\nexport const SCHEDULE_RULE = `Some waits are long: a local stand or a database copy that takes half an hour to come up, a slow build or import, a service that answers only after a while, logs that show a problem only over hours. Do not sleep or poll in a loop for that. Propose a schedule with the ${PROPOSE_SCHEDULE_TOOL_NAME} tool: a short check repeated on a timer by a cheap model that reads the notes of earlier runs and reports one line in the task chat. Give it a clear condition that ends it, the longest interval that still helps, and when to call a person. With wait true this run ends its turn and continues here when the schedule ends. Never use a schedule for work you can finish now, for a check you can do once, or to watch something \"just in case\". Without autopilot a person starts it first; on autopilot it starts at once within the project limits.`\r\n\r\nconst ALREADY_WAITING_TEXT = 'This run already waits for something. End your turn now: the conversation continues when it comes.'\r\n\r\nfunction reply(text: string, isError = false) {\r\n return { content: [{ type: 'text' as const, text }], ...(isError ? { isError: true } : {}) }\r\n}\r\n\r\n// Предложение расписания: сайт проверяет пределы проекта, на автопилоте заводит его сам, иначе спрашивает человека\r\nexport function proposeScheduleTool(tools: ScheduleTools, waiting: ScheduleWaiting) {\r\n return tool(\r\n PROPOSE_SCHEDULE_TOOL_NAME,\r\n 'Repeat a short check on a timer while the task waits for something slow. Each run is a short cheap run that reads the notes of earlier runs and reports one line in the task chat; it ends when its condition comes, its time runs out or it made its runs.',\r\n {\r\n title: z.string().describe('a short name, at most 80 characters: \"Wait for the local stand\"'),\r\n instruction: z.string().describe('what each run does, in plain words, as a note to yourself: what to look at and what counts as new'),\r\n everyMinutes: z.number().int().nullable().describe('repeat every N minutes (60 for every hour); null when dailyAt is set'),\r\n dailyAt: z.string().nullable().describe('repeat every day at this time, HH:MM in the project time zone; null when everyMinutes is set'),\r\n days: z.array(z.number().int().min(0).max(6)).describe('with dailyAt: days of the week, 0 is Sunday; empty means every day'),\r\n until: z.string().describe('the condition each run checks, for example \"the stand answers on localhost:3100\"; when it comes, the schedule ends. Empty when it simply runs for its time'),\r\n forMinutes: z.number().int().nullable().describe('how long it runs at most, in minutes; null takes the longest the project allows'),\r\n maxRuns: z.number().int().nullable().describe('the number of runs at most, or null'),\r\n where: z.enum(['computer', 'cloud']).describe('computer: this computer, free, skipped while it sleeps; cloud: any time, for credits'),\r\n callWhen: z.string().describe('when to call a person, in plain words: \"a container keeps restarting\"'),\r\n callOnTimeout: z.boolean().describe('true: call a person when the condition did not come in time'),\r\n keepAfterStep: z.boolean().describe('true only when it must keep running after the task leaves this step'),\r\n wait: z.boolean().describe('true: this run ends its turn now and continues here when the schedule ends'),\r\n },\r\n async (args) => {\r\n if (args.wait && (waiting.waitingFor !== null || waiting.waitingForSchedule !== null)) {\r\n return reply(ALREADY_WAITING_TEXT, true)\r\n }\r\n let answer: RunnerScheduleReply\r\n try {\r\n answer = await tools.send({ ...args, title: args.title.trim(), instruction: args.instruction.trim(), until: args.until.trim(), callWhen: args.callWhen.trim(), model: null })\r\n }\r\n catch (error) {\r\n return reply(`Could not send the schedule: ${error instanceof Error ? error.message : String(error)}. Wait another way or say so in your report.`, true)\r\n }\r\n if (answer.id === null) {\r\n return reply(answer.text, true)\r\n }\r\n if (answer.waits) {\r\n waiting.waitingForSchedule = answer.id\r\n }\r\n return reply(answer.text)\r\n },\r\n )\r\n}\r\n\r\nexport function scheduleTools(api: ApiClient, path: string): ScheduleTools {\r\n return { send: body => api.request<RunnerScheduleReply>('POST', path, body) }\r\n}\r\n\r\n// Проверка идёт в папке шага, если она есть на этом компьютере, иначе в своей папке расписания\r\nexport async function checkFolder(home: string, job: RunnerCheckJob): Promise<string> {\r\n if (job.taskId !== null) {\r\n const step = taskFolder(home, { taskId: job.taskId, stage: { columnId: job.stepColumnId } })\r\n if (existsSync(step)) {\r\n return step\r\n }\r\n }\r\n const own = tamsenseFolder(home, 'schedules', `schedule-${job.scheduleId}`)\r\n await mkdir(own, { recursive: true })\r\n return own\r\n}\r\n\r\nconst CHECK_RULES = [\r\n 'You run one check of a TAMsense schedule on the user\\'s own computer: a short check repeated on a timer while a task waits for something. Nobody answers during the run.',\r\n 'You only look. Never change, fix, restart, stop or delete anything, never commit, and never send anything to people. Reading commands are fine: listing containers, requesting a local address, reading logs, git log.',\r\n 'This is the person\\'s own computer. Never touch programs you did not start, and stop the processes you started yourself by their process id before you finish.',\r\n 'Keep it short: a few commands at most. If you cannot check something, say so in the summary instead of guessing.',\r\n `Finish by calling ${REPORT_CHECK_TOOL_NAME} exactly once: summary is one short line for the task chat with the numbers that matter; changed is true only when something differs from the last note; note is the full facts only when something changed; conditionMet is true when the condition of the schedule came; callPerson is true when the schedule says to call a person for what you saw.`,\r\n 'Write in the language of the task. No em dashes and no en dashes.',\r\n]\r\n\r\nfunction noteLine(note: RunnerCheckNote): string {\r\n const head = `- ${note.at.slice(0, 16).replace('T', ' ')} (${note.outcome}): ${note.summary}`\r\n return note.note ? `${head}\\n ${note.note.replace(/\\n/g, '\\n ')}` : head\r\n}\r\n\r\n// Задание одной проверки: расписание, его условие, когда звать человека и заметки прошлых запусков, самая новая последней\r\nexport function buildCheckPrompt(job: RunnerCheckJob, folder: string): TaskPrompt {\r\n const prompt = [\r\n `Schedule \"${job.title}\", run ${job.runNumber}${job.taskId === null ? '' : `, on TAMsense task #${job.taskId}: ${job.taskTitle}`}`,\r\n `What to check on each run:\\n${job.instruction}`,\r\n job.until ? `The schedule ends when: ${job.until}` : 'The schedule runs for its time; there is no condition to wait for.',\r\n job.callWhen ? `Call a person when: ${job.callWhen}` : 'Call a person only when something is clearly wrong.',\r\n job.earlier.length > 0 ? `Notes of the earlier runs, the newest last:\\n${job.earlier.map(noteLine).join('\\n')}` : 'This is the first run: there are no earlier notes.',\r\n ].join('\\n\\n')\r\n const instructions = [\r\n CHECK_RULES.join('\\n'),\r\n workplaceText(job.computer, folder),\r\n job.safetyRules.trim() || null,\r\n ].filter(Boolean).join('\\n\\n')\r\n return { prompt, instructions }\r\n}\r\n\r\nexport interface CheckRequest {\r\n cwd: string\r\n prompt: TaskPrompt\r\n model: string | null\r\n env: Record<string, string | undefined>\r\n signal: AbortSignal\r\n}\r\n\r\nexport interface CheckOutcome {\r\n completed: boolean\r\n report: string\r\n model: string | null\r\n usage: RunnerUsage | null\r\n result: RunnerCheckResult | null\r\n}\r\n\r\nexport type RunCheck = (request: CheckRequest) => Promise<CheckOutcome>\r\n\r\nconst SERVER_NAME = 'tamsense'\r\n// проверка короткая: если Claude ходит кругами, её останавливает предел ходов\r\nconst MAX_CHECK_TURNS = 30\r\n\r\nexport function reportCheckTool(onReport: (result: RunnerCheckResult) => void) {\r\n return tool(\r\n REPORT_CHECK_TOOL_NAME,\r\n 'Hand in the result of this check. Call it exactly once, at the very end.',\r\n {\r\n changed: z.boolean().describe('true when something changed since the earlier runs'),\r\n summary: z.string().describe('one short line for the task chat: what you saw'),\r\n note: z.string().nullable().describe('when something changed: the full facts, the exact lines and what they mean; null otherwise'),\r\n conditionMet: z.boolean().describe('true when the condition of the schedule came'),\r\n callPerson: z.boolean().describe('true when a person must look now, as the schedule says'),\r\n },\r\n async (args) => {\r\n onReport({ changed: args.changed, summary: args.summary.trim(), note: args.note?.trim() || null, conditionMet: args.conditionMet, callPerson: args.callPerson })\r\n return reply('Result received. You can stop now.')\r\n },\r\n )\r\n}\r\n\r\n// Короткий запуск Claude Code для проверки: только инструмент итога, без вопросов людям, с моделью расписания\r\nexport function claudeCheckRunner(startQuery: typeof query): RunCheck {\r\n return async (request) => {\r\n request.signal.throwIfAborted()\r\n const abortController = new AbortController()\r\n request.signal.addEventListener('abort', () => abortController.abort(), { once: true })\r\n let result: RunnerCheckResult | null = null\r\n const report = reportCheckTool((found) => { result = found })\r\n const messages = startQuery({\r\n prompt: request.prompt.prompt,\r\n options: {\r\n cwd: request.cwd,\r\n systemPrompt: { type: 'preset', preset: 'claude_code', append: request.prompt.instructions },\r\n settingSources: ['user', 'project', 'local'],\r\n permissionMode: 'auto',\r\n canUseTool: denyWhenAsked,\r\n mcpServers: { [SERVER_NAME]: createSdkMcpServer({ name: SERVER_NAME, version: '1.0.0', tools: [report] }) },\r\n allowedTools: [`mcp__${SERVER_NAME}__${REPORT_CHECK_TOOL_NAME}`],\r\n maxTurns: MAX_CHECK_TURNS,\r\n abortController,\r\n env: request.env,\r\n ...(request.model === null ? {} : { model: request.model }),\r\n },\r\n })\r\n let outcome: CheckOutcome = { completed: false, report: 'Claude finished without a result.', model: null, usage: null, result: null }\r\n for await (const message of messages) {\r\n if (message.type === 'result') {\r\n const finished = outcomeOf(message, '')\r\n outcome = { completed: finished.completed, report: finished.report, model: finished.model, usage: finished.usage, result: null }\r\n }\r\n }\r\n return { ...outcome, result }\r\n }\r\n}\r\n\r\n// Сигнал проверки раз в полминуты: сайт отвечает keepGoing false, если проверку уже закрыли, и она обрывается\r\nexport function checkHeartbeat(api: ApiClient, job: RunnerCheckJob, abort: () => void): () => Promise<void> {\r\n return async () => {\r\n try {\r\n const answer = await api.request<RunnerHeartbeatResponse>('POST', checkPath(job.checkId, 'heartbeat'), undefined, { safeToRepeat: true })\r\n if (!answer.keepGoing) {\r\n abort()\r\n }\r\n }\r\n catch {}\r\n }\r\n}\r\n\r\nexport function checkFinishBody(outcome: CheckOutcome): RunnerCheckFinishBody {\r\n return { outcome: outcome.completed && outcome.result ? 'succeeded' : 'failed', report: outcome.report, model: outcome.model, usage: outcome.usage, result: outcome.result }\r\n}\r\n\r\n// Строка в окне программы: что нашла проверка\r\nexport function checkLine(body: RunnerCheckFinishBody): string {\r\n if (!body.result) {\r\n return `Check failed: ${body.report}`\r\n }\r\n if (body.result.conditionMet) {\r\n return `Check: ${body.result.summary}. The condition came, the schedule ends.`\r\n }\r\n return `Check: ${body.result.summary}`\r\n}\r\n\r\nexport const runClaudeCheck: RunCheck = claudeCheckRunner(query)\r\n","// ТЗ задачи компактным текстом для Claude: те же разделы, что видит человек на сайте.\r\n// Повторяет specPromptText сайта (apps/web/shared/utils/taskSpecView.ts): программа ставится на компьютер отдельно\r\n// и пакетов сайта не видит. Старые ТЗ (критерии и допущения строками, без новых разделов) читаются так же\r\n\r\ntype Fields = Record<string, unknown>\r\n\r\nfunction isRecord(value: unknown): value is Fields {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\nfunction text(value: unknown): string {\r\n return typeof value === 'string' ? value.trim() : ''\r\n}\r\n\r\nfunction texts(value: unknown): string[] {\r\n return Array.isArray(value) ? value.map(text).filter(Boolean) : []\r\n}\r\n\r\nfunction records(value: unknown): Fields[] {\r\n return Array.isArray(value) ? value.filter(isRecord) : []\r\n}\r\n\r\nfunction section(title: string, lines: string[]): string | null {\r\n return lines.length > 0 ? `${title}\\n${lines.join('\\n')}` : null\r\n}\r\n\r\nfunction criterionLine(value: unknown): string | null {\r\n if (typeof value === 'string') {\r\n return value.trim() ? `- ${value.trim()}` : null\r\n }\r\n if (!isRecord(value) || !text(value.text)) {\r\n return null\r\n }\r\n const kind = ['main', 'edge', 'keep'].includes(value.kind as string) ? `(${value.kind as string})` : ''\r\n const head = [text(value.id), kind].filter(Boolean).join(' ')\r\n return `- ${head ? `${head}: ` : ''}${text(value.text)}`\r\n}\r\n\r\nfunction assumptionLine(value: unknown): string | null {\r\n if (typeof value === 'string') {\r\n return value.trim() ? `- ${value.trim()}` : null\r\n }\r\n if (!isRecord(value) || !text(value.text)) {\r\n return null\r\n }\r\n return `- ${text(value.text)}${text(value.reason) ? ` (${text(value.reason)})` : ''}`\r\n}\r\n\r\nfunction changeLines(change: Fields, index: number): string[] {\r\n const action = ['create', 'modify', 'delete'].includes(change.action as string) ? ` (${change.action as string})` : ''\r\n const [produces, consumes, acceptance] = [texts(change.produces), texts(change.consumes), texts(change.acceptance)]\r\n return [\r\n `${index + 1}. ${text(change.repo) ? `${text(change.repo)} ` : ''}${text(change.file)}${action}: ${text(change.description)}`,\r\n ...(produces.length > 0 ? [` Produces: ${produces.join('; ')}`] : []),\r\n ...(consumes.length > 0 ? [` Consumes: ${consumes.join('; ')}`] : []),\r\n ...(acceptance.length > 0 ? [` Makes true: ${acceptance.join(', ')}`] : []),\r\n ]\r\n}\r\n\r\nconst CODE_USE_TEXT: Record<string, string> = { reuse: 'reuse', change: 'change', keep: 'do not touch' }\r\nconst RISK_LEVELS = ['low', 'medium', 'high']\r\n\r\nfunction present(lines: Array<string | null>): string[] {\r\n return lines.filter((line): line is string => line !== null)\r\n}\r\n\r\nfunction decisionLines(spec: Fields): string[] {\r\n return records(spec.decisions)\r\n .filter(item => text(item.question) && text(item.answer))\r\n .map(item => `- ${text(item.question)} → ${text(item.answer)} (${item.by === 'agent' ? 'agent\\'s call' : 'owner'})`)\r\n}\r\n\r\n// Бриф результата доски без кода: что должно быть в результате и критерии, по которым его судят проверки\r\nfunction briefPromptText(brief: Fields): string {\r\n return [\r\n text(brief.goal) && `Goal: ${text(brief.goal)}`,\r\n text(brief.context) && `Summary: ${text(brief.context)}`,\r\n section('The result must contain:', texts(brief.contains).map(item => `- ${item}`)),\r\n section('Acceptance criteria:', present(Array.isArray(brief.acceptance) ? brief.acceptance.map(criterionLine) : [])),\r\n section('Constraints:', texts(brief.constraints).map(item => `- ${item}`)),\r\n section('Out of scope (do not make):', texts(brief.outOfScope).map(item => `- ${item}`)),\r\n section('Sources to use:', records(brief.sources).filter(item => text(item.source)).map(item => `- ${text(item.source)}${text(item.use) ? `: ${text(item.use)}` : ''}`)),\r\n text(brief.approver) && `Approved by: ${text(brief.approver)}`,\r\n section('Decisions:', decisionLines(brief)),\r\n section('Assumptions:', present(Array.isArray(brief.assumptions) ? brief.assumptions.map(assumptionLine) : [])),\r\n section('Open questions (not answered yet):', records(brief.openQuestions).filter(item => text(item.question)).map(item => `- ${text(item.question)}`)),\r\n ].filter(Boolean).join('\\n\\n')\r\n}\r\n\r\nexport function specPromptText(spec: unknown): string | null {\r\n if (!isRecord(spec)) {\r\n return null\r\n }\r\n if (spec.format === 'brief') {\r\n return briefPromptText(spec)\r\n }\r\n const structured = Array.isArray(spec.outOfScope) || isRecord(spec.testPlan)\r\n const plan = isRecord(spec.testPlan) ? spec.testPlan : {}\r\n const risks = records(spec.risks).filter(item => text(item.risk))\r\n const riskLevel = RISK_LEVELS.includes(spec.riskLevel as string) ? spec.riskLevel as string : null\r\n return [\r\n text(spec.goal) && `Goal: ${text(spec.goal)}`,\r\n text(spec.context) && `${structured ? 'Summary' : 'Context'}: ${text(spec.context)}`,\r\n section('In scope:', texts(spec.inScope).map(item => `- ${item}`)),\r\n section('Out of scope (do not build):', texts(spec.outOfScope).map(item => `- ${item}`)),\r\n section('Decisions:', decisionLines(spec)),\r\n section('Code map:', records(spec.codeMap)\r\n .filter(item => text(item.file))\r\n .map(item => `- ${text(item.repo) ? `${text(item.repo)} ` : ''}${text(item.file)}: ${text(item.role)}${CODE_USE_TEXT[item.use as string] ? ` [${CODE_USE_TEXT[item.use as string]}]` : ''}`)),\r\n section('Changes, in order:', records(spec.changes).filter(item => text(item.file)).flatMap(changeLines)),\r\n section('Acceptance criteria:', present(Array.isArray(spec.acceptance) ? spec.acceptance.map(criterionLine) : [])),\r\n section('Test plan:', [\r\n ...records(plan.automated).filter(item => text(item.command)).map(item => `- Run in ${text(item.repo) || 'the repository'}: ${text(item.command)}${text(item.expect) ? `. Passing: ${text(item.expect)}` : ''}`),\r\n ...records(plan.newTests).filter(item => text(item.seam)).map(item => `- New test at ${text(item.seam)}${texts(item.covers).length > 0 ? `, covers ${texts(item.covers).join(', ')}` : ''}`),\r\n ...records(plan.manual).filter(item => text(item.steps)).map(item => `- By hand${text(item.page) ? ` on ${text(item.page)}` : ''}: ${text(item.steps)}`),\r\n ]),\r\n risks.length > 0\r\n ? section(`Risks${riskLevel ? ` (risk level ${riskLevel})` : ''}:`, risks.map(item => `- ${text(item.risk)}${text(item.mitigation) ? `. Mitigation: ${text(item.mitigation)}` : ''}`))\r\n : riskLevel && `Risk level: ${riskLevel}`,\r\n section('Assumptions:', present(Array.isArray(spec.assumptions) ? spec.assumptions.map(assumptionLine) : [])),\r\n section('Open questions (not answered yet):', records(spec.openQuestions).filter(item => text(item.question)).map(item => `- ${text(item.question)}`)),\r\n ].filter(Boolean).join('\\n\\n')\r\n}\r\n","import type { RunnerJob, RunnerPerson, RunnerSpecWork } from './protocol'\r\nimport type { Workspace } from './workspace'\r\nimport { personLine } from './askPerson'\r\nimport { handoffTargetLine } from './handoff'\r\nimport { knowledgeText, workplaceText } from './knowledge'\r\nimport { isLocalJob } from './localWorkspace'\r\nimport { ADD_LINK_TOOL_NAME, ATTACH_FILE_TOOL_NAME, HAND_OVER_TOOL_NAME, isFolderJob, RESULTS_FOLDER, SAVE_DOCUMENT_TOOL_NAME, SAVE_TABLE_TOOL_NAME } from './results'\r\nimport { SCHEDULE_RULE } from './schedules'\r\nimport { specPromptText } from './specText'\r\nimport { stepFolder } from './workspace'\r\n\r\nexport interface TaskPrompt {\r\n // первое сообщение: сама задача\r\n prompt: string\r\n // дописывается к стандартным инструкциям Claude Code: как работать в этом запуске\r\n instructions: string\r\n}\r\n\r\nexport const VERDICT_TOOL_NAME = 'submit_verdict'\r\nexport const SCREENSHOT_TOOL_NAME = 'attach_screenshot'\r\nexport const REPLAY_TOOL_NAME = 'attach_replay'\r\nexport const VIDEO_TOOL_NAME = 'attach_video'\r\nexport const HANDOFF_TOOL_NAME = 'hand_off'\r\nexport const SUBMIT_QUESTIONS_TOOL_NAME = 'submit_questions'\r\nexport const SUBMIT_SPEC_TOOL_NAME = 'submit_spec'\r\nexport const ASK_PERSON_TOOL_NAME = 'ask_person'\r\n\r\nconst MESSAGES_RULE = 'You are working on a TAMsense task on the user\\'s own computer. People on the task may send you messages during this run, each starting with \"Message from <name>:\". Follow them: they take priority over the original task where the two disagree.'\r\n\r\nconst UNATTENDED_RULE = `${MESSAGES_RULE} Nobody answers questions during the run, so never stop to wait for a reply: decide yourself, keep working, and explain your decisions in your report.`\r\n\r\nconst ASKING_RULE = `${MESSAGES_RULE} Decide yourself when you reasonably can, keep working, and explain your decisions in your report. When you are stuck, or the decision belongs to a person (intent, design, access, how the team runs something locally), ask one of the people on the task with the ${ASK_PERSON_TOOL_NAME} tool instead of guessing, and say what you tried. Never ask what the code or the project knowledge already answers. Nobody watches your messages, so ask only with the tool.`\r\n\r\n// Действия, о которых Claude Code спросил бы человека, решают правила проекта или люди задачи на сайте\r\nconst APPROVAL_RULE = 'Some actions need a person\\'s approval. When Claude Code would ask for one, TAMsense checks the project safety rules: a Never action is refused at once, an Allowed one goes ahead, and anything else goes to a person on the task while your tool call waits up to 15 minutes. Before such an action, write one short line: why you need it and what you will do instead if it is denied. Never get around a refused action with another command that has the same effect.'\r\n\r\nconst MACHINE_RULE = 'This is the person\\'s own computer, and their other programs may be running on it. Stop only the processes you started yourself, by their process id, and stop them before you finish. Never kill processes by name or pattern (taskkill /IM, pkill, killall, Stop-Process -Name) and never touch programs you did not start.'\r\n\r\nconst CODE_RULES = [\r\n 'Make the changes the task asks for, and keep them focused on it.',\r\n 'When there is an approved spec, follow it: make the changes in its order with the names and data shapes it gives, build nothing from its out of scope list, and add the tests and run the commands of its test plan.',\r\n 'Check your work with the project\\'s own tools: run the tests, type checks and linters that exist, and fix what you broke.',\r\n 'Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch and opens the pull request.',\r\n]\r\n\r\n// Репозитории из папок человека: ветку задачи отправляет TAMsense, если у папки есть удалённый, а сливает человек\r\nconst LOCAL_COMMIT_RULE = 'Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch when the folder has a remote, and the person merges it.'\r\n\r\nconst LOCAL_FOLDERS_RULE = 'Each repository here is a git worktree of the person\\'s own folder on this computer. Work only here: never open, change or check out branches in the person\\'s original folder.'\r\n\r\nconst REWORK_RULE = 'This is a rework. The branch already has the earlier work and maybe the reviewer\\'s own commits: treat the current branch as the source of truth, keep the reviewer\\'s changes unless a comment asks otherwise, and address every comment.'\r\n\r\nconst CODE_REPORT_RULE = 'End with a short report for the reviewer: what you changed, how you checked it, and anything left open.'\r\n\r\n// Роль только для чтения на шаге кода: работа это исследование и отчёт, ничего не коммитится и не отправляется\r\nconst READ_ONLY_CODE_RULES = [\r\n 'Your role on this step only reads. Do the work the task asks for by researching and running what helps, then report. Do not change, commit or push product code: nothing from this run is pushed. You may create throwaway files outside the repositories.',\r\n 'Check what you report with the project\\'s own tools where they exist, and say how you checked it.',\r\n]\r\n\r\nconst READ_ONLY_REPORT_RULE = 'End with a short report: what you found, how you checked it, and what is left open.'\r\n\r\nconst CHECK_RULES = [\r\n 'The work to check is on the current branch. See it with git diff against the base branch named for each repository below, and read the code around it.',\r\n 'Do not change, commit or push product code. You may run anything and create throwaway files outside the repositories.',\r\n 'Judge against the task, the spec and its acceptance criteria, checking each criterion by its id (AC-1 and so on) when the spec has ids. Be specific: every problem gets a file, a line and what to do about it.',\r\n `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`,\r\n]\r\n\r\nconst SCREENSHOT_RULE = `If you render screens (screenshots of the interface), attach the ones that show the result with the ${SCREENSHOT_TOOL_NAME} tool. People see them on the task without opening the code.`\r\n\r\nconst NAMING_RULE = 'Name everything you attach (screenshots, replays, videos) by what it shows and why it was made: a short title such as \"Design option 1\", \"Feature testing\" or \"Feature testing, attempt 2\", and a one-line purpose such as \"Comparing two layouts for the empty orders state\". Number design variants, name test runs by their goal and attempt, and never use generic names like \"screenshot1\".'\r\n\r\nconst RECORDING_RULE = `Every browser you open with Playwright from Node is recorded for the task as a replay people can watch, requests from the tested site to the TAMsense tracker are blocked, so your runs never reach analytics, and requests other than GET, HEAD and OPTIONS to addresses outside this computer are aborted. In every browser script, load the recorder from the TAMSENSE_RECORDER environment variable (require(process.env.TAMSENSE_RECORDER), or createRequire(import.meta.url)(process.env.TAMSENSE_RECORDER) in an .mjs script), open each context with await recorder.newRecordedContext(browser, { title, purpose, context: { viewport } }), call await recorder.mark(page, 'what just happened') at every meaningful step, and end with await recorder.closeRecorded(context, { notes }), where notes say in your own words what you tested in that browser and what happened: what you opened, what you clicked or typed, what you expected, and whether it passed or failed (at most 600 characters). People watching the replay read them first. Add video: true when the page has a canvas, a map, a video player or iframes from another site (a replay cannot show them), or when someone asks for a video. Recordings are attached to the task when the run ends; ${REPLAY_TOOL_NAME} and ${VIDEO_TOOL_NAME} attach one earlier with a better title.`\r\n\r\nconst BROWSER_RULE = 'A headless browser is installed for interface checks. If the project has no Playwright of its own, load playwright-core from the folder in the TAMSENSE_PLAYWRIGHT environment variable, with PLAYWRIGHT_BROWSERS_PATH set to the folder in TAMSENSE_PLAYWRIGHT_BROWSERS.'\r\n\r\n// Доска без кода: репозиториев нет, работа идёт в папке задачи, а результат кладётся на задачу инструментами TAMsense\r\nconst FOLDER_RULE = 'This board works without code: there are no repositories. The current folder belongs to this task: work in it and keep your throwaway files here.'\r\n\r\nconst RESULT_HOME_RULE = 'The result lives only on the TAMsense task: never save it in outside services such as Google Drive or a shared folder. Use connected services only to read. Sending anything to real people, such as an email or a message, is an action that needs approval like any other.'\r\n\r\nconst FOLDER_DO_RULES = [\r\n `Save what you make on the task with the TAMsense tools: ${SAVE_DOCUMENT_TOOL_NAME} for text in Markdown, ${SAVE_TABLE_TOOL_NAME} for rows of data as CSV, ${ATTACH_FILE_TOOL_NAME} for files you made in this folder, ${ADD_LINK_TOOL_NAME} for pages the result relies on. Keep one title per document and save every change as a new version of it.`,\r\n 'Use only facts from the task, the notes, the earlier results and what you read yourself; never invent numbers, names or dates.',\r\n RESULT_HOME_RULE,\r\n]\r\n\r\nconst FOLDER_REWORK_RULE = 'The task came back with notes. Read the earlier results in the results folder, fix what the notes ask and save new versions of the same documents.'\r\n\r\nconst FOLDER_REPORT_RULE = 'End with a short report for the people on the task: what you made, what you decided yourself and what is still open.'\r\n\r\nconst FOLDER_CHECK_RULES = [\r\n `The result to check is in the ${RESULTS_FOLDER} folder: the latest version of each document (.md) and table (.csv), the files and links.md. Read all of it.`,\r\n `Do not change the result. You may create throwaway files outside the ${RESULTS_FOLDER} folder.`,\r\n 'Judge it against the task, the notes of earlier steps and the instructions of this step. Be specific: every problem names the document, the place in it and what to change.',\r\n `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`,\r\n]\r\n\r\nconst DEPENDENCIES_RULE = 'The checkout may have no dependencies installed. To run the app or its tests, install the project\\'s own dependencies from its lockfile with its package manager (pnpm install --frozen-lockfile, npm ci, yarn install --immutable and similar). Never add packages, change a lockfile or download browsers.'\r\n\r\n// Свой compose-проект у каждого шага задачи: проверки одной задачи поднимают приложение одновременно и не трогают контейнеры друг друга\r\nexport function composeProjectName(job: Pick<RunnerJob, 'taskId' | 'stage'>): string {\r\n return ['tamsense', ...stepFolder(job)].join('-')\r\n}\r\n\r\n// Приложение поднимается только локально из папки задачи; не поднялось из-за компьютера: проверка ждёт человека, а не проходит и не уходит назад.\r\n// Как команда готовит локальную копию, агент спрашивает у человека, если может\r\nfunction localAppRule(job: Pick<RunnerJob, 'taskId' | 'stage'>, isCheck: boolean, canAsk: boolean): string {\r\n const project = composeProjectName(job)\r\n const cannotRun = isCheck\r\n ? `call ${VERDICT_TOOL_NAME} with passed false and couldNotRun true, write the exact reason and what a person should do in the report, and do not hand off: the task waits on this step for a person.`\r\n : 'say so in your report with the exact reason and what a person should do.'\r\n return [\r\n 'When you need the running app, start a local copy from this task folder only.',\r\n 'Find how the project runs locally yourself: the README, compose files named local or dev, .env.example and local env files, package scripts.',\r\n canAsk ? `If you still cannot tell how the team prepares it, for example how the local database gets its tables, ask with ${ASK_PERSON_TOOL_NAME} instead of guessing.` : null,\r\n 'Use only settings meant for a local run that are in this checkout. Never read or copy .env files from elsewhere on this computer, including the person\\'s own checkout, and never use staging, shared dev or production addresses.',\r\n 'Before starting, check every address in the settings you use: databases, caches, queues and APIs must point at localhost or at containers you started, and keys of outside services stay empty.',\r\n `Start containers as a separate compose project named ${project} on free host ports (an override file in a temp folder), never stop or change containers you did not start, and remove yours with their volumes at the end (docker compose -p ${project} down -v).`,\r\n `If Docker is not running, there is no local setup, or the settings point outside this computer, do not start anything: ${cannotRun}`,\r\n ].filter(Boolean).join(' ')\r\n}\r\n\r\n// browser: на компьютере стоит браузер TAMsense для проверок интерфейса; recorder: браузер агента записывается;\r\n// results: что лежит в папке results на доске без кода\r\nexport interface TaskPromptOptions {\r\n browser?: boolean\r\n recorder?: boolean\r\n results?: string[] | null\r\n}\r\n\r\nconst STAGE_GOALS = {\r\n code: 'Implement',\r\n review: 'Review the code for',\r\n test: 'Test',\r\n} as const\r\n\r\nconst READ_ONLY_GOAL = 'Work on'\r\n\r\n// Шаг кода с ролью только для чтения ведёт себя как проверка без вердикта: ничего не меняет и не отправляет\r\nexport function isReadOnlyCode(stage: Pick<RunnerJob['stage'], 'kind' | 'role'>): boolean {\r\n return stage.kind === 'code' && stage.role?.access === 'read'\r\n}\r\n\r\nconst SPEC_UNATTENDED_RULE = 'You are preparing a TAMsense task on the user\\'s own computer. Nobody answers you during this run: the owner answers your questions and reviews the spec on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with \"Message from <name>:\". Follow them.'\r\n\r\nconst READ_ONLY_RULE = 'Read the code to research the task. Do not change, commit or push anything: nothing from this run is pushed, and the code step starts from a clean branch.'\r\n\r\nconst RESEARCH_RULE = 'Start by loading the tamsense:task-interview skill with the Skill tool and follow it: it holds the question test, the corner sweep and the rules for rounds. Research before asking: read the code in the repositories here and the evidence of the linked action, if there is one. Finding facts is your job; the decisions about intent are the owner\\'s.'\r\n\r\nconst BRIEF_RULE = 'Write the brief as you go: findings (what the code does today that matters, each with owner/repo path:line), dataFacts (numbers and facts from the linked action, each with its source; empty when none), approach (2 to 4 sentences), codeMap (the files the change will touch or must reuse, each with its role), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none).'\r\n\r\nconst QUESTION_SHAPE_RULE = 'Each question has a snake_case id, one plain sentence the owner can answer without reading code, a whyItMatters line, and 2 to 4 concrete options grounded in what you saw, each saying what happens if it is chosen. Put the option you recommend first and set recommended on it (in a multiSelect question, on every option you recommend). multiSelect is true only when the options are additive. Never add an \"Other\" option: the page adds a free-text field. An unanswered question takes your recommended option, so recommend the one you would defend.'\r\n\r\nconst QUESTIONS_REPLY_RULE = 'Then reply in one or two plain sentences: what you understood from the code and why you ask these questions, or that no questions are needed. Do not repeat the questions: the page shows them.'\r\n\r\nconst SPEC_RULES = [\r\n 'Start by loading the tamsense:spec-writing skill with the Skill tool and follow it: it holds the shape of the spec, the rules for acceptance criteria, the size budget, the lint list and the builder check.',\r\n 'Start from the brief and the conversation below. Verify in the code what the spec depends on and research only what is missing; without a brief, research first as the tamsense task-interview skill describes.',\r\n 'Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked \"Recommended option applied, not answered\" is an assumption with the reason \"not answered, the recommended option applies\". If an answer contradicts the code or another answer, check the code before following it and say so in your reply.',\r\n 'Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the scope or the behaviour becomes an open question; keep them to 3 at most.',\r\n 'Before you hand the spec in, run the builder check of the skill once. It is required, also for a small task: start one fresh general-purpose subagent with the Agent tool, give it only the spec as text inside the builder check prompt of the skill, and settle each point it reports as the skill says.',\r\n `Hand in the FULL spec with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it and checks the files, and sends it back once if no fresh reader has checked it; if it returns feedback, the spec was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of lint feedback the spec is saved as it is.`,\r\n 'When the task already has a saved spec, the conversation holds the owner\\'s feedback or answers to its open questions: apply them and hand in the full updated spec. An answered open question becomes a decision; a corrected or confirmed assumption becomes a decision and leaves the assumptions.',\r\n 'Then reply with a short plain summary: the approach, the risk level and the open questions, if any. Do not paste the spec: the page shows it.',\r\n]\r\n\r\nconst SPEC_WORK_MISSING = 'TAMsense sent a spec step without its round and brief.'\r\n\r\n// Шаг Clarify на доске без кода: вопросы и бриф результата вместо ТЗ разработки; кода и навыков разработки здесь нет\r\nconst BRIEF_UNATTENDED_RULE = 'You are preparing a TAMsense task on the user\\'s own computer. Nobody answers you during this run: the owner answers your questions and reviews the brief on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with \"Message from <name>:\". Follow them.'\r\n\r\nconst BRIEF_RESEARCH_RULE = 'Research before asking: read what the task, the project knowledge, the files in this folder and the connected services give you. Finding facts is your job; the decisions about intent are the owner\\'s.'\r\n\r\nconst RESEARCH_BRIEF_RULE = 'Write the research brief as you go: findings (what the sources say that matters, each with where: the source), dataFacts (numbers and facts, each with its source; empty when none), approach (2 to 4 sentences), codeMap (always empty: this board has no code), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none).'\r\n\r\nconst RESULT_BRIEF_RULES = [\r\n 'Write the brief of the result: goal, context (the summary), contains (what the result must contain), acceptance (criteria with ids AC-1, AC-2 and so on in order, each one observable property a checker confirms by reading the result), constraints, outOfScope, sources (where the facts come from: knowledge, linked items, files of the task, connected services that are read only), approver (who approves the result), decisions, assumptions and openQuestions. Set format to \"brief\". Never write a code map, files to touch or repositories.',\r\n 'Start from the research brief and the conversation below. Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked \"Recommended option applied, not answered\" is an assumption with the reason \"not answered, the recommended option applies\".',\r\n 'Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the result becomes an open question; keep them to 3 at most. Never invent numbers, names or dates.',\r\n `Hand in the FULL brief with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it; if it returns feedback, the brief was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of feedback the brief is saved as it is.`,\r\n 'When the task already has a saved brief, the conversation holds the owner\\'s feedback or answers to its open questions: apply them and hand in the full updated brief. An answered open question becomes a decision.',\r\n 'Then reply with a short plain summary: what the result is and the open questions, if any. Do not paste the brief: the page shows it.',\r\n]\r\n\r\nconst BRIEF_STYLE_RULE = 'Write the research brief, the questions, the brief and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real names, numbers and sources; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.'\r\n\r\nconst SPEC_STYLE_RULE = 'Write the brief, the questions, the spec and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real file paths, function and table names; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.'\r\n\r\nfunction specText(spec: unknown): string | null {\r\n const text = specPromptText(spec)\r\n return text ? `Approved spec:\\n${text}` : null\r\n}\r\n\r\n// Утверждённый бриф доски без кода: по его критериям AC-1 и так далее судят проверки\r\nfunction briefText(spec: unknown): string | null {\r\n const text = specPromptText(spec)\r\n return text ? `Approved brief:\\n${text}` : null\r\n}\r\n\r\n// Контекст проекта пишет команда в настройках доски: Claude следует ему, как правилам проекта\r\nfunction contextText(context: string): string | null {\r\n return context.trim() ? `Project context from the team (follow it like the project's own rules):\\n${context.trim()}` : null\r\n}\r\n\r\n// Правила безопасности проекта от команды идут после постоянных правил: агент знает, что можно, о чём спросить и чего никогда\r\nfunction safetyText(job: RunnerJob): string | null {\r\n return job.safetyRules?.trim() || null\r\n}\r\n\r\nfunction bulletList(lines: string[]): string {\r\n return lines.map(line => `- ${line}`).join('\\n')\r\n}\r\n\r\nfunction repositoryLines(job: RunnerJob, workspace: Workspace): string[] {\r\n const sourceOf = new Map(job.repositories.map(repository => [repository.fullName, repository]))\r\n return workspace.repositories.map((repository) => {\r\n const source = sourceOf.get(repository.fullName)\r\n const pull = source?.pullRequestUrl ? `, pull request ${source.pullRequestUrl}` : ''\r\n const base = repository.local ? repository.local.baseBranch : `origin/${source?.defaultBranch ?? 'main'}`\r\n return `${repository.folder} (${repository.fullName}) at commit ${repository.baseSha.slice(0, 7)}, base branch ${base}${pull}`\r\n })\r\n}\r\n\r\nfunction codeRules(job: RunnerJob): string[] {\r\n return isLocalJob(job) ? [...CODE_RULES.slice(0, -1), LOCAL_COMMIT_RULE] : CODE_RULES\r\n}\r\n\r\nfunction stepLine(stage: RunnerJob['stage']): string {\r\n return `This step of the board is \"${stage.name}\"${stage.helper ? `, and you work as the ${stage.helper}` : ''}.`\r\n}\r\n\r\n// Сообщения людей, которые ждали этого запуска, идут вместе с задачей\r\nfunction teamMessagesText(messages: RunnerJob['messages']): string | null {\r\n return messages.length > 0 ? `Messages from the team before this run:\\n${messages.map(message => `${message.authorName}: ${message.text}`).join('\\n')}` : null\r\n}\r\n\r\n// Кого из людей задачи агент может спросить и как выбрать: исполнителя первым, вопрос по теме тому, чья роль подходит\r\nfunction peopleText(people: RunnerPerson[]): string | null {\r\n if (people.length === 0) {\r\n return null\r\n }\r\n return [\r\n `People on this task you can ask with ${ASK_PERSON_TOOL_NAME} (id: name, relation, access, then their team roles with what each owns):`,\r\n bulletList(people.map(personLine)),\r\n 'Ask the assignee first. A question on a topic goes to the person whose team role fits it, and then why names that role and what it owns, for example \"Designer, owns empty states\". Set blocking only when you cannot go on without the answer: the run then pauses and the computer is freed for other tasks. Otherwise give the assumption you continue with.',\r\n ].join('\\n')\r\n}\r\n\r\n// Заметку из ответа человека предлагает агент, который спрашивает людей; сайт прежней версии знаний не принимает\r\nexport function canProposeKnowledge(job: RunnerJob): boolean {\r\n return job.knowledge !== undefined && (job.people ?? []).length > 0 && job.stage.kind !== 'interview' && job.stage.kind !== 'spec'\r\n}\r\n\r\n// Что задача уже сделала: файлы папки results; на доске без кода вместо списка репозиториев\r\nfunction folderText(root: string, results: string[] | null | undefined): string {\r\n const lines = results ?? []\r\n return lines.length > 0\r\n ? `The task folder is ${root}. The results of the task so far, latest versions:\\n${bulletList(lines)}`\r\n : `The task folder is ${root}. The task has no result yet.`\r\n}\r\n\r\n// Другая доска проекта, на которую шаг может передать задачу; агент сам решает, нужна ли передача\r\nfunction handOverText(boards: RunnerJob['handOverBoards']): string | null {\r\n const names = (boards ?? []).map(board => board.name)\r\n if (names.length === 0) {\r\n return null\r\n }\r\n return `When part of the work belongs to another board, hand it over with the ${HAND_OVER_TOOL_NAME} tool: ${names.join(', ')}. Write a plain title and a note on what you found and what that board should do; never put names, emails or other personal data of clients in the note. With wait true this task waits on its step until the linked task is done, then it goes on and you get its result note.`\r\n}\r\n\r\n// Куда агент может передать задачу по правилам доски; на шаге проверки вердикт всё равно нужен\r\nfunction handoffText(targets: RunnerJob['allowedHandoffs'], isCheck: boolean): string | null {\r\n if (targets.length === 0) {\r\n return null\r\n }\r\n return [\r\n `When you finish, hand the task on with the ${HANDOFF_TOOL_NAME} tool. Write a clear note for the next agent and the task owner: what was done, what to check and what is still open. The steps this task can go to from here:`,\r\n bulletList(targets.map(handoffTargetLine)),\r\n `If none of them fits, just finish without handing off.${isCheck ? ` Hand in your verdict with ${VERDICT_TOOL_NAME} as well.` : ''}`,\r\n ].join('\\n')\r\n}\r\n\r\nfunction threadText(thread: RunnerSpecWork['thread']): string | null {\r\n if (thread.length === 0) {\r\n return null\r\n }\r\n const lines = thread.map(message => `${message.role === 'user' ? 'Owner' : 'You, earlier'}: ${message.text}`)\r\n return `The conversation on the task so far (answers to the questions and the owner's feedback):\\n${lines.join('\\n\\n')}`\r\n}\r\n\r\nfunction questionRules(work: RunnerSpecWork): string[] {\r\n const handIn = `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`\r\n if (work.round === 1) {\r\n return [\r\n RESEARCH_RULE,\r\n BRIEF_RULE,\r\n `Ask 0 to ${work.maxQuestions} questions, ideally 2 or 3, the most important first, and only the ones that pass the three tests of the skill. Zero questions is a good result: the spec is then written right away. Ask only questions that do not depend on another question of this round; put a dependent decision into pendingDecisions.`,\r\n QUESTION_SHAPE_RULE,\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n }\r\n const taken = work.takenIds.length > 0 ? ` These ids are taken by round 1: ${work.takenIds.join(', ')}.` : ''\r\n return [\r\n `This is the follow-up round ${work.round} of 2, and there is no round after it. Follow the tamsense:task-interview skill (load it with the Skill tool if this conversation has not loaded it yet). Read the answers in the conversation below. Do not research again what your brief covers; check only what the answers changed.`,\r\n `Ask at most ${work.maxQuestions} more questions, and only when an answer is vague (\"not sure\", \"depends\", \"maybe\"), contradicts another answer or the code, or unlocked a decision from pendingDecisions that passes the tests of the tamsense task-interview skill. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,\r\n QUESTION_SHAPE_RULE,\r\n 'Update the brief with what the answers changed and leave pendingDecisions empty.',\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n}\r\n\r\nfunction specGoal(job: RunnerJob, work: RunnerSpecWork): string {\r\n if (work.round === null) {\r\n return `Write the spec for TAMsense task #${job.taskId}: ${job.title}`\r\n }\r\n return work.round > 1 ? `Follow-up questions for TAMsense task #${job.taskId}: ${job.title}` : `Prepare TAMsense task #${job.taskId} for its spec: ${job.title}`\r\n}\r\n\r\nfunction briefQuestionRules(work: RunnerSpecWork): string[] {\r\n const handIn = `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the research brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`\r\n if (work.round === 1) {\r\n return [\r\n BRIEF_RESEARCH_RULE,\r\n RESEARCH_BRIEF_RULE,\r\n `Ask 0 to ${work.maxQuestions} questions, ideally 2 or 3, the most important first, and only the ones research cannot settle, whose answers change the result, and where a wrong default is costly. Zero questions is a good result: the brief is then written right away.`,\r\n QUESTION_SHAPE_RULE,\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n }\r\n const taken = work.takenIds.length > 0 ? ` These ids are taken by round 1: ${work.takenIds.join(', ')}.` : ''\r\n return [\r\n `This is the follow-up round ${work.round} of 2, and there is no round after it. Read the answers in the conversation below; do not research again what your research brief covers.`,\r\n `Ask at most ${work.maxQuestions} more questions, and only when an answer is vague, contradicts another answer or the facts, or unlocked a decision from pendingDecisions. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,\r\n QUESTION_SHAPE_RULE,\r\n 'Update the research brief with what the answers changed and leave pendingDecisions empty.',\r\n handIn,\r\n QUESTIONS_REPLY_RULE,\r\n ]\r\n}\r\n\r\nfunction briefGoal(job: RunnerJob, work: RunnerSpecWork): string {\r\n if (work.round === null) {\r\n return `Write the brief for TAMsense task #${job.taskId}: ${job.title}`\r\n }\r\n return work.round > 1 ? `Follow-up questions for TAMsense task #${job.taskId}: ${job.title}` : `Prepare TAMsense task #${job.taskId} for its brief: ${job.title}`\r\n}\r\n\r\n// Шаг Clarify на доске без кода: Claude изучает источники задачи и сдаёт вопросы или бриф результата инструментами TAMsense\r\nfunction buildBriefPrompt(job: RunnerJob, work: RunnerSpecWork, workspace: Workspace): TaskPrompt {\r\n const prompt = [\r\n briefGoal(job, work),\r\n job.description,\r\n work.linkedAction,\r\n work.brief ? `Research brief from the clarification phase (verify only what you rely on):\\n${JSON.stringify(work.brief)}` : null,\r\n work.round === null && job.spec ? `Current saved brief (hand in the full updated version):\\n${JSON.stringify(job.spec)}` : null,\r\n job.handoffNote && `Note from another step: ${job.handoffNote}`,\r\n threadText(work.thread),\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const instructions = [\r\n BRIEF_UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n FOLDER_RULE,\r\n RESULT_HOME_RULE,\r\n `How to work:\\n${bulletList(work.round === null ? RESULT_BRIEF_RULES : briefQuestionRules(work))}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, false),\r\n BRIEF_STYLE_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n\r\n// Шаг ТЗ: Claude изучает код и сдаёт вопросы с брифом или ТЗ инструментами TAMsense; метод лежит в навыках task-interview и spec-writing\r\nfunction buildSpecPrompt(job: RunnerJob, work: RunnerSpecWork, workspace: Workspace): TaskPrompt {\r\n const prompt = [\r\n specGoal(job, work),\r\n job.description,\r\n work.linkedAction,\r\n work.brief ? `Brief from the clarification phase (research already done, verify only what you rely on):\\n${JSON.stringify(work.brief)}` : null,\r\n work.round === null && job.spec ? `Current saved spec (hand in the full updated version):\\n${JSON.stringify(job.spec)}` : null,\r\n job.handoffNote && `Note from another step: ${job.handoffNote}`,\r\n threadText(work.thread),\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const instructions = [\r\n SPEC_UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n `The repositories for this task are checked out in the current folder, one subfolder each:\\n${bulletList(repositoryLines(job, workspace))}`,\r\n isLocalJob(job) && LOCAL_FOLDERS_RULE,\r\n READ_ONLY_RULE,\r\n `How to work:\\n${bulletList(work.round === null ? SPEC_RULES : questionRules(work))}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, false),\r\n SPEC_STYLE_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n\r\nconst FOLDER_GOALS = {\r\n code: 'Work on',\r\n review: 'Check the result of',\r\n test: 'Check the result of',\r\n} as const\r\n\r\n// Доска без кода: шаг Do кладёт результат на задачу, проверка читает его из папки results\r\nfunction buildFolderPrompt(job: RunnerJob, workspace: Workspace, options: TaskPromptOptions): TaskPrompt {\r\n const kind = job.stage.kind === 'review' || job.stage.kind === 'test' ? job.stage.kind : 'code'\r\n const isCheck = kind !== 'code'\r\n const people = job.people ?? []\r\n const canAsk = people.length > 0\r\n const prompt = [\r\n `${FOLDER_GOALS[kind]} TAMsense task #${job.taskId}: ${job.title}`,\r\n job.description,\r\n briefText(job.spec),\r\n job.handoffNote && `Note from the previous step: ${job.handoffNote}`,\r\n job.review && `Notes to address:\\n${job.review}`,\r\n job.scheduleNote,\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const rules = isCheck\r\n ? FOLDER_CHECK_RULES\r\n : [...FOLDER_DO_RULES, ...(job.review ? [FOLDER_REWORK_RULE] : []), FOLDER_REPORT_RULE]\r\n const instructions = [\r\n canAsk ? ASKING_RULE : UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n `${FOLDER_RULE} ${folderText(workspace.root, options.results)}`,\r\n `How to work:\\n${bulletList(rules)}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job)),\r\n APPROVAL_RULE,\r\n peopleText(people),\r\n isCheck ? null : handOverText(job.handOverBoards),\r\n handoffText(job.allowedHandoffs, isCheck),\r\n job.canSchedule && SCHEDULE_RULE,\r\n options.browser && BROWSER_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n\r\nexport function buildTaskPrompt(job: RunnerJob, workspace: Workspace, options: TaskPromptOptions = {}): TaskPrompt {\r\n const { kind } = job.stage\r\n if (kind === 'interview' || kind === 'spec') {\r\n if (!job.specWork) {\r\n throw new Error(SPEC_WORK_MISSING)\r\n }\r\n return isFolderJob(job) ? buildBriefPrompt(job, job.specWork, workspace) : buildSpecPrompt(job, job.specWork, workspace)\r\n }\r\n if (isFolderJob(job)) {\r\n return buildFolderPrompt(job, workspace, options)\r\n }\r\n const isCheck = kind !== 'code'\r\n const readOnly = isReadOnlyCode(job.stage)\r\n const people = job.people ?? []\r\n const canAsk = people.length > 0\r\n const prompt = [\r\n `${readOnly ? READ_ONLY_GOAL : STAGE_GOALS[kind]} TAMsense task #${job.taskId}: ${job.title}`,\r\n job.description,\r\n specText(job.spec),\r\n job.handoffNote && `Note from the previous step: ${job.handoffNote}`,\r\n job.review && `Review comments to address:\\n${job.review}`,\r\n job.scheduleNote,\r\n teamMessagesText(job.messages),\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n const evidence = [SCREENSHOT_RULE, ...(options.recorder ? [RECORDING_RULE] : []), NAMING_RULE]\r\n const localApp = localAppRule(job, isCheck, canAsk)\r\n const rules = isCheck\r\n ? [...CHECK_RULES, DEPENDENCIES_RULE, localApp, ...evidence]\r\n : readOnly\r\n ? [...READ_ONLY_CODE_RULES, DEPENDENCIES_RULE, localApp, ...evidence, READ_ONLY_REPORT_RULE]\r\n : [...codeRules(job), ...(job.review ? [REWORK_RULE] : []), DEPENDENCIES_RULE, localApp, ...evidence, CODE_REPORT_RULE]\r\n const instructions = [\r\n canAsk ? ASKING_RULE : UNATTENDED_RULE,\r\n MACHINE_RULE,\r\n workplaceText(job.computer, workspace.root),\r\n stepLine(job.stage),\r\n job.stage.instructions,\r\n contextText(job.projectContext),\r\n `The repositories for this task are checked out in the current folder, one subfolder each, ${isCheck ? 'at the latest commit of branch' : 'on branch'} ${job.branch}:\\n${bulletList(repositoryLines(job, workspace))}`,\r\n isLocalJob(job) && LOCAL_FOLDERS_RULE,\r\n `How to work:\\n${bulletList(rules)}`,\r\n safetyText(job),\r\n knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job)),\r\n APPROVAL_RULE,\r\n peopleText(people),\r\n handoffText(job.allowedHandoffs, isCheck),\r\n job.canSchedule && SCHEDULE_RULE,\r\n options.browser && BROWSER_RULE,\r\n ].filter(Boolean).join('\\n\\n')\r\n\r\n return { prompt, instructions }\r\n}\r\n","import type { RunnerQuestions, RunnerReviewBody, RunnerReviewReply, RunnerSpecResult } from './protocol'\r\nimport { tool } from '@anthropic-ai/claude-agent-sdk'\r\nimport { z } from 'zod'\r\nimport { SUBMIT_QUESTIONS_TOOL_NAME, SUBMIT_SPEC_TOOL_NAME } from './prompt'\r\n\r\n// Схемы повторяют questionsPayloadSchema и specSchema агентов облака (packages/agents) слово в слово:\r\n// программа ставится на компьютер отдельно и пакетов сайта не видит. Тест следит, чтобы они не разошлись\r\n\r\nconst MAX_FIRST_ROUND = 4\r\n\r\nexport const questionSchema = z.object({\r\n id: z.string().describe('Stable snake_case id, unique in the task, for example \"empty_state\"'),\r\n question: z.string().describe('One plain sentence the owner can answer without reading code'),\r\n whyItMatters: z.string().describe('One line: what changes in the result depending on the answer'),\r\n options: z.array(z.object({\r\n label: z.string().describe('Short, concrete answer (1-6 words), without \"(Recommended)\": use the recommended flag'),\r\n description: z.string().describe('One sentence: what happens if this option is chosen'),\r\n recommended: z.boolean().describe('true on the option you recommend (in a multiSelect question, on every option you recommend). Recommended options come first'),\r\n })).min(2).max(4).describe('2-4 mutually exclusive options grounded in the code and data. The UI always adds a free-text \"Other\" field, so never add \"Other\" yourself'),\r\n multiSelect: z.boolean().describe('true only when the options are additive and several can apply together'),\r\n})\r\n\r\nexport const briefSchema = z.object({\r\n findings: z.array(z.object({\r\n fact: z.string().describe('What the code does today that matters for this task'),\r\n where: z.string().describe('Where it is: owner/repo path:line'),\r\n })).describe('Findings from the code'),\r\n dataFacts: z.array(z.string()).describe('Numbers and facts from the events or the business data, each with its source. Empty when the task needs no data'),\r\n approach: z.string().describe('The approach you would take, in 2-4 sentences'),\r\n codeMap: z.array(z.object({\r\n repo: z.string().describe('Repository full name (owner/name)'),\r\n file: z.string().describe('Path relative to the repository root'),\r\n role: z.string().describe('Why it matters: what to change or reuse there'),\r\n })).describe('The files the change will touch or must reuse'),\r\n assumptions: z.array(z.object({\r\n text: z.string().describe('The default you chose'),\r\n reason: z.string().describe('Why it is safe or why it was chosen'),\r\n })).describe('Corners settled with a default instead of a question'),\r\n pendingDecisions: z.array(z.string()).describe('Decisions that depend on an answer to this round and cannot be asked yet. Empty when none'),\r\n})\r\n\r\nexport const questionsPayloadSchema = z.object({\r\n brief: briefSchema,\r\n questions: z.array(questionSchema).max(MAX_FIRST_ROUND).describe(`0-${MAX_FIRST_ROUND} questions that pass the question test, most important first. An empty list when research settled everything`),\r\n})\r\n\r\nexport const specSchema = z.object({\r\n goal: z.string().describe('The problem solved, for whom, and the outcome a user or the business will notice'),\r\n context: z.string().describe('Summary: the problem in 1-2 sentences, then the chosen approach in 1-2 sentences'),\r\n inScope: z.array(z.string()).describe('What this task delivers, one item each'),\r\n outOfScope: z.array(z.string()).describe('What this task does not do: nearby additions a coder might be tempted to make and approaches that are ruled out. Never empty'),\r\n decisions: z.array(z.object({\r\n question: z.string(),\r\n answer: z.string(),\r\n by: z.enum(['owner', 'agent']).describe('owner: answered by a person; agent: your own call'),\r\n })).describe('Every answered question with by: owner, and your own calls that matter with by: agent'),\r\n codeMap: z.array(z.object({\r\n repo: z.string().describe('Repository full name (owner/name)'),\r\n file: z.string().describe('Path relative to the repository root'),\r\n role: z.string().describe('What the coder needs to know about this file'),\r\n use: z.enum(['reuse', 'change', 'keep']).describe('reuse: build on it; change: it is in changes; keep: do not touch'),\r\n })).describe('The files a coder must read before starting'),\r\n changes: z.array(z.object({\r\n repo: z.string().describe('Repository full name (owner/name) where the change happens'),\r\n file: z.string().describe('Path to the file, relative to the repository root'),\r\n action: z.enum(['create', 'modify', 'delete']),\r\n description: z.string().describe('The behaviour the file has after the change and which existing functions or patterns to extend. No code snippets or diffs'),\r\n produces: z.array(z.string()).describe('Names with signatures or data shapes this change creates or changes for other changes. Empty when none'),\r\n consumes: z.array(z.string()).describe('Names with signatures or data shapes this change uses from earlier changes. Empty when none'),\r\n acceptance: z.array(z.string()).describe('Ids of the criteria this change makes true, like AC-1'),\r\n })).min(1).describe('File changes in dependency order: each uses only what earlier changes or the code already provide'),\r\n acceptance: z.array(z.object({\r\n id: z.string().describe('AC-1, AC-2 and so on, in order'),\r\n kind: z.enum(['main', 'edge', 'keep']).describe('main: the main path; edge: an error, empty or limit case; keep: existing behaviour that must keep working'),\r\n text: z.string().describe('\"Given <state>, when <action>, then <observable result>\", saying where to look. No file paths or function names'),\r\n })).min(1).describe('Acceptance criteria: main path first, then edge cases, then what must keep working'),\r\n testPlan: z.object({\r\n automated: z.array(z.object({\r\n repo: z.string(),\r\n command: z.string().describe('The exact command to run'),\r\n expect: z.string().describe('What passing looks like'),\r\n })).describe('At least one command'),\r\n newTests: z.array(z.object({\r\n seam: z.string().describe('Where the test goes: the highest seam that covers the behaviour (an endpoint, a page, a public function)'),\r\n covers: z.array(z.string()).describe('Ids of the criteria it covers'),\r\n })),\r\n manual: z.array(z.object({\r\n page: z.string().describe('The page or screen'),\r\n steps: z.string().describe('What a person does and sees'),\r\n })),\r\n }),\r\n risks: z.array(z.object({\r\n risk: z.string(),\r\n mitigation: z.string(),\r\n })).describe('What could go wrong: migrations, existing data, access, performance, compatibility'),\r\n riskLevel: z.enum(['low', 'medium', 'high']).describe('How closely a person should review the result'),\r\n assumptions: z.array(z.object({\r\n text: z.string().describe('The default you chose'),\r\n reason: z.string().describe('Why, so the owner can confirm or correct it quickly'),\r\n })).describe('Defaults chosen where the task was silent. Empty when nothing was assumed'),\r\n openQuestions: z.array(questionSchema).describe('Questions only the owner can settle; the spec cannot be approved until each is answered or dismissed. Empty when none'),\r\n split: z.array(z.string()).describe('When the task holds more than one shippable goal or more than about 8 files: the separate tasks you propose, one line each. Empty otherwise'),\r\n})\r\n\r\n// Бриф результата доски без кода: что сделать и как проверить, без карты кода, файлов и репозиториев\r\nexport const resultBriefSchema = z.object({\r\n format: z.literal('brief').describe('Always \"brief\"'),\r\n goal: z.string().describe('What the work is for, for whom, and the outcome people will notice'),\r\n context: z.string().describe('Summary: the situation in 1-2 sentences, then how the work will be done in 1-2 sentences'),\r\n contains: z.array(z.string()).min(1).describe('What the result must contain, one item each: the parts, facts and attachments a reader expects'),\r\n acceptance: z.array(z.object({\r\n id: z.string().describe('AC-1, AC-2 and so on, in order'),\r\n text: z.string().describe('One observable property of the result that a checker can confirm by reading it'),\r\n })).min(1).describe('Acceptance criteria the checks judge the result by, the most important first'),\r\n constraints: z.array(z.string()).describe('Rules the result must keep: length, tone, language, deadlines, what must not be promised. Empty when none'),\r\n outOfScope: z.array(z.string()).describe('What this work does not do: nearby things someone might be tempted to add. Empty when nothing is tempting'),\r\n sources: z.array(z.object({\r\n source: z.string().describe('Where the facts come from: a project knowledge note, a linked item, a file of the task, a connected service read only'),\r\n use: z.string().describe('What to take from it'),\r\n })).describe('Sources the work must use. Empty when the task itself is enough'),\r\n approver: z.string().describe('Who approves the result, as the task or the board names them'),\r\n decisions: z.array(z.object({\r\n question: z.string(),\r\n answer: z.string(),\r\n by: z.enum(['owner', 'agent']).describe('owner: answered by a person; agent: your own call'),\r\n })).describe('Every answered question with by: owner, and your own calls that matter with by: agent'),\r\n assumptions: z.array(z.object({\r\n text: z.string().describe('The default you chose'),\r\n reason: z.string().describe('Why, so the owner can confirm or correct it quickly'),\r\n })).describe('Defaults chosen where the task was silent. Empty when nothing was assumed'),\r\n openQuestions: z.array(questionSchema).describe('Questions only the owner can settle; the brief cannot be approved until each is answered or dismissed. Empty when none'),\r\n})\r\n\r\n// Проверка на сайте теми же правилами, что в облаке; ответ уходит Claude как результат инструмента\r\nexport type ReviewRequest = (body: RunnerReviewBody) => Promise<RunnerReviewReply>\r\n\r\nfunction reply(text: string, isError = false) {\r\n return { content: [{ type: 'text' as const, text }], ...(isError ? { isError: true } : {}) }\r\n}\r\n\r\nfunction unreachable(error: unknown, toolName: string) {\r\n const message = error instanceof Error ? error.message : String(error)\r\n return reply(`Could not reach TAMsense to check this: ${message}. Call ${toolName} again in a moment.`, true)\r\n}\r\n\r\n// Принятые вопросы заменяют прежние; отклонённые не сохраняются, Claude получает замечания и присылает заново\r\nexport function questionsTool(review: ReviewRequest, onSubmit: (questions: RunnerQuestions) => void) {\r\n return tool(\r\n SUBMIT_QUESTIONS_TOOL_NAME,\r\n 'Hand in the brief and the clarifying questions of this round. Call it once, after the research. Pass an empty questions list when research settled everything: the spec is then written next. If it returns problems, the questions were not saved: fix them and call it again.',\r\n questionsPayloadSchema.shape,\r\n async (args) => {\r\n let verdict: RunnerReviewReply\r\n try {\r\n verdict = await review({ questions: args })\r\n }\r\n catch (error) {\r\n return unreachable(error, SUBMIT_QUESTIONS_TOOL_NAME)\r\n }\r\n if (!verdict.accepted) {\r\n return reply(verdict.feedback, true)\r\n }\r\n onSubmit(args)\r\n return reply(args.questions.length > 0\r\n ? 'Questions saved. The owner answers them on the task page after this run. Finish now with a reply of one or two sentences.'\r\n : 'Saved with no questions: the spec is written next. Finish now with a reply of one or two sentences.')\r\n },\r\n )\r\n}\r\n\r\nconst BUILDER_CHECK_FIRST = 'The spec passed the lint but was not saved yet: the builder check of the tamsense:spec-writing skill has not run in this session. Start one fresh general-purpose subagent with the Agent tool, give it only the spec as text inside the builder check prompt of the skill, settle each point it reports as the skill says, then call submit_spec again with the full spec.'\r\n\r\n// Сколько раз ТЗ уже возвращали, считает сам инструмент: сайт после двух доработок принимает ТЗ как есть.\r\n// Как в облаке, ТЗ без проверки свежим читателем один раз возвращается: builderCheckRan говорит, звал ли Claude помощника\r\nexport function specTool(review: ReviewRequest, onSubmit: (result: RunnerSpecResult) => void, builderCheckRan: () => boolean) {\r\n let repairs = 0\r\n let reminded = false\r\n return tool(\r\n SUBMIT_SPEC_TOOL_NAME,\r\n 'Hand in the implementation spec. It replaces the previous version entirely, so always send the FULL spec. TAMsense lints it and checks the files first; if it returns feedback, the spec was not saved: fix it and call submit_spec again.',\r\n specSchema.shape,\r\n async (args) => {\r\n let verdict: RunnerReviewReply\r\n try {\r\n verdict = await review({ spec: args, repairs })\r\n }\r\n catch (error) {\r\n return unreachable(error, SUBMIT_SPEC_TOOL_NAME)\r\n }\r\n if (!verdict.accepted) {\r\n repairs += 1\r\n return reply(verdict.feedback, true)\r\n }\r\n if (!builderCheckRan() && !reminded) {\r\n reminded = true\r\n return reply(BUILDER_CHECK_FIRST, true)\r\n }\r\n onSubmit({ spec: args, problems: verdict.problems })\r\n return reply(verdict.problems.length > 0\r\n ? `Spec saved with these problems left: ${verdict.problems.join('; ')}. Mention them in your reply.`\r\n : 'Spec saved.')\r\n },\r\n )\r\n}\r\n\r\n// На доске без кода тем же инструментом сдаётся бриф результата: его проверяет линт сайта, свежего читателя у брифа нет\r\nexport function briefTool(review: ReviewRequest, onSubmit: (result: RunnerSpecResult) => void) {\r\n let repairs = 0\r\n return tool(\r\n SUBMIT_SPEC_TOOL_NAME,\r\n 'Hand in the brief of the result. It replaces the previous version entirely, so always send the FULL brief. TAMsense lints it first; if it returns feedback, the brief was not saved: fix it and call submit_spec again.',\r\n resultBriefSchema.shape,\r\n async (args) => {\r\n let verdict: RunnerReviewReply\r\n try {\r\n verdict = await review({ spec: args, repairs })\r\n }\r\n catch (error) {\r\n return unreachable(error, SUBMIT_SPEC_TOOL_NAME)\r\n }\r\n if (!verdict.accepted) {\r\n repairs += 1\r\n return reply(verdict.feedback, true)\r\n }\r\n onSubmit({ spec: args, problems: verdict.problems })\r\n return reply(verdict.problems.length > 0\r\n ? `Brief saved with these problems left: ${verdict.problems.join('; ')}. Mention them in your reply.`\r\n : 'Brief saved.')\r\n },\r\n )\r\n}\r\n","import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'\r\n\r\nexport const USAGE_LIMIT_REPORT = 'Claude hit its usage limit'\r\n\r\nconst DEFAULT_PAUSE_MS = 30 * 60_000\r\nconst DAY_MS = 24 * 60 * 60_000\r\n// время сброса в секундах Unix, если число меньше этого; иначе в миллисекундах\r\nconst SECONDS_BELOW = 1e11\r\n\r\n// Тексты Claude Code про исчерпанный лимит (USAGE_LIMIT_ERROR_PREFIXES в SDK) и старый вид «usage limit reached|<время>»\r\nconst LIMIT_TEXT = /You['’]ve (?:hit|reached) your|You['’]re out of (?:usage credits|extra usage)|usage limit/i\r\nconst EPOCH_RESET = /\\|(\\d{10,13})\\b/\r\n// «resets 3pm», «resets 5:30am (Europe/Berlin)», «resets Oct 25, 3pm», «resets at 15:00»\r\nconst CLOCK_RESET = /resets?\\s+(?:at\\s+)?(?:([a-z]{3})[a-z]*\\.?\\s+(\\d{1,2}),?\\s+(?:at\\s+)?)?(\\d{1,2})(?::(\\d{2}))?\\s*([ap]m)?/i\r\nconst MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']\r\n\r\nfunction fromEpoch(value: number): Date {\r\n return new Date(value < SECONDS_BELOW ? value * 1000 : value)\r\n}\r\n\r\nfunction hourOf(hour: number, meridiem: string | undefined): number | null {\r\n if (!meridiem) {\r\n return hour < 24 ? hour : null\r\n }\r\n if (hour < 1 || hour > 12) {\r\n return null\r\n }\r\n return (hour % 12) + (meridiem.toLowerCase() === 'pm' ? 12 : 0)\r\n}\r\n\r\n// Время сброса из текста Claude Code, в часовом поясе этого компьютера; null, если его нет или оно уже прошло\r\nexport function resetTimeOf(text: string, now: Date): Date | null {\r\n const epoch = EPOCH_RESET.exec(text)\r\n if (epoch) {\r\n const reset = fromEpoch(Number(epoch[1]))\r\n return reset > now ? reset : null\r\n }\r\n const clock = CLOCK_RESET.exec(text)\r\n if (!clock) {\r\n return null\r\n }\r\n const [, monthName, day, hourText, minuteText, meridiem] = clock\r\n const hour = hourOf(Number(hourText), meridiem)\r\n const minute = Number(minuteText ?? 0)\r\n const month = monthName ? MONTHS.indexOf(monthName.toLowerCase()) : now.getMonth()\r\n if (hour === null || minute > 59 || month < 0) {\r\n return null\r\n }\r\n const reset = new Date(now.getFullYear(), month, day ? Number(day) : now.getDate(), hour, minute)\r\n if (reset > now) {\r\n return reset\r\n }\r\n if (!monthName) {\r\n return new Date(reset.getTime() + DAY_MS)\r\n }\r\n // «Jan 2» в конце декабря: сброс в следующем году\r\n const nextYear = new Date(now.getFullYear() + 1, month, Number(day), hour, minute)\r\n return nextYear.getTime() - now.getTime() < 31 * DAY_MS ? nextYear : null\r\n}\r\n\r\nfunction assistantText(message: Extract<SDKMessage, { type: 'assistant' }>): string {\r\n return message.message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\\n')\r\n}\r\n\r\nexport interface UsageLimitWatch {\r\n observe: (message: SDKMessage) => void\r\n // когда можно продолжить (ISO), если запуск не закончен из-за лимита; null, если лимит ни при чём\r\n pausedUntil: (completed: boolean, now?: Date) => string | null\r\n}\r\n\r\n// Следит за сообщениями Claude Code: отказ по лимиту подписки, ошибка rate_limit или текст про исчерпанный лимит.\r\n// Удачный итог хода стирает прошлые признаки: значит, Claude продолжил работу\r\nexport function usageLimitWatch(): UsageLimitWatch {\r\n let limitedByText = false\r\n let limitedByEvent = false\r\n let eventReset: Date | null = null\r\n let texts: string[] = []\r\n\r\n function hitText(text: string): void {\r\n limitedByText = true\r\n texts.push(text)\r\n }\r\n\r\n return {\r\n observe(message) {\r\n if (message.type === 'rate_limit_event') {\r\n const info = message.rate_limit_info\r\n limitedByEvent = info.status === 'rejected' && !info.isUsingOverage\r\n eventReset = limitedByEvent && info.resetsAt ? fromEpoch(info.resetsAt) : null\r\n }\r\n else if (message.type === 'assistant' && message.error) {\r\n const text = assistantText(message)\r\n if (message.error === 'rate_limit' || LIMIT_TEXT.test(text)) {\r\n hitText(text)\r\n }\r\n }\r\n else if (message.type === 'result') {\r\n if (message.subtype === 'success' && !message.is_error) {\r\n limitedByText = false\r\n texts = []\r\n return\r\n }\r\n const text = message.subtype === 'success' ? message.result : message.errors.join('\\n')\r\n if (LIMIT_TEXT.test(text)) {\r\n hitText(text)\r\n }\r\n }\r\n },\r\n pausedUntil(completed, now = new Date()) {\r\n if (completed || !(limitedByText || limitedByEvent)) {\r\n return null\r\n }\r\n const fromText = texts.map(text => resetTimeOf(text, now)).find(reset => reset !== null)\r\n const reset = eventReset && eventReset > now ? eventReset : fromText ?? new Date(now.getTime() + DEFAULT_PAUSE_MS)\r\n return reset.toISOString()\r\n },\r\n }\r\n}\r\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\r\nimport { homedir } from 'node:os'\r\nimport { dirname, join } from 'node:path'\r\n\r\nexport interface RunnerConfig {\r\n server: string\r\n token: string\r\n deviceId: number\r\n deviceName: string\r\n}\r\n\r\nconst OWNER_ONLY_FILE = 0o600\r\nconst OWNER_ONLY_FOLDER = 0o700\r\n\r\nexport function configPath(home: string = homedir()): string {\r\n return join(home, '.tamsense', 'runner.json')\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\nfunction parseConfig(value: unknown): RunnerConfig | null {\r\n if (!isRecord(value)) {\r\n return null\r\n }\r\n const { server, token, deviceId, deviceName } = value\r\n const isValid = typeof server === 'string' && typeof token === 'string' && typeof deviceId === 'number' && typeof deviceName === 'string'\r\n return isValid ? { server, token, deviceId, deviceName } : null\r\n}\r\n\r\nfunction isMissingFile(error: unknown): boolean {\r\n return error instanceof Error && 'code' in error && error.code === 'ENOENT'\r\n}\r\n\r\n// Испорченный файл считается отсутствующим: программа предложит подключиться заново\r\nexport async function readConfig(path: string = configPath()): Promise<RunnerConfig | null> {\r\n try {\r\n return parseConfig(JSON.parse(await readFile(path, 'utf8')))\r\n }\r\n catch (error) {\r\n if (isMissingFile(error) || error instanceof SyntaxError) {\r\n return null\r\n }\r\n throw error\r\n }\r\n}\r\n\r\n// Ключ компьютера читает только его владелец\r\nexport async function writeConfig(config: RunnerConfig, path: string = configPath()): Promise<void> {\r\n await mkdir(dirname(path), { recursive: true, mode: OWNER_ONLY_FOLDER })\r\n await writeFile(path, `${JSON.stringify(config, null, 4)}\\n`, { mode: OWNER_ONLY_FILE })\r\n}\r\n\r\nexport async function removeConfig(path: string = configPath()): Promise<void> {\r\n await rm(path, { force: true })\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { ConnectedDevice, PairingStartResponse, PairingTokenResponse } from './protocol'\r\nimport { LOGIN_COMMAND } from './commands'\r\nimport { PAIRING_TOKEN_PATH, PAIRINGS_PATH } from './protocol'\r\n\r\nexport interface LoginRequest {\r\n name: string\r\n platform: string\r\n}\r\n\r\nexport interface LoginTools {\r\n api: ApiClient\r\n openBrowser: (url: string) => void\r\n print: (line: string) => void\r\n sleep: (ms: number) => Promise<void>\r\n now: () => number\r\n}\r\n\r\nexport interface LoginResult {\r\n token: string\r\n device: ConnectedDevice\r\n}\r\n\r\nconst MS_PER_SECOND = 1000\r\nconst EXPIRED_TEXT = `The code expired before it was confirmed. Run ${LOGIN_COMMAND} again.`\r\nconst DENIED_TEXT = 'The connection was cancelled in the browser. This computer was not connected.'\r\n\r\n// Как вход в gh: программа показывает код и ссылку, человек подтверждает их на сайте, программа ждёт ключ\r\nexport async function login(tools: LoginTools, request: LoginRequest): Promise<LoginResult> {\r\n const pairing = await tools.api.request<PairingStartResponse>('POST', PAIRINGS_PATH, request)\r\n const pageUrl = new URL(pairing.verificationPath, tools.api.server).toString()\r\n const expiresAt = Date.parse(pairing.expiresAt)\r\n\r\n tools.print(`Your code: ${pairing.userCode}`)\r\n tools.print(`Confirm it in the browser: ${pageUrl}`)\r\n tools.print('Waiting for confirmation…')\r\n tools.openBrowser(pageUrl)\r\n\r\n while (tools.now() < expiresAt) {\r\n await tools.sleep(pairing.intervalSeconds * MS_PER_SECOND)\r\n const reply = await tools.api.request<PairingTokenResponse>('POST', PAIRING_TOKEN_PATH, { deviceCode: pairing.deviceCode })\r\n if (reply.status === 'connected') {\r\n return { token: reply.token, device: reply.device }\r\n }\r\n if (reply.status === 'denied') {\r\n throw new Error(DENIED_TEXT)\r\n }\r\n if (reply.status === 'expired') {\r\n break\r\n }\r\n }\r\n throw new Error(EXPIRED_TEXT)\r\n}\r\n","import type { RunnerJob } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { join } from 'node:path'\r\nimport { fileURLToPath } from 'node:url'\r\nimport { DEFAULT_PURPOSE_ENV, DEFAULT_TITLE_ENV, MAX_PURPOSE_LENGTH, MAX_TITLE_LENGTH, RECORDINGS_DIR_ENV, SERVER_ENV } from './recorder'\r\n\r\nexport const RECORDER_ENV = 'TAMSENSE_RECORDER'\r\n\r\n// Модуль записи собирается рядом с программой; в исходниках его нет, и тогда браузер агента не записывается\r\nexport function bundledRecorder(): string | null {\r\n const path = fileURLToPath(new URL('./recorder.cjs', import.meta.url))\r\n return existsSync(path) ? path : null\r\n}\r\n\r\n// Своя папка у каждого запуска: что не ушло на сайт, удаляется вместе с ней\r\nexport function recordingsFolder(home: string, runId: number): string {\r\n return join(home, '.tamsense', 'recordings', `run-${runId}`)\r\n}\r\n\r\nexport interface RecorderEnvInput {\r\n recorder: string\r\n folder: string\r\n server: string\r\n job: Pick<RunnerJob, 'taskId' | 'stage'>\r\n nodeOptions: string | undefined\r\n}\r\n\r\n// Внутри NODE_OPTIONS обратная косая черта в кавычках считается экранированием: путь идёт с прямыми\r\nexport function nodeOptionsWithRecorder(recorder: string, nodeOptions: string | undefined): string {\r\n return [nodeOptions?.trim(), `--require \"${recorder.replaceAll('\\\\', '/')}\"`].filter(Boolean).join(' ')\r\n}\r\n\r\n// Каждый процесс Node агента подгружает запись, и любой браузер Playwright записывается сам, с названием шага по умолчанию\r\nexport function recorderEnv(input: RecorderEnvInput): Record<string, string> {\r\n const { job } = input\r\n return {\r\n [RECORDER_ENV]: input.recorder,\r\n [RECORDINGS_DIR_ENV]: input.folder,\r\n [SERVER_ENV]: input.server,\r\n [DEFAULT_TITLE_ENV]: `${job.stage.name} browser session`.slice(0, MAX_TITLE_LENGTH),\r\n [DEFAULT_PURPOSE_ENV]: `Recorded automatically in the ${job.stage.name} step of task #${job.taskId}`.slice(0, MAX_PURPOSE_LENGTH),\r\n NODE_OPTIONS: nodeOptionsWithRecorder(input.recorder, input.nodeOptions),\r\n }\r\n}\r\n","import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\r\nimport Module, { createRequire } from 'node:module'\r\nimport { basename, dirname, join } from 'node:path'\r\nimport process from 'node:process'\r\n\r\n// Запись браузера агента: rrweb пишет каждую страницу в свой файл NDJSON в папке запуска, по желанию ещё и видео.\r\n// Модуль самодостаточный: программа подключает его ко всем процессам Node агента через NODE_OPTIONS --require,\r\n// и тогда любой браузер Playwright записывается сам; скрипты агента импортируют его же, чтобы дать записи название и отметки\r\n\r\nexport const RECORDINGS_DIR_ENV = 'TAMSENSE_RECORDINGS_DIR'\r\nexport const SERVER_ENV = 'TAMSENSE_SERVER'\r\nexport const DEFAULT_TITLE_ENV = 'TAMSENSE_RECORDING_TITLE'\r\nexport const DEFAULT_PURPOSE_ENV = 'TAMSENSE_RECORDING_PURPOSE'\r\nexport const MARK_TAG = 'tamsense.mark'\r\nexport const REPLAY_SUFFIX = '.rrweb.ndjson'\r\nexport const META_SUFFIX = '.json'\r\nexport const MAX_TITLE_LENGTH = 60\r\nexport const MAX_PURPOSE_LENGTH = 160\r\nexport const MAX_NOTES_LENGTH = 600\r\n// трекер TAMsense по умолчанию шлёт сюда; адрес сервера из настроек программы добавляется к нему\r\nexport const DEFAULT_TRACKER_ORIGINS = ['https://my.tamsense.com']\r\n\r\n// эти методы только читают; остальные к адресам вне компьютера отменяются\r\nexport const READ_METHODS = ['GET', 'HEAD', 'OPTIONS']\r\n\r\nconst BINDING = '__tamsenseRecord'\r\nconst STATE_KEY = Symbol.for('tamsense.recorder')\r\nconst FLUSH_MS = 100\r\nconst MAX_SESSION_BYTES = 300 * 1024 * 1024\r\nconst GAP_CHECK_MS = 1500\r\nconst MAX_LABEL_LENGTH = 120\r\nconst RRWEB_INCREMENTAL = 3\r\nconst RRWEB_META = 4\r\nconst RRWEB_SOURCE_MOUSE_INTERACTION = 2\r\nconst RRWEB_SOURCE_INPUT = 5\r\n\r\nexport interface RecordingCaption {\r\n title: string\r\n purpose: string\r\n}\r\n\r\nexport interface RecordedContextOptions extends RecordingCaption {\r\n // видео в придачу к записи: для canvas, карт, видео на странице, чужих iframe или когда его попросили\r\n video?: boolean\r\n // обычные настройки browser.newContext: viewport, storageState и другие\r\n context?: Record<string, unknown>\r\n}\r\n\r\nexport interface RecordingNotes {\r\n notes?: string\r\n}\r\n\r\nexport interface SessionMark {\r\n ts: number\r\n label: string\r\n}\r\n\r\n// Что знает программа о записи одной страницы; лежит рядом с файлом записи\r\nexport interface SessionMeta {\r\n version: 1\r\n id: string\r\n title: string\r\n purpose: string\r\n startedAt: number\r\n endedAt: number | null\r\n url: string\r\n marks: SessionMark[]\r\n video: string | null\r\n gaps: string[]\r\n blockedTrackerRequests: number\r\n blockedExternalWrites: number\r\n truncated: boolean\r\n // записано само, без названия от агента\r\n automatic: boolean\r\n // что агент проверял и чем кончилось, его словами; пусто, пока он не закрыл запись с заметкой\r\n notes: string\r\n}\r\n\r\nexport interface RecordingSummary {\r\n title: string\r\n purpose: string\r\n notes: string\r\n replay: string\r\n video: string | null\r\n marks: number\r\n gaps: string[]\r\n blockedTrackerRequests: number\r\n blockedExternalWrites: number\r\n}\r\n\r\ninterface PlaywrightVideo {\r\n path: () => Promise<string>\r\n}\r\n\r\ninterface PlaywrightPage {\r\n url: () => string\r\n isClosed: () => boolean\r\n on: (event: 'close', listener: () => void) => unknown\r\n evaluate: (fn: string | ((arg: never) => unknown), arg?: unknown) => Promise<unknown>\r\n video: () => PlaywrightVideo | null\r\n}\r\n\r\ninterface PlaywrightRoute {\r\n fulfill: (response: { status: number, contentType: string, body: string }) => Promise<void>\r\n abort: (errorCode?: string) => Promise<void>\r\n continue: () => Promise<void>\r\n request: () => { method: () => string, url: () => string, frame: () => { page: () => PlaywrightPage } }\r\n}\r\n\r\nexport interface PlaywrightContext {\r\n pages: () => PlaywrightPage[]\r\n on: (event: 'page' | 'close', listener: (page: PlaywrightPage) => void) => unknown\r\n exposeBinding: (name: string, callback: (source: { page: PlaywrightPage }, payload: string) => unknown) => Promise<void>\r\n addInitScript: (script: { content: string }) => Promise<void>\r\n route: (url: (url: URL) => boolean, handler: (route: PlaywrightRoute) => Promise<void>) => Promise<void>\r\n close: (options?: unknown) => Promise<void>\r\n}\r\n\r\nexport interface PlaywrightBrowser {\r\n newContext: (options?: Record<string, unknown>) => Promise<PlaywrightContext>\r\n}\r\n\r\ninterface Instrumentation {\r\n addListener: (listener: { runAfterCreateBrowserContext: (context: PlaywrightContext) => Promise<void> }) => void\r\n}\r\n\r\ninterface ContextRecording {\r\n caption: RecordingCaption\r\n automatic: boolean\r\n sessions: Session[]\r\n}\r\n\r\ninterface RecorderState {\r\n contexts: WeakMap<PlaywrightContext, ContextRecording>\r\n pages: WeakMap<PlaywrightPage, Session>\r\n instrumented: WeakSet<object>\r\n script: string | null\r\n hooked: boolean\r\n}\r\n\r\nfunction state(): RecorderState {\r\n const holder = globalThis as { [STATE_KEY]?: RecorderState }\r\n holder[STATE_KEY] ??= { contexts: new WeakMap(), pages: new WeakMap(), instrumented: new WeakSet(), script: null, hooked: false }\r\n return holder[STATE_KEY]\r\n}\r\n\r\nfunction recordingsDir(): string {\r\n const dir = process.env[RECORDINGS_DIR_ENV]\r\n if (!dir) {\r\n throw new Error(`${RECORDINGS_DIR_ENV} is not set: recordings work only inside a TAMsense run`)\r\n }\r\n return dir\r\n}\r\n\r\nfunction warn(text: string): void {\r\n process.stderr.write(`[tamsense recorder] ${text}\\n`)\r\n}\r\n\r\n// Правило блокировки трекера TAMsense: запрос на адрес, который кончается на /api/events, с ключом проекта в ?key=\r\n// или к серверу TAMsense. Так записи агента не попадают в аналитику и недельный разбор\r\nexport function isTrackerIngest(url: URL, tamsenseOrigins: readonly string[]): boolean {\r\n if (!/\\/api\\/events\\/?$/.test(url.pathname)) {\r\n return false\r\n }\r\n return url.searchParams.has('key') || tamsenseOrigins.includes(url.origin)\r\n}\r\n\r\n// Своим считается только сам компьютер: localhost, *.localhost, адреса loopback и 0.0.0.0. Имена контейнеров браузер\r\n// на компьютере не видит, поэтому имя без точки тоже чужое\r\nexport function isLocalHost(hostname: string): boolean {\r\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '').replace(/\\.$/, '')\r\n return host === 'localhost' || host.endsWith('.localhost') || host === '::1' || host === '0.0.0.0' || /^127(?:\\.\\d{1,3}){3}$/.test(host)\r\n}\r\n\r\n// Запрос, который может что-то изменить у чужого сервиса: метод не только читает, а адрес вне этого компьютера\r\nexport function isExternalWrite(method: string, url: URL): boolean {\r\n return !READ_METHODS.includes(method.toUpperCase()) && !isLocalHost(url.hostname)\r\n}\r\n\r\nexport function trackerOrigins(env: Record<string, string | undefined> = process.env): string[] {\r\n const origins = new Set(DEFAULT_TRACKER_ORIGINS)\r\n try {\r\n if (env[SERVER_ENV]) {\r\n origins.add(new URL(env[SERVER_ENV]).origin)\r\n }\r\n }\r\n catch {}\r\n return [...origins]\r\n}\r\n\r\nexport function checkCaption(caption: Partial<RecordingCaption> | undefined): RecordingCaption {\r\n const title = typeof caption?.title === 'string' ? caption.title.trim() : ''\r\n const purpose = typeof caption?.purpose === 'string' ? caption.purpose.trim() : ''\r\n if (!title || !purpose) {\r\n throw new Error('Give it a title (what it shows, for example \"Design option 1\" or \"Feature testing, attempt 2\") and a purpose (one line on why it was made).')\r\n }\r\n if (title.length > MAX_TITLE_LENGTH || purpose.length > MAX_PURPOSE_LENGTH) {\r\n throw new Error(`Keep the title within ${MAX_TITLE_LENGTH} characters and the purpose within ${MAX_PURPOSE_LENGTH}.`)\r\n }\r\n return { title, purpose }\r\n}\r\n\r\n// Заметка к записи от инструментов attach_replay и attach_video: длиннее предела не принимается, агент её сократит\r\nexport function checkNotes(notes: unknown): string {\r\n const text = typeof notes === 'string' ? notes.trim() : ''\r\n if (text.length > MAX_NOTES_LENGTH) {\r\n throw new Error(`Keep the notes within ${MAX_NOTES_LENGTH} characters: what you opened, clicked and expected, and whether it passed.`)\r\n }\r\n return text\r\n}\r\n\r\nexport function clip(text: string, max: number): string {\r\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\r\n}\r\n\r\n// Название записи, которую никто не назвал: из настроек запуска и имени скрипта, который открыл браузер\r\nexport function automaticCaption(env: Record<string, string | undefined> = process.env, script: string | undefined = process.argv[1]): RecordingCaption {\r\n const from = script ? `, opened by ${basename(script)}` : ''\r\n return {\r\n title: clip(env[DEFAULT_TITLE_ENV]?.trim() || 'Browser session', MAX_TITLE_LENGTH),\r\n purpose: clip(`${env[DEFAULT_PURPOSE_ENV]?.trim() || 'Recorded automatically while the agent used a browser'}${from}`, MAX_PURPOSE_LENGTH),\r\n }\r\n}\r\n\r\n// Сборка rrweb без модулей: UMD кладёт её в локальный module.exports, страница своего rrweb не видит и не теряет\r\nfunction rrwebSource(): string {\r\n const entry = createRequire(import.meta.url).resolve('rrweb')\r\n return readFileSync(join(dirname(entry), 'rrweb.umd.min.cjs'), 'utf8')\r\n}\r\n\r\n// Скрипт для каждой страницы: пишет только верхний документ, iframe того же сайта rrweb снимает сам.\r\n// События уходят в программу пачками через exposeBinding, пароли скрыты, как в rrweb по умолчанию.\r\n// Клики и ввод уходят сразу: клик по ссылке уводит страницу раньше, чем сработает таймер, и Playwright выбрасывает\r\n// пачку, пришедшую от уже закрытой страницы. Клик без координат (element.click() из скрипта) ставится в центр элемента\r\nexport function pageScript(rrweb: string): string {\r\n return `(() => {\r\nif (window.top !== window || window.__tamsenseRecording) return;\r\nwindow.__tamsenseRecording = true;\r\nconst rrweb = (() => { const module = { exports: {} }; const exports = module.exports; ${rrweb}\r\n; return module.exports; })();\r\nlet buffer = [];\r\nlet timer = null;\r\nconst send = (extra) => {\r\n clearTimeout(timer);\r\n timer = null;\r\n if (!buffer.length && !extra) return Promise.resolve();\r\n const events = buffer;\r\n buffer = [];\r\n return window.${BINDING}(JSON.stringify({ events, ...extra })).catch(() => {});\r\n};\r\nconst urgent = (event) => event.type === ${RRWEB_INCREMENTAL} && (event.data.source === ${RRWEB_SOURCE_MOUSE_INTERACTION} || event.data.source === ${RRWEB_SOURCE_INPUT});\r\nconst placeAtTarget = (interaction) => {\r\n if (interaction.x !== 0 || interaction.y !== 0) return;\r\n const node = rrweb.record.mirror.getNode(interaction.id);\r\n if (!node || typeof node.getBoundingClientRect !== 'function') return;\r\n const box = node.getBoundingClientRect();\r\n interaction.x = Math.round(box.left + box.width / 2);\r\n interaction.y = Math.round(box.top + box.height / 2);\r\n};\r\nrrweb.record({\r\n emit(event) {\r\n buffer.push(event);\r\n if (urgent(event)) send();\r\n else if (!timer) timer = setTimeout(send, ${FLUSH_MS});\r\n },\r\n hooks: { mouseInteraction: placeAtTarget },\r\n maskInputOptions: { password: true },\r\n});\r\nconst crossOrigin = (frame) => { try { return new URL(frame.src, location.href).origin !== location.origin; } catch { return false; } };\r\nconst checkGaps = () => {\r\n const gaps = [];\r\n if (document.querySelector('canvas')) gaps.push('canvas');\r\n if (document.querySelector('video')) gaps.push('video');\r\n if ([...document.querySelectorAll('iframe')].some(crossOrigin)) gaps.push('cross-origin iframe');\r\n if (gaps.length) send({ gaps });\r\n};\r\nwindow.__tamsenseFlush = () => send();\r\nwindow.__tamsenseMark = (label) => { rrweb.record.addCustomEvent('${MARK_TAG}', { label }); return send(); };\r\naddEventListener('beforeunload', () => { send(); });\r\naddEventListener('pagehide', () => { send(); });\r\naddEventListener('load', () => setTimeout(checkGaps, ${GAP_CHECK_MS}));\r\n})();`\r\n}\r\n\r\nfunction script(): string {\r\n const current = state()\r\n current.script ??= pageScript(rrwebSource())\r\n return current.script\r\n}\r\n\r\nclass Session {\r\n readonly file: string\r\n readonly metaFile: string\r\n readonly meta: SessionMeta\r\n private bytes = 0\r\n\r\n constructor(dir: string, recording: ContextRecording, url: string) {\r\n const id = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`\r\n this.file = join(dir, `${id}${REPLAY_SUFFIX}`)\r\n this.metaFile = join(dir, `${id}${META_SUFFIX}`)\r\n this.meta = { version: 1, id, ...recording.caption, startedAt: Date.now(), endedAt: null, url, marks: [], video: null, gaps: [], blockedTrackerRequests: 0, blockedExternalWrites: 0, truncated: false, automatic: recording.automatic, notes: '' }\r\n writeFileSync(this.file, '')\r\n this.save()\r\n }\r\n\r\n save(): void {\r\n writeFileSync(this.metaFile, `${JSON.stringify(this.meta, null, 2)}\\n`)\r\n }\r\n\r\n receive(payload: string): void {\r\n let batch: { events?: unknown[], gaps?: string[] }\r\n try {\r\n batch = JSON.parse(payload) as typeof batch\r\n }\r\n catch {\r\n return\r\n }\r\n if (batch.gaps?.length) {\r\n this.meta.gaps = [...new Set([...this.meta.gaps, ...batch.gaps])]\r\n this.save()\r\n }\r\n const page = (batch.events as Array<{ type?: unknown, data?: { href?: unknown } }> | undefined)?.find(event => event.type === RRWEB_META && typeof event.data?.href === 'string' && event.data.href !== 'about:blank')\r\n if (page && this.meta.url === 'about:blank') {\r\n this.meta.url = String(page.data!.href)\r\n this.save()\r\n }\r\n const lines = (batch.events ?? []).map(event => JSON.stringify(event)).join('\\n')\r\n if (!lines || this.meta.truncated) {\r\n return\r\n }\r\n if (this.bytes + lines.length > MAX_SESSION_BYTES) {\r\n this.meta.truncated = true\r\n this.save()\r\n return\r\n }\r\n this.bytes += lines.length + 1\r\n appendFileSync(this.file, `${lines}\\n`)\r\n }\r\n\r\n end(): void {\r\n if (this.meta.endedAt === null) {\r\n this.meta.endedAt = Date.now()\r\n this.save()\r\n }\r\n }\r\n\r\n summary(): RecordingSummary {\r\n const { title, purpose, notes, video, marks, gaps, blockedTrackerRequests, blockedExternalWrites } = this.meta\r\n return { title, purpose, notes, replay: this.file, video, marks: marks.length, gaps, blockedTrackerRequests, blockedExternalWrites }\r\n }\r\n}\r\n\r\nfunction countBlocked(route: PlaywrightRoute, counter: 'blockedTrackerRequests' | 'blockedExternalWrites'): void {\r\n try {\r\n const session = state().pages.get(route.request().frame().page())\r\n if (session) {\r\n session.meta[counter] += 1\r\n session.save()\r\n }\r\n }\r\n catch {}\r\n}\r\n\r\nasync function startSession(recording: ContextRecording, page: PlaywrightPage, injectNow: boolean): Promise<void> {\r\n const current = state()\r\n if (current.pages.has(page)) {\r\n return\r\n }\r\n const session = new Session(recordingsDir(), recording, page.url())\r\n current.pages.set(page, session)\r\n recording.sessions.push(session)\r\n page.on('close', () => session.end())\r\n const video = page.video()\r\n if (video) {\r\n session.meta.video = await video.path().catch(() => null)\r\n session.save()\r\n }\r\n // страница уже была открыта до записи: скрипт для будущих страниц её не коснётся\r\n if (injectNow) {\r\n await page.evaluate(script()).catch(() => {})\r\n }\r\n}\r\n\r\nasync function flush(context: PlaywrightContext): Promise<void> {\r\n await Promise.all(context.pages().filter(page => !page.isClosed()).map(page =>\r\n page.evaluate(() => (window as unknown as { __tamsenseFlush?: () => Promise<void> }).__tamsenseFlush?.(), undefined).catch(() => {}),\r\n ))\r\n}\r\n\r\n// Записывает контекст Playwright; второй вызов для того же контекста только меняет название и назначение записи.\r\n// automatic: контекст записан сам, название придумала программа\r\nexport async function recordContext(context: PlaywrightContext, caption: RecordingCaption, automatic = false): Promise<void> {\r\n const checked = checkCaption(caption)\r\n const current = state()\r\n const known = current.contexts.get(context)\r\n if (known) {\r\n Object.assign(known, { caption: checked, automatic })\r\n for (const session of known.sessions) {\r\n Object.assign(session.meta, checked, { automatic })\r\n session.save()\r\n }\r\n return\r\n }\r\n mkdirSync(recordingsDir(), { recursive: true })\r\n const recording: ContextRecording = { caption: checked, automatic, sessions: [] }\r\n current.contexts.set(context, recording)\r\n const origins = trackerOrigins()\r\n // маршруты Playwright идут от последнего к первому: трекер отвечает раньше, запись наружу отменяется после\r\n await context.route(url => !isLocalHost(url.hostname), async (route) => {\r\n const request = route.request()\r\n if (!isExternalWrite(request.method(), new URL(request.url()))) {\r\n // страница могла закрыться, пока запрос шёл\r\n await route.continue().catch(() => {})\r\n return\r\n }\r\n countBlocked(route, 'blockedExternalWrites')\r\n await route.abort('blockedbyclient').catch(() => {})\r\n })\r\n await context.route(url => isTrackerIngest(url, origins), async (route) => {\r\n countBlocked(route, 'blockedTrackerRequests')\r\n await route.fulfill({ status: 200, contentType: 'application/json', body: '{\"ok\":true}' })\r\n })\r\n await context.exposeBinding(BINDING, ({ page }, payload) => current.pages.get(page)?.receive(payload))\r\n await context.addInitScript({ content: script() })\r\n context.on('page', page => void startSession(recording, page, false))\r\n context.on('close', () => recording.sessions.forEach(session => session.end()))\r\n for (const page of context.pages()) {\r\n await startSession(recording, page, true)\r\n }\r\n const close = context.close.bind(context)\r\n context.close = async (options?: unknown) => {\r\n await flush(context)\r\n return close(options)\r\n }\r\n}\r\n\r\n// Новый записанный контекст: название и назначение обязательны, video: true добавляет видео Playwright\r\nexport async function newRecordedContext(browser: PlaywrightBrowser, options: RecordedContextOptions): Promise<PlaywrightContext> {\r\n const caption = checkCaption(options)\r\n const extra = options.video ? { recordVideo: { dir: join(recordingsDir(), 'video') } } : {}\r\n const context = await browser.newContext({ ...options.context, ...extra })\r\n await recordContext(context, caption)\r\n return context\r\n}\r\n\r\n// Отметка шага: видна на шкале плеера и в списке отметок, клик по ней перематывает запись к этому месту\r\nexport async function mark(page: PlaywrightPage, label: string): Promise<void> {\r\n const text = clip(String(label ?? '').trim(), MAX_LABEL_LENGTH)\r\n const session = state().pages.get(page)\r\n if (!text || !session) {\r\n warn(text ? 'mark() was called for a page that is not recorded' : 'mark() needs a label')\r\n return\r\n }\r\n session.meta.marks.push({ ts: Date.now(), label: text })\r\n session.save()\r\n await page.evaluate(value => (window as unknown as { __tamsenseMark?: (label: string) => Promise<void> }).__tamsenseMark?.(value), text).catch(() => {})\r\n}\r\n\r\n// Закрывает записанный контекст и говорит, что записано; подсказывает видео, если записи не хватит.\r\n// notes: что агент проверял в этой записи и чем кончилось, его словами; длинная заметка обрезается, а не теряется\r\nexport async function closeRecorded(context: PlaywrightContext, options: RecordingNotes = {}): Promise<RecordingSummary[]> {\r\n const sessions = state().contexts.get(context)?.sessions ?? []\r\n const notes = clip(String(options.notes ?? '').trim(), MAX_NOTES_LENGTH)\r\n if (notes) {\r\n for (const session of sessions) {\r\n session.meta.notes = notes\r\n session.save()\r\n }\r\n }\r\n await context.close()\r\n const summaries = sessions.map(session => session.summary())\r\n for (const summary of summaries) {\r\n if (summary.gaps.length > 0 && !summary.video) {\r\n warn(`\"${summary.title}\" has ${summary.gaps.join(', ')}: the replay may not show it. Record it again with video: true.`)\r\n }\r\n }\r\n return summaries\r\n}\r\n\r\nfunction instrument(exports: unknown): void {\r\n const chromium = (exports as { chromium?: { launch?: unknown, _instrumentation?: Instrumentation } } | null)?.chromium\r\n const instrumentation = (exports as { _instrumentation?: Instrumentation } | null)?._instrumentation ?? chromium?._instrumentation\r\n const current = state()\r\n if (typeof chromium?.launch !== 'function' || typeof instrumentation?.addListener !== 'function' || current.instrumented.has(instrumentation)) {\r\n return\r\n }\r\n current.instrumented.add(instrumentation)\r\n instrumentation.addListener({\r\n runAfterCreateBrowserContext: async (context) => {\r\n try {\r\n if (!current.contexts.has(context)) {\r\n await recordContext(context, automaticCaption(), true)\r\n }\r\n }\r\n catch (error) {\r\n warn(`could not record this browser: ${error instanceof Error ? error.message : String(error)}`)\r\n }\r\n },\r\n })\r\n}\r\n\r\n// Сам цепляется к Playwright, как только его загрузят: агент не может забыть включить запись\r\nexport function installAutoRecording(): void {\r\n const current = state()\r\n if (current.hooked) {\r\n return\r\n }\r\n current.hooked = true\r\n const loader = Module as unknown as { _load: (request: string, parent: unknown, isMain: boolean) => unknown }\r\n const load = loader._load\r\n loader._load = function (this: unknown, ...args: [string, unknown, boolean]) {\r\n const exports = load.apply(this, args)\r\n try {\r\n instrument(exports)\r\n }\r\n catch {}\r\n return exports\r\n }\r\n}\r\n\r\nif (process.env[RECORDINGS_DIR_ENV]) {\r\n installAutoRecording()\r\n}\r\n","import type { RunnerIdentityResponse } from './protocol'\r\n\r\nconst TEAM_KEY_MISSING_TEXT = 'This is a team computer: Claude must run on the Anthropic API key of your company, not on a personal Claude subscription. Set ANTHROPIC_API_KEY on this computer and start again.'\r\n\r\n// Через общий компьютер работает вся команда, а подписку Claude правила Anthropic разрешают только её владельцу\r\nexport function teamKeyProblem(isTeam: boolean, env: Record<string, string | undefined>): string | null {\r\n return isTeam && !env.ANTHROPIC_API_KEY?.trim() ? TEAM_KEY_MISSING_TEXT : null\r\n}\r\n\r\nexport function describeDevice(identity: RunnerIdentityResponse): string {\r\n return identity.device.team\r\n ? `a team computer of ${identity.projectName}`\r\n : `the personal computer of ${identity.email ?? 'its owner'}`\r\n}\r\n","import { spawn } from 'node:child_process'\r\nimport { hostname } from 'node:os'\r\nimport { delimiter } from 'node:path'\r\nimport { setTimeout as wait } from 'node:timers/promises'\r\n\r\nconst PLATFORM_LABELS: Partial<Record<NodeJS.Platform, string>> = {\r\n win32: 'Windows',\r\n darwin: 'macOS',\r\n linux: 'Linux',\r\n}\r\n\r\nconst MAX_NAME_LENGTH = 80\r\n\r\nexport function platformLabel(platform: NodeJS.Platform = process.platform): string {\r\n return PLATFORM_LABELS[platform] ?? platform\r\n}\r\n\r\nexport function defaultDeviceName(): string {\r\n return hostname().slice(0, MAX_NAME_LENGTH) || 'My computer'\r\n}\r\n\r\n// На Windows ссылку открывает rundll32: через cmd знак & в адресе разрезал бы команду\r\nfunction browserCommand(url: string, platform: NodeJS.Platform): [string, string[]] {\r\n if (platform === 'win32') {\r\n return ['rundll32', ['url.dll,FileProtocolHandler', url]]\r\n }\r\n return [platform === 'darwin' ? 'open' : 'xdg-open', [url]]\r\n}\r\n\r\n// Браузер может не открыться (сервер без экрана): ссылка уже напечатана, человек откроет её сам\r\nexport function openBrowser(url: string, platform: NodeJS.Platform = process.platform): void {\r\n const [command, args] = browserCommand(url, platform)\r\n const child = spawn(command, args, { detached: true, stdio: 'ignore' })\r\n child.on('error', () => {})\r\n child.unref()\r\n}\r\n\r\n// Claude и проекты зовут node и npm: если программу запустили так, что её папки Node нет в PATH, папка добавляется в конец\r\nexport function withNodeOnPath(env: NodeJS.ProcessEnv, nodeDir: string, separator: string = delimiter): NodeJS.ProcessEnv {\r\n const key = Object.keys(env).find(name => name.toUpperCase() === 'PATH') ?? 'PATH'\r\n const entries = (env[key] ?? '').split(separator).filter(Boolean)\r\n const normalize = (dir: string) => dir.replace(/[\\\\/]+$/, '').toLowerCase()\r\n if (entries.some(entry => normalize(entry) === normalize(nodeDir))) {\r\n return env\r\n }\r\n return { ...env, [key]: [...entries, nodeDir].join(separator) }\r\n}\r\n\r\n// Остановка будит ожидание сразу: Ctrl+C не ждёт конца паузы между опросами\r\nexport async function sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n await wait(ms, undefined, { signal }).catch(() => {})\r\n}\r\n","import { spawn } from 'node:child_process'\r\nimport { readFileSync } from 'node:fs'\r\nimport process from 'node:process'\r\n\r\nexport const PACKAGE_NAME = '@tamsensedev/runner'\r\nexport const RELAUNCHED_ENV = 'TAMSENSE_RELAUNCHED'\r\nexport const NO_UPDATE_ENV = 'TAMSENSE_NO_UPDATE'\r\n\r\nconst REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME.replace('/', '%2f')}/latest`\r\nconst REGISTRY_TIMEOUT_MS = 3000\r\nconst RELEASE_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)$/\r\n\r\nexport interface UpdateCheck {\r\n current: string\r\n latest: string\r\n}\r\n\r\ntype FetchLatest = (url: string, signal: AbortSignal) => Promise<unknown>\r\n\r\nfunction releaseParts(version: string): number[] | null {\r\n const match = RELEASE_PATTERN.exec(version.trim())\r\n return match ? match.slice(1).map(Number) : null\r\n}\r\n\r\n// Сравниваются только выпуски вида 1.2.3: предварительные версии и сборки из исходников не обновляются\r\nexport function isNewer(latest: string, current: string): boolean {\r\n const next = releaseParts(latest)\r\n const now = releaseParts(current)\r\n if (!next || !now) {\r\n return false\r\n }\r\n const index = next.findIndex((part, position) => part !== now[position])\r\n return index !== -1 && next[index]! > now[index]!\r\n}\r\n\r\nexport function currentVersion(): string | null {\r\n try {\r\n const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { name?: unknown, version?: unknown }\r\n return manifest.name === PACKAGE_NAME && typeof manifest.version === 'string' ? manifest.version : null\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\nasync function fetchLatest(url: string, signal: AbortSignal): Promise<unknown> {\r\n const response = await fetch(url, { signal, headers: { accept: 'application/json' } })\r\n return response.ok ? await response.json() : null\r\n}\r\n\r\n// Последняя версия на npm; без сети или при ответе с ошибкой обновления нет, программа работает как есть\r\nexport async function latestVersion(fetchJson: FetchLatest = fetchLatest, timeoutMs = REGISTRY_TIMEOUT_MS): Promise<string | null> {\r\n try {\r\n const body = await fetchJson(REGISTRY_URL, AbortSignal.timeout(timeoutMs))\r\n const version = body && typeof body === 'object' ? (body as { version?: unknown }).version : null\r\n return typeof version === 'string' ? version : null\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\n// После перезапуска и при TAMSENSE_NO_UPDATE обновление не проверяется: так перезапуск не зацикливается\r\nexport async function updateToRun(env: NodeJS.ProcessEnv, current: string | null, latest: () => Promise<string | null>): Promise<UpdateCheck | null> {\r\n if (!current || env[RELAUNCHED_ENV] || env[NO_UPDATE_ENV]) {\r\n return null\r\n }\r\n const found = await latest()\r\n return found && isNewer(found, current) ? { current, latest: found } : null\r\n}\r\n\r\nexport function relaunchArgs(argv: string[]): string[] {\r\n return ['--yes', `${PACKAGE_NAME}@latest`, ...argv]\r\n}\r\n\r\n// Новая версия запускается в этом же окне; Ctrl+C достаётся ей, а эта ждёт и выходит с её кодом\r\nexport function relaunch(argv: string[], env: NodeJS.ProcessEnv): Promise<number> {\r\n const isWindows = process.platform === 'win32'\r\n return new Promise((resolve, reject) => {\r\n const child = spawn(isWindows ? 'npx.cmd' : 'npx', relaunchArgs(argv), { stdio: 'inherit', shell: isWindows, env: { ...env, [RELAUNCHED_ENV]: '1' } })\r\n const ignoreInterrupt = () => {}\r\n process.on('SIGINT', ignoreInterrupt)\r\n child.on('error', (error) => {\r\n process.off('SIGINT', ignoreInterrupt)\r\n reject(error)\r\n })\r\n child.on('close', (code) => {\r\n process.off('SIGINT', ignoreInterrupt)\r\n resolve(code ?? 1)\r\n })\r\n })\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { InstalledBrowser } from './browser'\r\nimport type { AskingTools, RunAgent, SpecWorkTools } from './claude'\r\nimport type { FeedDraft } from './feed'\r\nimport type { FeedQueue } from './feedQueue'\r\nimport type { FolderTools } from './folders'\r\nimport type { InputChannel } from './input'\r\nimport type { KnowledgeTools } from './knowledge'\r\nimport type { PermissionTools } from './permissions'\r\nimport type { RunnerAskReply, RunnerAttachmentResponse, RunnerCapabilities, RunnerCheckFinishBody, RunnerCheckJob, RunnerFinishBody, RunnerGitTokenResponse, RunnerHelperStop, RunnerInbox, RunnerInboxBody, RunnerInboxMessage, RunnerJob, RunnerKnowledgeEntry, RunnerKnowledgeReply, RunnerNextBody, RunnerNextResponse, RunnerPermissionReply, RunnerReviewReply, RunnerSession } from './protocol'\r\nimport type { RunCheck } from './schedules'\r\nimport type { AttachFields } from './screenshot'\r\nimport type { Workspace, WorkspaceTools } from './workspace'\r\nimport { rm } from 'node:fs/promises'\r\nimport { dirname } from 'node:path'\r\nimport process from 'node:process'\r\nimport { alreadyReported, ApiError } from './api'\r\nimport { browserEnv } from './browser'\r\nimport { capabilitiesLine } from './capabilities'\r\nimport { backoffMs } from './connection'\r\nimport { consoleLine } from './feed'\r\nimport { feedQueue } from './feedQueue'\r\nimport { checkFolders, reportFoundFolders } from './folders'\r\nimport { helperStoppedNote, helperStopRequest } from './helpers'\r\nimport { inputChannel, switchLine } from './input'\r\nimport { knowledgeIds } from './knowledge'\r\nimport { allowedSkills, prepareLibrary, roleLibraryFolder } from './library'\r\nimport { isLocalJob, localBranchLine, prepareLocalWorkspace, publishLocalWorkspace } from './localWorkspace'\r\nimport { keyedLock } from './lock'\r\nimport { buildTaskPrompt, canProposeKnowledge, isReadOnlyCode } from './prompt'\r\nimport { CAPABILITIES_PATH, checkPath, knowledgePath, MAX_ATTACHMENTS_PER_RUN, NEXT_RUN_PATH, permissionPath, runPath } from './protocol'\r\nimport { recorderEnv, recordingsFolder } from './recorderEnv'\r\nimport { attachRecording, uploadPendingRecordings } from './recordings'\r\nimport { handOverTools, isFolderJob, prepareFolder, resultTools } from './results'\r\nimport { buildCheckPrompt, checkFinishBody, checkFolder, checkHeartbeat, checkLine, scheduleTools } from './schedules'\r\nimport { readScreenshot } from './screenshot'\r\nimport { withNodeOnPath } from './system'\r\nimport { USAGE_LIMIT_REPORT } from './usageLimit'\r\nimport { commitIdentityEnv, prepareWorkspace, publishWorkspace, stepFolder, taskFolder } from './workspace'\r\n\r\nexport interface WorkerTools extends WorkspaceTools {\r\n api: ApiClient\r\n runAgent: RunAgent\r\n print: (line: string) => void\r\n sleep: (ms: number, signal?: AbortSignal) => Promise<void>\r\n // браузер для проверок интерфейса, если человек согласился его поставить\r\n browser?: InstalledBrowser | null\r\n // что умеет Claude на компьютере, без траты токенов; без него сайт этого не узнаёт\r\n capabilities?: (stop: AbortSignal) => Promise<RunnerCapabilities>\r\n // модуль записи браузера агента (recorder.cjs рядом с программой); без него браузер не записывается\r\n recorder?: string | null\r\n // проверка папки, которую человек добавил на доску; без неё программа папки не проверяет\r\n inspectFolder?: FolderTools['inspect']\r\n // папка, откуда запустили программу: в ней и на уровень ниже она ищет код; без неё не ищет\r\n startFolder?: string\r\n // проверки расписаний и версия программы, которую она называет сайту; без них программа проверок не берёт\r\n runCheck?: RunCheck\r\n version?: string\r\n}\r\n\r\nconst POLL_INTERVAL_MS = 5_000\r\n// пауза после сбоя растёт с каждой неудачей до минуты\r\nconst MAX_RETRY_WAIT_MS = 60_000\r\n// опрос со всеми повторами укладывается в это время; дольше значит, что он завис, и сторож спрашивает снова\r\nconst POLL_DEADLINE_MS = 3 * 60_000\r\n// заодно с сообщениями людей сайт узнаёт, что компьютер ещё работает над задачей\r\nconst INBOX_INTERVAL_MS = 2_000\r\nconst MAX_MESSAGE_LINE = 200\r\nconst CAPABILITIES_INTERVAL_MS = 30 * 60_000\r\n// журнал уходит на сайт почти сразу: человек видит работу Claude вживую\r\nconst FEED_INTERVAL_MS = 1_500\r\nconst FATAL_STATUSES = new Set([401, 403])\r\n// проверки расписаний короткие и идут рядом с заданиями шагов, но не больше двух сразу\r\nconst MAX_CHECKS = 2\r\nconst CHECK_HEARTBEAT_MS = 30_000\r\n// проверка, которая идёт дольше, обрывается: расписание пропустит её и проверит снова на следующем тике\r\nconst CHECK_DEADLINE_MS = 10 * 60_000\r\n\r\nconst STOPPED_HERE = 'stopped_here'\r\nconst CLOSED_ON_SITE = 'closed_on_site'\r\n\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error)\r\n}\r\n\r\n// Отказ в доступе не лечится повтором: компьютер отключили или у человека забрали задачи\r\nfunction isFatal(error: unknown): boolean {\r\n return error instanceof ApiError && FATAL_STATUSES.has(error.status)\r\n}\r\n\r\nasync function gitToken(api: ApiClient, job: RunnerJob): Promise<string> {\r\n return (await api.request<RunnerGitTokenResponse>('POST', runPath(job.runId, 'git-token'))).token\r\n}\r\n\r\n// Ответ уходит Claude: приложено, или почему нет, чтобы он поправил путь или формат\r\nasync function attachScreenshot(api: ApiClient, job: RunnerJob, screenshot: AttachFields, attached: { count: number }): Promise<string> {\r\n if (attached.count >= MAX_ATTACHMENTS_PER_RUN) {\r\n return `This run already attached ${MAX_ATTACHMENTS_PER_RUN} screenshots. Mention the rest in your report.`\r\n }\r\n const body = await readScreenshot(screenshot)\r\n if (typeof body === 'string') {\r\n return body\r\n }\r\n try {\r\n await api.request<RunnerAttachmentResponse>('POST', runPath(job.runId, 'attachments'), body)\r\n }\r\n catch (error) {\r\n return `Could not attach the screenshot: ${messageOf(error)}`\r\n }\r\n attached.count += 1\r\n return 'Attached to the task.'\r\n}\r\n\r\n// Сайт показывает, какая модель и чей вход взялись за задачу; сбой не мешает работе\r\nfunction sessionReporter(api: ApiClient, job: RunnerJob, print: (line: string) => void): { report: (session: RunnerSession) => void, sent: () => Promise<void> } {\r\n let sending: Promise<void> | null = null\r\n return {\r\n report: (session) => {\r\n sending ??= api.request('POST', runPath(job.runId, 'session'), session, { safeToRepeat: true })\r\n .then(() => {}, error => print(`Could not tell TAMsense which Claude took the task: ${messageOf(error)}`))\r\n },\r\n sent: async () => {\r\n await sending\r\n },\r\n }\r\n}\r\n\r\n// Запись журнала уходит на сайт, а действия Claude ещё и печатаются здесь\r\nfunction journal(feed: FeedQueue, print: (line: string) => void): (draft: FeedDraft) => void {\r\n return (draft) => {\r\n feed.push(draft)\r\n const line = consoleLine(draft)\r\n if (line) {\r\n print(` ${line}`)\r\n }\r\n }\r\n}\r\n\r\nfunction messageLine(message: Pick<RunnerInboxMessage, 'authorName' | 'text'>): string {\r\n const line = `Message from ${message.authorName}: ${message.text}`.replace(/\\s+/g, ' ').trim()\r\n return line.length > MAX_MESSAGE_LINE ? `${line.slice(0, MAX_MESSAGE_LINE - 1)}…` : line\r\n}\r\n\r\n// Помощника останавливает Claude Code, и Claude узнаёт, кто его остановил; если Claude Code о нём не сообщил, Claude просят остановить его самого\r\nasync function stopHelper(input: InputChannel, stop: RunnerHelperStop, print: (line: string) => void): Promise<void> {\r\n try {\r\n const result = await input.stopHelper(stop.ref)\r\n if (result === 'stopped') {\r\n input.note(helperStoppedNote(stop), 'queue')\r\n print(` ${stop.byName} stopped the helper ${stop.name}`)\r\n }\r\n else if (result === 'unknown' && input.note(helperStopRequest(stop), 'steer')) {\r\n print(` ${stop.byName} asked Claude to stop the helper ${stop.name}`)\r\n }\r\n }\r\n catch (error) {\r\n print(`Could not stop the helper ${stop.name}: ${messageOf(error)}`)\r\n }\r\n}\r\n\r\n// input: что уходит Claude; после его хода входящие проверяются сразу, не дожидаясь очередной проверки\r\ninterface Inbox {\r\n input: InputChannel\r\n poll: () => Promise<void>\r\n settled: () => Promise<void>\r\n}\r\n\r\n// Входящие запуска: жив ли он на сайте, новые сообщения людей и смена модели; в ответ сайт узнаёт, что уже передано Claude.\r\n// Сбой не останавливает работу: следующая проверка повторит, а строка печатается одна на всю полосу сбоев\r\nfunction inbox(job: RunnerJob, tools: WorkerTools, record: (draft: FeedDraft) => void, closedOnSite: () => void): Inbox {\r\n const input = inputChannel({ onIdle: () => void poll() })\r\n // сайт присылает остановки за весь запуск: каждая выполняется один раз\r\n const stopsSeen = new Set<string>()\r\n let running: Promise<void> | null = null\r\n let again = false\r\n let failing = false\r\n\r\n async function check(): Promise<void> {\r\n const ack = input.takeDelivered()\r\n const body: RunnerInboxBody = { ack }\r\n let reply: RunnerInbox\r\n try {\r\n reply = await tools.api.request<RunnerInbox>('POST', runPath(job.runId, 'inbox'), body, { safeToRepeat: true })\r\n }\r\n catch (error) {\r\n input.restoreDelivered(ack)\r\n if (!failing && !alreadyReported(error)) {\r\n tools.print(`Could not check for new messages on TAMsense, trying again: ${messageOf(error)}`)\r\n }\r\n failing = true\r\n return\r\n }\r\n failing = false\r\n if (!reply.keepGoing) {\r\n closedOnSite()\r\n return\r\n }\r\n for (const message of reply.messages) {\r\n if (input.push(message)) {\r\n tools.print(` ${messageLine(message)}`)\r\n }\r\n }\r\n for (const stop of reply.helperStops ?? []) {\r\n if (!stopsSeen.has(stop.ref)) {\r\n stopsSeen.add(stop.ref)\r\n void stopHelper(input, stop, tools.print)\r\n }\r\n }\r\n if (reply.model !== null || reply.effort !== null) {\r\n const change = { model: reply.model, effort: reply.effort }\r\n void input.switchModel(change).then(\r\n (applied) => {\r\n if (applied) {\r\n record({ kind: 'tool', text: switchLine(change), tool: 'model', ref: null, parent: null, failed: false })\r\n }\r\n },\r\n error => tools.print(`Could not switch the model: ${messageOf(error)}`),\r\n )\r\n }\r\n }\r\n\r\n // проверка, попросившая слова во время другой, идёт сразу после неё: Claude мог как раз закончить ход\r\n function poll(): Promise<void> {\r\n if (running) {\r\n again = true\r\n return running\r\n }\r\n running = input.checking(check).finally(() => {\r\n running = null\r\n if (again) {\r\n again = false\r\n void poll()\r\n }\r\n })\r\n return running\r\n }\r\n\r\n return {\r\n input,\r\n poll,\r\n settled: async () => {\r\n for (let current = running; current; current = running) {\r\n await current\r\n }\r\n },\r\n }\r\n}\r\n\r\n// Шаг ТЗ: вопросы и ТЗ сдаются инструментами, а сайт проверяет их теми же правилами, что в облаке\r\nfunction specWorkTools(job: RunnerJob, api: ApiClient): SpecWorkTools | null {\r\n const { kind } = job.stage\r\n if (kind !== 'interview' && kind !== 'spec') {\r\n return null\r\n }\r\n return { stage: kind, review: body => api.request<RunnerReviewReply>('POST', runPath(job.runId, 'review'), body), brief: isFolderJob(job) }\r\n}\r\n\r\n// Спросить можно людей задачи на шагах кода и проверки; у шага ТЗ свои раунды вопросов\r\nfunction askingTools(job: RunnerJob, api: ApiClient): AskingTools | null {\r\n const people = job.people ?? []\r\n if (people.length === 0 || job.stage.kind === 'interview' || job.stage.kind === 'spec') {\r\n return null\r\n }\r\n return { people, send: body => api.request<RunnerAskReply>('POST', runPath(job.runId, 'questions'), body) }\r\n}\r\n\r\n// Действия, о которых Claude Code спросил бы человека, решают правила проекта или люди задачи на сайте; у шага ТЗ таких запросов нет.\r\n// Пока решения нет, здесь печатается, чего ждёт вызов Claude\r\nfunction permissionTools(job: RunnerJob, tools: WorkerTools): PermissionTools | null {\r\n if (job.stage.kind === 'interview' || job.stage.kind === 'spec') {\r\n return null\r\n }\r\n return {\r\n send: async (body) => {\r\n const reply = await tools.api.request<RunnerPermissionReply>('POST', runPath(job.runId, 'permissions'), body)\r\n if (reply.decision === 'wait') {\r\n tools.print(` Waiting for a person on TAMsense to allow: ${body.title || body.action}`)\r\n }\r\n return reply\r\n },\r\n check: id => tools.api.request<RunnerPermissionReply>('GET', permissionPath(job.runId, id)),\r\n sleep: tools.sleep,\r\n }\r\n}\r\n\r\n// Знания приходят только с нового сайта: прочесть, проверить и поправить можно заметки этого запуска, предложить заметку тот, кто спрашивает людей\r\nfunction knowledgeTools(job: RunnerJob, api: ApiClient): KnowledgeTools | null {\r\n if (!job.knowledge) {\r\n return null\r\n }\r\n return {\r\n entryIds: knowledgeIds(job.knowledge),\r\n moreIds: job.knowledge.more.map(entry => entry.id),\r\n canPropose: canProposeKnowledge(job),\r\n propose: body => api.request<RunnerKnowledgeReply>('POST', runPath(job.runId, 'knowledge'), body),\r\n update: (entryId, body) => api.request<RunnerKnowledgeReply>('POST', knowledgePath(job.runId, entryId), body),\r\n read: entryId => api.request<RunnerKnowledgeEntry>('GET', knowledgePath(job.runId, entryId)),\r\n }\r\n}\r\n\r\n// Роль с узким списком навыков: человек у компьютера видит, какие навыки Claude дано\r\nfunction skillsLine(skills: string[]): string {\r\n return `This role uses ${skills.length} skill(s) from the library: ${skills.join(', ')}.`\r\n}\r\n\r\n// Доска с кодом: репозитории задачи на её ветке. Доска без кода: папка задачи с её результатом, git не нужен\r\nasync function prepare(job: RunnerJob, tools: WorkerTools): Promise<{ workspace: Workspace, results: string[] | null, identity: Record<string, string> }> {\r\n if (isFolderJob(job)) {\r\n tools.print('Preparing the task folder…')\r\n const { results, ...workspace } = await prepareFolder(job, taskFolder(tools.home, job), tools.api)\r\n return { workspace, results, identity: {} }\r\n }\r\n if (isLocalJob(job)) {\r\n tools.print('Preparing the code from the folders on this computer…')\r\n const workspace = await prepareLocalWorkspace(job, tools)\r\n return { workspace, results: null, identity: await commitIdentityEnv(job.repositories[0]?.folder ?? tools.home) }\r\n }\r\n tools.print('Preparing the code…')\r\n const workspace = await prepareWorkspace(job, await gitToken(tools.api, job), tools)\r\n return { workspace, results: null, identity: await commitIdentityEnv(tools.home) }\r\n}\r\n\r\n// Ветка из папки на компьютере уходит входом человека, ветка с GitHub ключом TAMsense\r\nasync function publish(workspace: Workspace, job: RunnerJob, api: ApiClient, identity: Record<string, string>): Promise<RunnerFinishBody['pushed']> {\r\n return isLocalJob(job) ? await publishLocalWorkspace(workspace, job, identity) : await publishWorkspace(workspace, job, await gitToken(api, job), identity)\r\n}\r\n\r\nasync function executeJob(job: RunnerJob, tools: WorkerTools, signal: AbortSignal, input: InputChannel, record: (draft: FeedDraft) => void): Promise<RunnerFinishBody> {\r\n const isCheck = job.stage.kind === 'review' || job.stage.kind === 'test'\r\n const { workspace, results, identity } = await prepare(job, tools)\r\n const skills = allowedSkills(job.stage)\r\n if (skills) {\r\n tools.print(skillsLine(skills))\r\n }\r\n const library = await prepareLibrary(roleLibraryFolder(tools.home, job.runId), skills)\r\n signal.throwIfAborted()\r\n\r\n tools.print('Claude is working…')\r\n const attached = { count: 0 }\r\n const session = sessionReporter(tools.api, job, tools.print)\r\n const recordings = recordingsFolder(tools.home, job.runId)\r\n const baseEnv = withNodeOnPath(process.env, dirname(process.execPath))\r\n const recording = tools.recorder ? recorderEnv({ recorder: tools.recorder, folder: recordings, server: tools.api.server, job, nodeOptions: baseEnv.NODE_OPTIONS }) : {}\r\n const outcome = await tools.runAgent({\r\n cwd: workspace.root,\r\n library,\r\n prompt: buildTaskPrompt(job, workspace, { browser: Boolean(tools.browser), recorder: Boolean(tools.recorder), results }),\r\n input,\r\n env: { ...baseEnv, ...(tools.browser ? browserEnv(tools.browser) : {}), ...recording, ...identity },\r\n signal,\r\n wantsVerdict: isCheck,\r\n specWork: specWorkTools(job, tools.api),\r\n asking: askingTools(job, tools.api),\r\n permissions: permissionTools(job, tools),\r\n knowledge: knowledgeTools(job, tools.api),\r\n handoffs: job.allowedHandoffs,\r\n resumeSessionId: job.resumeSessionId,\r\n model: job.model,\r\n effort: job.effort,\r\n onFeed: record,\r\n onSession: session.report,\r\n attach: request => request.kind === 'screenshot'\r\n ? attachScreenshot(tools.api, job, request, attached)\r\n : attachRecording(tools.api, job.runId, recordings, request),\r\n results: resultTools(job, tools.api, workspace.root),\r\n handOver: handOverTools(job, tools.api),\r\n schedules: job.canSchedule ? scheduleTools(tools.api, runPath(job.runId, 'schedules')) : null,\r\n })\r\n await session.sent()\r\n signal.throwIfAborted()\r\n\r\n // ключ живёт час, а работа бывает дольше: для отправки ветки берётся свежий\r\n // проверка и шаг ТЗ ничего не отправляют: их итог это вердикт, вопросы или ТЗ; роль только для чтения на шаге кода тоже.\r\n // Код отправляется, только если Claude закончил работу, а не ждёт ответа человека или итога расписания: тогда работа остаётся в папке задачи до продолжения.\r\n // На доске без кода отправлять нечего: результат уже на задаче\r\n const waitingFor = outcome.waitingFor ?? null\r\n const waitingForSchedule = outcome.waitingForSchedule ?? null\r\n const shouldPublish = job.stage.kind === 'code' && !isFolderJob(job) && !isReadOnlyCode(job.stage) && outcome.completed && waitingFor === null && waitingForSchedule === null\r\n const pushed = shouldPublish ? await publish(workspace, job, tools.api, identity) : []\r\n return {\r\n outcome: outcome.completed ? 'succeeded' : 'failed',\r\n report: outcome.report,\r\n pushed,\r\n model: outcome.model,\r\n usage: outcome.usage,\r\n verdict: outcome.verdict,\r\n handoff: outcome.handoff ?? null,\r\n sessionId: outcome.sessionId ?? null,\r\n pausedUntil: outcome.pausedUntil ?? null,\r\n questions: outcome.questions ?? null,\r\n spec: outcome.spec ?? null,\r\n waitingFor,\r\n waitingForSchedule,\r\n }\r\n}\r\n\r\nfunction clockOf(iso: string): string {\r\n const time = new Date(iso)\r\n return `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`\r\n}\r\n\r\n// folder: доска без кода, результат уже лежит на задаче; local: ветки в папках этого компьютера\r\nexport function resultLine(finish: RunnerFinishBody, workspace: RunnerJob['workspace'] = 'repositories'): string {\r\n if (finish.pausedUntil) {\r\n return `Paused: ${USAGE_LIMIT_REPORT}, resumes at ${clockOf(finish.pausedUntil)}`\r\n }\r\n if (finish.waitingFor !== null) {\r\n return 'Waiting for an answer on TAMsense. This computer is free for other tasks; the run continues here after the answer.'\r\n }\r\n if (finish.waitingForSchedule) {\r\n return 'Waiting for a schedule on TAMsense. This computer is free for other tasks; the run continues here when the schedule ends.'\r\n }\r\n if (finish.verdict?.couldNotRun) {\r\n return `Check could not run: ${finish.verdict.report}`\r\n }\r\n if (finish.verdict) {\r\n return finish.verdict.passed ? 'Check passed.' : `Check did not pass: ${finish.verdict.report}`\r\n }\r\n if (finish.questions) {\r\n return finish.questions.questions.length > 0 ? `Done. ${finish.questions.questions.length} question(s) wait for the owner on TAMsense.` : 'Done. No questions needed, the spec is written next.'\r\n }\r\n if (finish.spec) {\r\n return 'Done. The spec waits for the owner on TAMsense.'\r\n }\r\n if (finish.outcome !== 'succeeded') {\r\n return `Failed: ${finish.report}`\r\n }\r\n if (workspace === 'folder') {\r\n return 'Done. The result is on the task on TAMsense.'\r\n }\r\n if (workspace === 'local') {\r\n return finish.pushed.length > 0 ? `Done. ${finish.pushed.map(localBranchLine).join(' ')}` : 'Done. Nothing new to commit.'\r\n }\r\n return `Done. Pushed ${finish.pushed.length} branch(es), the pull requests open on TAMsense.`\r\n}\r\n\r\n// Всё, что браузер агента записал и агент не приложил сам, уходит в задачу до итога запуска: потом сайт закроет запуск\r\nasync function sendRecordings(job: RunnerJob, tools: WorkerTools): Promise<void> {\r\n const folder = recordingsFolder(tools.home, job.runId)\r\n try {\r\n const report = await uploadPendingRecordings(tools.api, job.runId, folder)\r\n if (report.uploaded > 0) {\r\n tools.print(`Attached ${report.uploaded} browser recording(s) to the task.`)\r\n }\r\n if (report.skipped > 0) {\r\n tools.print(`${report.skipped} more recording(s) were not attached: the run reached its limit.`)\r\n }\r\n for (const line of report.failed) {\r\n tools.print(`Could not attach a recording: ${line}`)\r\n }\r\n }\r\n finally {\r\n await rm(folder, { recursive: true, force: true })\r\n }\r\n}\r\n\r\nfunction failureReport(error: unknown, signal: AbortSignal): string {\r\n return signal.reason === STOPPED_HERE ? 'The run was stopped on the computer.' : `The run stopped on the computer: ${messageOf(error)}`\r\n}\r\n\r\nexport async function runJob(job: RunnerJob, tools: WorkerTools, stop: AbortSignal): Promise<void> {\r\n tools.print(`Task #${job.taskId}: ${job.title} (${job.stage.name})`)\r\n const run = new AbortController()\r\n const stopRun = () => run.abort(STOPPED_HERE)\r\n if (stop.aborted) {\r\n stopRun()\r\n }\r\n stop.addEventListener('abort', stopRun, { once: true })\r\n const feed = feedQueue(entries => tools.api.request('POST', runPath(job.runId, 'feed'), { entries }, { safeToRepeat: true }))\r\n const feeding = setInterval(() => void feed.flush(), FEED_INTERVAL_MS)\r\n const record = journal(feed, tools.print)\r\n const messages = inbox(job, tools, record, () => run.abort(CLOSED_ON_SITE))\r\n const { input } = messages\r\n run.signal.addEventListener('abort', input.close, { once: true })\r\n void messages.poll()\r\n const polling = setInterval(() => void messages.poll(), INBOX_INTERVAL_MS)\r\n\r\n let finish: RunnerFinishBody\r\n try {\r\n finish = await executeJob(job, tools, run.signal, input, record)\r\n }\r\n catch (error) {\r\n finish = { outcome: 'failed', report: failureReport(error, run.signal), pushed: [], model: null, usage: null, verdict: null, handoff: null, sessionId: null, pausedUntil: null, questions: null, spec: null, waitingFor: null }\r\n }\r\n finally {\r\n await rm(roleLibraryFolder(tools.home, job.runId), { recursive: true, force: true })\r\n clearInterval(polling)\r\n clearInterval(feeding)\r\n stop.removeEventListener('abort', stopRun)\r\n input.close()\r\n }\r\n // последние переданные Claude сообщения подтверждаются до итога\r\n await messages.settled()\r\n if (input.hasDelivered()) {\r\n await messages.poll()\r\n }\r\n await feed.flush()\r\n\r\n if (run.signal.reason === CLOSED_ON_SITE) {\r\n await rm(recordingsFolder(tools.home, job.runId), { recursive: true, force: true })\r\n tools.print('TAMsense closed this run while it was in progress. The result was not sent.')\r\n return\r\n }\r\n await sendRecordings(job, tools).catch(error => tools.print(`Could not attach the browser recordings: ${messageOf(error)}`))\r\n try {\r\n await tools.api.request('POST', runPath(job.runId, 'finish'), finish)\r\n tools.print(resultLine(finish, job.workspace))\r\n }\r\n catch (error) {\r\n tools.print(`Could not send the result to TAMsense: ${messageOf(error)}`)\r\n }\r\n}\r\n\r\nconst CHECK_TOO_LONG = 'check_too_long'\r\n\r\nfunction checkFailure(error: unknown, signal: AbortSignal): string {\r\n return signal.reason === CHECK_TOO_LONG ? `The check took longer than ${CHECK_DEADLINE_MS / 60_000} minutes.` : failureReport(error, signal)\r\n}\r\n\r\n// Проверка расписания: короткий запуск Claude в папке шага, сигнал сайту раз в полминуты, итог одной строкой.\r\n// Проверку, которую сайт уже закрыл, программа бросает без итога\r\nexport async function runCheckJob(check: RunnerCheckJob, runCheck: RunCheck, tools: WorkerTools, stop: AbortSignal): Promise<void> {\r\n tools.print(`Schedule check: ${check.title}${check.taskId === null ? '' : ` (task #${check.taskId})`}`)\r\n const run = new AbortController()\r\n const stopRun = () => run.abort(STOPPED_HERE)\r\n if (stop.aborted) {\r\n stopRun()\r\n }\r\n stop.addEventListener('abort', stopRun, { once: true })\r\n const deadline = setTimeout(() => run.abort(CHECK_TOO_LONG), CHECK_DEADLINE_MS)\r\n const beat = checkHeartbeat(tools.api, check, () => run.abort(CLOSED_ON_SITE))\r\n const beating = setInterval(() => void beat(), CHECK_HEARTBEAT_MS)\r\n let body: RunnerCheckFinishBody\r\n try {\r\n const folder = await checkFolder(tools.home, check)\r\n const env = withNodeOnPath(process.env, dirname(process.execPath))\r\n body = checkFinishBody(await runCheck({ cwd: folder, prompt: buildCheckPrompt(check, folder), model: check.model, env, signal: run.signal }))\r\n }\r\n catch (error) {\r\n body = { outcome: 'failed', report: checkFailure(error, run.signal), model: null, usage: null, result: null }\r\n }\r\n finally {\r\n clearTimeout(deadline)\r\n clearInterval(beating)\r\n stop.removeEventListener('abort', stopRun)\r\n }\r\n if (run.signal.reason === CLOSED_ON_SITE) {\r\n tools.print('TAMsense closed this check while it was in progress. The result was not sent.')\r\n return\r\n }\r\n try {\r\n await tools.api.request('POST', checkPath(check.checkId, 'finish'), body)\r\n tools.print(checkLine(body))\r\n }\r\n catch (error) {\r\n tools.print(`Could not send the check result to TAMsense: ${messageOf(error)}`)\r\n }\r\n}\r\n\r\n// При запуске и потом раз в полчаса: вход и модели могли смениться. Ошибка печатается один раз и ничего не останавливает\r\nasync function reportCapabilities(tools: WorkerTools, stop: AbortSignal): Promise<void> {\r\n if (!tools.capabilities) {\r\n return\r\n }\r\n let printed = false\r\n let complained = false\r\n while (!stop.aborted) {\r\n try {\r\n const capabilities = await tools.capabilities(stop)\r\n await tools.api.request('PUT', CAPABILITIES_PATH, capabilities)\r\n if (!printed) {\r\n tools.print(capabilitiesLine(capabilities))\r\n printed = true\r\n }\r\n }\r\n catch (error) {\r\n if (!complained && !stop.aborted && !alreadyReported(error)) {\r\n tools.print(`Could not tell TAMsense which Claude models this computer has: ${messageOf(error)}`)\r\n complained = true\r\n }\r\n }\r\n await tools.sleep(CAPABILITIES_INTERVAL_MS, stop)\r\n }\r\n}\r\n\r\n// Сторож: зависший опрос бросается, и цикл идёт дальше\r\nasync function withDeadline<T>(work: Promise<T>, ms: number, sleep: WorkerTools['sleep']): Promise<T> {\r\n const settled = new AbortController()\r\n const deadline = new Promise<never>((_, reject) => {\r\n void sleep(ms, settled.signal).then(() => {\r\n if (!settled.signal.aborted) {\r\n reject(new Error(`TAMsense did not answer for ${Math.round(ms / 60_000)} minutes`))\r\n }\r\n })\r\n })\r\n try {\r\n return await Promise.race([work, deadline])\r\n }\r\n finally {\r\n settled.abort()\r\n }\r\n}\r\n\r\n// Ожидание после сбоя кончается, как только связь вернулась: задачи из очереди берутся сразу\r\nfunction untilReconnect(api: ApiClient, stop: AbortSignal): AbortSignal {\r\n return api.nextReconnect ? AbortSignal.any([stop, api.nextReconnect()]) : stop\r\n}\r\n\r\n// Несколько запусков сразу: строки каждого подписаны номером задачи и шагом, иначе их не различить\r\nfunction jobTools(job: RunnerJob, tools: WorkerTools, parallel: number): WorkerTools {\r\n return parallel > 1 ? { ...tools, print: line => tools.print(`[#${job.taskId} ${job.stage.name}] ${line}`) } : tools\r\n}\r\n\r\n// Берёт не больше parallel запусков сразу; два запуска одного шага задачи идут по очереди, у них одна рабочая папка,\r\n// а проверки одной задачи на разных шагах идут одновременно. Проверки расписаний, если программа их умеет, идут рядом,\r\n// не больше двух сразу и по одной на расписание\r\nexport async function work(tools: WorkerTools, stop: AbortSignal, parallel: number = 1): Promise<void> {\r\n tools.print(parallel > 1 ? `Waiting for tasks, up to ${parallel} at once. Press Ctrl+C to stop.` : 'Waiting for tasks. Press Ctrl+C to stop.')\r\n // отказ в доступе тоже останавливает все идущие задачи, каждая отчитывается\r\n const halt = new AbortController()\r\n const stopAll = AbortSignal.any([stop, halt.signal])\r\n const running = new Set<Promise<void>>()\r\n const checks = new Set<Promise<void>>()\r\n const folderLock = keyedLock()\r\n const scheduleLock = keyedLock()\r\n const runCheck = tools.version ? tools.runCheck : undefined\r\n const reporting = reportCapabilities(tools, stopAll)\r\n const checking = tools.inspectFolder ? checkFolders({ ...tools, inspect: tools.inspectFolder }, stopAll) : Promise.resolve()\r\n if (tools.startFolder) {\r\n await reportFoundFolders(tools, tools.startFolder)\r\n }\r\n const start = (job: RunnerJob) => {\r\n const runTools = jobTools(job, tools, parallel)\r\n const task: Promise<void> = folderLock.run(stepFolder(job).join('/'), () => runJob(job, runTools, stopAll))\r\n .catch(error => runTools.print(`The run stopped: ${messageOf(error)}`))\r\n .finally(() => running.delete(task))\r\n running.add(task)\r\n }\r\n const startCheck = (check: RunnerCheckJob, runner: RunCheck) => {\r\n const task: Promise<void> = scheduleLock.run(`schedule-${check.scheduleId}`, () => runCheckJob(check, runner, tools, stopAll))\r\n .catch(error => tools.print(`The check stopped: ${messageOf(error)}`))\r\n .finally(() => checks.delete(task))\r\n checks.add(task)\r\n }\r\n // сбои подряд: строка печатается одна на всю полосу, пауза растёт\r\n let failures = 0\r\n try {\r\n while (!stopAll.aborted) {\r\n const jobsFull = running.size >= parallel\r\n const checksFull = !runCheck || checks.size >= MAX_CHECKS\r\n if (jobsFull && checksFull) {\r\n await Promise.race([...running, ...checks])\r\n continue\r\n }\r\n const body: RunnerNextBody | undefined = runCheck && tools.version ? { version: tools.version, jobs: !jobsFull, checks: !checksFull } : undefined\r\n let reply: RunnerNextResponse\r\n try {\r\n // взятый запуск, ответ о котором потерялся, сайт сочтёт пропавшим; повтор лишь берёт следующий\r\n reply = await withDeadline(tools.api.request<RunnerNextResponse>('POST', NEXT_RUN_PATH, body, { safeToRepeat: true }), POLL_DEADLINE_MS, tools.sleep)\r\n }\r\n catch (error) {\r\n if (isFatal(error)) {\r\n halt.abort()\r\n throw error\r\n }\r\n failures += 1\r\n if (failures === 1 && !alreadyReported(error)) {\r\n tools.print(`Could not ask TAMsense for tasks: ${messageOf(error)}. Trying again.`)\r\n }\r\n await tools.sleep(backoffMs(failures, POLL_INTERVAL_MS, MAX_RETRY_WAIT_MS), untilReconnect(tools.api, stopAll))\r\n continue\r\n }\r\n failures = 0\r\n if (reply.check && runCheck) {\r\n startCheck(reply.check, runCheck)\r\n }\r\n else if (reply.job) {\r\n start(reply.job)\r\n }\r\n else {\r\n await tools.sleep(POLL_INTERVAL_MS, stopAll)\r\n }\r\n }\r\n }\r\n finally {\r\n await Promise.allSettled([...running, ...checks, reporting, checking])\r\n }\r\n}\r\n","import type { FeedDraft } from './feed'\r\nimport type { RunnerFeedEntry } from './protocol'\r\n\r\nexport interface FeedQueue {\r\n push: (draft: FeedDraft) => RunnerFeedEntry\r\n flush: () => Promise<void>\r\n}\r\n\r\nconst MAX_BATCH = 200\r\nconst MAX_WAITING = 2_000\r\n\r\n// Журнал копится и уходит на сайт пачками. Неотправленное ждёт следующей попытки;\r\n// если сайт долго недоступен, теряются самые старые записи, а не работа Claude\r\nexport function feedQueue(send: (entries: RunnerFeedEntry[]) => Promise<void>): FeedQueue {\r\n let seq = 0\r\n let waiting: RunnerFeedEntry[] = []\r\n let sending: Promise<void> = Promise.resolve()\r\n // за идущей отправкой ждёт не больше одной: пока сайт недоступен, вызовы не копятся очередью\r\n let queued = false\r\n\r\n async function drain(): Promise<void> {\r\n while (waiting.length > 0) {\r\n const batch = waiting.slice(0, MAX_BATCH)\r\n try {\r\n await send(batch)\r\n }\r\n catch {\r\n return\r\n }\r\n const lastSent = batch.at(-1)!.seq\r\n waiting = waiting.filter(entry => entry.seq > lastSent)\r\n }\r\n }\r\n\r\n return {\r\n push(draft) {\r\n seq += 1\r\n const entry = { ...draft, seq }\r\n waiting.push(entry)\r\n if (waiting.length > MAX_WAITING) {\r\n waiting = waiting.slice(-MAX_WAITING)\r\n }\r\n return entry\r\n },\r\n flush() {\r\n if (!queued) {\r\n queued = true\r\n sending = sending.then(() => {\r\n queued = false\r\n return drain()\r\n })\r\n }\r\n return sending\r\n },\r\n }\r\n}\r\n","import type { RunnerStage } from './protocol'\r\nimport { existsSync } from 'node:fs'\r\nimport { cp, mkdir, rm } from 'node:fs/promises'\r\nimport { join } from 'node:path'\r\nimport { fileURLToPath } from 'node:url'\r\n\r\n// Библиотека помощников и навыков TAMsense лежит рядом с программой и подключается к Claude Code плагином на время задачи\r\nexport const LIBRARY_PATH = fileURLToPath(new URL('../library', import.meta.url))\r\n\r\nconst SKILL_NAME = /^[a-z0-9-]+$/\r\n\r\n// Навыки, которые постоянные правила шага велят загрузить: они остаются и у роли с узким списком\r\nconst STAGE_SKILLS: Partial<Record<RunnerStage['kind'], string[]>> = {\r\n interview: ['task-interview'],\r\n spec: ['spec-writing', 'task-interview'],\r\n}\r\n\r\n// Навыки запуска: null, если роль разрешает всю библиотеку (или сайт роли не прислал). Имя навыка только из букв, цифр и дефисов\r\nexport function allowedSkills(stage: Pick<RunnerStage, 'kind' | 'role'>): string[] | null {\r\n const skills = stage.role?.skills ?? null\r\n return skills && [...new Set([...skills, ...STAGE_SKILLS[stage.kind] ?? []])].filter(name => SKILL_NAME.test(name))\r\n}\r\n\r\nexport function roleLibraryFolder(home: string, runId: number): string {\r\n return join(home, '.tamsense', 'libraries', `run-${runId}`)\r\n}\r\n\r\n// Роль с «Only these»: своя копия плагина, где из навыков лежат только разрешённые. Остальные навыки библиотеки Claude Code\r\n// в этом запуске не находит вовсе, а свои навыки человека и проекта видны как обычно. Помощники библиотеки остаются\r\nexport async function prepareLibrary(folder: string, skills: string[] | null, source: string = LIBRARY_PATH): Promise<string> {\r\n if (skills === null) {\r\n return source\r\n }\r\n await rm(folder, { recursive: true, force: true })\r\n await mkdir(join(folder, 'skills'), { recursive: true })\r\n await cp(join(source, '.claude-plugin'), join(folder, '.claude-plugin'), { recursive: true })\r\n await cp(join(source, 'agents'), join(folder, 'agents'), { recursive: true })\r\n // навыка, которого нет в этой версии программы, просто нет и в копии\r\n for (const skill of skills.filter(name => SKILL_NAME.test(name) && existsSync(join(source, 'skills', name)))) {\r\n await cp(join(source, 'skills', skill), join(folder, 'skills', skill), { recursive: true })\r\n }\r\n return folder\r\n}\r\n","import type { ApiClient } from './api'\r\nimport type { RunnerAttachmentResponse, RunnerRecordingBody, RunnerRecordingMark, RunnerVideoType } from './protocol'\r\nimport type { RecordingCaption, SessionMeta } from './recorder'\r\nimport type { AttachFields } from './screenshot'\r\nimport { randomUUID } from 'node:crypto'\r\nimport { createReadStream } from 'node:fs'\r\nimport { readdir, readFile, stat, writeFile } from 'node:fs/promises'\r\nimport { basename, dirname, extname, isAbsolute, join } from 'node:path'\r\nimport { createInterface } from 'node:readline'\r\nimport { Readable } from 'node:stream'\r\nimport { promisify } from 'node:util'\r\nimport { gzip } from 'node:zlib'\r\nimport { ApiError } from './api'\r\nimport { MAX_VIDEO_BYTES, recordingUploadPath, runPath } from './protocol'\r\nimport { checkCaption, checkNotes, clip, MARK_TAG, MAX_NOTES_LENGTH, MAX_TITLE_LENGTH, META_SUFFIX, REPLAY_SUFFIX } from './recorder'\r\n\r\nconst gzipAsync = promisify(gzip)\r\n\r\n// кусок записи до сжатия; сайт принимает до 4 МБ сжатого и 32 МБ несжатого\r\nexport const CHUNK_TARGET_BYTES = 2 * 1024 * 1024\r\nconst MAX_LINE_BYTES = 30 * 1024 * 1024\r\nconst LEDGER_FILE = 'uploaded.json'\r\nconst RRWEB_FULL_SNAPSHOT = 2\r\nconst RRWEB_META = 4\r\nconst RRWEB_CUSTOM = 5\r\n\r\nconst VIDEO_TYPES_BY_EXTENSION: Record<string, RunnerVideoType> = {\r\n '.webm': 'video/webm',\r\n '.mp4': 'video/mp4',\r\n}\r\n\r\nexport type RecordingKind = 'replay' | 'video'\r\n\r\n// Запись или видео в папке запуска, которые ещё не ушли на сайт\r\nexport interface PendingRecording {\r\n kind: RecordingKind\r\n file: string\r\n meta: SessionMeta | null\r\n}\r\n\r\ninterface ReplayEvent {\r\n type: number\r\n timestamp: number\r\n data?: { tag?: unknown, payload?: { label?: unknown } }\r\n}\r\n\r\ninterface ReplayScan {\r\n startTs: number\r\n snapshot: boolean\r\n marks: Array<{ ts: number, label: string }>\r\n}\r\n\r\nfunction eventOf(line: string): ReplayEvent | null {\r\n try {\r\n const event = JSON.parse(line) as ReplayEvent\r\n return typeof event?.type === 'number' && typeof event.timestamp === 'number' ? event : null\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\n// Новый кусок начинается там, где rrweb снимает страницу заново (переход на другую страницу), или когда кусок вырос:\r\n// так же режет записи трекер, и плеер перематывает от ближайшего снимка\r\nexport async function* replayChunks(lines: AsyncIterable<string>, scan: ReplayScan, targetBytes: number = CHUNK_TARGET_BYTES): AsyncGenerator<string> {\r\n let chunk: string[] = []\r\n let bytes = 0\r\n let previous: ReplayEvent | null = null\r\n for await (const line of lines) {\r\n const event = line.trim() ? eventOf(line) : null\r\n if (!event) {\r\n continue\r\n }\r\n if (line.length > MAX_LINE_BYTES) {\r\n throw new Error('One event of this replay is larger than 30 MB, so it cannot be uploaded.')\r\n }\r\n const startsGroup = event.type === RRWEB_META || (event.type === RRWEB_FULL_SNAPSHOT && previous?.type !== RRWEB_META)\r\n if (chunk.length > 0 && (startsGroup || bytes + line.length > targetBytes)) {\r\n yield `${chunk.join('\\n')}\\n`\r\n chunk = []\r\n bytes = 0\r\n }\r\n chunk.push(line)\r\n bytes += line.length + 1\r\n previous = event\r\n scan.startTs = Math.min(scan.startTs, event.timestamp)\r\n scan.snapshot ||= event.type === RRWEB_FULL_SNAPSHOT\r\n if (event.type === RRWEB_CUSTOM && event.data?.tag === MARK_TAG && typeof event.data.payload?.label === 'string') {\r\n scan.marks.push({ ts: event.timestamp, label: event.data.payload.label })\r\n }\r\n }\r\n if (chunk.length > 0) {\r\n yield `${chunk.join('\\n')}\\n`\r\n }\r\n}\r\n\r\n// Подпись записи на сайте: название, назначение и что агент в ней проверял\r\ninterface RecordingLabel extends RecordingCaption {\r\n notes: string\r\n}\r\n\r\n// Заметка записи: слова агента из closeRecorded, а если их нет, его отметки шагов по порядку\r\nexport function recordingNotes(meta: Pick<SessionMeta, 'notes' | 'marks'> | null): string {\r\n const notes = meta?.notes?.trim()\r\n if (notes) {\r\n return notes\r\n }\r\n const marks = meta?.marks ?? []\r\n return marks.length > 0 ? clip(`Steps marked: ${marks.map(mark => mark.label).join('; ')}.`, MAX_NOTES_LENGTH) : ''\r\n}\r\n\r\nexport function marksFrom(marks: Array<{ ts: number, label: string }>, startTs: number): RunnerRecordingMark[] {\r\n return marks.map(mark => ({ at: Math.max(0, mark.ts - startTs), label: mark.label })).sort((a, b) => a.at - b.at)\r\n}\r\n\r\nfunction sessionIdOf(file: string): string {\r\n return basename(file).replace(REPLAY_SUFFIX, '')\r\n}\r\n\r\nasync function readMeta(file: string): Promise<SessionMeta | null> {\r\n try {\r\n return JSON.parse(await readFile(file, 'utf8')) as SessionMeta\r\n }\r\n catch {\r\n return null\r\n }\r\n}\r\n\r\n// Подпись рядом с записью: файл .json того же сеанса; у видео из записи сеанс находится по пути видео\r\nasync function metaFor(kind: RecordingKind, file: string): Promise<SessionMeta | null> {\r\n if (kind === 'replay') {\r\n return readMeta(join(dirname(file), `${sessionIdOf(file)}${META_SUFFIX}`))\r\n }\r\n const folder = basename(dirname(file)) === 'video' ? dirname(dirname(file)) : dirname(file)\r\n for (const name of await readdir(folder).catch(() => [])) {\r\n if (name.endsWith(META_SUFFIX) && name !== LEDGER_FILE) {\r\n const meta = await readMeta(join(folder, name))\r\n if (meta?.video === file) {\r\n return meta\r\n }\r\n }\r\n }\r\n return null\r\n}\r\n\r\nasync function ledger(dir: string): Promise<Set<string>> {\r\n try {\r\n return new Set(JSON.parse(await readFile(join(dir, LEDGER_FILE), 'utf8')) as string[])\r\n }\r\n catch {\r\n return new Set()\r\n }\r\n}\r\n\r\nasync function remember(dir: string, file: string): Promise<void> {\r\n const done = await ledger(dir)\r\n done.add(file)\r\n await writeFile(join(dir, LEDGER_FILE), JSON.stringify([...done]))\r\n}\r\n\r\nasync function nonEmpty(file: string | null): Promise<boolean> {\r\n return file !== null && ((await stat(file).catch(() => null))?.size ?? 0) > 0\r\n}\r\n\r\nexport async function pendingRecordings(dir: string): Promise<PendingRecording[]> {\r\n const done = await ledger(dir)\r\n const names = (await readdir(dir).catch(() => [] as string[])).sort()\r\n const pending: PendingRecording[] = []\r\n for (const name of names) {\r\n if (!name.endsWith(META_SUFFIX) || name === LEDGER_FILE) {\r\n continue\r\n }\r\n const meta = await readMeta(join(dir, name))\r\n const replay = join(dir, `${name.slice(0, -META_SUFFIX.length)}${REPLAY_SUFFIX}`)\r\n if (!done.has(replay) && await nonEmpty(replay)) {\r\n pending.push({ kind: 'replay', file: replay, meta })\r\n }\r\n if (meta?.video && !done.has(meta.video) && await nonEmpty(meta.video)) {\r\n pending.push({ kind: 'video', file: meta.video, meta })\r\n }\r\n }\r\n return pending\r\n}\r\n\r\ninterface Upload {\r\n api: ApiClient\r\n runId: number\r\n}\r\n\r\nasync function uploadReplay(upload: Upload, file: string, label: RecordingLabel): Promise<number> {\r\n const id = randomUUID()\r\n const scan: ReplayScan = { startTs: Number.POSITIVE_INFINITY, snapshot: false, marks: [] }\r\n const lines = createInterface({ input: createReadStream(file, 'utf8'), crlfDelay: Infinity })\r\n for await (const chunk of replayChunks(lines, scan)) {\r\n await upload.api.upload('POST', recordingUploadPath(upload.runId, id, 'chunks'), new Uint8Array(await gzipAsync(chunk)), 'application/gzip')\r\n }\r\n if (!scan.snapshot) {\r\n throw new Error('This replay has no page snapshot, so it cannot be played. Was the page ever opened?')\r\n }\r\n return finish(upload, { kind: 'replay', upload: id, name: basename(file), ...label, contentType: 'application/x-ndjson', durationMs: null, marks: marksFrom(scan.marks, scan.startTs) })\r\n}\r\n\r\nasync function uploadVideo(upload: Upload, file: string, label: RecordingLabel, meta: SessionMeta | null): Promise<number> {\r\n const contentType = VIDEO_TYPES_BY_EXTENSION[extname(file).toLowerCase()]!\r\n const info = await stat(file)\r\n if (info.size > MAX_VIDEO_BYTES) {\r\n throw new Error(`The video is larger than ${MAX_VIDEO_BYTES / 1024 / 1024} MB. Record a shorter one.`)\r\n }\r\n const id = randomUUID()\r\n const body = Readable.toWeb(createReadStream(file)) as ReadableStream<Uint8Array>\r\n await upload.api.upload('PUT', recordingUploadPath(upload.runId, id, 'video'), body, contentType)\r\n const startedAt = meta?.startedAt ?? info.birthtimeMs\r\n const endedAt = meta?.endedAt ?? info.mtimeMs\r\n const durationMs = Math.max(0, Math.round(endedAt - startedAt))\r\n const marks = marksFrom(meta?.marks ?? [], startedAt).filter(mark => mark.at <= durationMs)\r\n return finish(upload, { kind: 'video', upload: id, name: basename(file), ...label, contentType, durationMs, marks })\r\n}\r\n\r\nasync function finish(upload: Upload, body: RunnerRecordingBody): Promise<number> {\r\n return (await upload.api.request<RunnerAttachmentResponse>('POST', runPath(upload.runId, 'recordings'), body)).id\r\n}\r\n\r\n// Ответ Claude на attach_replay и attach_video: приложено, или почему нет, чтобы он поправил путь или подпись\r\nexport async function attachRecording(api: ApiClient, runId: number, recordingsDir: string, request: AttachFields & { kind: RecordingKind }): Promise<string> {\r\n if (!isAbsolute(request.path)) {\r\n return `Give the absolute path of the ${request.kind === 'replay' ? 'replay (.rrweb.ndjson)' : 'video (.webm or .mp4)'} file.`\r\n }\r\n const fitsKind = request.kind === 'replay' ? request.path.endsWith(REPLAY_SUFFIX) : extname(request.path).toLowerCase() in VIDEO_TYPES_BY_EXTENSION\r\n if (!fitsKind) {\r\n return request.kind === 'replay' ? `A replay is the ${REPLAY_SUFFIX} file the TAMsense recorder wrote.` : 'Only WebM and MP4 videos can be attached.'\r\n }\r\n if (!await nonEmpty(request.path)) {\r\n return `There is no recording at ${request.path}. Close the browser context first (closeRecorded) so the file is complete.`\r\n }\r\n let caption: RecordingCaption\r\n let notes: string\r\n try {\r\n caption = checkCaption(request)\r\n notes = checkNotes(request.notes)\r\n }\r\n catch (error) {\r\n return error instanceof Error ? error.message : String(error)\r\n }\r\n try {\r\n const meta = await metaFor(request.kind, request.path)\r\n const label = { ...caption, notes: notes || recordingNotes(meta) }\r\n const upload = { api, runId }\r\n await (request.kind === 'replay' ? uploadReplay(upload, request.path, label) : uploadVideo(upload, request.path, label, meta))\r\n await remember(recordingsDir, request.path)\r\n }\r\n catch (error) {\r\n return `Could not attach the ${request.kind}: ${error instanceof Error ? error.message : String(error)}`\r\n }\r\n return 'Attached to the task.'\r\n}\r\n\r\nfunction numbered(key: string, title: string, used: Map<string, number>): string {\r\n const count = (used.get(key) ?? 0) + 1\r\n used.set(key, count)\r\n const suffix = count > 1 ? ` (${count})` : ''\r\n return `${title.slice(0, MAX_TITLE_LENGTH - suffix.length)}${suffix}`\r\n}\r\n\r\n// сайт отказал, потому что запуск уже приложил сколько можно записей этого вида\r\nfunction overLimit(error: unknown): boolean {\r\n return error instanceof ApiError && error.status === 422 && /at most/i.test(error.message)\r\n}\r\n\r\nexport interface UploadReport {\r\n uploaded: number\r\n failed: string[]\r\n // не ушли из-за лимита запуска\r\n skipped: number\r\n}\r\n\r\n// В конце запуска на сайт уходит всё записанное, что агент не приложил сам: сначала то, что агент назвал сам, потом\r\n// записанное автоматически. Одинаковые названия получают номер\r\nexport async function uploadPendingRecordings(api: ApiClient, runId: number, dir: string): Promise<UploadReport> {\r\n const report: UploadReport = { uploaded: 0, failed: [], skipped: 0 }\r\n const used = new Map<string, number>()\r\n const full = new Set<RecordingKind>()\r\n const upload = { api, runId }\r\n const pending = (await pendingRecordings(dir)).sort((a, b) => Number(a.meta?.automatic ?? true) - Number(b.meta?.automatic ?? true))\r\n for (const recording of pending) {\r\n if (full.has(recording.kind)) {\r\n report.skipped += 1\r\n continue\r\n }\r\n const title = recording.meta?.title || 'Browser session'\r\n const caption = checkCaption({\r\n title: numbered(`${recording.kind}:${title}`, title, used),\r\n purpose: recording.meta?.purpose || 'Recorded automatically while the agent used a browser',\r\n })\r\n const label = { ...caption, notes: recordingNotes(recording.meta) }\r\n try {\r\n await (recording.kind === 'replay' ? uploadReplay(upload, recording.file, label) : uploadVideo(upload, recording.file, label, recording.meta))\r\n await remember(dir, recording.file)\r\n report.uploaded += 1\r\n }\r\n catch (error) {\r\n if (overLimit(error)) {\r\n full.add(recording.kind)\r\n report.skipped += 1\r\n continue\r\n }\r\n report.failed.push(`${caption.title} (${recording.kind}): ${error instanceof Error ? error.message : String(error)}`)\r\n }\r\n }\r\n return report\r\n}\r\n","import type { RunnerAttachmentBody, RunnerAttachmentType } from './protocol'\r\nimport { readFile, stat } from 'node:fs/promises'\r\nimport { basename, extname, isAbsolute } from 'node:path'\r\nimport { MAX_ATTACHMENT_BYTES } from './protocol'\r\nimport { checkCaption } from './recorder'\r\n\r\nconst TYPES_BY_EXTENSION: Record<string, RunnerAttachmentType> = {\r\n '.png': 'image/png',\r\n '.jpg': 'image/jpeg',\r\n '.jpeg': 'image/jpeg',\r\n '.webp': 'image/webp',\r\n}\r\n\r\n// Файл, который Claude прикладывает к задаче: путь, что на нём (title) и зачем он сделан (purpose)\r\nexport interface AttachFields {\r\n path: string\r\n title: string\r\n purpose: string\r\n // у записи и видео: что агент в ней проверял и чем кончилось\r\n notes?: string\r\n}\r\n\r\nexport type AttachRequest = AttachFields & ({ kind: 'screenshot' } | { kind: 'replay' | 'video' })\r\n\r\n// Ответ Claude, если файл не годится: он поправит путь, формат или подпись и попробует снова\r\nexport async function readScreenshot(request: AttachFields): Promise<RunnerAttachmentBody | string> {\r\n if (!isAbsolute(request.path)) {\r\n return 'Give the absolute path of the image file.'\r\n }\r\n const contentType = TYPES_BY_EXTENSION[extname(request.path).toLowerCase()]\r\n if (!contentType) {\r\n return 'Only PNG, JPEG and WebP images can be attached.'\r\n }\r\n let caption: { title: string, purpose: string }\r\n try {\r\n caption = checkCaption(request)\r\n }\r\n catch (error) {\r\n return error instanceof Error ? error.message : String(error)\r\n }\r\n const info = await stat(request.path).catch(() => null)\r\n if (!info?.isFile()) {\r\n return `There is no file at ${request.path}.`\r\n }\r\n if (info.size > MAX_ATTACHMENT_BYTES) {\r\n return 'The image is larger than 5 MB. Take a smaller screenshot (viewport instead of full page, or JPEG).'\r\n }\r\n const data = (await readFile(request.path)).toString('base64')\r\n return { name: basename(request.path), ...caption, contentType, data }\r\n}\r\n"],"mappings":"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","names":["existsSync","homedir","process","print","reply","sleep","attempt","resolve","text","present","resolve","createSdkMcpServer","query","tool","z","text","tool","HELPER_TOOLS","text","tool","ref","task","text","applySwitch","resolve","work","text","STOPPED_TEXT","messageOf","reply","existsSync","join","resolve","existsSync","join","process","Buffer","spawn","process","resolve","text","join","process","existsSync","work","done","resolve","existsSync","rm","join","resolve","join","existsSync","rm","text","resolve","print","releaseBranch","work","resolve","keptBase","existsSync","join","sent","mkdir","readFile","rm","writeFile","basename","isAbsolute","join","resolve","tool","z","rm","mkdir","writeFile","join","isAbsolute","resolve","basename","readFile","text","tool","z","existsSync","mkdir","query","tool","z","ALREADY_WAITING_TEXT","reply","text","tool","z","existsSync","mkdir","query","CHECK_RULES","text","sourceOf","work","tool","z","z","reply","text","tool","text","reset","texts","SERVER_NAME","z","tool","reply","createSdkMcpServer","query","mkdir","readFile","rm","writeFile","homedir","dirname","join","isRecord","reply","existsSync","join","basename","dirname","join","process","process","text","hostname","clip","script","basename","join","dirname","process","existsSync","join","spawn","delimiter","wait","MAX_NAME_LENGTH","spawn","readFileSync","process","resolve","rm","dirname","process","existsSync","mkdir","rm","join","fileURLToPath","readdir","readFile","stat","writeFile","basename","dirname","extname","isAbsolute","join","createInterface","RRWEB_META","clip","basename","readFile","join","dirname","readdir","done","writeFile","stat","createInterface","extname","recordingsDir","isAbsolute","readFile","stat","basename","extname","isAbsolute","TYPES_BY_EXTENSION","isAbsolute","extname","stat","readFile","basename","messageOf","print","reply","specWorkTools","knowledgeTools","workspace","process","dirname","finish","rm","run","work","sleep","process","existsSync","problem","homedir"]}
|