@quiel/cli 0.5.2 → 0.5.3
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 +3 -0
- package/dist/cli.js +1537 -1273
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1317,13 +1317,22 @@ var taskPayloadLeaseSchema = z23.object({
|
|
|
1317
1317
|
resumeToken: z23.string().min(1),
|
|
1318
1318
|
expiresAt: z23.string().datetime()
|
|
1319
1319
|
});
|
|
1320
|
+
var taskPayloadContinueFromSchema = z23.object({
|
|
1321
|
+
branchName: z23.string().min(1),
|
|
1322
|
+
sha: z23.string().optional(),
|
|
1323
|
+
/** Короткая записка прошлого исполнителя «что успел». */
|
|
1324
|
+
note: z23.string().optional(),
|
|
1325
|
+
savedAt: z23.string()
|
|
1326
|
+
});
|
|
1320
1327
|
var taskPayloadSchema = z23.object({
|
|
1321
1328
|
task: taskPayloadTaskSchema,
|
|
1322
1329
|
project: taskPayloadProjectSchema,
|
|
1323
1330
|
dependencies: z23.array(taskPayloadDependencySchema),
|
|
1324
1331
|
openQuestions: z23.array(taskPayloadOpenQuestionSchema),
|
|
1325
1332
|
trust: taskPayloadTrustSchema,
|
|
1326
|
-
lease: taskPayloadLeaseSchema
|
|
1333
|
+
lease: taskPayloadLeaseSchema,
|
|
1334
|
+
/** Есть только у задач, которые уже начинали и прервали. */
|
|
1335
|
+
continueFrom: taskPayloadContinueFromSchema.optional()
|
|
1327
1336
|
});
|
|
1328
1337
|
var taskClaimPayloadSchema = z23.object({
|
|
1329
1338
|
/**
|
|
@@ -1440,7 +1449,21 @@ var contextPayloadSchema = z23.object({
|
|
|
1440
1449
|
});
|
|
1441
1450
|
var agentRateLimitedPayloadSchema = z23.object({
|
|
1442
1451
|
retryAfterSec: z23.number().int().positive().optional(),
|
|
1443
|
-
taskId: z23.string().min(1).optional()
|
|
1452
|
+
taskId: z23.string().min(1).optional(),
|
|
1453
|
+
/**
|
|
1454
|
+
* Когда лимит сбросится (LIM-06) — разобрано клиентом из текста самой
|
|
1455
|
+
* ошибки Claude Code («resets 3:45pm»).
|
|
1456
|
+
*
|
|
1457
|
+
* НЕ ПУТАТЬ С `retryAfterSec`. Тот — сколько секунд подождать, как
|
|
1458
|
+
* посчитал сам Claude Code, и его часто нет вовсе. Этот — абсолютный
|
|
1459
|
+
* момент, уже приведённый к UTC клиентом: в сообщении время местное и
|
|
1460
|
+
* без пояса, и разобрать его может только тот, кто стоит на той же
|
|
1461
|
+
* машине.
|
|
1462
|
+
*
|
|
1463
|
+
* Необязательное: формат сообщения может смениться, и тогда клиент
|
|
1464
|
+
* честно ничего не присылает, а не гадает.
|
|
1465
|
+
*/
|
|
1466
|
+
resetsAt: z23.string().datetime().optional()
|
|
1444
1467
|
});
|
|
1445
1468
|
var agentRateLimitedAckSchema = z23.object({
|
|
1446
1469
|
ok: z23.boolean(),
|
|
@@ -1520,6 +1543,18 @@ var taskUsagePayloadSchema = z23.object({
|
|
|
1520
1543
|
entries: z23.array(taskUsageEntrySchema).min(1).max(10)
|
|
1521
1544
|
});
|
|
1522
1545
|
var taskUsageAckSchema = z23.object({ ok: z23.boolean() });
|
|
1546
|
+
var taskWorkSavedPayloadSchema = z23.object({
|
|
1547
|
+
taskId: z23.string().min(1),
|
|
1548
|
+
/** Ветка, в которую ушла незаконченная работа. */
|
|
1549
|
+
branchName: z23.string().min(1).max(255),
|
|
1550
|
+
sha: z23.string().min(7).max(64).optional(),
|
|
1551
|
+
/**
|
|
1552
|
+
* Короткая записка «что успел» — её увидит следующий исполнитель.
|
|
1553
|
+
* Потолок жёсткий: это подпись к ветке, а не отчёт о работе.
|
|
1554
|
+
*/
|
|
1555
|
+
note: z23.string().max(500).optional()
|
|
1556
|
+
});
|
|
1557
|
+
var taskWorkSavedAckSchema = z23.object({ ok: z23.boolean() });
|
|
1523
1558
|
var sessionLimitReachedPayloadSchema = z23.object({
|
|
1524
1559
|
tasksCompletedInSession: z23.number().int().nonnegative()
|
|
1525
1560
|
});
|
|
@@ -3423,7 +3458,22 @@ var CurrentTaskSchema = z30.object({
|
|
|
3423
3458
|
* Необязательно: состояния, записанные прежними версиями клиента, поля
|
|
3424
3459
|
* не имеют — тогда `submit_work` работает как раньше, в режиме DIRECT.
|
|
3425
3460
|
*/
|
|
3426
|
-
gitMode: z30.enum([GitMode.DIRECT, GitMode.PR]).optional()
|
|
3461
|
+
gitMode: z30.enum([GitMode.DIRECT, GitMode.PR]).optional(),
|
|
3462
|
+
/**
|
|
3463
|
+
* Имя ветки задачи и префикс сообщения коммита — как их посчитал
|
|
3464
|
+
* СЕРВЕР (`project.branchName`, `project.commitPrefix`, §10.3 ТЗ).
|
|
3465
|
+
*
|
|
3466
|
+
* Добавлены для спасения незаконченной работы (LIM-01): когда агент
|
|
3467
|
+
* упирается в лимит подписки, коммитить и отправлять надо уже без
|
|
3468
|
+
* него — он в этот момент ничего не может. Значит и ветку, и префикс
|
|
3469
|
+
* надо помнить заранее, а не спрашивать в момент прерывания.
|
|
3470
|
+
*
|
|
3471
|
+
* Необязательны: у состояний, записанных прежними версиями клиента,
|
|
3472
|
+
* полей нет — тогда работа спасается только коммитом (без push) или
|
|
3473
|
+
* не спасается вовсе, и об этом честно сообщается.
|
|
3474
|
+
*/
|
|
3475
|
+
branchName: z30.string().min(1).optional(),
|
|
3476
|
+
commitPrefix: z30.string().min(1).optional()
|
|
3427
3477
|
});
|
|
3428
3478
|
var TrustCacheSchema = z30.object({
|
|
3429
3479
|
profileKey: z30.string().min(1),
|
|
@@ -4175,7 +4225,14 @@ var createSocket = (url, options) => io(url, {
|
|
|
4175
4225
|
// src/hooks/audit-sender.ts
|
|
4176
4226
|
var DEFAULT_CONNECT_TIMEOUT_MS = 3e3;
|
|
4177
4227
|
var DEFAULT_FLUSH_GRACE_MS = 150;
|
|
4178
|
-
var WIRE_EVENTS = /* @__PURE__ */ new Set([
|
|
4228
|
+
var WIRE_EVENTS = /* @__PURE__ */ new Set([
|
|
4229
|
+
"audit:command",
|
|
4230
|
+
"task:usage",
|
|
4231
|
+
"session-limit-reached",
|
|
4232
|
+
"agent:rate_limited",
|
|
4233
|
+
// LIM-02: о спасённой ветке сервер узнаёт отсюда же.
|
|
4234
|
+
"task:work_saved"
|
|
4235
|
+
]);
|
|
4179
4236
|
function sendHookActions(actions, options = {}) {
|
|
4180
4237
|
const wireActions = actions.filter((action) => WIRE_EVENTS.has(action.type));
|
|
4181
4238
|
if (wireActions.length === 0) return;
|
|
@@ -4360,1297 +4417,1528 @@ function renderHookOutput(format, hook, output) {
|
|
|
4360
4417
|
return { stdout: "{}", stderr: "", exitCode: 0 };
|
|
4361
4418
|
}
|
|
4362
4419
|
|
|
4363
|
-
// src/
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4420
|
+
// src/git/exec.ts
|
|
4421
|
+
import { execFile } from "child_process";
|
|
4422
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
4423
|
+
var MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
4424
|
+
function runGit(args, options) {
|
|
4425
|
+
return new Promise((resolvePromise) => {
|
|
4426
|
+
execFile(
|
|
4427
|
+
"git",
|
|
4428
|
+
[...args],
|
|
4429
|
+
{
|
|
4430
|
+
cwd: options.cwd,
|
|
4431
|
+
shell: false,
|
|
4432
|
+
timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
4433
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
4434
|
+
env: {
|
|
4435
|
+
...process.env,
|
|
4436
|
+
...options.env,
|
|
4437
|
+
// Без человека за терминалом: если git вдруг решит спросить
|
|
4438
|
+
// логин/пароль, зависший prompt выглядел бы как неожиданное
|
|
4439
|
+
// поведение (требование подзадачи: «любая неожиданность —
|
|
4440
|
+
// понятный отказ, а не попробую починить»), а не как честный
|
|
4441
|
+
// отказ команды с кодом возврата.
|
|
4442
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
4443
|
+
}
|
|
4444
|
+
},
|
|
4445
|
+
(error, stdout, stderr) => {
|
|
4446
|
+
if (!error) {
|
|
4447
|
+
resolvePromise({ ok: true, code: 0, signal: null, stdout, stderr });
|
|
4448
|
+
return;
|
|
4449
|
+
}
|
|
4450
|
+
const code = typeof error.code === "number" ? error.code : null;
|
|
4451
|
+
resolvePromise({
|
|
4452
|
+
ok: false,
|
|
4453
|
+
code,
|
|
4454
|
+
signal: error.signal ?? null,
|
|
4455
|
+
stdout,
|
|
4456
|
+
// Если git ничего не написал в stderr (например, сам процесс не
|
|
4457
|
+
// запустился — ENOENT), берём текст ошибки Node, чтобы вызывающий
|
|
4458
|
+
// код не остался с пустым сообщением.
|
|
4459
|
+
stderr: stderr.length > 0 ? stderr : error.message
|
|
4460
|
+
});
|
|
4461
|
+
}
|
|
4462
|
+
);
|
|
4463
|
+
});
|
|
4368
4464
|
}
|
|
4369
|
-
function
|
|
4370
|
-
|
|
4465
|
+
function describeGitFailure(commandLabel, result) {
|
|
4466
|
+
const stderr = result.stderr.trim();
|
|
4467
|
+
const codeText = result.code === null ? "" : ` (\u043A\u043E\u0434 ${result.code})`;
|
|
4468
|
+
return stderr.length > 0 ? `${commandLabel}${codeText}: ${stderr}` : `${commandLabel} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439${codeText}`;
|
|
4371
4469
|
}
|
|
4372
4470
|
|
|
4373
|
-
// src/
|
|
4374
|
-
function
|
|
4375
|
-
|
|
4376
|
-
const maxMs = options.maxMs ?? 3e4;
|
|
4377
|
-
const safeAttempt = Math.max(0, Math.trunc(attempt));
|
|
4378
|
-
const exponent = Math.min(safeAttempt, 30);
|
|
4379
|
-
const raw = baseMs * 2 ** exponent;
|
|
4380
|
-
return Math.min(raw, maxMs);
|
|
4471
|
+
// src/git/result.ts
|
|
4472
|
+
function gitOk(data) {
|
|
4473
|
+
return { ok: true, data };
|
|
4381
4474
|
}
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
import { EventEmitter } from "events";
|
|
4385
|
-
|
|
4386
|
-
// src/socket/emit-with-timeout.ts
|
|
4387
|
-
function toError(raw, context) {
|
|
4388
|
-
return raw instanceof Error ? raw : new Error(`${context}: ${String(raw)}`);
|
|
4475
|
+
function gitErr(message) {
|
|
4476
|
+
return { ok: false, message };
|
|
4389
4477
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
4396
|
-
socket.emitWithAck(event, payload).then((value) => {
|
|
4397
|
-
clearTimeout(timer);
|
|
4398
|
-
resolve4(value);
|
|
4399
|
-
}).catch((err) => {
|
|
4400
|
-
clearTimeout(timer);
|
|
4401
|
-
reject(toError(err, event));
|
|
4402
|
-
});
|
|
4478
|
+
|
|
4479
|
+
// src/git/repo.ts
|
|
4480
|
+
async function localBranchExists(repoRoot, branch) {
|
|
4481
|
+
const result = await runGit(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
4482
|
+
cwd: repoRoot
|
|
4403
4483
|
});
|
|
4484
|
+
return result.ok;
|
|
4404
4485
|
}
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
updatedAt: now().toISOString(),
|
|
4412
|
-
rules
|
|
4413
|
-
};
|
|
4486
|
+
async function getHeadSha(worktreeDir) {
|
|
4487
|
+
const result = await runGit(["rev-parse", "HEAD"], { cwd: worktreeDir });
|
|
4488
|
+
if (!result.ok) {
|
|
4489
|
+
return gitErr(describeGitFailure("git rev-parse HEAD", result));
|
|
4490
|
+
}
|
|
4491
|
+
return gitOk(result.stdout.trim());
|
|
4414
4492
|
}
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4493
|
+
async function currentBranch(worktreeDir) {
|
|
4494
|
+
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreeDir });
|
|
4495
|
+
if (!result.ok) {
|
|
4496
|
+
return gitErr(describeGitFailure("git rev-parse --abbrev-ref HEAD", result));
|
|
4497
|
+
}
|
|
4498
|
+
return gitOk(result.stdout.trim());
|
|
4419
4499
|
}
|
|
4420
4500
|
|
|
4421
|
-
// src/
|
|
4422
|
-
var
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
if (
|
|
4428
|
-
|
|
4501
|
+
// src/git/status.ts
|
|
4502
|
+
var CONFLICT_CODES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
4503
|
+
async function getWorktreeStatus(worktreeDir) {
|
|
4504
|
+
const result = await runGit(["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
4505
|
+
cwd: worktreeDir
|
|
4506
|
+
});
|
|
4507
|
+
if (!result.ok) {
|
|
4508
|
+
return gitErr(describeGitFailure("git status", result));
|
|
4509
|
+
}
|
|
4510
|
+
const changedPaths = [];
|
|
4511
|
+
const conflictedPaths = [];
|
|
4512
|
+
for (const line of result.stdout.split("\n")) {
|
|
4513
|
+
if (line.length === 0) continue;
|
|
4514
|
+
const code = line.slice(0, 2);
|
|
4515
|
+
const rawPath = line.slice(3);
|
|
4516
|
+
const arrowIndex = rawPath.indexOf(" -> ");
|
|
4517
|
+
const path = arrowIndex >= 0 ? rawPath.slice(arrowIndex + 4) : rawPath;
|
|
4518
|
+
changedPaths.push(path);
|
|
4519
|
+
if (CONFLICT_CODES.has(code)) conflictedPaths.push(path);
|
|
4520
|
+
}
|
|
4521
|
+
return gitOk({ clean: changedPaths.length === 0, changedPaths, conflictedPaths });
|
|
4429
4522
|
}
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
// По умолчанию — файл ИМЕННО этого агента (state/store.ts::getStateFilePath(agentId)),
|
|
4439
|
-
// а не общий `state.json`: не трогаем src/state/**, но обязаны использовать
|
|
4440
|
-
// его правильно (OQ-05 — состояние на агента, не на машину).
|
|
4441
|
-
stateFilePath: options.stateFilePath ?? getStateFilePath(options.agentId),
|
|
4442
|
-
heartbeatIntervalMs: options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
4443
|
-
ackTimeoutMs: options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS,
|
|
4444
|
-
backoffBaseMs: options.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,
|
|
4445
|
-
backoffMaxMs: options.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS,
|
|
4446
|
-
now: options.now ?? (() => /* @__PURE__ */ new Date())
|
|
4447
|
-
};
|
|
4523
|
+
|
|
4524
|
+
// src/git/commit.ts
|
|
4525
|
+
function buildCommitMessage(params) {
|
|
4526
|
+
const subject = `${params.commitPrefix} ${params.title}`.trim();
|
|
4527
|
+
return `${subject}
|
|
4528
|
+
|
|
4529
|
+
${buildTaskTrailer(params.taskKey)}
|
|
4530
|
+
`;
|
|
4448
4531
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
stopped = true;
|
|
4454
|
-
reconnectAttempt = 0;
|
|
4455
|
-
reconnectTimer;
|
|
4456
|
-
heartbeatTimer;
|
|
4457
|
-
lastHelloAck;
|
|
4458
|
-
paused = false;
|
|
4459
|
-
pauseReasonText;
|
|
4460
|
-
leaseLostTasks = /* @__PURE__ */ new Map();
|
|
4461
|
-
constructor(options) {
|
|
4462
|
-
super();
|
|
4463
|
-
this.opts = resolveOptions(options);
|
|
4464
|
-
this.agentId = options.agentId;
|
|
4465
|
-
const factory = options.createSocket ?? createSocket;
|
|
4466
|
-
this.socket = factory(`${trimTrailingSlash2(options.url)}/agent`, {
|
|
4467
|
-
auth: { token: options.token },
|
|
4468
|
-
autoConnect: false,
|
|
4469
|
-
reconnection: false
|
|
4470
|
-
});
|
|
4471
|
-
this.registerSocketListeners();
|
|
4472
|
-
}
|
|
4473
|
-
// -- Публичное API -----------------------------------------------------
|
|
4474
|
-
on(event, listener) {
|
|
4475
|
-
return super.on(event, listener);
|
|
4532
|
+
async function commitAll(params) {
|
|
4533
|
+
const addResult = await runGit(["add", "-A"], { cwd: params.worktreeDir });
|
|
4534
|
+
if (!addResult.ok) {
|
|
4535
|
+
return gitErr(describeGitFailure("git add -A", addResult));
|
|
4476
4536
|
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4537
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4538
|
+
if (!status.ok) return status;
|
|
4539
|
+
if (status.data.clean) {
|
|
4540
|
+
return gitOk({ committed: false, sha: null });
|
|
4479
4541
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4542
|
+
const message = buildCommitMessage(params);
|
|
4543
|
+
const commitResult = await runGit(["commit", "-m", message], { cwd: params.worktreeDir });
|
|
4544
|
+
if (!commitResult.ok) {
|
|
4545
|
+
return gitErr(describeGitFailure("git commit", commitResult));
|
|
4482
4546
|
}
|
|
4483
|
-
|
|
4484
|
-
|
|
4547
|
+
const sha = await getHeadSha(params.worktreeDir);
|
|
4548
|
+
return gitOk({ committed: true, sha: sha.ok ? sha.data : null });
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4551
|
+
// src/git/branch-name.ts
|
|
4552
|
+
var FORBIDDEN_CHARS = /[\s~^:?*[\\\x00-\x1f\x7f]/;
|
|
4553
|
+
function isValidGitBranchName(name) {
|
|
4554
|
+
if (name.length === 0 || name.length > 255) return false;
|
|
4555
|
+
if (name.startsWith("-")) return false;
|
|
4556
|
+
if (name.startsWith("/") || name.endsWith("/")) return false;
|
|
4557
|
+
if (name.endsWith(".")) return false;
|
|
4558
|
+
if (name.includes("..")) return false;
|
|
4559
|
+
if (name.includes("//")) return false;
|
|
4560
|
+
if (name.includes("@{")) return false;
|
|
4561
|
+
if (name === "@") return false;
|
|
4562
|
+
if (FORBIDDEN_CHARS.test(name)) return false;
|
|
4563
|
+
const segments = name.split("/");
|
|
4564
|
+
for (const segment of segments) {
|
|
4565
|
+
if (segment.length === 0) return false;
|
|
4566
|
+
if (segment.startsWith(".")) return false;
|
|
4567
|
+
if (segment.endsWith(".lock")) return false;
|
|
4485
4568
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
}
|
|
4494
|
-
/** Останавливает heartbeat, отменяет запланированное переподключение, закрывает сокет. После `stop()` клиент больше не переподключается сам. */
|
|
4495
|
-
stop() {
|
|
4496
|
-
this.stopped = true;
|
|
4497
|
-
if (this.reconnectTimer) {
|
|
4498
|
-
clearTimeout(this.reconnectTimer);
|
|
4499
|
-
this.reconnectTimer = void 0;
|
|
4500
|
-
}
|
|
4501
|
-
this.stopHeartbeatTimer();
|
|
4502
|
-
this.socket.disconnect();
|
|
4569
|
+
return true;
|
|
4570
|
+
}
|
|
4571
|
+
function requireValidBranchName(name, label) {
|
|
4572
|
+
if (!isValidGitBranchName(name)) {
|
|
4573
|
+
return gitErr(
|
|
4574
|
+
`${label} "${name}" \u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0438\u043C\u044F git-\u0432\u0435\u0442\u043A\u0438 \u2014 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F.`
|
|
4575
|
+
);
|
|
4503
4576
|
}
|
|
4504
|
-
|
|
4505
|
-
|
|
4577
|
+
return gitOk(name);
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
// src/git/worktree.ts
|
|
4581
|
+
import { createHash as createHash2 } from "crypto";
|
|
4582
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, realpathSync } from "fs";
|
|
4583
|
+
import { dirname as dirname4, isAbsolute, join as join9, relative, resolve as resolve2 } from "path";
|
|
4584
|
+
var WORKTREES_RELATIVE_ROOT = join9(".claude", "worktrees");
|
|
4585
|
+
function getDefaultWorktreesRoot(repoRoot) {
|
|
4586
|
+
return join9(repoRoot, WORKTREES_RELATIVE_ROOT);
|
|
4587
|
+
}
|
|
4588
|
+
function resolveWorktreeDir(worktreesRoot, repoRoot, branchName) {
|
|
4589
|
+
if (isInsideRepo(worktreesRoot, repoRoot)) return join9(worktreesRoot, branchName);
|
|
4590
|
+
const repoHash = createHash2("sha1").update(resolve2(repoRoot)).digest("hex").slice(0, 12);
|
|
4591
|
+
return join9(worktreesRoot, repoHash, branchName);
|
|
4592
|
+
}
|
|
4593
|
+
function isInsideRepo(candidate, repoRoot) {
|
|
4594
|
+
const rel = relative(resolve2(repoRoot), resolve2(candidate));
|
|
4595
|
+
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
4596
|
+
}
|
|
4597
|
+
function canonicalPath(path) {
|
|
4598
|
+
try {
|
|
4599
|
+
return realpathSync(path);
|
|
4600
|
+
} catch {
|
|
4601
|
+
return resolve2(path);
|
|
4506
4602
|
}
|
|
4507
|
-
|
|
4508
|
-
|
|
4603
|
+
}
|
|
4604
|
+
async function listWorktrees(repoRoot) {
|
|
4605
|
+
const result = await runGit(["worktree", "list", "--porcelain"], { cwd: repoRoot });
|
|
4606
|
+
if (!result.ok) {
|
|
4607
|
+
return gitErr(describeGitFailure("git worktree list", result));
|
|
4509
4608
|
}
|
|
4510
|
-
|
|
4511
|
-
|
|
4609
|
+
const entries = [];
|
|
4610
|
+
let path;
|
|
4611
|
+
let branch = null;
|
|
4612
|
+
const flush = () => {
|
|
4613
|
+
if (path) entries.push({ path, branch });
|
|
4614
|
+
path = void 0;
|
|
4615
|
+
branch = null;
|
|
4616
|
+
};
|
|
4617
|
+
for (const line of result.stdout.split("\n")) {
|
|
4618
|
+
if (line.length === 0) {
|
|
4619
|
+
flush();
|
|
4620
|
+
continue;
|
|
4621
|
+
}
|
|
4622
|
+
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
4623
|
+
else if (line.startsWith("branch refs/heads/")) branch = line.slice("branch refs/heads/".length);
|
|
4512
4624
|
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4625
|
+
flush();
|
|
4626
|
+
return gitOk(entries);
|
|
4627
|
+
}
|
|
4628
|
+
async function ensureWorktree(params) {
|
|
4629
|
+
const remote = params.remote ?? "origin";
|
|
4630
|
+
const branchCheck = requireValidBranchName(params.branchName, "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438");
|
|
4631
|
+
if (!branchCheck.ok) return branchCheck;
|
|
4632
|
+
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
4633
|
+
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
4634
|
+
const worktreesRoot = params.worktreesRoot ?? getDefaultWorktreesRoot(params.repoRoot);
|
|
4635
|
+
const worktreeDir = resolveWorktreeDir(worktreesRoot, params.repoRoot, params.branchName);
|
|
4636
|
+
const list = await listWorktrees(params.repoRoot);
|
|
4637
|
+
if (!list.ok) return list;
|
|
4638
|
+
const existing = list.data.find((wt) => canonicalPath(wt.path) === canonicalPath(worktreeDir));
|
|
4639
|
+
if (existing) {
|
|
4640
|
+
if (existing.branch === params.branchName) {
|
|
4641
|
+
return gitOk({ worktreeDir, reused: true });
|
|
4642
|
+
}
|
|
4643
|
+
return gitErr(
|
|
4644
|
+
`\u041F\u043E \u043F\u0443\u0442\u0438 ${worktreeDir} \u0443\u0436\u0435 \u0435\u0441\u0442\u044C git worktree \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 "${existing.branch ?? "(\u043E\u0442\u0441\u043E\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u044B\u0439 HEAD)"}", \u0430 \u043E\u0436\u0438\u0434\u0430\u043B\u0430\u0441\u044C "${params.branchName}". \u0420\u0430\u0437\u0431\u0435\u0440\u0438\u0441\u044C \u0440\u0443\u043A\u0430\u043C\u0438: "git -C ${params.repoRoot} worktree remove ${worktreeDir}" (\u0434\u043E\u0431\u0430\u0432\u044C "--force", \u0435\u0441\u043B\u0438 \u0442\u0430\u043C \u043D\u0435\u0442 \u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u0430\u0436\u043D\u043E\u0433\u043E), \u0437\u0430\u0442\u0435\u043C \u043F\u043E\u0432\u0442\u043E\u0440\u0438.`
|
|
4645
|
+
);
|
|
4515
4646
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4647
|
+
if (existsSync9(worktreeDir)) {
|
|
4648
|
+
return gitErr(
|
|
4649
|
+
`\u041F\u0443\u0442\u044C ${worktreeDir} \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E git \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u0432\u043E\u0435\u0439 worktree. \u0423\u0431\u0435\u0440\u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u2014 \u0442\u0440\u043E\u0433\u0430\u0442\u044C \u0447\u0443\u0436\u043E\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E.`
|
|
4650
|
+
);
|
|
4520
4651
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
* есть ВСЁ, что не `agent:hello`/`agent:heartbeat`/`agent:mode` (у
|
|
4528
|
-
* первых двух своя логика согласования состояния в этом классе, третий
|
|
4529
|
-
* — короткоживущий отдельный сокет, см. `send-agent-mode.ts`). Нарочно
|
|
4530
|
-
* НЕ отдельное соединение: `task:claim` — блокирующий долгий long-poll,
|
|
4531
|
-
* и держать его на том же сокете, где уже идёт heartbeat/presence этого
|
|
4532
|
-
* агента, а не поднимать вторую параллельную сессию — единственный
|
|
4533
|
-
* вариант, не удваивающий presence на сервере.
|
|
4534
|
-
*/
|
|
4535
|
-
callServer(event, payload, timeoutMs = this.opts.ackTimeoutMs) {
|
|
4536
|
-
return emitWithAckTimeout(this.socket, event, payload, timeoutMs);
|
|
4652
|
+
await runGit(["worktree", "prune"], { cwd: params.repoRoot });
|
|
4653
|
+
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
4654
|
+
cwd: params.repoRoot
|
|
4655
|
+
});
|
|
4656
|
+
if (!fetchResult.ok) {
|
|
4657
|
+
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
4537
4658
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
* это только ограничение по времени для вызывающей стороны (`connect.ts`).
|
|
4543
|
-
*/
|
|
4544
|
-
waitForFirstConnection(timeoutMs = 5e3) {
|
|
4545
|
-
return new Promise((resolve4) => {
|
|
4546
|
-
let settled = false;
|
|
4547
|
-
const onConnected = (ack) => {
|
|
4548
|
-
if (settled) return;
|
|
4549
|
-
settled = true;
|
|
4550
|
-
clearTimeout(timer);
|
|
4551
|
-
this.off("connected", onConnected);
|
|
4552
|
-
resolve4({ ok: true, ack });
|
|
4553
|
-
};
|
|
4554
|
-
const onAuthRejected = (message) => {
|
|
4555
|
-
if (settled) return;
|
|
4556
|
-
settled = true;
|
|
4557
|
-
clearTimeout(timer);
|
|
4558
|
-
this.off("connected", onConnected);
|
|
4559
|
-
this.off("auth-rejected", onAuthRejected);
|
|
4560
|
-
resolve4({ ok: false, message });
|
|
4561
|
-
};
|
|
4562
|
-
const timer = setTimeout(() => {
|
|
4563
|
-
if (settled) return;
|
|
4564
|
-
settled = true;
|
|
4565
|
-
this.off("connected", onConnected);
|
|
4566
|
-
this.off("auth-rejected", onAuthRejected);
|
|
4567
|
-
resolve4({
|
|
4568
|
-
ok: false,
|
|
4569
|
-
message: `\u041D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430 agent:hello \u0437\u0430 ${timeoutMs} \u043C\u0441 \u2014 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430. \u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0432 \u0444\u043E\u043D\u0435 (backoff \xA711.4 \u0422\u0417).`
|
|
4570
|
-
});
|
|
4571
|
-
}, timeoutMs);
|
|
4572
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
4573
|
-
this.on("connected", onConnected);
|
|
4574
|
-
this.on("auth-rejected", onAuthRejected);
|
|
4659
|
+
let startPoint = `${remote}/${params.defaultBranch}`;
|
|
4660
|
+
if (params.continueFromBranch) {
|
|
4661
|
+
const continued = await runGit(["fetch", remote, params.continueFromBranch], {
|
|
4662
|
+
cwd: params.repoRoot
|
|
4575
4663
|
});
|
|
4664
|
+
if (continued.ok) startPoint = `${remote}/${params.continueFromBranch}`;
|
|
4576
4665
|
}
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
this.socket.on("mode:set", (raw) => this.handleModeSet(raw));
|
|
4584
|
-
this.socket.on("agent:pause", (raw) => this.handleAgentPause(raw));
|
|
4585
|
-
this.socket.on("trust:updated", (raw) => this.handleTrustUpdated(raw));
|
|
4586
|
-
this.socket.on("task:updated", (raw) => this.handleTaskUpdated(raw));
|
|
4587
|
-
this.socket.on("question:answered", (raw) => this.handleQuestionAnswered(raw));
|
|
4588
|
-
this.socket.on("approval:decided", (raw) => this.handleApprovalDecided(raw));
|
|
4666
|
+
mkdirSync6(dirname4(worktreeDir), { recursive: true });
|
|
4667
|
+
const branchExists = await localBranchExists(params.repoRoot, params.branchName);
|
|
4668
|
+
const addArgs = branchExists ? ["worktree", "add", worktreeDir, params.branchName] : ["worktree", "add", "-b", params.branchName, worktreeDir, startPoint];
|
|
4669
|
+
const addResult = await runGit(addArgs, { cwd: params.repoRoot });
|
|
4670
|
+
if (!addResult.ok) {
|
|
4671
|
+
return gitErr(describeGitFailure("git worktree add", addResult));
|
|
4589
4672
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4673
|
+
return gitOk({ worktreeDir, reused: false });
|
|
4674
|
+
}
|
|
4675
|
+
async function removeWorktree(repoRoot, worktreeDir) {
|
|
4676
|
+
const result = await runGit(["worktree", "remove", worktreeDir], { cwd: repoRoot });
|
|
4677
|
+
if (!result.ok) {
|
|
4678
|
+
return gitErr(describeGitFailure("git worktree remove", result));
|
|
4594
4679
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4680
|
+
return gitOk(void 0);
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
// src/git/submit.ts
|
|
4684
|
+
async function rebaseOntoDefault(params) {
|
|
4685
|
+
const remote = params.remote ?? "origin";
|
|
4686
|
+
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
4687
|
+
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
4688
|
+
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
4689
|
+
cwd: params.worktreeDir
|
|
4690
|
+
});
|
|
4691
|
+
if (!fetchResult.ok) {
|
|
4692
|
+
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
4603
4693
|
}
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4694
|
+
const rebaseResult = await runGit(["rebase", `${remote}/${params.defaultBranch}`], {
|
|
4695
|
+
cwd: params.worktreeDir
|
|
4696
|
+
});
|
|
4697
|
+
if (rebaseResult.ok) {
|
|
4698
|
+
return gitOk({ outcome: "rebased", conflictedFiles: [] });
|
|
4608
4699
|
}
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
const
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
if (this.stopped) return;
|
|
4620
|
-
this.socket.connect();
|
|
4621
|
-
}, delayMs);
|
|
4700
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4701
|
+
if (status.ok && status.data.conflictedPaths.length > 0) {
|
|
4702
|
+
const conflictedFiles = status.data.conflictedPaths;
|
|
4703
|
+
const abortResult = await runGit(["rebase", "--abort"], { cwd: params.worktreeDir });
|
|
4704
|
+
if (!abortResult.ok) {
|
|
4705
|
+
return gitErr(
|
|
4706
|
+
`\u041A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u0440\u0438 rebase (${conflictedFiles.join(", ")}), \u0438 "git rebase --abort" \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u043B: ${describeGitFailure("git rebase --abort", abortResult)}. Worktree \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u043D\u0443\u0436\u043D\u043E \u0440\u0443\u0447\u043D\u043E\u0435 \u0432\u043C\u0435\u0448\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E.`
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4709
|
+
return gitOk({ outcome: "conflict", conflictedFiles });
|
|
4622
4710
|
}
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
// `claudeVersion` шлётся ТОЛЬКО для Claude Code — и только чтобы
|
|
4633
|
-
// клиент работал против ещё не обновлённого сервера, который знает
|
|
4634
|
-
// одно это поле. Для Codex поле с таким именем было бы неправдой.
|
|
4635
|
-
...this.opts.agentKind === AgentKind.CLAUDE_CODE ? { claudeVersion: this.opts.agentVersion } : {},
|
|
4636
|
-
roleKeys: [...this.opts.roleKeys],
|
|
4637
|
-
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
|
|
4638
|
-
});
|
|
4639
|
-
let raw;
|
|
4640
|
-
try {
|
|
4641
|
-
raw = await emitWithAckTimeout(this.socket, "agent:hello", payload, this.opts.ackTimeoutMs);
|
|
4642
|
-
} catch (err) {
|
|
4643
|
-
this.emit("client-error", toError2(err, "agent:hello"));
|
|
4644
|
-
return;
|
|
4645
|
-
}
|
|
4646
|
-
if (this.stopped) return;
|
|
4647
|
-
if (isAckError(raw)) {
|
|
4648
|
-
this.emit("client-error", new Error(`agent:hello: ${raw.error.code}: ${raw.error.message}`));
|
|
4649
|
-
return;
|
|
4650
|
-
}
|
|
4651
|
-
const parsed = agentHelloAckSchema.safeParse(raw);
|
|
4652
|
-
if (!parsed.success) {
|
|
4653
|
-
this.emit(
|
|
4654
|
-
"client-error",
|
|
4655
|
-
new Error(`agent:hello: \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}`)
|
|
4656
|
-
);
|
|
4657
|
-
return;
|
|
4658
|
-
}
|
|
4659
|
-
const ack = parsed.data;
|
|
4660
|
-
this.lastHelloAck = ack;
|
|
4661
|
-
this.paused = false;
|
|
4662
|
-
this.pauseReasonText = void 0;
|
|
4663
|
-
this.applyHelloAck(ack);
|
|
4664
|
-
this.emit("connected", ack);
|
|
4665
|
-
}
|
|
4666
|
-
applyHelloAck(ack) {
|
|
4667
|
-
const state = readState(this.opts.stateFilePath);
|
|
4668
|
-
const patch = {
|
|
4669
|
-
// OQ-06: сервер — источник правды, локальный режим перекрывается безусловно.
|
|
4670
|
-
mode: ack.mode,
|
|
4671
|
-
trust: toTrustCache(ack.trust, this.opts.now)
|
|
4672
|
-
};
|
|
4673
|
-
if (!ack.currentTask || ack.currentTask.id !== state.currentTask?.taskId) {
|
|
4674
|
-
patch.currentTask = null;
|
|
4675
|
-
}
|
|
4676
|
-
updateState(patch, this.opts.stateFilePath);
|
|
4711
|
+
return gitErr(describeGitFailure("git rebase", rebaseResult));
|
|
4712
|
+
}
|
|
4713
|
+
async function assertReadyToPush(params) {
|
|
4714
|
+
const branch = await currentBranch(params.worktreeDir);
|
|
4715
|
+
if (!branch.ok) return branch;
|
|
4716
|
+
if (branch.data !== params.branchName) {
|
|
4717
|
+
return gitErr(
|
|
4718
|
+
`\u041E\u0442\u043A\u0430\u0437: worktree \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u043D\u0430 \u0432\u0435\u0442\u043A\u0443 "${branch.data}", \u0430 \u043E\u0436\u0438\u0434\u0430\u043B\u0430\u0441\u044C "${params.branchName}" \u2014 \u0440\u0430\u0431\u043E\u0447\u0435\u0435 \u0434\u0435\u0440\u0435\u0432\u043E "\u0443\u0448\u043B\u043E \u043D\u0435 \u0442\u0443\u0434\u0430", push \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F.`
|
|
4719
|
+
);
|
|
4677
4720
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4721
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4722
|
+
if (!status.ok) return status;
|
|
4723
|
+
if (!status.data.clean) {
|
|
4724
|
+
return gitErr(
|
|
4725
|
+
`\u041E\u0442\u043A\u0430\u0437: \u0440\u0430\u0431\u043E\u0447\u0435\u0435 \u0434\u0435\u0440\u0435\u0432\u043E \u0433\u0440\u044F\u0437\u043D\u043E\u0435, \u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u044B\u0435 \u043F\u0443\u0442\u0438: ${status.data.changedPaths.join(", ")}. Push \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F.`
|
|
4726
|
+
);
|
|
4684
4727
|
}
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4728
|
+
return gitOk(void 0);
|
|
4729
|
+
}
|
|
4730
|
+
async function pushFastForward(params) {
|
|
4731
|
+
const remote = params.remote ?? "origin";
|
|
4732
|
+
const branchCheck = requireValidBranchName(params.branchName, "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438");
|
|
4733
|
+
if (!branchCheck.ok) return branchCheck;
|
|
4734
|
+
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
4735
|
+
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
4736
|
+
const ready = await assertReadyToPush(params);
|
|
4737
|
+
if (!ready.ok) return ready;
|
|
4738
|
+
const pushResult = await runGit(["push", remote, `HEAD:refs/heads/${params.defaultBranch}`], {
|
|
4739
|
+
cwd: params.worktreeDir
|
|
4740
|
+
});
|
|
4741
|
+
if (!pushResult.ok) {
|
|
4742
|
+
return gitErr(
|
|
4743
|
+
describeGitFailure(`git push ${remote} HEAD:refs/heads/${params.defaultBranch}`, pushResult)
|
|
4744
|
+
);
|
|
4689
4745
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4746
|
+
return gitOk({ pushedTo: `${remote}/${params.defaultBranch}` });
|
|
4747
|
+
}
|
|
4748
|
+
async function submitDirectTask(params) {
|
|
4749
|
+
const remote = params.remote ?? "origin";
|
|
4750
|
+
let worktreeDir;
|
|
4751
|
+
if (params.worktreeDir) {
|
|
4752
|
+
worktreeDir = params.worktreeDir;
|
|
4753
|
+
} else {
|
|
4754
|
+
const worktree = await ensureWorktree({
|
|
4755
|
+
repoRoot: params.repoRoot,
|
|
4756
|
+
branchName: params.branchName,
|
|
4757
|
+
defaultBranch: params.defaultBranch,
|
|
4758
|
+
remote,
|
|
4759
|
+
worktreesRoot: params.worktreesRoot
|
|
4697
4760
|
});
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
raw = await emitWithAckTimeout(
|
|
4701
|
-
this.socket,
|
|
4702
|
-
"agent:heartbeat",
|
|
4703
|
-
payload,
|
|
4704
|
-
this.opts.ackTimeoutMs
|
|
4705
|
-
);
|
|
4706
|
-
} catch (err) {
|
|
4707
|
-
this.emit("client-error", toError2(err, "agent:heartbeat"));
|
|
4708
|
-
return;
|
|
4709
|
-
}
|
|
4710
|
-
if (isAckError(raw)) {
|
|
4711
|
-
this.emit(
|
|
4712
|
-
"client-error",
|
|
4713
|
-
new Error(`agent:heartbeat: ${raw.error.code}: ${raw.error.message}`)
|
|
4714
|
-
);
|
|
4715
|
-
return;
|
|
4716
|
-
}
|
|
4717
|
-
const parsed = agentHeartbeatAckSchema.safeParse(raw);
|
|
4718
|
-
if (!parsed.success) {
|
|
4719
|
-
this.emit(
|
|
4720
|
-
"client-error",
|
|
4721
|
-
new Error(`agent:heartbeat: \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}`)
|
|
4722
|
-
);
|
|
4723
|
-
return;
|
|
4724
|
-
}
|
|
4725
|
-
this.emit("heartbeat", parsed.data);
|
|
4726
|
-
}
|
|
4727
|
-
// -- Пуш-события сервера (§11.2 ТЗ) --------------------------------------
|
|
4728
|
-
handleLeaseLost(raw) {
|
|
4729
|
-
const parsed = leaseLostPayloadSchema.safeParse(raw);
|
|
4730
|
-
if (!parsed.success) {
|
|
4731
|
-
this.emit(
|
|
4732
|
-
"client-error",
|
|
4733
|
-
new Error(`lease:lost: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
4734
|
-
);
|
|
4735
|
-
return;
|
|
4736
|
-
}
|
|
4737
|
-
const { taskId, reason } = parsed.data;
|
|
4738
|
-
this.leaseLostTasks.set(taskId, reason);
|
|
4739
|
-
const state = readState(this.opts.stateFilePath);
|
|
4740
|
-
if (state.currentTask?.taskId === taskId) {
|
|
4741
|
-
updateState({ currentTask: null }, this.opts.stateFilePath);
|
|
4742
|
-
}
|
|
4743
|
-
this.emit("lease:lost", parsed.data);
|
|
4761
|
+
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
4762
|
+
worktreeDir = worktree.data.worktreeDir;
|
|
4744
4763
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4764
|
+
const commit = await commitAll({
|
|
4765
|
+
worktreeDir,
|
|
4766
|
+
commitPrefix: params.commitPrefix,
|
|
4767
|
+
title: params.taskTitle,
|
|
4768
|
+
taskKey: params.taskKey
|
|
4769
|
+
});
|
|
4770
|
+
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
4771
|
+
const rebase = await rebaseOntoDefault({
|
|
4772
|
+
worktreeDir,
|
|
4773
|
+
defaultBranch: params.defaultBranch,
|
|
4774
|
+
remote
|
|
4775
|
+
});
|
|
4776
|
+
if (!rebase.ok) return { status: "refused", worktreeDir, message: rebase.message };
|
|
4777
|
+
if (rebase.data.outcome === "conflict") {
|
|
4778
|
+
const files = rebase.data.conflictedFiles;
|
|
4779
|
+
return {
|
|
4780
|
+
status: "conflict",
|
|
4781
|
+
worktreeDir,
|
|
4782
|
+
conflictedFiles: files,
|
|
4783
|
+
message: `Rebase \u043D\u0430 ${remote}/${params.defaultBranch} \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0432 \u0444\u0430\u0439\u043B\u0430\u0445: ${files.join(", ")}. Rebase \u043E\u0442\u043C\u0435\u043D\u0451\u043D ("git rebase --abort"), \u0432\u0435\u0442\u043A\u0430 "${params.branchName}" \u0438 \u0435\u0451 worktree \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u044B \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`
|
|
4784
|
+
};
|
|
4753
4785
|
}
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
);
|
|
4761
|
-
return;
|
|
4762
|
-
}
|
|
4763
|
-
this.paused = true;
|
|
4764
|
-
this.pauseReasonText = parsed.data.reason;
|
|
4765
|
-
this.emit("pause", parsed.data);
|
|
4786
|
+
if (params.requireTestsPassed && !params.testsPassed) {
|
|
4787
|
+
return {
|
|
4788
|
+
status: "refused",
|
|
4789
|
+
worktreeDir,
|
|
4790
|
+
message: "\u0414\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 requireTests=true, \u0430 testsPassed \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D (\u0438\u043B\u0438 false) \u2014 push \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F."
|
|
4791
|
+
};
|
|
4766
4792
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4793
|
+
const push = await pushFastForward({
|
|
4794
|
+
worktreeDir,
|
|
4795
|
+
branchName: params.branchName,
|
|
4796
|
+
defaultBranch: params.defaultBranch,
|
|
4797
|
+
remote
|
|
4798
|
+
});
|
|
4799
|
+
if (!push.ok) return { status: "refused", worktreeDir, message: push.message };
|
|
4800
|
+
const sha = await getHeadSha(worktreeDir);
|
|
4801
|
+
let worktreeRemoved = false;
|
|
4802
|
+
if (params.removeWorktreeOnSuccess !== false) {
|
|
4803
|
+
const removed = await removeWorktree(params.repoRoot, worktreeDir);
|
|
4804
|
+
if (removed.ok) {
|
|
4805
|
+
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
4806
|
+
worktreeRemoved = true;
|
|
4775
4807
|
}
|
|
4776
|
-
updateState({ trust: toTrustCache(parsed.data, this.opts.now) }, this.opts.stateFilePath);
|
|
4777
|
-
this.emit("trust:updated", parsed.data);
|
|
4778
|
-
}
|
|
4779
|
-
handleTaskUpdated(raw) {
|
|
4780
|
-
const parsed = taskUpdatedAgentPayloadSchema.safeParse(raw);
|
|
4781
|
-
if (parsed.success) this.emit("task:updated", parsed.data);
|
|
4782
4808
|
}
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4809
|
+
return {
|
|
4810
|
+
status: "pushed",
|
|
4811
|
+
worktreeDir,
|
|
4812
|
+
commitSha: sha.ok ? sha.data : null,
|
|
4813
|
+
pushedTo: push.data.pushedTo,
|
|
4814
|
+
worktreeRemoved
|
|
4815
|
+
};
|
|
4816
|
+
}
|
|
4817
|
+
|
|
4818
|
+
// src/git/submit-pr.ts
|
|
4819
|
+
async function pushTaskBranch(params) {
|
|
4820
|
+
const remote = params.remote ?? "origin";
|
|
4821
|
+
const branchCheck = requireValidBranchName(params.branchName, "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438");
|
|
4822
|
+
if (!branchCheck.ok) return branchCheck;
|
|
4823
|
+
const ready = await assertReadyToPush(params);
|
|
4824
|
+
if (!ready.ok) return ready;
|
|
4825
|
+
const pushResult = await runGit(
|
|
4826
|
+
["push", "--set-upstream", remote, `HEAD:refs/heads/${params.branchName}`],
|
|
4827
|
+
{ cwd: params.worktreeDir }
|
|
4828
|
+
);
|
|
4829
|
+
if (!pushResult.ok) {
|
|
4830
|
+
return gitErr(
|
|
4831
|
+
describeGitFailure(`git push ${remote} HEAD:refs/heads/${params.branchName}`, pushResult)
|
|
4832
|
+
);
|
|
4786
4833
|
}
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
*/
|
|
4805
|
-
handleApprovalDecided(raw) {
|
|
4806
|
-
const parsed = approvalDecidedPayloadSchema.safeParse(raw);
|
|
4807
|
-
if (!parsed.success) {
|
|
4808
|
-
this.emit(
|
|
4809
|
-
"client-error",
|
|
4810
|
-
new Error(`approval:decided: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
4811
|
-
);
|
|
4812
|
-
return;
|
|
4813
|
-
}
|
|
4814
|
-
this.applyApprovalDecided(parsed.data);
|
|
4815
|
-
this.emit("approval:decided", parsed.data);
|
|
4834
|
+
return gitOk({ pushedTo: `${remote}/${params.branchName}` });
|
|
4835
|
+
}
|
|
4836
|
+
async function submitPullRequestTask(params) {
|
|
4837
|
+
const remote = params.remote ?? "origin";
|
|
4838
|
+
let worktreeDir;
|
|
4839
|
+
if (params.worktreeDir) {
|
|
4840
|
+
worktreeDir = params.worktreeDir;
|
|
4841
|
+
} else {
|
|
4842
|
+
const worktree = await ensureWorktree({
|
|
4843
|
+
repoRoot: params.repoRoot,
|
|
4844
|
+
branchName: params.branchName,
|
|
4845
|
+
defaultBranch: params.defaultBranch,
|
|
4846
|
+
remote,
|
|
4847
|
+
worktreesRoot: params.worktreesRoot
|
|
4848
|
+
});
|
|
4849
|
+
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
4850
|
+
worktreeDir = worktree.data.worktreeDir;
|
|
4816
4851
|
}
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4852
|
+
const commit = await commitAll({
|
|
4853
|
+
worktreeDir,
|
|
4854
|
+
commitPrefix: params.commitPrefix,
|
|
4855
|
+
title: params.taskTitle,
|
|
4856
|
+
taskKey: params.taskKey
|
|
4857
|
+
});
|
|
4858
|
+
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
4859
|
+
const rebase = await rebaseOntoDefault({
|
|
4860
|
+
worktreeDir,
|
|
4861
|
+
defaultBranch: params.defaultBranch,
|
|
4862
|
+
remote
|
|
4863
|
+
});
|
|
4864
|
+
if (!rebase.ok) return { status: "refused", worktreeDir, message: rebase.message };
|
|
4865
|
+
const conflicted = rebase.data.outcome === "conflict";
|
|
4866
|
+
const push = await pushTaskBranch({ worktreeDir, branchName: params.branchName, remote });
|
|
4867
|
+
if (!push.ok) return { status: "refused", worktreeDir, message: push.message };
|
|
4868
|
+
const sha = await getHeadSha(worktreeDir);
|
|
4869
|
+
let worktreeRemoved = false;
|
|
4870
|
+
if (params.removeWorktreeOnSuccess !== false) {
|
|
4871
|
+
const removed = await removeWorktree(params.repoRoot, worktreeDir);
|
|
4872
|
+
if (removed.ok) {
|
|
4873
|
+
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
4874
|
+
worktreeRemoved = true;
|
|
4836
4875
|
}
|
|
4837
|
-
updateState(clearPendingApproval(state, payload.approvalId), this.opts.stateFilePath);
|
|
4838
4876
|
}
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4877
|
+
return {
|
|
4878
|
+
status: "pushed",
|
|
4879
|
+
worktreeDir,
|
|
4880
|
+
branchName: params.branchName,
|
|
4881
|
+
commitSha: sha.ok ? sha.data : null,
|
|
4882
|
+
pushedTo: push.data.pushedTo,
|
|
4883
|
+
conflicted,
|
|
4884
|
+
conflictedFiles: rebase.data.conflictedFiles,
|
|
4885
|
+
worktreeRemoved
|
|
4886
|
+
};
|
|
4842
4887
|
}
|
|
4843
4888
|
|
|
4844
|
-
// src/
|
|
4845
|
-
var
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4889
|
+
// src/git/rescue.ts
|
|
4890
|
+
var RescueReason = {
|
|
4891
|
+
/** Модель упёрлась в лимит подписки (`StopFailure`, matcher `rate_limit`). */
|
|
4892
|
+
RATE_LIMIT: "rate_limit",
|
|
4893
|
+
/** Сервер забрал задачу: аренда истекла или её отдали другому. */
|
|
4894
|
+
LEASE_LOST: "lease_lost"
|
|
4895
|
+
};
|
|
4896
|
+
var REASON_TEXT = {
|
|
4897
|
+
[RescueReason.RATE_LIMIT]: "\u043F\u0440\u0435\u0440\u0432\u0430\u043D\u043E: \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438",
|
|
4898
|
+
[RescueReason.LEASE_LOST]: "\u043F\u0440\u0435\u0440\u0432\u0430\u043D\u043E: \u0437\u0430\u0434\u0430\u0447\u0430 \u043E\u0442\u043E\u0437\u0432\u0430\u043D\u0430 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u043E\u0439"
|
|
4899
|
+
};
|
|
4900
|
+
async function rescueUnfinishedWork(params) {
|
|
4901
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4902
|
+
if (!status.ok) return status;
|
|
4903
|
+
if (status.data.clean) {
|
|
4904
|
+
return gitOk({ committed: false, sha: null, pushedTo: null });
|
|
4905
|
+
}
|
|
4906
|
+
const what = params.title?.trim();
|
|
4907
|
+
const commit = await commitAll({
|
|
4908
|
+
worktreeDir: params.worktreeDir,
|
|
4909
|
+
commitPrefix: params.commitPrefix,
|
|
4910
|
+
title: what ? `${what} (${REASON_TEXT[params.reason]})` : REASON_TEXT[params.reason],
|
|
4911
|
+
taskKey: params.taskKey
|
|
4856
4912
|
});
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
return { ok: false, message: "\u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B agent:mode (ok:false)" };
|
|
4870
|
-
}
|
|
4871
|
-
return { ok: true, mode: parsed.data.mode ?? options.mode };
|
|
4872
|
-
} catch (err) {
|
|
4873
|
-
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
4874
|
-
} finally {
|
|
4875
|
-
socket.disconnect();
|
|
4913
|
+
if (!commit.ok) return commit;
|
|
4914
|
+
if (!commit.data.committed) {
|
|
4915
|
+
return gitOk({ committed: false, sha: null, pushedTo: null });
|
|
4916
|
+
}
|
|
4917
|
+
const push = await pushTaskBranch({
|
|
4918
|
+
worktreeDir: params.worktreeDir,
|
|
4919
|
+
branchName: params.branchName
|
|
4920
|
+
});
|
|
4921
|
+
if (!push.ok) {
|
|
4922
|
+
return gitErr(
|
|
4923
|
+
`\u0420\u0430\u0431\u043E\u0442\u0430 \u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u0430 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E (${commit.data.sha ?? "\u0431\u0435\u0437 sha"}), \u043D\u043E \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0432\u0435\u0442\u043A\u0443 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${push.message}`
|
|
4924
|
+
);
|
|
4876
4925
|
}
|
|
4926
|
+
return gitOk({
|
|
4927
|
+
committed: true,
|
|
4928
|
+
sha: commit.data.sha,
|
|
4929
|
+
pushedTo: push.data.pushedTo
|
|
4930
|
+
});
|
|
4877
4931
|
}
|
|
4878
4932
|
|
|
4879
|
-
// src/
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4933
|
+
// src/hooks/stop-failure.ts
|
|
4934
|
+
import { z as z37 } from "zod";
|
|
4935
|
+
|
|
4936
|
+
// src/hooks/reset-time.ts
|
|
4937
|
+
var DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
4938
|
+
function parseClock(hour, minute, meridiem) {
|
|
4939
|
+
const lower = meridiem.toLowerCase();
|
|
4940
|
+
let h = hour % 12;
|
|
4941
|
+
if (lower === "pm") h += 12;
|
|
4942
|
+
return { h, m: minute };
|
|
4943
|
+
}
|
|
4944
|
+
function parseResetAt(message, now) {
|
|
4945
|
+
if (!message) return void 0;
|
|
4946
|
+
const withDay = message.match(
|
|
4947
|
+
/resets\s+(sun|mon|tue|wed|thu|fri|sat)[a-z]*\s+(\d{1,2}):(\d{2})\s*(am|pm)/i
|
|
4889
4948
|
);
|
|
4949
|
+
if (withDay) {
|
|
4950
|
+
const [, dayRaw = "", hourRaw = "", minuteRaw = "", meridiem = ""] = withDay;
|
|
4951
|
+
const targetDay = DAYS.indexOf(dayRaw.slice(0, 3).toLowerCase());
|
|
4952
|
+
if (targetDay === -1) return void 0;
|
|
4953
|
+
const { h, m } = parseClock(Number(hourRaw), Number(minuteRaw), meridiem);
|
|
4954
|
+
const result = new Date(now);
|
|
4955
|
+
result.setHours(h, m, 0, 0);
|
|
4956
|
+
let shift = (targetDay - result.getDay() + 7) % 7;
|
|
4957
|
+
if (shift === 0 && result.getTime() <= now.getTime()) shift = 7;
|
|
4958
|
+
result.setDate(result.getDate() + shift);
|
|
4959
|
+
return result;
|
|
4960
|
+
}
|
|
4961
|
+
const withTime = message.match(/resets\s+(\d{1,2}):(\d{2})\s*(am|pm)/i);
|
|
4962
|
+
if (withTime) {
|
|
4963
|
+
const [, hourRaw = "", minuteRaw = "", meridiem = ""] = withTime;
|
|
4964
|
+
const { h, m } = parseClock(Number(hourRaw), Number(minuteRaw), meridiem);
|
|
4965
|
+
const result = new Date(now);
|
|
4966
|
+
result.setHours(h, m, 0, 0);
|
|
4967
|
+
if (result.getTime() <= now.getTime()) result.setDate(result.getDate() + 1);
|
|
4968
|
+
return result;
|
|
4969
|
+
}
|
|
4970
|
+
return void 0;
|
|
4890
4971
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4972
|
+
|
|
4973
|
+
// src/hooks/stop-failure.ts
|
|
4974
|
+
var StopFailureErrorTypeSchema = z37.enum([
|
|
4975
|
+
"rate_limit",
|
|
4976
|
+
"overloaded",
|
|
4977
|
+
"authentication_failed",
|
|
4978
|
+
"oauth_org_not_allowed",
|
|
4979
|
+
"account_on_hold",
|
|
4980
|
+
"billing_error",
|
|
4981
|
+
"invalid_request",
|
|
4982
|
+
"model_not_found",
|
|
4983
|
+
"server_error",
|
|
4984
|
+
"max_output_tokens",
|
|
4985
|
+
// Добавлен 21 сентября 2026 по живой документации: его в нашем
|
|
4986
|
+
// перечислении не было, и `StopFailureInputSchema` отклонила бы вход с
|
|
4987
|
+
// ним целиком — хук отработал бы вхолостую, а сервер не узнал бы, что
|
|
4988
|
+
// у агента что-то случилось.
|
|
4989
|
+
"cloud_credential_error",
|
|
4990
|
+
"unknown"
|
|
4991
|
+
]);
|
|
4992
|
+
var StopFailureInputSchema = z37.object({
|
|
4993
|
+
hook_event_name: z37.literal("StopFailure"),
|
|
4994
|
+
session_id: z37.string(),
|
|
4995
|
+
cwd: z37.string(),
|
|
4996
|
+
error: StopFailureErrorTypeSchema,
|
|
4997
|
+
error_details: z37.unknown().optional(),
|
|
4998
|
+
last_assistant_message: z37.string().optional()
|
|
4999
|
+
}).passthrough();
|
|
5000
|
+
function extractRetryAfterSec(errorDetails) {
|
|
5001
|
+
if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
|
|
5002
|
+
return errorDetails;
|
|
5003
|
+
}
|
|
5004
|
+
if (errorDetails && typeof errorDetails === "object") {
|
|
5005
|
+
const record = errorDetails;
|
|
5006
|
+
for (const key of ["retryAfterSec", "retryAfter", "retry_after", "retry_after_seconds"]) {
|
|
5007
|
+
const value = record[key];
|
|
5008
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
if (typeof errorDetails === "string") {
|
|
5012
|
+
const match = errorDetails.match(/retry[-_ ]?after[^0-9]{0,10}(\d+)/i);
|
|
5013
|
+
if (match?.[1] !== void 0) return Number(match[1]);
|
|
5014
|
+
}
|
|
5015
|
+
return void 0;
|
|
4895
5016
|
}
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
5017
|
+
var RESCUE_WORK_ACTION = "rescue-work";
|
|
5018
|
+
function stopFailureHook(input, state) {
|
|
5019
|
+
const retryAfterSec = extractRetryAfterSec(input.error_details);
|
|
5020
|
+
const resetAt = parseResetAt(input.last_assistant_message, /* @__PURE__ */ new Date());
|
|
5021
|
+
const actions = [
|
|
5022
|
+
{
|
|
5023
|
+
type: "agent:rate_limited",
|
|
5024
|
+
payload: {
|
|
5025
|
+
error: input.error,
|
|
5026
|
+
...retryAfterSec === void 0 ? {} : { retryAfterSec },
|
|
5027
|
+
...resetAt ? { resetsAt: resetAt.toISOString() } : {}
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
];
|
|
5031
|
+
const task = state.currentTask;
|
|
5032
|
+
if (task?.worktreeDir && task.branchName && task.commitPrefix) {
|
|
5033
|
+
actions.push({
|
|
5034
|
+
type: RESCUE_WORK_ACTION,
|
|
5035
|
+
payload: {
|
|
5036
|
+
reason: "rate_limit",
|
|
5037
|
+
taskId: task.taskId,
|
|
5038
|
+
worktreeDir: task.worktreeDir,
|
|
5039
|
+
branchName: task.branchName,
|
|
5040
|
+
commitPrefix: task.commitPrefix,
|
|
5041
|
+
taskKey: task.taskKey ?? task.taskId
|
|
5042
|
+
}
|
|
5043
|
+
});
|
|
4900
5044
|
}
|
|
4901
|
-
return {
|
|
5045
|
+
return { output: {}, serverActions: actions };
|
|
4902
5046
|
}
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
5047
|
+
|
|
5048
|
+
// src/hooks/rescue-runner.ts
|
|
5049
|
+
var NOTHING = {
|
|
5050
|
+
attempted: false,
|
|
5051
|
+
committed: false,
|
|
5052
|
+
pushedTo: null,
|
|
5053
|
+
problem: null,
|
|
5054
|
+
report: null
|
|
5055
|
+
};
|
|
5056
|
+
function readParams(action) {
|
|
5057
|
+
const p = action.payload ?? {};
|
|
5058
|
+
const str = (key) => {
|
|
5059
|
+
const value = p[key];
|
|
5060
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
5061
|
+
};
|
|
5062
|
+
const worktreeDir = str("worktreeDir");
|
|
5063
|
+
const branchName = str("branchName");
|
|
5064
|
+
const commitPrefix = str("commitPrefix");
|
|
5065
|
+
const taskKey = str("taskKey");
|
|
5066
|
+
const taskId = str("taskId");
|
|
5067
|
+
if (!worktreeDir || !branchName || !commitPrefix || !taskKey || !taskId) return null;
|
|
5068
|
+
const reason = p.reason === RescueReason.LEASE_LOST ? RescueReason.LEASE_LOST : RescueReason.RATE_LIMIT;
|
|
5069
|
+
return { worktreeDir, branchName, commitPrefix, taskKey, taskId, reason };
|
|
5070
|
+
}
|
|
5071
|
+
async function runRescueAction(actions) {
|
|
5072
|
+
const action = actions.find((candidate) => candidate.type === RESCUE_WORK_ACTION);
|
|
5073
|
+
if (!action) return NOTHING;
|
|
5074
|
+
const params = readParams(action);
|
|
5075
|
+
if (!params) {
|
|
5076
|
+
return { ...NOTHING, attempted: true, problem: "\u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u043E \u0437\u0430\u0434\u0430\u0447\u0435" };
|
|
4912
5077
|
}
|
|
4913
|
-
|
|
5078
|
+
try {
|
|
5079
|
+
const result = await rescueUnfinishedWork(params);
|
|
5080
|
+
if (!result.ok) {
|
|
5081
|
+
return { ...NOTHING, attempted: true, problem: result.message };
|
|
5082
|
+
}
|
|
5083
|
+
const report = result.data.committed ? {
|
|
5084
|
+
type: "task:work_saved",
|
|
5085
|
+
payload: {
|
|
5086
|
+
taskId: params.taskId,
|
|
5087
|
+
branchName: params.branchName,
|
|
5088
|
+
...result.data.sha ? { sha: result.data.sha } : {}
|
|
5089
|
+
}
|
|
5090
|
+
} : null;
|
|
4914
5091
|
return {
|
|
4915
|
-
|
|
4916
|
-
|
|
5092
|
+
attempted: true,
|
|
5093
|
+
committed: result.data.committed,
|
|
5094
|
+
pushedTo: result.data.pushedTo,
|
|
5095
|
+
problem: null,
|
|
5096
|
+
report
|
|
4917
5097
|
};
|
|
4918
|
-
}
|
|
4919
|
-
const parsed = schema.safeParse(raw);
|
|
4920
|
-
if (!parsed.success) {
|
|
5098
|
+
} catch (error) {
|
|
4921
5099
|
return {
|
|
4922
|
-
|
|
4923
|
-
|
|
5100
|
+
...NOTHING,
|
|
5101
|
+
attempted: true,
|
|
5102
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
4924
5103
|
};
|
|
4925
5104
|
}
|
|
4926
|
-
|
|
5105
|
+
}
|
|
5106
|
+
function describeRescue(result) {
|
|
5107
|
+
if (!result.attempted) return null;
|
|
5108
|
+
if (result.problem) return `Quiel: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043D\u0435\u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D\u043D\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u2014 ${result.problem}`;
|
|
5109
|
+
if (!result.committed) return null;
|
|
5110
|
+
return `Quiel: \u043D\u0435\u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430${result.pushedTo ? ` \u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0430 \u0432 ${result.pushedTo}` : ""}`;
|
|
4927
5111
|
}
|
|
4928
5112
|
|
|
4929
|
-
// src/
|
|
4930
|
-
var
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
)
|
|
4934
|
-
targetRole: questionAskPayloadSchema.shape.targetRole.describe(
|
|
4935
|
-
'\u0420\u043E\u043B\u044C \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, "owner") \u2014 \u0432\u043E\u043F\u0440\u043E\u0441 \u0443\u0432\u0438\u0434\u044F\u0442 \u0432\u0441\u0435 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441 \u044D\u0442\u043E\u0439 \u0440\u043E\u043B\u044C\u044E, \u043E\u0442\u0432\u0435\u0442\u0438\u0442 \u043F\u0435\u0440\u0432\u044B\u0439. \u041D\u0443\u0436\u0435\u043D \u0420\u041E\u0412\u041D\u041E \u041E\u0414\u0418\u041D \u0430\u0434\u0440\u0435\u0441\u0430\u0442: targetRole \u0418\u041B\u0418 targetMemberId, \u043D\u0435 \u043E\u0431\u0430 \u0441\u0440\u0430\u0437\u0443 \u0438 \u043D\u0435 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E.'
|
|
4936
|
-
),
|
|
4937
|
-
targetMemberId: questionAskPayloadSchema.shape.targetMemberId.describe(
|
|
4938
|
-
"ID \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u0433\u043E \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0430\u0434\u0440\u0435\u0441\u0430\u0442 \u0432\u043E\u043F\u0440\u043E\u0441\u0430. \u041D\u0443\u0436\u0435\u043D \u0420\u041E\u0412\u041D\u041E \u041E\u0414\u0418\u041D \u0430\u0434\u0440\u0435\u0441\u0430\u0442: targetRole \u0418\u041B\u0418 targetMemberId, \u043D\u0435 \u043E\u0431\u0430 \u0441\u0440\u0430\u0437\u0443 \u0438 \u043D\u0435 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E."
|
|
4939
|
-
),
|
|
4940
|
-
wantsFiles: questionAskPayloadSchema.shape.wantsFiles.describe(
|
|
4941
|
-
"\u041F\u043E\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u043A \u043E\u0442\u0432\u0435\u0442\u0443 (\u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442, \u043B\u043E\u0433, \u0432\u044B\u0433\u0440\u0443\u0437\u043A\u0443). \u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u0441 \u0432\u044B\u0431\u043E\u0440\u043E\u043C \u0444\u0430\u0439\u043B\u0430. \u042D\u0442\u043E \u041F\u0420\u041E\u0421\u042C\u0411\u0410, \u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u0435: \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0432\u043F\u0440\u0430\u0432\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u0431\u0435\u0437 \u0444\u0430\u0439\u043B\u043E\u0432, \u0438 \u0442\u043E\u0433\u0434\u0430 `attachments` \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043F\u0443\u0441\u0442\u044B\u043C."
|
|
4942
|
-
),
|
|
4943
|
-
options: questionAskPayloadSchema.shape.options.describe(
|
|
4944
|
-
"\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u043E\u0442\u0432\u0435\u0442\u0430 (\u043F\u043E\u043A\u0430\u0436\u0443\u0442\u0441\u044F \u043A\u043D\u043E\u043F\u043A\u0430\u043C\u0438 \u0432 UI/Telegram). \u041D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439 \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043E\u0442\u0432\u0435\u0442\u0438\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C."
|
|
4945
|
-
),
|
|
4946
|
-
blocking: questionAskPayloadSchema.shape.blocking.describe(
|
|
4947
|
-
'true \u2014 \u0442\u0432\u043E\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441 WAITING_HUMAN, \u0438 \u044D\u0442\u043E\u0442 \u0432\u044B\u0437\u043E\u0432 \u0441\u0430\u043C \u0436\u0434\u0451\u0442 \u043E\u0442\u0432\u0435\u0442 \u0434\u043E 600 \u0441; \u0435\u0441\u043B\u0438 \u0437\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435\u0442, \u0432\u044B\u0437\u043E\u0432 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0441 \u043F\u043E\u043C\u0435\u0442\u043A\u043E\u0439 "pending" \u2014 \u0442\u043E\u0433\u0434\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId \u0438\u0437 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430, \u043D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430. false \u2014 \u0432\u043E\u043F\u0440\u043E\u0441 \u043F\u0440\u043E\u0441\u0442\u043E \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F, \u0431\u0435\u0437 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0438 \u0431\u0435\u0437 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 (\u0434\u043B\u044F \u043D\u0435 \u0441\u0440\u043E\u0447\u043D\u044B\u0445 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0439).'
|
|
4948
|
-
)
|
|
4949
|
-
};
|
|
4950
|
-
var BLOCKING_WAIT_SEC = 600;
|
|
4951
|
-
var ASK_ACK_BUFFER_MS = 15e3;
|
|
4952
|
-
async function askHumanHandler(args, runtime) {
|
|
4953
|
-
const state = readState(runtime.stateFilePath);
|
|
4954
|
-
const taskId = state.currentTask?.taskId;
|
|
4955
|
-
const waitSec = args.blocking ? BLOCKING_WAIT_SEC : 0;
|
|
4956
|
-
const parsedArgs = parseArgsOrError(questionAskPayloadSchema, {
|
|
4957
|
-
...taskId ? { taskId } : {},
|
|
4958
|
-
textMd: args.textMd,
|
|
4959
|
-
...args.targetRole ? { targetRole: args.targetRole } : {},
|
|
4960
|
-
...args.targetMemberId ? { targetMemberId: args.targetMemberId } : {},
|
|
4961
|
-
...args.options && args.options.length > 0 ? { options: args.options } : {},
|
|
4962
|
-
...args.wantsFiles ? { wantsFiles: true } : {},
|
|
4963
|
-
blocking: args.blocking,
|
|
4964
|
-
waitSec
|
|
4965
|
-
});
|
|
4966
|
-
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
4967
|
-
if (taskId) {
|
|
4968
|
-
const lease = runtime.client.isLeaseLost(taskId);
|
|
4969
|
-
if (lease.lost) return leaseLostResult(state.currentTask?.taskKey ?? taskId, lease.reason);
|
|
4970
|
-
}
|
|
4971
|
-
const result = await callServerOrError(
|
|
4972
|
-
runtime.client,
|
|
4973
|
-
"question:ask",
|
|
4974
|
-
parsedArgs.data,
|
|
4975
|
-
questionAskAckSchema,
|
|
4976
|
-
waitSec * 1e3 + ASK_ACK_BUFFER_MS
|
|
4977
|
-
);
|
|
4978
|
-
if (!result.ok) return errorResult(result.message);
|
|
4979
|
-
const settled = describeSettledQuestion(result.data.question);
|
|
4980
|
-
if (settled) return textResult(settled);
|
|
4981
|
-
if (args.blocking) {
|
|
4982
|
-
return textResult(
|
|
4983
|
-
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u0437\u0430\u0434\u0430\u043D, \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u0430 \u0432 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 (WAITING_HUMAN). \u041E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E ${BLOCKING_WAIT_SEC} \u0441 (pending) \u2014 \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId="${result.data.question.id}", \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0436\u0434\u0430\u0442\u044C. \u041D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443 \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435, \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u0448\u044C \u043E\u0442\u0432\u0435\u0442.`
|
|
4984
|
-
);
|
|
4985
|
-
}
|
|
4986
|
-
return textResult(
|
|
4987
|
-
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D (\u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u041E\u0442\u0432\u0435\u0442\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u2014 \u0435\u0441\u043B\u0438 \u043E\u043D \u043D\u0443\u0436\u0435\u043D, \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C, \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId="${result.data.question.id}".`
|
|
4988
|
-
);
|
|
5113
|
+
// src/socket/auth-reject.ts
|
|
5114
|
+
var AUTH_REJECT_REASONS = /* @__PURE__ */ new Set(["missing", "invalid"]);
|
|
5115
|
+
function isAuthRejection(error) {
|
|
5116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5117
|
+
return AUTH_REJECT_REASONS.has(message.trim());
|
|
4989
5118
|
}
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
"ID \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u2014 \u0438\u0437 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430 ask_human (question.id) \u0438\u043B\u0438 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0433\u043E \u0432\u044B\u0437\u043E\u0432\u0430 wait_answer."
|
|
4993
|
-
),
|
|
4994
|
-
waitSec: questionWaitPayloadSchema.shape.waitSec.optional().describe("\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C \u043E\u0442\u0432\u0435\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u044B\u0437\u043E\u0432\u0435, 0..600. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 300.")
|
|
4995
|
-
};
|
|
4996
|
-
var DEFAULT_WAIT_ANSWER_SEC = 300;
|
|
4997
|
-
var WAIT_ANSWER_ACK_BUFFER_MS = 15e3;
|
|
4998
|
-
async function waitAnswerHandler(args, runtime) {
|
|
4999
|
-
const waitSec = args.waitSec ?? DEFAULT_WAIT_ANSWER_SEC;
|
|
5000
|
-
const parsedArgs = parseArgsOrError(questionWaitPayloadSchema, {
|
|
5001
|
-
questionId: args.questionId,
|
|
5002
|
-
waitSec
|
|
5003
|
-
});
|
|
5004
|
-
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5005
|
-
const result = await callServerOrError(
|
|
5006
|
-
runtime.client,
|
|
5007
|
-
"question:wait",
|
|
5008
|
-
parsedArgs.data,
|
|
5009
|
-
questionWaitAckSchema,
|
|
5010
|
-
waitSec * 1e3 + WAIT_ANSWER_ACK_BUFFER_MS
|
|
5011
|
-
);
|
|
5012
|
-
if (!result.ok) return errorResult(result.message);
|
|
5013
|
-
const settled = describeSettledQuestion(result.data.question);
|
|
5014
|
-
if (settled) return textResult(settled);
|
|
5015
|
-
return textResult(
|
|
5016
|
-
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u0432\u0441\u0451 \u0435\u0449\u0451 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 (\u0436\u0434\u0430\u043B ${waitSec} \u0441). \u0412\u044B\u0437\u043E\u0432\u0438 wait_answer \u0435\u0449\u0451 \u0440\u0430\u0437 \u0441 \u0442\u0435\u043C \u0436\u0435 questionId, \u043A\u043E\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0443\u043C\u0435\u0441\u0442\u043D\u043E.`
|
|
5017
|
-
);
|
|
5119
|
+
function describeAuthRejection(agentId) {
|
|
5120
|
+
return `\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B\u0430 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 ${agentId}: \u043E\u043D \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D, \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0438\u043B\u0438 \u0438\u0441\u043F\u043E\u0440\u0447\u0435\u043D. \u041F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442 \u2014 \u0432\u044B\u043F\u0443\u0441\u0442\u0438 \u043D\u043E\u0432\u044B\u0439 \u0442\u043E\u043A\u0435\u043D \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u0430\u0433\u0435\u043D\u0442\u0430 \u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438 "quiel init" \u0437\u0430\u043D\u043E\u0432\u043E.`;
|
|
5018
5121
|
}
|
|
5019
|
-
function describeAnswerAttachments(question) {
|
|
5020
|
-
if (question.attachments.length === 0) return "";
|
|
5021
|
-
const lines = question.attachments.map(
|
|
5022
|
-
(file) => file.downloadUrl ? `- ${file.fileName} (${file.mimeType}) \u2014 \u0441\u043A\u0430\u0447\u0430\u0439: ${file.downloadUrl}` : `- ${file.fileName} (${file.mimeType}) \u2014 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430, \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442`
|
|
5023
|
-
);
|
|
5024
|
-
return `
|
|
5025
5122
|
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
if (question.status === QuestionStatus.DISMISSED) {
|
|
5035
|
-
return `\u0412\u043E\u043F\u0440\u043E\u0441 ${question.id} \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D (DISMISSED) \u2014 \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442. \u0420\u0435\u0448\u0438, \u043A\u0430\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C \u0434\u0430\u043B\u044C\u0448\u0435, \u0441\u0430\u043C, \u0438\u043B\u0438 \u0437\u0430\u0434\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0437\u0430\u043D\u043E\u0432\u043E.`;
|
|
5036
|
-
}
|
|
5037
|
-
return void 0;
|
|
5123
|
+
// src/socket/backoff.ts
|
|
5124
|
+
function nextReconnectDelayMs(attempt, options = {}) {
|
|
5125
|
+
const baseMs = options.baseMs ?? 1e3;
|
|
5126
|
+
const maxMs = options.maxMs ?? 3e4;
|
|
5127
|
+
const safeAttempt = Math.max(0, Math.trunc(attempt));
|
|
5128
|
+
const exponent = Math.min(safeAttempt, 30);
|
|
5129
|
+
const raw = baseMs * 2 ** exponent;
|
|
5130
|
+
return Math.min(raw, maxMs);
|
|
5038
5131
|
}
|
|
5039
5132
|
|
|
5040
|
-
//
|
|
5041
|
-
|
|
5042
|
-
name: "@quiel/cli",
|
|
5043
|
-
version: "0.5.2",
|
|
5044
|
-
description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 (Claude Code, Codex, Cursor, OpenCode) \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
|
|
5045
|
-
homepage: "https://quiel.app",
|
|
5046
|
-
repository: {
|
|
5047
|
-
type: "git",
|
|
5048
|
-
url: "git+https://github.com/Quiel-App/quiel-cli.git"
|
|
5049
|
-
},
|
|
5050
|
-
bugs: {
|
|
5051
|
-
url: "https://github.com/Quiel-App/quiel-cli/issues"
|
|
5052
|
-
},
|
|
5053
|
-
keywords: [
|
|
5054
|
-
"quiel",
|
|
5055
|
-
"claude-code",
|
|
5056
|
-
"codex",
|
|
5057
|
-
"cursor",
|
|
5058
|
-
"opencode",
|
|
5059
|
-
"mcp",
|
|
5060
|
-
"mcp-server",
|
|
5061
|
-
"ai-agents",
|
|
5062
|
-
"task-tracker"
|
|
5063
|
-
],
|
|
5064
|
-
license: "Apache-2.0",
|
|
5065
|
-
type: "module",
|
|
5066
|
-
bin: {
|
|
5067
|
-
quiel: "dist/cli.js"
|
|
5068
|
-
},
|
|
5069
|
-
files: [
|
|
5070
|
-
"dist",
|
|
5071
|
-
"README.md",
|
|
5072
|
-
"LICENSE"
|
|
5073
|
-
],
|
|
5074
|
-
engines: {
|
|
5075
|
-
node: ">=22"
|
|
5076
|
-
},
|
|
5077
|
-
publishConfig: {
|
|
5078
|
-
access: "public"
|
|
5079
|
-
},
|
|
5080
|
-
scripts: {
|
|
5081
|
-
build: "tsup",
|
|
5082
|
-
dev: "tsup --watch",
|
|
5083
|
-
lint: "biome check .",
|
|
5084
|
-
typecheck: "tsc --noEmit",
|
|
5085
|
-
test: "vitest run --passWithNoTests"
|
|
5086
|
-
},
|
|
5087
|
-
dependencies: {
|
|
5088
|
-
"@modelcontextprotocol/sdk": "1.30.0",
|
|
5089
|
-
commander: "15.0.0",
|
|
5090
|
-
"socket.io-client": "4.8.3",
|
|
5091
|
-
zod: "4.5.4"
|
|
5092
|
-
},
|
|
5093
|
-
devDependencies: {
|
|
5094
|
-
"@quiel/config": "workspace:*",
|
|
5095
|
-
"@quiel/shared": "workspace:*",
|
|
5096
|
-
"@types/node": "20.19.43",
|
|
5097
|
-
tsup: "8.5.1",
|
|
5098
|
-
typescript: "7.0.2",
|
|
5099
|
-
vitest: "5.0.0"
|
|
5100
|
-
}
|
|
5101
|
-
};
|
|
5102
|
-
|
|
5103
|
-
// src/version.ts
|
|
5104
|
-
var VERSION = package_default.version;
|
|
5133
|
+
// src/socket/client.ts
|
|
5134
|
+
import { EventEmitter } from "events";
|
|
5105
5135
|
|
|
5106
|
-
// src/
|
|
5107
|
-
|
|
5108
|
-
|
|
5136
|
+
// src/socket/emit-with-timeout.ts
|
|
5137
|
+
function toError(raw, context) {
|
|
5138
|
+
return raw instanceof Error ? raw : new Error(`${context}: ${String(raw)}`);
|
|
5139
|
+
}
|
|
5140
|
+
async function emitWithAckTimeout(socket, event, payload, timeoutMs) {
|
|
5141
|
+
return await new Promise((resolve4, reject) => {
|
|
5142
|
+
const timer = setTimeout(() => {
|
|
5143
|
+
reject(new Error(`${event}: \u043D\u0435\u0442 ack \u043E\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0437\u0430 ${timeoutMs} \u043C\u0441`));
|
|
5144
|
+
}, timeoutMs);
|
|
5145
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5146
|
+
socket.emitWithAck(event, payload).then((value) => {
|
|
5147
|
+
clearTimeout(timer);
|
|
5148
|
+
resolve4(value);
|
|
5149
|
+
}).catch((err) => {
|
|
5150
|
+
clearTimeout(timer);
|
|
5151
|
+
reject(toError(err, event));
|
|
5152
|
+
});
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5109
5155
|
|
|
5110
|
-
// src/
|
|
5111
|
-
var
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
"documents",
|
|
5119
|
-
"related",
|
|
5120
|
-
"repo"
|
|
5121
|
-
];
|
|
5122
|
-
var getContextInputShape = {
|
|
5123
|
-
taskId: contextGetPayloadSchema.shape.taskId.describe(
|
|
5124
|
-
"ID \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u043B\u044F \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043D\u0443\u0436\u0435\u043D \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442. \u0415\u0441\u043B\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0430\u0433\u0435\u043D\u0442\u0430 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C)."
|
|
5125
|
-
),
|
|
5126
|
-
include: contextGetPayloadSchema.shape.include.optional().describe(
|
|
5127
|
-
"\u0427\u0442\u043E \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C: spec | decisions | related | repo. \u041D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u0438\u043B\u0438 \u043F\u0443\u0441\u0442 \u2014 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0432\u0441\u0451 \u0441\u0440\u0430\u0437\u0443."
|
|
5128
|
-
)
|
|
5129
|
-
};
|
|
5130
|
-
async function getContextHandler(args, runtime) {
|
|
5131
|
-
const state = readState(runtime.stateFilePath);
|
|
5132
|
-
const taskId = args.taskId ?? state.currentTask?.taskId;
|
|
5133
|
-
if (taskId) {
|
|
5134
|
-
const lease = runtime.client.isLeaseLost(taskId);
|
|
5135
|
-
if (lease.lost) return leaseLostResult(taskId, lease.reason);
|
|
5136
|
-
}
|
|
5137
|
-
const include = args.include && args.include.length > 0 ? args.include : [...ALL_CONTEXT_INCLUDES];
|
|
5138
|
-
const payload = contextGetPayloadSchema.parse({ ...taskId ? { taskId } : {}, include });
|
|
5139
|
-
const result = await callServerOrError(
|
|
5140
|
-
runtime.client,
|
|
5141
|
-
"context:get",
|
|
5142
|
-
payload,
|
|
5143
|
-
contextPayloadSchema
|
|
5144
|
-
);
|
|
5145
|
-
if (!result.ok) return errorResult(result.message);
|
|
5146
|
-
return textResult(JSON.stringify(result.data, null, 2));
|
|
5156
|
+
// src/socket/trust-cache.ts
|
|
5157
|
+
var UNKNOWN_TRUST_PROFILE_KEY = "unknown";
|
|
5158
|
+
function toTrustCache(rules, now = () => /* @__PURE__ */ new Date()) {
|
|
5159
|
+
return {
|
|
5160
|
+
profileKey: UNKNOWN_TRUST_PROFILE_KEY,
|
|
5161
|
+
updatedAt: now().toISOString(),
|
|
5162
|
+
rules
|
|
5163
|
+
};
|
|
5147
5164
|
}
|
|
5148
5165
|
|
|
5149
|
-
// src/
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
};
|
|
5153
|
-
async function getTaskHandler(args, runtime) {
|
|
5154
|
-
const lease = runtime.client.isLeaseLost(args.taskId);
|
|
5155
|
-
if (lease.lost) return leaseLostResult(args.taskId, lease.reason);
|
|
5156
|
-
const payload = taskGetPayloadSchema.parse({ taskId: args.taskId });
|
|
5157
|
-
const result = await callServerOrError(runtime.client, "task:get", payload, taskGetAckSchema);
|
|
5158
|
-
if (!result.ok) return errorResult(result.message);
|
|
5159
|
-
return textResult(JSON.stringify(result.data, null, 2));
|
|
5166
|
+
// src/socket/types.ts
|
|
5167
|
+
function isAckError(raw) {
|
|
5168
|
+
return typeof raw === "object" && raw !== null && "error" in raw && typeof raw.error === "object";
|
|
5160
5169
|
}
|
|
5161
5170
|
|
|
5162
|
-
// src/
|
|
5163
|
-
var
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
return errorResult(
|
|
5171
|
-
"\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430 \u0432 \u0435\u0451 \u043B\u0435\u043D\u0442\u0443. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0437\u043E\u0432\u0438 next_task."
|
|
5172
|
-
);
|
|
5173
|
-
}
|
|
5174
|
-
const lease = runtime.client.isLeaseLost(state.currentTask.taskId);
|
|
5175
|
-
if (lease.lost) {
|
|
5176
|
-
return leaseLostResult(state.currentTask.taskKey ?? state.currentTask.taskId, lease.reason);
|
|
5177
|
-
}
|
|
5178
|
-
const payload = taskProgressPayloadSchema.parse({
|
|
5179
|
-
taskId: state.currentTask.taskId,
|
|
5180
|
-
note: args.message,
|
|
5181
|
-
...args.level ? { level: args.level } : {}
|
|
5182
|
-
});
|
|
5183
|
-
const result = await callServerOrError(
|
|
5184
|
-
runtime.client,
|
|
5185
|
-
"task:progress",
|
|
5186
|
-
payload,
|
|
5187
|
-
taskProgressAckSchema
|
|
5188
|
-
);
|
|
5189
|
-
if (!result.ok) return errorResult(result.message);
|
|
5190
|
-
if (!result.data.ok) return errorResult("\u0421\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u0437\u0430\u043F\u0438\u0441\u044C \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 (ok:false).");
|
|
5191
|
-
return textResult("\u0417\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438.");
|
|
5192
|
-
}
|
|
5193
|
-
|
|
5194
|
-
// src/git/result.ts
|
|
5195
|
-
function gitOk(data) {
|
|
5196
|
-
return { ok: true, data };
|
|
5171
|
+
// src/socket/client.ts
|
|
5172
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
5173
|
+
var DEFAULT_ACK_TIMEOUT_MS = 1e4;
|
|
5174
|
+
var DEFAULT_BACKOFF_BASE_MS = 1e3;
|
|
5175
|
+
var DEFAULT_BACKOFF_MAX_MS = 3e4;
|
|
5176
|
+
function toError2(raw, context) {
|
|
5177
|
+
if (raw instanceof Error) return raw;
|
|
5178
|
+
return new Error(`${context}: ${String(raw)}`);
|
|
5197
5179
|
}
|
|
5198
|
-
function
|
|
5199
|
-
return {
|
|
5180
|
+
function resolveOptions(options) {
|
|
5181
|
+
return {
|
|
5182
|
+
roleKeys: options.roleKeys,
|
|
5183
|
+
cwd: options.cwd,
|
|
5184
|
+
clientVersion: options.clientVersion,
|
|
5185
|
+
agentVersion: options.agentVersion ?? "unknown",
|
|
5186
|
+
agentKind: options.agentKind ?? DEFAULT_AGENT_KIND,
|
|
5187
|
+
os: options.os ?? process.platform,
|
|
5188
|
+
// По умолчанию — файл ИМЕННО этого агента (state/store.ts::getStateFilePath(agentId)),
|
|
5189
|
+
// а не общий `state.json`: не трогаем src/state/**, но обязаны использовать
|
|
5190
|
+
// его правильно (OQ-05 — состояние на агента, не на машину).
|
|
5191
|
+
stateFilePath: options.stateFilePath ?? getStateFilePath(options.agentId),
|
|
5192
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
5193
|
+
ackTimeoutMs: options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS,
|
|
5194
|
+
backoffBaseMs: options.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,
|
|
5195
|
+
backoffMaxMs: options.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS,
|
|
5196
|
+
now: options.now ?? (() => /* @__PURE__ */ new Date())
|
|
5197
|
+
};
|
|
5200
5198
|
}
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5199
|
+
var AgentSocketClient = class extends EventEmitter {
|
|
5200
|
+
socket;
|
|
5201
|
+
opts;
|
|
5202
|
+
agentId;
|
|
5203
|
+
stopped = true;
|
|
5204
|
+
reconnectAttempt = 0;
|
|
5205
|
+
reconnectTimer;
|
|
5206
|
+
heartbeatTimer;
|
|
5207
|
+
lastHelloAck;
|
|
5208
|
+
paused = false;
|
|
5209
|
+
pauseReasonText;
|
|
5210
|
+
leaseLostTasks = /* @__PURE__ */ new Map();
|
|
5211
|
+
constructor(options) {
|
|
5212
|
+
super();
|
|
5213
|
+
this.opts = resolveOptions(options);
|
|
5214
|
+
this.agentId = options.agentId;
|
|
5215
|
+
const factory = options.createSocket ?? createSocket;
|
|
5216
|
+
this.socket = factory(`${trimTrailingSlash2(options.url)}/agent`, {
|
|
5217
|
+
auth: { token: options.token },
|
|
5218
|
+
autoConnect: false,
|
|
5219
|
+
reconnection: false
|
|
5220
|
+
});
|
|
5221
|
+
this.registerSocketListeners();
|
|
5219
5222
|
}
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
if (!isValidGitBranchName(name)) {
|
|
5224
|
-
return gitErr(
|
|
5225
|
-
`${label} "${name}" \u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\u0435 \u0438\u043C\u044F git-\u0432\u0435\u0442\u043A\u0438 \u2014 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F.`
|
|
5226
|
-
);
|
|
5223
|
+
// -- Публичное API -----------------------------------------------------
|
|
5224
|
+
on(event, listener) {
|
|
5225
|
+
return super.on(event, listener);
|
|
5227
5226
|
}
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5227
|
+
once(event, listener) {
|
|
5228
|
+
return super.once(event, listener);
|
|
5229
|
+
}
|
|
5230
|
+
off(event, listener) {
|
|
5231
|
+
return super.off(event, listener);
|
|
5232
|
+
}
|
|
5233
|
+
emit(event, ...args) {
|
|
5234
|
+
return super.emit(event, ...args);
|
|
5235
|
+
}
|
|
5236
|
+
/** Поднимает соединение и heartbeat-таймер. Идемпотентно — повторный вызов на уже запущенном клиенте не делает ничего. */
|
|
5237
|
+
start() {
|
|
5238
|
+
if (!this.stopped) return;
|
|
5239
|
+
this.stopped = false;
|
|
5240
|
+
this.reconnectAttempt = 0;
|
|
5241
|
+
this.startHeartbeatTimer();
|
|
5242
|
+
this.socket.connect();
|
|
5243
|
+
}
|
|
5244
|
+
/** Останавливает heartbeat, отменяет запланированное переподключение, закрывает сокет. После `stop()` клиент больше не переподключается сам. */
|
|
5245
|
+
stop() {
|
|
5246
|
+
this.stopped = true;
|
|
5247
|
+
if (this.reconnectTimer) {
|
|
5248
|
+
clearTimeout(this.reconnectTimer);
|
|
5249
|
+
this.reconnectTimer = void 0;
|
|
5250
|
+
}
|
|
5251
|
+
this.stopHeartbeatTimer();
|
|
5252
|
+
this.socket.disconnect();
|
|
5253
|
+
}
|
|
5254
|
+
get connected() {
|
|
5255
|
+
return this.socket.connected;
|
|
5256
|
+
}
|
|
5257
|
+
get lastHello() {
|
|
5258
|
+
return this.lastHelloAck;
|
|
5259
|
+
}
|
|
5260
|
+
get isPaused() {
|
|
5261
|
+
return this.paused;
|
|
5262
|
+
}
|
|
5263
|
+
get pauseReason() {
|
|
5264
|
+
return this.pauseReasonText;
|
|
5265
|
+
}
|
|
5266
|
+
/** §11.2 ТЗ: «следующий вызов любого тула по этой задаче должен сообщить модели, что работу надо прекратить» — тулы (P2-12) сверяются с этим геттером. */
|
|
5267
|
+
isLeaseLost(taskId) {
|
|
5268
|
+
const reason = this.leaseLostTasks.get(taskId);
|
|
5269
|
+
return reason === void 0 ? { lost: false } : { lost: true, reason };
|
|
5270
|
+
}
|
|
5271
|
+
/**
|
|
5272
|
+
* Общий emit+ack поверх ЭТОГО ЖЕ постоянного сокета — тем же приёмом
|
|
5273
|
+
* (`emitWithAckTimeout`), которым `sendHello`/`sendHeartbeat` уже
|
|
5274
|
+
* пользуются внутри класса, но публично: MCP-тулы (P2-12/13,
|
|
5275
|
+
* `../mcp/**`) шлют через него `task:claim`/`task:start`/`task:progress`/
|
|
5276
|
+
* `task:get`/`task:report`/`task:release`/`context:get` (§11.1 ТЗ) — то
|
|
5277
|
+
* есть ВСЁ, что не `agent:hello`/`agent:heartbeat`/`agent:mode` (у
|
|
5278
|
+
* первых двух своя логика согласования состояния в этом классе, третий
|
|
5279
|
+
* — короткоживущий отдельный сокет, см. `send-agent-mode.ts`). Нарочно
|
|
5280
|
+
* НЕ отдельное соединение: `task:claim` — блокирующий долгий long-poll,
|
|
5281
|
+
* и держать его на том же сокете, где уже идёт heartbeat/presence этого
|
|
5282
|
+
* агента, а не поднимать вторую параллельную сессию — единственный
|
|
5283
|
+
* вариант, не удваивающий presence на сервере.
|
|
5284
|
+
*/
|
|
5285
|
+
callServer(event, payload, timeoutMs = this.opts.ackTimeoutMs) {
|
|
5286
|
+
return emitWithAckTimeout(this.socket, event, payload, timeoutMs);
|
|
5287
|
+
}
|
|
5288
|
+
/**
|
|
5289
|
+
* Ждёт первого успешного `agent:hello` не дольше `timeoutMs` (по
|
|
5290
|
+
* умолчанию 5 с — как раньше HTTP-пинг в `network.ts`). Таймаут НЕ
|
|
5291
|
+
* останавливает клиент: переподключение по backoff продолжается в фоне,
|
|
5292
|
+
* это только ограничение по времени для вызывающей стороны (`connect.ts`).
|
|
5293
|
+
*/
|
|
5294
|
+
waitForFirstConnection(timeoutMs = 5e3) {
|
|
5295
|
+
return new Promise((resolve4) => {
|
|
5296
|
+
let settled = false;
|
|
5297
|
+
const onConnected = (ack) => {
|
|
5298
|
+
if (settled) return;
|
|
5299
|
+
settled = true;
|
|
5300
|
+
clearTimeout(timer);
|
|
5301
|
+
this.off("connected", onConnected);
|
|
5302
|
+
resolve4({ ok: true, ack });
|
|
5303
|
+
};
|
|
5304
|
+
const onAuthRejected = (message) => {
|
|
5305
|
+
if (settled) return;
|
|
5306
|
+
settled = true;
|
|
5307
|
+
clearTimeout(timer);
|
|
5308
|
+
this.off("connected", onConnected);
|
|
5309
|
+
this.off("auth-rejected", onAuthRejected);
|
|
5310
|
+
resolve4({ ok: false, message });
|
|
5311
|
+
};
|
|
5312
|
+
const timer = setTimeout(() => {
|
|
5313
|
+
if (settled) return;
|
|
5314
|
+
settled = true;
|
|
5315
|
+
this.off("connected", onConnected);
|
|
5316
|
+
this.off("auth-rejected", onAuthRejected);
|
|
5317
|
+
resolve4({
|
|
5263
5318
|
ok: false,
|
|
5264
|
-
|
|
5265
|
-
signal: error.signal ?? null,
|
|
5266
|
-
stdout,
|
|
5267
|
-
// Если git ничего не написал в stderr (например, сам процесс не
|
|
5268
|
-
// запустился — ENOENT), берём текст ошибки Node, чтобы вызывающий
|
|
5269
|
-
// код не остался с пустым сообщением.
|
|
5270
|
-
stderr: stderr.length > 0 ? stderr : error.message
|
|
5319
|
+
message: `\u041D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430 agent:hello \u0437\u0430 ${timeoutMs} \u043C\u0441 \u2014 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430. \u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0432 \u0444\u043E\u043D\u0435 (backoff \xA711.4 \u0422\u0417).`
|
|
5271
5320
|
});
|
|
5272
|
-
}
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5321
|
+
}, timeoutMs);
|
|
5322
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5323
|
+
this.on("connected", onConnected);
|
|
5324
|
+
this.on("auth-rejected", onAuthRejected);
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
// -- Подключение --------------------------------------------------------
|
|
5328
|
+
registerSocketListeners() {
|
|
5329
|
+
this.socket.on("connect", () => this.handleConnect());
|
|
5330
|
+
this.socket.on("connect_error", (err) => this.handleConnectError(err));
|
|
5331
|
+
this.socket.on("disconnect", (reason) => this.handleDisconnect(String(reason)));
|
|
5332
|
+
this.socket.on("lease:lost", (raw) => this.handleLeaseLost(raw));
|
|
5333
|
+
this.socket.on("mode:set", (raw) => this.handleModeSet(raw));
|
|
5334
|
+
this.socket.on("agent:pause", (raw) => this.handleAgentPause(raw));
|
|
5335
|
+
this.socket.on("trust:updated", (raw) => this.handleTrustUpdated(raw));
|
|
5336
|
+
this.socket.on("task:updated", (raw) => this.handleTaskUpdated(raw));
|
|
5337
|
+
this.socket.on("question:answered", (raw) => this.handleQuestionAnswered(raw));
|
|
5338
|
+
this.socket.on("approval:decided", (raw) => this.handleApprovalDecided(raw));
|
|
5339
|
+
}
|
|
5340
|
+
handleConnect() {
|
|
5341
|
+
if (this.stopped) return;
|
|
5342
|
+
this.reconnectAttempt = 0;
|
|
5343
|
+
void this.sendHello();
|
|
5344
|
+
}
|
|
5345
|
+
handleConnectError(err) {
|
|
5346
|
+
if (isAuthRejection(err)) {
|
|
5347
|
+
this.stop();
|
|
5348
|
+
this.emit("auth-rejected", describeAuthRejection(this.agentId));
|
|
5349
|
+
return;
|
|
5350
|
+
}
|
|
5351
|
+
this.emit("client-error", toError2(err, "connect_error"));
|
|
5352
|
+
this.scheduleReconnect();
|
|
5353
|
+
}
|
|
5354
|
+
handleDisconnect(reason) {
|
|
5355
|
+
this.emit("disconnected", reason);
|
|
5356
|
+
if (this.stopped) return;
|
|
5357
|
+
this.scheduleReconnect();
|
|
5358
|
+
}
|
|
5359
|
+
scheduleReconnect() {
|
|
5360
|
+
if (this.stopped || this.reconnectTimer) return;
|
|
5361
|
+
const delayMs = nextReconnectDelayMs(this.reconnectAttempt, {
|
|
5362
|
+
baseMs: this.opts.backoffBaseMs,
|
|
5363
|
+
maxMs: this.opts.backoffMaxMs
|
|
5364
|
+
});
|
|
5365
|
+
this.reconnectAttempt += 1;
|
|
5366
|
+
this.emit("reconnecting", { attempt: this.reconnectAttempt, delayMs });
|
|
5367
|
+
this.reconnectTimer = setTimeout(() => {
|
|
5368
|
+
this.reconnectTimer = void 0;
|
|
5369
|
+
if (this.stopped) return;
|
|
5370
|
+
this.socket.connect();
|
|
5371
|
+
}, delayMs);
|
|
5372
|
+
}
|
|
5373
|
+
// -- agent:hello ---------------------------------------------------------
|
|
5374
|
+
async sendHello() {
|
|
5375
|
+
const state = readState(this.opts.stateFilePath);
|
|
5376
|
+
const payload = agentHelloPayloadSchema.parse({
|
|
5377
|
+
clientVersion: this.opts.clientVersion,
|
|
5378
|
+
cwd: this.opts.cwd,
|
|
5379
|
+
os: this.opts.os,
|
|
5380
|
+
agentKind: this.opts.agentKind,
|
|
5381
|
+
agentVersion: this.opts.agentVersion,
|
|
5382
|
+
// `claudeVersion` шлётся ТОЛЬКО для Claude Code — и только чтобы
|
|
5383
|
+
// клиент работал против ещё не обновлённого сервера, который знает
|
|
5384
|
+
// одно это поле. Для Codex поле с таким именем было бы неправдой.
|
|
5385
|
+
...this.opts.agentKind === AgentKind.CLAUDE_CODE ? { claudeVersion: this.opts.agentVersion } : {},
|
|
5386
|
+
roleKeys: [...this.opts.roleKeys],
|
|
5387
|
+
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
|
|
5388
|
+
});
|
|
5389
|
+
let raw;
|
|
5390
|
+
try {
|
|
5391
|
+
raw = await emitWithAckTimeout(this.socket, "agent:hello", payload, this.opts.ackTimeoutMs);
|
|
5392
|
+
} catch (err) {
|
|
5393
|
+
this.emit("client-error", toError2(err, "agent:hello"));
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
if (this.stopped) return;
|
|
5397
|
+
if (isAckError(raw)) {
|
|
5398
|
+
this.emit("client-error", new Error(`agent:hello: ${raw.error.code}: ${raw.error.message}`));
|
|
5399
|
+
return;
|
|
5400
|
+
}
|
|
5401
|
+
const parsed = agentHelloAckSchema.safeParse(raw);
|
|
5402
|
+
if (!parsed.success) {
|
|
5403
|
+
this.emit(
|
|
5404
|
+
"client-error",
|
|
5405
|
+
new Error(`agent:hello: \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}`)
|
|
5406
|
+
);
|
|
5407
|
+
return;
|
|
5408
|
+
}
|
|
5409
|
+
const ack = parsed.data;
|
|
5410
|
+
this.lastHelloAck = ack;
|
|
5411
|
+
this.paused = false;
|
|
5412
|
+
this.pauseReasonText = void 0;
|
|
5413
|
+
this.applyHelloAck(ack);
|
|
5414
|
+
this.emit("connected", ack);
|
|
5415
|
+
}
|
|
5416
|
+
applyHelloAck(ack) {
|
|
5417
|
+
const state = readState(this.opts.stateFilePath);
|
|
5418
|
+
const patch = {
|
|
5419
|
+
// OQ-06: сервер — источник правды, локальный режим перекрывается безусловно.
|
|
5420
|
+
mode: ack.mode,
|
|
5421
|
+
trust: toTrustCache(ack.trust, this.opts.now)
|
|
5422
|
+
};
|
|
5423
|
+
if (!ack.currentTask || ack.currentTask.id !== state.currentTask?.taskId) {
|
|
5424
|
+
patch.currentTask = null;
|
|
5425
|
+
}
|
|
5426
|
+
updateState(patch, this.opts.stateFilePath);
|
|
5427
|
+
}
|
|
5428
|
+
// -- agent:heartbeat -------------------------------------------------------
|
|
5429
|
+
startHeartbeatTimer() {
|
|
5430
|
+
if (this.heartbeatTimer) return;
|
|
5431
|
+
this.heartbeatTimer = setInterval(() => {
|
|
5432
|
+
void this.sendHeartbeat();
|
|
5433
|
+
}, this.opts.heartbeatIntervalMs);
|
|
5434
|
+
}
|
|
5435
|
+
stopHeartbeatTimer() {
|
|
5436
|
+
if (!this.heartbeatTimer) return;
|
|
5437
|
+
clearInterval(this.heartbeatTimer);
|
|
5438
|
+
this.heartbeatTimer = void 0;
|
|
5439
|
+
}
|
|
5440
|
+
async sendHeartbeat() {
|
|
5441
|
+
if (!this.socket.connected) return;
|
|
5442
|
+
const state = readState(this.opts.stateFilePath);
|
|
5443
|
+
const payload = agentHeartbeatPayloadSchema.parse({
|
|
5444
|
+
...state.currentTask?.taskId ? { currentTaskId: state.currentTask.taskId } : {},
|
|
5445
|
+
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {},
|
|
5446
|
+
status: state.currentTask ? AgentStatus.BUSY : AgentStatus.IDLE
|
|
5447
|
+
});
|
|
5448
|
+
let raw;
|
|
5449
|
+
try {
|
|
5450
|
+
raw = await emitWithAckTimeout(
|
|
5451
|
+
this.socket,
|
|
5452
|
+
"agent:heartbeat",
|
|
5453
|
+
payload,
|
|
5454
|
+
this.opts.ackTimeoutMs
|
|
5455
|
+
);
|
|
5456
|
+
} catch (err) {
|
|
5457
|
+
this.emit("client-error", toError2(err, "agent:heartbeat"));
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
if (isAckError(raw)) {
|
|
5461
|
+
this.emit(
|
|
5462
|
+
"client-error",
|
|
5463
|
+
new Error(`agent:heartbeat: ${raw.error.code}: ${raw.error.message}`)
|
|
5464
|
+
);
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
const parsed = agentHeartbeatAckSchema.safeParse(raw);
|
|
5468
|
+
if (!parsed.success) {
|
|
5469
|
+
this.emit(
|
|
5470
|
+
"client-error",
|
|
5471
|
+
new Error(`agent:heartbeat: \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}`)
|
|
5472
|
+
);
|
|
5473
|
+
return;
|
|
5474
|
+
}
|
|
5475
|
+
this.emit("heartbeat", parsed.data);
|
|
5476
|
+
}
|
|
5477
|
+
// -- Пуш-события сервера (§11.2 ТЗ) --------------------------------------
|
|
5478
|
+
handleLeaseLost(raw) {
|
|
5479
|
+
const parsed = leaseLostPayloadSchema.safeParse(raw);
|
|
5480
|
+
if (!parsed.success) {
|
|
5481
|
+
this.emit(
|
|
5482
|
+
"client-error",
|
|
5483
|
+
new Error(`lease:lost: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5484
|
+
);
|
|
5485
|
+
return;
|
|
5486
|
+
}
|
|
5487
|
+
const { taskId, reason } = parsed.data;
|
|
5488
|
+
this.leaseLostTasks.set(taskId, reason);
|
|
5489
|
+
const state = readState(this.opts.stateFilePath);
|
|
5490
|
+
if (state.currentTask?.taskId === taskId) {
|
|
5491
|
+
updateState({ currentTask: null }, this.opts.stateFilePath);
|
|
5492
|
+
}
|
|
5493
|
+
this.emit("lease:lost", parsed.data);
|
|
5494
|
+
}
|
|
5495
|
+
handleModeSet(raw) {
|
|
5496
|
+
const parsed = modeSetPayloadSchema.safeParse(raw);
|
|
5497
|
+
if (!parsed.success) {
|
|
5498
|
+
this.emit("client-error", new Error(`mode:set: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`));
|
|
5499
|
+
return;
|
|
5500
|
+
}
|
|
5501
|
+
updateState({ mode: parsed.data.mode }, this.opts.stateFilePath);
|
|
5502
|
+
this.emit("mode:set", parsed.data);
|
|
5503
|
+
}
|
|
5504
|
+
handleAgentPause(raw) {
|
|
5505
|
+
const parsed = agentPausePayloadSchema.safeParse(raw);
|
|
5506
|
+
if (!parsed.success) {
|
|
5507
|
+
this.emit(
|
|
5508
|
+
"client-error",
|
|
5509
|
+
new Error(`agent:pause: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5510
|
+
);
|
|
5511
|
+
return;
|
|
5512
|
+
}
|
|
5513
|
+
this.paused = true;
|
|
5514
|
+
this.pauseReasonText = parsed.data.reason;
|
|
5515
|
+
this.emit("pause", parsed.data);
|
|
5516
|
+
}
|
|
5517
|
+
handleTrustUpdated(raw) {
|
|
5518
|
+
const parsed = trustUpdatedPayloadSchema.safeParse(raw);
|
|
5519
|
+
if (!parsed.success) {
|
|
5520
|
+
this.emit(
|
|
5521
|
+
"client-error",
|
|
5522
|
+
new Error(`trust:updated: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5523
|
+
);
|
|
5524
|
+
return;
|
|
5525
|
+
}
|
|
5526
|
+
updateState({ trust: toTrustCache(parsed.data, this.opts.now) }, this.opts.stateFilePath);
|
|
5527
|
+
this.emit("trust:updated", parsed.data);
|
|
5528
|
+
}
|
|
5529
|
+
handleTaskUpdated(raw) {
|
|
5530
|
+
const parsed = taskUpdatedAgentPayloadSchema.safeParse(raw);
|
|
5531
|
+
if (parsed.success) this.emit("task:updated", parsed.data);
|
|
5293
5532
|
}
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreeDir });
|
|
5298
|
-
if (!result.ok) {
|
|
5299
|
-
return gitErr(describeGitFailure("git rev-parse --abbrev-ref HEAD", result));
|
|
5533
|
+
handleQuestionAnswered(raw) {
|
|
5534
|
+
const parsed = questionAnsweredPayloadSchema.safeParse(raw);
|
|
5535
|
+
if (parsed.success) this.emit("question:answered", parsed.data);
|
|
5300
5536
|
}
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5537
|
+
/**
|
|
5538
|
+
* §14.4 ТЗ, п.4-5 — асинхронный путь (в дополнение к синхронному в
|
|
5539
|
+
* `mcp/request-approval.ts::settleApproval`, который срабатывает, когда
|
|
5540
|
+
* решение приходит в рамках ОДНОГО вызова `request_approval`/
|
|
5541
|
+
* `wait_approval`). Этот пуш ловит случай, когда решение пришло уже
|
|
5542
|
+
* ПОСЛЕ того, как собственный long-poll тула истёк (вернул `PENDING`),
|
|
5543
|
+
* а модель ещё не успела/не стала звать `wait_approval` снова — на
|
|
5544
|
+
* постоянном соединении (`quiel mcp`, единственный держатель ЭТОГО
|
|
5545
|
+
* класса) `approval:decided` долетит и без явного повторного вызова.
|
|
5546
|
+
*
|
|
5547
|
+
* `approvalDecidedPayloadSchema` несёт только `approvalId`+`status`+
|
|
5548
|
+
* `oneTimeToken?` — НЕ `command`/`cwd` (провод их не отдаёт, см.
|
|
5549
|
+
* `state/schema.ts::PendingApprovalSchema`), поэтому command+cwd берутся
|
|
5550
|
+
* из `state.pendingApprovals[approvalId]`, куда их кладёт
|
|
5551
|
+
* `request_approval` сразу после создания запроса. Если записи там нет
|
|
5552
|
+
* (approval создан не этим клиентом/до перезапуска процесса, либо уже
|
|
5553
|
+
* обработан) — писать пока нечего, `pendingApprovals` и так пуст.
|
|
5554
|
+
*/
|
|
5555
|
+
handleApprovalDecided(raw) {
|
|
5556
|
+
const parsed = approvalDecidedPayloadSchema.safeParse(raw);
|
|
5557
|
+
if (!parsed.success) {
|
|
5558
|
+
this.emit(
|
|
5559
|
+
"client-error",
|
|
5560
|
+
new Error(`approval:decided: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5561
|
+
);
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5564
|
+
this.applyApprovalDecided(parsed.data);
|
|
5565
|
+
this.emit("approval:decided", parsed.data);
|
|
5312
5566
|
}
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
if (
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5567
|
+
applyApprovalDecided(payload) {
|
|
5568
|
+
const state = readState(this.opts.stateFilePath);
|
|
5569
|
+
const pending = state.pendingApprovals[payload.approvalId];
|
|
5570
|
+
if (!pending) return;
|
|
5571
|
+
if (payload.status === ApprovalStatus.APPROVED && payload.oneTimeToken) {
|
|
5572
|
+
const approvalTtlMin = state.trust?.rules.approvalTtlMin ?? 60;
|
|
5573
|
+
const expiresAt = new Date(this.opts.now().getTime() + approvalTtlMin * 6e4).toISOString();
|
|
5574
|
+
updateState(
|
|
5575
|
+
{
|
|
5576
|
+
...storeApprovedCommand(
|
|
5577
|
+
state,
|
|
5578
|
+
{ command: pending.command, cwd: pending.cwd, token: payload.oneTimeToken, expiresAt },
|
|
5579
|
+
this.opts.now
|
|
5580
|
+
),
|
|
5581
|
+
...clearPendingApproval(state, payload.approvalId)
|
|
5582
|
+
},
|
|
5583
|
+
this.opts.stateFilePath
|
|
5584
|
+
);
|
|
5585
|
+
return;
|
|
5586
|
+
}
|
|
5587
|
+
updateState(clearPendingApproval(state, payload.approvalId), this.opts.stateFilePath);
|
|
5323
5588
|
}
|
|
5324
|
-
|
|
5589
|
+
};
|
|
5590
|
+
function trimTrailingSlash2(url) {
|
|
5591
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5325
5592
|
}
|
|
5326
5593
|
|
|
5327
|
-
// src/
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
return
|
|
5331
|
-
|
|
5332
|
-
${buildTaskTrailer(params.taskKey)}
|
|
5333
|
-
`;
|
|
5594
|
+
// src/socket/send-agent-mode.ts
|
|
5595
|
+
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
5596
|
+
function trimTrailingSlash3(url) {
|
|
5597
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5334
5598
|
}
|
|
5335
|
-
async function
|
|
5336
|
-
const
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5599
|
+
async function sendAgentMode(options) {
|
|
5600
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
5601
|
+
const factory = options.createSocket ?? createSocket;
|
|
5602
|
+
const socket = factory(`${trimTrailingSlash3(options.url)}/agent`, {
|
|
5603
|
+
auth: { token: options.token },
|
|
5604
|
+
autoConnect: false,
|
|
5605
|
+
reconnection: false
|
|
5606
|
+
});
|
|
5607
|
+
try {
|
|
5608
|
+
await connectOnce(socket, timeoutMs);
|
|
5609
|
+
const payload = agentModePayloadSchema.parse({ mode: options.mode });
|
|
5610
|
+
const raw = await emitWithAckTimeout(socket, "agent:mode", payload, timeoutMs);
|
|
5611
|
+
if (isAckError(raw)) {
|
|
5612
|
+
return { ok: false, message: `${raw.error.code}: ${raw.error.message}` };
|
|
5613
|
+
}
|
|
5614
|
+
const parsed = agentModeAckSchema.safeParse(raw);
|
|
5615
|
+
if (!parsed.success) {
|
|
5616
|
+
return { ok: false, message: `\u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}` };
|
|
5617
|
+
}
|
|
5618
|
+
if (!parsed.data.ok) {
|
|
5619
|
+
return { ok: false, message: "\u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B agent:mode (ok:false)" };
|
|
5620
|
+
}
|
|
5621
|
+
return { ok: true, mode: parsed.data.mode ?? options.mode };
|
|
5622
|
+
} catch (err) {
|
|
5623
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5624
|
+
} finally {
|
|
5625
|
+
socket.disconnect();
|
|
5349
5626
|
}
|
|
5350
|
-
const sha = await getHeadSha(params.worktreeDir);
|
|
5351
|
-
return gitOk({ committed: true, sha: sha.ok ? sha.data : null });
|
|
5352
5627
|
}
|
|
5353
5628
|
|
|
5354
|
-
// src/
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
import { dirname as dirname4, isAbsolute, join as join9, relative, resolve as resolve2 } from "path";
|
|
5358
|
-
var WORKTREES_RELATIVE_ROOT = join9(".claude", "worktrees");
|
|
5359
|
-
function getDefaultWorktreesRoot(repoRoot) {
|
|
5360
|
-
return join9(repoRoot, WORKTREES_RELATIVE_ROOT);
|
|
5361
|
-
}
|
|
5362
|
-
function resolveWorktreeDir(worktreesRoot, repoRoot, branchName) {
|
|
5363
|
-
if (isInsideRepo(worktreesRoot, repoRoot)) return join9(worktreesRoot, branchName);
|
|
5364
|
-
const repoHash = createHash2("sha1").update(resolve2(repoRoot)).digest("hex").slice(0, 12);
|
|
5365
|
-
return join9(worktreesRoot, repoHash, branchName);
|
|
5366
|
-
}
|
|
5367
|
-
function isInsideRepo(candidate, repoRoot) {
|
|
5368
|
-
const rel = relative(resolve2(repoRoot), resolve2(candidate));
|
|
5369
|
-
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
5629
|
+
// src/mcp/helpers.ts
|
|
5630
|
+
function textResult(text) {
|
|
5631
|
+
return { content: [{ type: "text", text }] };
|
|
5370
5632
|
}
|
|
5371
|
-
function
|
|
5372
|
-
|
|
5373
|
-
return realpathSync(path);
|
|
5374
|
-
} catch {
|
|
5375
|
-
return resolve2(path);
|
|
5376
|
-
}
|
|
5633
|
+
function errorResult(text) {
|
|
5634
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
5377
5635
|
}
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
}
|
|
5383
|
-
const entries = [];
|
|
5384
|
-
let path;
|
|
5385
|
-
let branch = null;
|
|
5386
|
-
const flush = () => {
|
|
5387
|
-
if (path) entries.push({ path, branch });
|
|
5388
|
-
path = void 0;
|
|
5389
|
-
branch = null;
|
|
5390
|
-
};
|
|
5391
|
-
for (const line of result.stdout.split("\n")) {
|
|
5392
|
-
if (line.length === 0) {
|
|
5393
|
-
flush();
|
|
5394
|
-
continue;
|
|
5395
|
-
}
|
|
5396
|
-
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5397
|
-
else if (line.startsWith("branch refs/heads/")) branch = line.slice("branch refs/heads/".length);
|
|
5398
|
-
}
|
|
5399
|
-
flush();
|
|
5400
|
-
return gitOk(entries);
|
|
5636
|
+
function leaseLostResult(taskLabel2, reason) {
|
|
5637
|
+
return errorResult(
|
|
5638
|
+
`\u0410\u0440\u0435\u043D\u0434\u0430 \u0437\u0430\u0434\u0430\u0447\u0438 ${taskLabel2} \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430: ${reason}. \u041F\u0440\u0435\u043A\u0440\u0430\u0442\u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430\u0434 \u043D\u0435\u0439 \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E \u2014 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0441\u0434\u0430\u0432\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430, \u0432\u044B\u0437\u043E\u0432\u0438 next_task \u0437\u0430 \u043D\u043E\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0435\u0439.`
|
|
5639
|
+
);
|
|
5401
5640
|
}
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
5407
|
-
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
5408
|
-
const worktreesRoot = params.worktreesRoot ?? getDefaultWorktreesRoot(params.repoRoot);
|
|
5409
|
-
const worktreeDir = resolveWorktreeDir(worktreesRoot, params.repoRoot, params.branchName);
|
|
5410
|
-
const list = await listWorktrees(params.repoRoot);
|
|
5411
|
-
if (!list.ok) return list;
|
|
5412
|
-
const existing = list.data.find((wt) => canonicalPath(wt.path) === canonicalPath(worktreeDir));
|
|
5413
|
-
if (existing) {
|
|
5414
|
-
if (existing.branch === params.branchName) {
|
|
5415
|
-
return gitOk({ worktreeDir, reused: true });
|
|
5416
|
-
}
|
|
5417
|
-
return gitErr(
|
|
5418
|
-
`\u041F\u043E \u043F\u0443\u0442\u0438 ${worktreeDir} \u0443\u0436\u0435 \u0435\u0441\u0442\u044C git worktree \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 "${existing.branch ?? "(\u043E\u0442\u0441\u043E\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u044B\u0439 HEAD)"}", \u0430 \u043E\u0436\u0438\u0434\u0430\u043B\u0430\u0441\u044C "${params.branchName}". \u0420\u0430\u0437\u0431\u0435\u0440\u0438\u0441\u044C \u0440\u0443\u043A\u0430\u043C\u0438: "git -C ${params.repoRoot} worktree remove ${worktreeDir}" (\u0434\u043E\u0431\u0430\u0432\u044C "--force", \u0435\u0441\u043B\u0438 \u0442\u0430\u043C \u043D\u0435\u0442 \u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u0430\u0436\u043D\u043E\u0433\u043E), \u0437\u0430\u0442\u0435\u043C \u043F\u043E\u0432\u0442\u043E\u0440\u0438.`
|
|
5419
|
-
);
|
|
5420
|
-
}
|
|
5421
|
-
if (existsSync9(worktreeDir)) {
|
|
5422
|
-
return gitErr(
|
|
5423
|
-
`\u041F\u0443\u0442\u044C ${worktreeDir} \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E git \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u0432\u043E\u0435\u0439 worktree. \u0423\u0431\u0435\u0440\u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u2014 \u0442\u0440\u043E\u0433\u0430\u0442\u044C \u0447\u0443\u0436\u043E\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E.`
|
|
5424
|
-
);
|
|
5425
|
-
}
|
|
5426
|
-
await runGit(["worktree", "prune"], { cwd: params.repoRoot });
|
|
5427
|
-
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
5428
|
-
cwd: params.repoRoot
|
|
5429
|
-
});
|
|
5430
|
-
if (!fetchResult.ok) {
|
|
5431
|
-
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
5432
|
-
}
|
|
5433
|
-
mkdirSync6(dirname4(worktreeDir), { recursive: true });
|
|
5434
|
-
const branchExists = await localBranchExists(params.repoRoot, params.branchName);
|
|
5435
|
-
const addArgs = branchExists ? ["worktree", "add", worktreeDir, params.branchName] : ["worktree", "add", "-b", params.branchName, worktreeDir, `${remote}/${params.defaultBranch}`];
|
|
5436
|
-
const addResult = await runGit(addArgs, { cwd: params.repoRoot });
|
|
5437
|
-
if (!addResult.ok) {
|
|
5438
|
-
return gitErr(describeGitFailure("git worktree add", addResult));
|
|
5439
|
-
}
|
|
5440
|
-
return gitOk({ worktreeDir, reused: false });
|
|
5641
|
+
function describeZodIssues(error) {
|
|
5642
|
+
return error.issues.map(
|
|
5643
|
+
(issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message
|
|
5644
|
+
).join("; ");
|
|
5441
5645
|
}
|
|
5442
|
-
|
|
5443
|
-
const
|
|
5444
|
-
if (!
|
|
5445
|
-
return
|
|
5646
|
+
function parseArgsOrError(schema, raw) {
|
|
5647
|
+
const parsed = schema.safeParse(raw);
|
|
5648
|
+
if (!parsed.success) {
|
|
5649
|
+
return { ok: false, message: `\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0435 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u044B: ${describeZodIssues(parsed.error)}` };
|
|
5446
5650
|
}
|
|
5447
|
-
return
|
|
5651
|
+
return { ok: true, data: parsed.data };
|
|
5448
5652
|
}
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
if (!fetchResult.ok) {
|
|
5459
|
-
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
5653
|
+
async function callServerOrError(client, event, payload, schema, timeoutMs) {
|
|
5654
|
+
let raw;
|
|
5655
|
+
try {
|
|
5656
|
+
raw = await client.callServer(event, payload, timeoutMs);
|
|
5657
|
+
} catch (err) {
|
|
5658
|
+
return {
|
|
5659
|
+
ok: false,
|
|
5660
|
+
message: `\u041D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430 \u043E\u0442 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u044B \u043D\u0430 ${event} (${err instanceof Error ? err.message : String(err)}). \u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442\u0441\u044F \u0432 \u0444\u043E\u043D\u0435 \u2014 \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0439 \u0435\u0449\u0451 \u0440\u0430\u0437 \u0447\u0443\u0442\u044C \u043F\u043E\u0437\u0436\u0435.`
|
|
5661
|
+
};
|
|
5460
5662
|
}
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5663
|
+
if (isAckError(raw)) {
|
|
5664
|
+
return {
|
|
5665
|
+
ok: false,
|
|
5666
|
+
message: `\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043E\u0442\u043A\u0430\u0437\u0430\u043B\u0430 \u043D\u0430 ${event}: ${raw.error.code}: ${raw.error.message}`
|
|
5667
|
+
};
|
|
5466
5668
|
}
|
|
5467
|
-
const
|
|
5468
|
-
if (
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
`\u041A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u0440\u0438 rebase (${conflictedFiles.join(", ")}), \u0438 "git rebase --abort" \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u043B: ${describeGitFailure("git rebase --abort", abortResult)}. Worktree \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u043D\u0443\u0436\u043D\u043E \u0440\u0443\u0447\u043D\u043E\u0435 \u0432\u043C\u0435\u0448\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E.`
|
|
5474
|
-
);
|
|
5475
|
-
}
|
|
5476
|
-
return gitOk({ outcome: "conflict", conflictedFiles });
|
|
5669
|
+
const parsed = schema.safeParse(raw);
|
|
5670
|
+
if (!parsed.success) {
|
|
5671
|
+
return {
|
|
5672
|
+
ok: false,
|
|
5673
|
+
message: `\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043F\u0440\u0438\u0441\u043B\u0430\u043B\u0430 \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442 \u043D\u0430 ${event}: ${parsed.error.message}`
|
|
5674
|
+
};
|
|
5477
5675
|
}
|
|
5478
|
-
return
|
|
5676
|
+
return { ok: true, data: parsed.data };
|
|
5479
5677
|
}
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5678
|
+
|
|
5679
|
+
// src/mcp/ask.ts
|
|
5680
|
+
var askHumanInputShape = {
|
|
5681
|
+
textMd: questionAskPayloadSchema.shape.textMd.describe(
|
|
5682
|
+
"\u0422\u0435\u043A\u0441\u0442 \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443, Markdown. \u041F\u0438\u0448\u0438 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0431\u0435\u0437 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0430 \u0441 \u043C\u043E\u0434\u0435\u043B\u044C\u044E."
|
|
5683
|
+
),
|
|
5684
|
+
targetRole: questionAskPayloadSchema.shape.targetRole.describe(
|
|
5685
|
+
'\u0420\u043E\u043B\u044C \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, "owner") \u2014 \u0432\u043E\u043F\u0440\u043E\u0441 \u0443\u0432\u0438\u0434\u044F\u0442 \u0432\u0441\u0435 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441 \u044D\u0442\u043E\u0439 \u0440\u043E\u043B\u044C\u044E, \u043E\u0442\u0432\u0435\u0442\u0438\u0442 \u043F\u0435\u0440\u0432\u044B\u0439. \u041D\u0443\u0436\u0435\u043D \u0420\u041E\u0412\u041D\u041E \u041E\u0414\u0418\u041D \u0430\u0434\u0440\u0435\u0441\u0430\u0442: targetRole \u0418\u041B\u0418 targetMemberId, \u043D\u0435 \u043E\u0431\u0430 \u0441\u0440\u0430\u0437\u0443 \u0438 \u043D\u0435 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E.'
|
|
5686
|
+
),
|
|
5687
|
+
targetMemberId: questionAskPayloadSchema.shape.targetMemberId.describe(
|
|
5688
|
+
"ID \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u0433\u043E \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0430\u0434\u0440\u0435\u0441\u0430\u0442 \u0432\u043E\u043F\u0440\u043E\u0441\u0430. \u041D\u0443\u0436\u0435\u043D \u0420\u041E\u0412\u041D\u041E \u041E\u0414\u0418\u041D \u0430\u0434\u0440\u0435\u0441\u0430\u0442: targetRole \u0418\u041B\u0418 targetMemberId, \u043D\u0435 \u043E\u0431\u0430 \u0441\u0440\u0430\u0437\u0443 \u0438 \u043D\u0435 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E."
|
|
5689
|
+
),
|
|
5690
|
+
wantsFiles: questionAskPayloadSchema.shape.wantsFiles.describe(
|
|
5691
|
+
"\u041F\u043E\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u043A \u043E\u0442\u0432\u0435\u0442\u0443 (\u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442, \u043B\u043E\u0433, \u0432\u044B\u0433\u0440\u0443\u0437\u043A\u0443). \u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u0441 \u0432\u044B\u0431\u043E\u0440\u043E\u043C \u0444\u0430\u0439\u043B\u0430. \u042D\u0442\u043E \u041F\u0420\u041E\u0421\u042C\u0411\u0410, \u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u0435: \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0432\u043F\u0440\u0430\u0432\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u0431\u0435\u0437 \u0444\u0430\u0439\u043B\u043E\u0432, \u0438 \u0442\u043E\u0433\u0434\u0430 `attachments` \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043F\u0443\u0441\u0442\u044B\u043C."
|
|
5692
|
+
),
|
|
5693
|
+
options: questionAskPayloadSchema.shape.options.describe(
|
|
5694
|
+
"\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u043E\u0442\u0432\u0435\u0442\u0430 (\u043F\u043E\u043A\u0430\u0436\u0443\u0442\u0441\u044F \u043A\u043D\u043E\u043F\u043A\u0430\u043C\u0438 \u0432 UI/Telegram). \u041D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439 \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043E\u0442\u0432\u0435\u0442\u0438\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C."
|
|
5695
|
+
),
|
|
5696
|
+
blocking: questionAskPayloadSchema.shape.blocking.describe(
|
|
5697
|
+
'true \u2014 \u0442\u0432\u043E\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441 WAITING_HUMAN, \u0438 \u044D\u0442\u043E\u0442 \u0432\u044B\u0437\u043E\u0432 \u0441\u0430\u043C \u0436\u0434\u0451\u0442 \u043E\u0442\u0432\u0435\u0442 \u0434\u043E 600 \u0441; \u0435\u0441\u043B\u0438 \u0437\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435\u0442, \u0432\u044B\u0437\u043E\u0432 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0441 \u043F\u043E\u043C\u0435\u0442\u043A\u043E\u0439 "pending" \u2014 \u0442\u043E\u0433\u0434\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId \u0438\u0437 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430, \u043D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430. false \u2014 \u0432\u043E\u043F\u0440\u043E\u0441 \u043F\u0440\u043E\u0441\u0442\u043E \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F, \u0431\u0435\u0437 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0438 \u0431\u0435\u0437 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 (\u0434\u043B\u044F \u043D\u0435 \u0441\u0440\u043E\u0447\u043D\u044B\u0445 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0439).'
|
|
5698
|
+
)
|
|
5699
|
+
};
|
|
5700
|
+
var BLOCKING_WAIT_SEC = 600;
|
|
5701
|
+
var ASK_ACK_BUFFER_MS = 15e3;
|
|
5702
|
+
async function askHumanHandler(args, runtime) {
|
|
5703
|
+
const state = readState(runtime.stateFilePath);
|
|
5704
|
+
const taskId = state.currentTask?.taskId;
|
|
5705
|
+
const waitSec = args.blocking ? BLOCKING_WAIT_SEC : 0;
|
|
5706
|
+
const parsedArgs = parseArgsOrError(questionAskPayloadSchema, {
|
|
5707
|
+
...taskId ? { taskId } : {},
|
|
5708
|
+
textMd: args.textMd,
|
|
5709
|
+
...args.targetRole ? { targetRole: args.targetRole } : {},
|
|
5710
|
+
...args.targetMemberId ? { targetMemberId: args.targetMemberId } : {},
|
|
5711
|
+
...args.options && args.options.length > 0 ? { options: args.options } : {},
|
|
5712
|
+
...args.wantsFiles ? { wantsFiles: true } : {},
|
|
5713
|
+
blocking: args.blocking,
|
|
5714
|
+
waitSec
|
|
5715
|
+
});
|
|
5716
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5717
|
+
if (taskId) {
|
|
5718
|
+
const lease = runtime.client.isLeaseLost(taskId);
|
|
5719
|
+
if (lease.lost) return leaseLostResult(state.currentTask?.taskKey ?? taskId, lease.reason);
|
|
5487
5720
|
}
|
|
5488
|
-
const
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5721
|
+
const result = await callServerOrError(
|
|
5722
|
+
runtime.client,
|
|
5723
|
+
"question:ask",
|
|
5724
|
+
parsedArgs.data,
|
|
5725
|
+
questionAskAckSchema,
|
|
5726
|
+
waitSec * 1e3 + ASK_ACK_BUFFER_MS
|
|
5727
|
+
);
|
|
5728
|
+
if (!result.ok) return errorResult(result.message);
|
|
5729
|
+
const settled = describeSettledQuestion(result.data.question);
|
|
5730
|
+
if (settled) return textResult(settled);
|
|
5731
|
+
if (args.blocking) {
|
|
5732
|
+
return textResult(
|
|
5733
|
+
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u0437\u0430\u0434\u0430\u043D, \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u0430 \u0432 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 (WAITING_HUMAN). \u041E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E ${BLOCKING_WAIT_SEC} \u0441 (pending) \u2014 \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId="${result.data.question.id}", \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0436\u0434\u0430\u0442\u044C. \u041D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443 \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435, \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u0448\u044C \u043E\u0442\u0432\u0435\u0442.`
|
|
5493
5734
|
);
|
|
5494
5735
|
}
|
|
5495
|
-
return
|
|
5736
|
+
return textResult(
|
|
5737
|
+
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D (\u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u041E\u0442\u0432\u0435\u0442\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u2014 \u0435\u0441\u043B\u0438 \u043E\u043D \u043D\u0443\u0436\u0435\u043D, \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C, \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId="${result.data.question.id}".`
|
|
5738
|
+
);
|
|
5496
5739
|
}
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5740
|
+
var waitAnswerInputShape = {
|
|
5741
|
+
questionId: questionWaitPayloadSchema.shape.questionId.describe(
|
|
5742
|
+
"ID \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u2014 \u0438\u0437 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430 ask_human (question.id) \u0438\u043B\u0438 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0433\u043E \u0432\u044B\u0437\u043E\u0432\u0430 wait_answer."
|
|
5743
|
+
),
|
|
5744
|
+
waitSec: questionWaitPayloadSchema.shape.waitSec.optional().describe("\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C \u043E\u0442\u0432\u0435\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u044B\u0437\u043E\u0432\u0435, 0..600. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 300.")
|
|
5745
|
+
};
|
|
5746
|
+
var DEFAULT_WAIT_ANSWER_SEC = 300;
|
|
5747
|
+
var WAIT_ANSWER_ACK_BUFFER_MS = 15e3;
|
|
5748
|
+
async function waitAnswerHandler(args, runtime) {
|
|
5749
|
+
const waitSec = args.waitSec ?? DEFAULT_WAIT_ANSWER_SEC;
|
|
5750
|
+
const parsedArgs = parseArgsOrError(questionWaitPayloadSchema, {
|
|
5751
|
+
questionId: args.questionId,
|
|
5752
|
+
waitSec
|
|
5507
5753
|
});
|
|
5508
|
-
if (!
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5754
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5755
|
+
const result = await callServerOrError(
|
|
5756
|
+
runtime.client,
|
|
5757
|
+
"question:wait",
|
|
5758
|
+
parsedArgs.data,
|
|
5759
|
+
questionWaitAckSchema,
|
|
5760
|
+
waitSec * 1e3 + WAIT_ANSWER_ACK_BUFFER_MS
|
|
5761
|
+
);
|
|
5762
|
+
if (!result.ok) return errorResult(result.message);
|
|
5763
|
+
const settled = describeSettledQuestion(result.data.question);
|
|
5764
|
+
if (settled) return textResult(settled);
|
|
5765
|
+
return textResult(
|
|
5766
|
+
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u0432\u0441\u0451 \u0435\u0449\u0451 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 (\u0436\u0434\u0430\u043B ${waitSec} \u0441). \u0412\u044B\u0437\u043E\u0432\u0438 wait_answer \u0435\u0449\u0451 \u0440\u0430\u0437 \u0441 \u0442\u0435\u043C \u0436\u0435 questionId, \u043A\u043E\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0443\u043C\u0435\u0441\u0442\u043D\u043E.`
|
|
5767
|
+
);
|
|
5514
5768
|
}
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
}
|
|
5528
|
-
|
|
5529
|
-
worktreeDir = worktree.data.worktreeDir;
|
|
5769
|
+
function describeAnswerAttachments(question) {
|
|
5770
|
+
if (question.attachments.length === 0) return "";
|
|
5771
|
+
const lines = question.attachments.map(
|
|
5772
|
+
(file) => file.downloadUrl ? `- ${file.fileName} (${file.mimeType}) \u2014 \u0441\u043A\u0430\u0447\u0430\u0439: ${file.downloadUrl}` : `- ${file.fileName} (${file.mimeType}) \u2014 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430, \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442`
|
|
5773
|
+
);
|
|
5774
|
+
return `
|
|
5775
|
+
|
|
5776
|
+
\u041A \u043E\u0442\u0432\u0435\u0442\u0443 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u044B \u0444\u0430\u0439\u043B\u044B (${question.attachments.length}). \u042D\u0442\u043E \u0447\u0430\u0441\u0442\u044C \u043E\u0442\u0432\u0435\u0442\u0430: \u0441\u043A\u0430\u0447\u0430\u0439 \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u041A\u0410\u0416\u0414\u042B\u0419, \u0441\u0441\u044B\u043B\u043A\u0438 \u0436\u0438\u0432\u0443\u0442 ${question.attachments[0]?.expiresInSeconds ?? 300} \u0441 \u2014 \u043D\u0435 \u043E\u0442\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0439.
|
|
5777
|
+
` + lines.join("\n");
|
|
5778
|
+
}
|
|
5779
|
+
function describeSettledQuestion(question) {
|
|
5780
|
+
if (question.status === QuestionStatus.ANSWERED) {
|
|
5781
|
+
return `\u0412\u043E\u043F\u0440\u043E\u0441 ${question.id} \u043F\u043E\u043B\u0443\u0447\u0438\u043B \u043E\u0442\u0432\u0435\u0442:
|
|
5782
|
+
${question.answerMd ?? ""}${describeAnswerAttachments(question)}`;
|
|
5530
5783
|
}
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
commitPrefix: params.commitPrefix,
|
|
5534
|
-
title: params.taskTitle,
|
|
5535
|
-
taskKey: params.taskKey
|
|
5536
|
-
});
|
|
5537
|
-
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5538
|
-
const rebase = await rebaseOntoDefault({
|
|
5539
|
-
worktreeDir,
|
|
5540
|
-
defaultBranch: params.defaultBranch,
|
|
5541
|
-
remote
|
|
5542
|
-
});
|
|
5543
|
-
if (!rebase.ok) return { status: "refused", worktreeDir, message: rebase.message };
|
|
5544
|
-
if (rebase.data.outcome === "conflict") {
|
|
5545
|
-
const files = rebase.data.conflictedFiles;
|
|
5546
|
-
return {
|
|
5547
|
-
status: "conflict",
|
|
5548
|
-
worktreeDir,
|
|
5549
|
-
conflictedFiles: files,
|
|
5550
|
-
message: `Rebase \u043D\u0430 ${remote}/${params.defaultBranch} \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0432 \u0444\u0430\u0439\u043B\u0430\u0445: ${files.join(", ")}. Rebase \u043E\u0442\u043C\u0435\u043D\u0451\u043D ("git rebase --abort"), \u0432\u0435\u0442\u043A\u0430 "${params.branchName}" \u0438 \u0435\u0451 worktree \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u044B \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`
|
|
5551
|
-
};
|
|
5784
|
+
if (question.status === QuestionStatus.DISMISSED) {
|
|
5785
|
+
return `\u0412\u043E\u043F\u0440\u043E\u0441 ${question.id} \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D (DISMISSED) \u2014 \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442. \u0420\u0435\u0448\u0438, \u043A\u0430\u043A \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C \u0434\u0430\u043B\u044C\u0448\u0435, \u0441\u0430\u043C, \u0438\u043B\u0438 \u0437\u0430\u0434\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0437\u0430\u043D\u043E\u0432\u043E.`;
|
|
5552
5786
|
}
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5787
|
+
return void 0;
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5790
|
+
// package.json
|
|
5791
|
+
var package_default = {
|
|
5792
|
+
name: "@quiel/cli",
|
|
5793
|
+
version: "0.5.3",
|
|
5794
|
+
description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 (Claude Code, Codex, Cursor, OpenCode) \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
|
|
5795
|
+
homepage: "https://quiel.app",
|
|
5796
|
+
repository: {
|
|
5797
|
+
type: "git",
|
|
5798
|
+
url: "git+https://github.com/Quiel-App/quiel-cli.git"
|
|
5799
|
+
},
|
|
5800
|
+
bugs: {
|
|
5801
|
+
url: "https://github.com/Quiel-App/quiel-cli/issues"
|
|
5802
|
+
},
|
|
5803
|
+
keywords: [
|
|
5804
|
+
"quiel",
|
|
5805
|
+
"claude-code",
|
|
5806
|
+
"codex",
|
|
5807
|
+
"cursor",
|
|
5808
|
+
"opencode",
|
|
5809
|
+
"mcp",
|
|
5810
|
+
"mcp-server",
|
|
5811
|
+
"ai-agents",
|
|
5812
|
+
"task-tracker"
|
|
5813
|
+
],
|
|
5814
|
+
license: "Apache-2.0",
|
|
5815
|
+
type: "module",
|
|
5816
|
+
bin: {
|
|
5817
|
+
quiel: "dist/cli.js"
|
|
5818
|
+
},
|
|
5819
|
+
files: [
|
|
5820
|
+
"dist",
|
|
5821
|
+
"README.md",
|
|
5822
|
+
"LICENSE"
|
|
5823
|
+
],
|
|
5824
|
+
engines: {
|
|
5825
|
+
node: ">=22"
|
|
5826
|
+
},
|
|
5827
|
+
publishConfig: {
|
|
5828
|
+
access: "public"
|
|
5829
|
+
},
|
|
5830
|
+
scripts: {
|
|
5831
|
+
build: "tsup",
|
|
5832
|
+
dev: "tsup --watch",
|
|
5833
|
+
lint: "biome check .",
|
|
5834
|
+
typecheck: "tsc --noEmit",
|
|
5835
|
+
test: "vitest run --passWithNoTests"
|
|
5836
|
+
},
|
|
5837
|
+
dependencies: {
|
|
5838
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
5839
|
+
commander: "15.0.0",
|
|
5840
|
+
"socket.io-client": "4.8.3",
|
|
5841
|
+
zod: "4.5.4"
|
|
5842
|
+
},
|
|
5843
|
+
devDependencies: {
|
|
5844
|
+
"@quiel/config": "workspace:*",
|
|
5845
|
+
"@quiel/shared": "workspace:*",
|
|
5846
|
+
"@types/node": "20.19.43",
|
|
5847
|
+
tsup: "8.5.1",
|
|
5848
|
+
typescript: "7.0.2",
|
|
5849
|
+
vitest: "5.0.0"
|
|
5559
5850
|
}
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5851
|
+
};
|
|
5852
|
+
|
|
5853
|
+
// src/version.ts
|
|
5854
|
+
var VERSION = package_default.version;
|
|
5855
|
+
|
|
5856
|
+
// src/mcp/constants.ts
|
|
5857
|
+
var MCP_SERVER_NAME = "quiel";
|
|
5858
|
+
var MCP_SERVER_VERSION = VERSION;
|
|
5859
|
+
|
|
5860
|
+
// src/mcp/get-context.ts
|
|
5861
|
+
var ALL_CONTEXT_INCLUDES = [
|
|
5862
|
+
"spec",
|
|
5863
|
+
"decisions",
|
|
5864
|
+
// ВСЕ документы проекта — по умолчанию, а не по особому запросу.
|
|
5865
|
+
// `spec`/`decisions` покрывают два типа из пяти, и человек, положивший
|
|
5866
|
+
// контекст под типом `DATA` или `PLAN`, вправе ожидать, что агент его
|
|
5867
|
+
// увидит. 14 сентября 2026 ровно этого и не случилось.
|
|
5868
|
+
"documents",
|
|
5869
|
+
"related",
|
|
5870
|
+
"repo"
|
|
5871
|
+
];
|
|
5872
|
+
var getContextInputShape = {
|
|
5873
|
+
taskId: contextGetPayloadSchema.shape.taskId.describe(
|
|
5874
|
+
"ID \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u043B\u044F \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043D\u0443\u0436\u0435\u043D \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442. \u0415\u0441\u043B\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0430\u0433\u0435\u043D\u0442\u0430 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C)."
|
|
5875
|
+
),
|
|
5876
|
+
include: contextGetPayloadSchema.shape.include.optional().describe(
|
|
5877
|
+
"\u0427\u0442\u043E \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C: spec | decisions | related | repo. \u041D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u0438\u043B\u0438 \u043F\u0443\u0441\u0442 \u2014 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0432\u0441\u0451 \u0441\u0440\u0430\u0437\u0443."
|
|
5878
|
+
)
|
|
5879
|
+
};
|
|
5880
|
+
async function getContextHandler(args, runtime) {
|
|
5881
|
+
const state = readState(runtime.stateFilePath);
|
|
5882
|
+
const taskId = args.taskId ?? state.currentTask?.taskId;
|
|
5883
|
+
if (taskId) {
|
|
5884
|
+
const lease = runtime.client.isLeaseLost(taskId);
|
|
5885
|
+
if (lease.lost) return leaseLostResult(taskId, lease.reason);
|
|
5575
5886
|
}
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5887
|
+
const include = args.include && args.include.length > 0 ? args.include : [...ALL_CONTEXT_INCLUDES];
|
|
5888
|
+
const payload = contextGetPayloadSchema.parse({ ...taskId ? { taskId } : {}, include });
|
|
5889
|
+
const result = await callServerOrError(
|
|
5890
|
+
runtime.client,
|
|
5891
|
+
"context:get",
|
|
5892
|
+
payload,
|
|
5893
|
+
contextPayloadSchema
|
|
5894
|
+
);
|
|
5895
|
+
if (!result.ok) return errorResult(result.message);
|
|
5896
|
+
return textResult(JSON.stringify(result.data, null, 2));
|
|
5583
5897
|
}
|
|
5584
5898
|
|
|
5585
|
-
// src/
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
const
|
|
5591
|
-
if (
|
|
5592
|
-
const
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
);
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5899
|
+
// src/mcp/get-task.ts
|
|
5900
|
+
var getTaskInputShape = {
|
|
5901
|
+
taskId: taskGetPayloadSchema.shape.taskId.describe("ID \u0437\u0430\u0434\u0430\u0447\u0438 (Task.id) \u2014 \u043D\u0435 \u043A\u043B\u044E\u0447 \u0432\u0438\u0434\u0430 REL-42")
|
|
5902
|
+
};
|
|
5903
|
+
async function getTaskHandler(args, runtime) {
|
|
5904
|
+
const lease = runtime.client.isLeaseLost(args.taskId);
|
|
5905
|
+
if (lease.lost) return leaseLostResult(args.taskId, lease.reason);
|
|
5906
|
+
const payload = taskGetPayloadSchema.parse({ taskId: args.taskId });
|
|
5907
|
+
const result = await callServerOrError(runtime.client, "task:get", payload, taskGetAckSchema);
|
|
5908
|
+
if (!result.ok) return errorResult(result.message);
|
|
5909
|
+
return textResult(JSON.stringify(result.data, null, 2));
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
// src/mcp/log.ts
|
|
5913
|
+
var logInputShape = {
|
|
5914
|
+
message: taskProgressPayloadSchema.shape.note.describe("\u0421\u0442\u0440\u043E\u043A\u0430 \u0432 \u043B\u0435\u043D\u0442\u0443 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438"),
|
|
5915
|
+
level: taskProgressPayloadSchema.shape.level.optional().describe("debug | info | warn | error, \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E info")
|
|
5916
|
+
};
|
|
5917
|
+
async function logHandler(args, runtime) {
|
|
5918
|
+
const state = readState(runtime.stateFilePath);
|
|
5919
|
+
if (!state.currentTask) {
|
|
5920
|
+
return errorResult(
|
|
5921
|
+
"\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430 \u0432 \u0435\u0451 \u043B\u0435\u043D\u0442\u0443. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0437\u043E\u0432\u0438 next_task."
|
|
5599
5922
|
);
|
|
5600
5923
|
}
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
const remote = params.remote ?? "origin";
|
|
5605
|
-
let worktreeDir;
|
|
5606
|
-
if (params.worktreeDir) {
|
|
5607
|
-
worktreeDir = params.worktreeDir;
|
|
5608
|
-
} else {
|
|
5609
|
-
const worktree = await ensureWorktree({
|
|
5610
|
-
repoRoot: params.repoRoot,
|
|
5611
|
-
branchName: params.branchName,
|
|
5612
|
-
defaultBranch: params.defaultBranch,
|
|
5613
|
-
remote,
|
|
5614
|
-
worktreesRoot: params.worktreesRoot
|
|
5615
|
-
});
|
|
5616
|
-
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
5617
|
-
worktreeDir = worktree.data.worktreeDir;
|
|
5924
|
+
const lease = runtime.client.isLeaseLost(state.currentTask.taskId);
|
|
5925
|
+
if (lease.lost) {
|
|
5926
|
+
return leaseLostResult(state.currentTask.taskKey ?? state.currentTask.taskId, lease.reason);
|
|
5618
5927
|
}
|
|
5619
|
-
const
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
taskKey: params.taskKey
|
|
5624
|
-
});
|
|
5625
|
-
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5626
|
-
const rebase = await rebaseOntoDefault({
|
|
5627
|
-
worktreeDir,
|
|
5628
|
-
defaultBranch: params.defaultBranch,
|
|
5629
|
-
remote
|
|
5928
|
+
const payload = taskProgressPayloadSchema.parse({
|
|
5929
|
+
taskId: state.currentTask.taskId,
|
|
5930
|
+
note: args.message,
|
|
5931
|
+
...args.level ? { level: args.level } : {}
|
|
5630
5932
|
});
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
if (
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
5641
|
-
worktreeRemoved = true;
|
|
5642
|
-
}
|
|
5643
|
-
}
|
|
5644
|
-
return {
|
|
5645
|
-
status: "pushed",
|
|
5646
|
-
worktreeDir,
|
|
5647
|
-
branchName: params.branchName,
|
|
5648
|
-
commitSha: sha.ok ? sha.data : null,
|
|
5649
|
-
pushedTo: push.data.pushedTo,
|
|
5650
|
-
conflicted,
|
|
5651
|
-
conflictedFiles: rebase.data.conflictedFiles,
|
|
5652
|
-
worktreeRemoved
|
|
5653
|
-
};
|
|
5933
|
+
const result = await callServerOrError(
|
|
5934
|
+
runtime.client,
|
|
5935
|
+
"task:progress",
|
|
5936
|
+
payload,
|
|
5937
|
+
taskProgressAckSchema
|
|
5938
|
+
);
|
|
5939
|
+
if (!result.ok) return errorResult(result.message);
|
|
5940
|
+
if (!result.data.ok) return errorResult("\u0421\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u0437\u0430\u043F\u0438\u0441\u044C \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 (ok:false).");
|
|
5941
|
+
return textResult("\u0417\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438.");
|
|
5654
5942
|
}
|
|
5655
5943
|
|
|
5656
5944
|
// src/mcp/task-worktree.ts
|
|
@@ -5725,7 +6013,9 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5725
6013
|
taskId: task.task.id,
|
|
5726
6014
|
taskKey: task.task.key,
|
|
5727
6015
|
resumeToken: task.lease.resumeToken,
|
|
5728
|
-
gitMode: task.project.gitMode
|
|
6016
|
+
gitMode: task.project.gitMode,
|
|
6017
|
+
branchName: task.project.branchName,
|
|
6018
|
+
commitPrefix: task.project.commitPrefix
|
|
5729
6019
|
}
|
|
5730
6020
|
},
|
|
5731
6021
|
stateFilePath
|
|
@@ -5738,7 +6028,12 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5738
6028
|
const worktree = await ensureWorktree({
|
|
5739
6029
|
repoRoot: runtime.cwd,
|
|
5740
6030
|
branchName: plan.branchName,
|
|
5741
|
-
defaultBranch: plan.defaultBranch
|
|
6031
|
+
defaultBranch: plan.defaultBranch,
|
|
6032
|
+
// LIM-03: задачу уже начинали и прервали — рабочее дерево заводим
|
|
6033
|
+
// ОТ ТОЙ ВЕТКИ, а не от основной. Иначе спасённая работа осталась
|
|
6034
|
+
// бы лежать на origin никем не прочитанной, и агент сделал бы её
|
|
6035
|
+
// заново, потратив свой лимит подписки на чужую.
|
|
6036
|
+
continueFromBranch: task.continueFrom?.branchName
|
|
5742
6037
|
});
|
|
5743
6038
|
if (worktree.ok) {
|
|
5744
6039
|
updateState(
|
|
@@ -5748,6 +6043,8 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5748
6043
|
taskKey: task.task.key,
|
|
5749
6044
|
resumeToken: task.lease.resumeToken,
|
|
5750
6045
|
gitMode: task.project.gitMode,
|
|
6046
|
+
branchName: task.project.branchName,
|
|
6047
|
+
commitPrefix: task.project.commitPrefix,
|
|
5751
6048
|
worktreeDir: worktree.data.worktreeDir
|
|
5752
6049
|
}
|
|
5753
6050
|
},
|
|
@@ -5766,11 +6063,14 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5766
6063
|
const warning = !started.ok ? `
|
|
5767
6064
|
|
|
5768
6065
|
\u0412\u041D\u0418\u041C\u0410\u041D\u0418\u0415: task:start \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D \u0441\u0435\u0440\u0432\u0435\u0440\u043E\u043C (${started.message}). \u0410\u0440\u0435\u043D\u0434\u0430 \u0442\u0432\u043E\u044F (\u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u044B\u0434\u0430\u043D\u0430), \u043D\u043E \u0441\u0442\u0430\u0442\u0443\u0441 \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043C\u043E\u0436\u0435\u0442 \u043E\u0441\u0442\u0430\u0442\u044C\u0441\u044F ASSIGNED \u0432\u043C\u0435\u0441\u0442\u043E IN_PROGRESS \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043A\u0430\u043A \u043E\u0431\u044B\u0447\u043D\u043E, report \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u0441\u0434\u0430\u0441\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442.` : !started.data.ok ? "\n\n\u0412\u041D\u0418\u041C\u0410\u041D\u0418\u0415: \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B task:start (ok:false). \u0410\u0440\u0435\u043D\u0434\u0430 \u0442\u0432\u043E\u044F, \u043D\u043E \u0441\u0442\u0430\u0442\u0443\u0441 \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0442\u044C \u2014 \u0443\u043F\u043E\u043C\u044F\u043D\u0438 \u044D\u0442\u043E \u0432 report." : "";
|
|
6066
|
+
const continueNote = task.continueFrom ? `
|
|
6067
|
+
|
|
6068
|
+
\u042D\u0422\u0423 \u0417\u0410\u0414\u0410\u0427\u0423 \u0423\u0416\u0415 \u041D\u0410\u0427\u0418\u041D\u0410\u041B\u0418. \u041D\u0435\u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u043B\u0435\u0436\u0438\u0442 \u0432 \u0432\u0435\u0442\u043A\u0435 "${task.continueFrom.branchName}"${task.continueFrom.sha ? ` (\u043A\u043E\u043C\u043C\u0438\u0442 ${task.continueFrom.sha.slice(0, 8)})` : ""}${task.continueFrom.note ? `: ${task.continueFrom.note}` : ""}. \u0420\u0430\u0431\u043E\u0447\u0435\u0435 \u0434\u0435\u0440\u0435\u0432\u043E \u0437\u0430\u0432\u0435\u0434\u0435\u043D\u043E \u043E\u0442 \u043D\u0435\u0451 \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u0438 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439, \u0430 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0439 \u0437\u0430\u043D\u043E\u0432\u043E. \u0420\u0435\u0448\u0438\u0448\u044C, \u0447\u0442\u043E \u043F\u0440\u043E\u0449\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u0430\u0442\u044C, \u2014 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439, \u043D\u043E \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438.` : "";
|
|
5769
6069
|
return textResult(
|
|
5770
6070
|
`\u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0430:
|
|
5771
6071
|
${JSON.stringify(task, null, 2)}
|
|
5772
6072
|
|
|
5773
|
-
${worktreeNote}${warning}`
|
|
6073
|
+
${worktreeNote}${continueNote}${warning}`
|
|
5774
6074
|
);
|
|
5775
6075
|
}
|
|
5776
6076
|
|
|
@@ -6010,7 +6310,7 @@ async function settleApproval(ack, commandCwd, runtime, waitSecUsed) {
|
|
|
6010
6310
|
}
|
|
6011
6311
|
|
|
6012
6312
|
// src/mcp/set-mode.ts
|
|
6013
|
-
import { z as
|
|
6313
|
+
import { z as z38 } from "zod";
|
|
6014
6314
|
|
|
6015
6315
|
// src/cli/mode.ts
|
|
6016
6316
|
function parseModeArg(raw) {
|
|
@@ -6051,7 +6351,7 @@ async function requestModeChange(modeArg, options = {}) {
|
|
|
6051
6351
|
|
|
6052
6352
|
// src/mcp/set-mode.ts
|
|
6053
6353
|
var setModeInputShape = {
|
|
6054
|
-
mode:
|
|
6354
|
+
mode: z38.enum(["auto", "manual"]).describe("auto \u2014 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430, manual \u2014 \u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C (\xA710.2 \u0422\u0417)")
|
|
6055
6355
|
};
|
|
6056
6356
|
async function setModeHandler(args, runtime) {
|
|
6057
6357
|
const result = await requestModeChange(args.mode, {
|
|
@@ -6066,20 +6366,20 @@ async function setModeHandler(args, runtime) {
|
|
|
6066
6366
|
}
|
|
6067
6367
|
|
|
6068
6368
|
// src/mcp/submit-work.ts
|
|
6069
|
-
import { z as
|
|
6369
|
+
import { z as z39 } from "zod";
|
|
6070
6370
|
var submitWorkInputShape = {
|
|
6071
|
-
branchName:
|
|
6371
|
+
branchName: z39.string().min(1).describe(
|
|
6072
6372
|
"\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0431\u0435\u0440\u0438 \u0440\u043E\u0432\u043D\u043E \u0438\u0437 project.branchName \u0432 TaskPayload, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0432\u0435\u0440\u043D\u0443\u043B next_task. \u041D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0441\u0432\u043E\u0451."
|
|
6073
6373
|
),
|
|
6074
|
-
defaultBranch:
|
|
6075
|
-
commitPrefix:
|
|
6076
|
-
taskKey:
|
|
6077
|
-
taskTitle:
|
|
6078
|
-
repoRoot:
|
|
6079
|
-
testsPassed:
|
|
6374
|
+
defaultBranch: z39.string().min(1).describe("\u041E\u0441\u043D\u043E\u0432\u043D\u0430\u044F \u0432\u0435\u0442\u043A\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u2014 \u0438\u0437 project.repos[].defaultBranch."),
|
|
6375
|
+
commitPrefix: z39.string().min(1).describe('\u041F\u0440\u0435\u0444\u0438\u043A\u0441 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u2014 \u0438\u0437 project.commitPrefix, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 "ATL-42:".'),
|
|
6376
|
+
taskKey: z39.string().min(1).describe("\u041A\u043B\u044E\u0447 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 ATL-42 \u2014 \u0438\u0437 task.key."),
|
|
6377
|
+
taskTitle: z39.string().min(1).describe("\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0438\u0437 task.title."),
|
|
6378
|
+
repoRoot: z39.string().min(1).optional().describe("\u041A\u043E\u0440\u0435\u043D\u044C git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u2014 \u0440\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0440\u043E\u0435\u043A\u0442\u0430."),
|
|
6379
|
+
testsPassed: z39.boolean().optional().describe(
|
|
6080
6380
|
"\u041F\u0440\u043E\u0448\u043B\u0438 \u043B\u0438 \u0442\u0435\u0441\u0442\u044B. \u0415\u0441\u043B\u0438 \u043F\u0440\u043E\u0435\u043A\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0437\u0435\u043B\u0451\u043D\u044B\u0445 \u0442\u0435\u0441\u0442\u043E\u0432, \u0430 \u0437\u0434\u0435\u0441\u044C false \u2014 \u0441\u0434\u0430\u0447\u0430 \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442\u0441\u044F, \u0438 \u044D\u0442\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E."
|
|
6081
6381
|
),
|
|
6082
|
-
requireTestsPassed:
|
|
6382
|
+
requireTestsPassed: z39.boolean().optional().describe("ProjectSettings.requireTests \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0438\u0437 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0438.")
|
|
6083
6383
|
};
|
|
6084
6384
|
async function submitWorkHandler(args, runtime) {
|
|
6085
6385
|
const state = readState(runtime.stateFilePath);
|
|
@@ -6553,13 +6853,13 @@ function formatConnectResult(result) {
|
|
|
6553
6853
|
}
|
|
6554
6854
|
|
|
6555
6855
|
// src/hooks/pre-compact.ts
|
|
6556
|
-
import { z as
|
|
6557
|
-
var PreCompactInputSchema =
|
|
6558
|
-
hook_event_name:
|
|
6559
|
-
session_id:
|
|
6560
|
-
cwd:
|
|
6561
|
-
trigger:
|
|
6562
|
-
custom_instructions:
|
|
6856
|
+
import { z as z40 } from "zod";
|
|
6857
|
+
var PreCompactInputSchema = z40.object({
|
|
6858
|
+
hook_event_name: z40.literal("PreCompact"),
|
|
6859
|
+
session_id: z40.string(),
|
|
6860
|
+
cwd: z40.string(),
|
|
6861
|
+
trigger: z40.enum(["manual", "auto"]),
|
|
6862
|
+
custom_instructions: z40.string().nullable()
|
|
6563
6863
|
}).passthrough();
|
|
6564
6864
|
function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6565
6865
|
const lastPreCompact = state.currentTask ? { ...state.currentTask, savedAt: now() } : null;
|
|
@@ -6568,14 +6868,14 @@ function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).
|
|
|
6568
6868
|
|
|
6569
6869
|
// src/hooks/pre-tool-use.ts
|
|
6570
6870
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
|
|
6571
|
-
import { z as
|
|
6572
|
-
var PreToolUseInputSchema =
|
|
6573
|
-
hook_event_name:
|
|
6574
|
-
session_id:
|
|
6575
|
-
cwd:
|
|
6576
|
-
tool_name:
|
|
6577
|
-
tool_input:
|
|
6578
|
-
tool_use_id:
|
|
6871
|
+
import { z as z41 } from "zod";
|
|
6872
|
+
var PreToolUseInputSchema = z41.object({
|
|
6873
|
+
hook_event_name: z41.literal("PreToolUse"),
|
|
6874
|
+
session_id: z41.string(),
|
|
6875
|
+
cwd: z41.string(),
|
|
6876
|
+
tool_name: z41.string(),
|
|
6877
|
+
tool_input: z41.record(z41.string(), z41.unknown()),
|
|
6878
|
+
tool_use_id: z41.string()
|
|
6579
6879
|
}).passthrough();
|
|
6580
6880
|
var NO_TRUST_CACHE_REASON = "\u041A\u044D\u0448 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043E\u0432\u0435\u0440\u0438\u044F (TrustRules) \u043F\u0443\u0441\u0442 \u2014 \u043A\u043B\u0438\u0435\u043D\u0442 \u043D\u0438 \u0440\u0430\u0437\u0443 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0430\u043B \u0438\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u043D\u0435\u0442 agent:hello/trust:updated). \u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 \u043F\u0440\u043E\u0444\u0438\u043B\u044C (full/guarded/approve-all) \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (fail-closed \u043F\u0440\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \xA710.4 \u0422\u0417) \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435: `quiel connect` (\u044D\u0442\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0430 \u0438\u043C\u0435\u043D\u043D\u043E \u0434\u043B\u044F \u0442\u0430\u043A\u043E\u0433\u043E \u0441\u043B\u0443\u0447\u0430\u044F, \u0441\u043C. isBootstrapCommand) \u2014 \u043F\u043E\u0441\u043B\u0435 \u0440\u0443\u043A\u043E\u043F\u043E\u0436\u0430\u0442\u0438\u044F \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043F\u0440\u0438\u0434\u0443\u0442, \u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043D\u0430\u0447\u043D\u0443\u0442 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442\u044C. \u0415\u0441\u043B\u0438 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C, \u0432\u044B\u0437\u043E\u0432\u0438 request_approval.";
|
|
6581
6881
|
var BOOTSTRAP_COMMAND_RE = /^quiel\s+(connect|status)\b/;
|
|
@@ -6807,13 +7107,13 @@ function readNewUsage(transcriptPath, offset) {
|
|
|
6807
7107
|
}
|
|
6808
7108
|
|
|
6809
7109
|
// src/hooks/session-start.ts
|
|
6810
|
-
import { z as
|
|
6811
|
-
var SessionStartInputSchema =
|
|
6812
|
-
hook_event_name:
|
|
6813
|
-
session_id:
|
|
6814
|
-
cwd:
|
|
6815
|
-
source:
|
|
6816
|
-
model:
|
|
7110
|
+
import { z as z42 } from "zod";
|
|
7111
|
+
var SessionStartInputSchema = z42.object({
|
|
7112
|
+
hook_event_name: z42.literal("SessionStart"),
|
|
7113
|
+
session_id: z42.string(),
|
|
7114
|
+
cwd: z42.string(),
|
|
7115
|
+
source: z42.enum(["startup", "resume", "clear", "compact", "fork"]),
|
|
7116
|
+
model: z42.string().optional()
|
|
6817
7117
|
}).passthrough();
|
|
6818
7118
|
var SESSION_RESET_SOURCES = /* @__PURE__ */ new Set([
|
|
6819
7119
|
"startup",
|
|
@@ -6852,13 +7152,13 @@ function sessionStartHook(input, state) {
|
|
|
6852
7152
|
}
|
|
6853
7153
|
|
|
6854
7154
|
// src/hooks/stop.ts
|
|
6855
|
-
import { z as
|
|
6856
|
-
var StopInputSchema =
|
|
6857
|
-
hook_event_name:
|
|
6858
|
-
session_id:
|
|
6859
|
-
cwd:
|
|
6860
|
-
stop_hook_active:
|
|
6861
|
-
last_assistant_message:
|
|
7155
|
+
import { z as z43 } from "zod";
|
|
7156
|
+
var StopInputSchema = z43.object({
|
|
7157
|
+
hook_event_name: z43.literal("Stop"),
|
|
7158
|
+
session_id: z43.string(),
|
|
7159
|
+
cwd: z43.string(),
|
|
7160
|
+
stop_hook_active: z43.boolean(),
|
|
7161
|
+
last_assistant_message: z43.string().optional()
|
|
6862
7162
|
}).passthrough();
|
|
6863
7163
|
function taskLabel(state) {
|
|
6864
7164
|
if (!state.currentTask) return "";
|
|
@@ -6901,55 +7201,6 @@ function stopHook(input, state) {
|
|
|
6901
7201
|
};
|
|
6902
7202
|
}
|
|
6903
7203
|
|
|
6904
|
-
// src/hooks/stop-failure.ts
|
|
6905
|
-
import { z as z43 } from "zod";
|
|
6906
|
-
var StopFailureErrorTypeSchema = z43.enum([
|
|
6907
|
-
"rate_limit",
|
|
6908
|
-
"overloaded",
|
|
6909
|
-
"authentication_failed",
|
|
6910
|
-
"oauth_org_not_allowed",
|
|
6911
|
-
"account_on_hold",
|
|
6912
|
-
"billing_error",
|
|
6913
|
-
"invalid_request",
|
|
6914
|
-
"model_not_found",
|
|
6915
|
-
"server_error",
|
|
6916
|
-
"max_output_tokens",
|
|
6917
|
-
"unknown"
|
|
6918
|
-
]);
|
|
6919
|
-
var StopFailureInputSchema = z43.object({
|
|
6920
|
-
hook_event_name: z43.literal("StopFailure"),
|
|
6921
|
-
session_id: z43.string(),
|
|
6922
|
-
cwd: z43.string(),
|
|
6923
|
-
error: StopFailureErrorTypeSchema,
|
|
6924
|
-
error_details: z43.unknown().optional(),
|
|
6925
|
-
last_assistant_message: z43.string().optional()
|
|
6926
|
-
}).passthrough();
|
|
6927
|
-
function extractRetryAfterSec(errorDetails) {
|
|
6928
|
-
if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
|
|
6929
|
-
return errorDetails;
|
|
6930
|
-
}
|
|
6931
|
-
if (errorDetails && typeof errorDetails === "object") {
|
|
6932
|
-
const record = errorDetails;
|
|
6933
|
-
for (const key of ["retryAfterSec", "retryAfter", "retry_after", "retry_after_seconds"]) {
|
|
6934
|
-
const value = record[key];
|
|
6935
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
6936
|
-
}
|
|
6937
|
-
}
|
|
6938
|
-
if (typeof errorDetails === "string") {
|
|
6939
|
-
const match = errorDetails.match(/retry[-_ ]?after[^0-9]{0,10}(\d+)/i);
|
|
6940
|
-
if (match?.[1] !== void 0) return Number(match[1]);
|
|
6941
|
-
}
|
|
6942
|
-
return void 0;
|
|
6943
|
-
}
|
|
6944
|
-
function stopFailureHook(input, _state) {
|
|
6945
|
-
const retryAfterSec = extractRetryAfterSec(input.error_details);
|
|
6946
|
-
const action = {
|
|
6947
|
-
type: "agent:rate_limited",
|
|
6948
|
-
payload: retryAfterSec === void 0 ? { error: input.error } : { error: input.error, retryAfterSec }
|
|
6949
|
-
};
|
|
6950
|
-
return { output: {}, serverActions: [action] };
|
|
6951
|
-
}
|
|
6952
|
-
|
|
6953
7204
|
// src/hooks/run.ts
|
|
6954
7205
|
var HookInputSchema = z44.discriminatedUnion("hook_event_name", [
|
|
6955
7206
|
SessionStartInputSchema,
|
|
@@ -7453,13 +7704,26 @@ function createProgram() {
|
|
|
7453
7704
|
if (result.stderr.length > 0) process.stderr.write(`${result.stderr}
|
|
7454
7705
|
`);
|
|
7455
7706
|
process.exitCode = result.exitCode;
|
|
7456
|
-
if (result.execution)
|
|
7707
|
+
if (result.execution) {
|
|
7708
|
+
const rescued = await runRescueAction(result.execution.serverActions);
|
|
7709
|
+
const rescuedNote = describeRescue(rescued);
|
|
7710
|
+
if (rescuedNote) process.stderr.write(`${rescuedNote}
|
|
7711
|
+
`);
|
|
7712
|
+
sendHookActions(
|
|
7713
|
+
rescued.report ? [...result.execution.serverActions, rescued.report] : result.execution.serverActions
|
|
7714
|
+
);
|
|
7715
|
+
}
|
|
7457
7716
|
return;
|
|
7458
7717
|
}
|
|
7459
7718
|
const execution = runCliHook(raw);
|
|
7460
7719
|
process.stdout.write(`${execution.stdout}
|
|
7461
7720
|
`);
|
|
7462
7721
|
process.exitCode = execution.exitCode;
|
|
7722
|
+
const rescue = await runRescueAction(execution.serverActions);
|
|
7723
|
+
const note = describeRescue(rescue);
|
|
7724
|
+
if (note) process.stderr.write(`${note}
|
|
7725
|
+
`);
|
|
7726
|
+
if (rescue.report) sendHookActions([rescue.report]);
|
|
7463
7727
|
sendHookActions(execution.serverActions);
|
|
7464
7728
|
});
|
|
7465
7729
|
program.command("mcp").description(
|