@quiel/cli 0.5.1 → 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/LICENSE +201 -0
- package/README.md +7 -2
- package/dist/cli.js +1539 -1279
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -430,7 +430,6 @@ import { z as z10 } from "zod";
|
|
|
430
430
|
|
|
431
431
|
// ../shared/src/enums/organization.ts
|
|
432
432
|
var Plan = {
|
|
433
|
-
SELF_HOSTED: "SELF_HOSTED",
|
|
434
433
|
FREE: "FREE",
|
|
435
434
|
TEAM: "TEAM",
|
|
436
435
|
ENTERPRISE: "ENTERPRISE"
|
|
@@ -1318,13 +1317,22 @@ var taskPayloadLeaseSchema = z23.object({
|
|
|
1318
1317
|
resumeToken: z23.string().min(1),
|
|
1319
1318
|
expiresAt: z23.string().datetime()
|
|
1320
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
|
+
});
|
|
1321
1327
|
var taskPayloadSchema = z23.object({
|
|
1322
1328
|
task: taskPayloadTaskSchema,
|
|
1323
1329
|
project: taskPayloadProjectSchema,
|
|
1324
1330
|
dependencies: z23.array(taskPayloadDependencySchema),
|
|
1325
1331
|
openQuestions: z23.array(taskPayloadOpenQuestionSchema),
|
|
1326
1332
|
trust: taskPayloadTrustSchema,
|
|
1327
|
-
lease: taskPayloadLeaseSchema
|
|
1333
|
+
lease: taskPayloadLeaseSchema,
|
|
1334
|
+
/** Есть только у задач, которые уже начинали и прервали. */
|
|
1335
|
+
continueFrom: taskPayloadContinueFromSchema.optional()
|
|
1328
1336
|
});
|
|
1329
1337
|
var taskClaimPayloadSchema = z23.object({
|
|
1330
1338
|
/**
|
|
@@ -1441,7 +1449,21 @@ var contextPayloadSchema = z23.object({
|
|
|
1441
1449
|
});
|
|
1442
1450
|
var agentRateLimitedPayloadSchema = z23.object({
|
|
1443
1451
|
retryAfterSec: z23.number().int().positive().optional(),
|
|
1444
|
-
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()
|
|
1445
1467
|
});
|
|
1446
1468
|
var agentRateLimitedAckSchema = z23.object({
|
|
1447
1469
|
ok: z23.boolean(),
|
|
@@ -1521,6 +1543,18 @@ var taskUsagePayloadSchema = z23.object({
|
|
|
1521
1543
|
entries: z23.array(taskUsageEntrySchema).min(1).max(10)
|
|
1522
1544
|
});
|
|
1523
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() });
|
|
1524
1558
|
var sessionLimitReachedPayloadSchema = z23.object({
|
|
1525
1559
|
tasksCompletedInSession: z23.number().int().nonnegative()
|
|
1526
1560
|
});
|
|
@@ -1878,11 +1912,7 @@ var INTEGRATIONS = [
|
|
|
1878
1912
|
requiresOrgAdmin: true
|
|
1879
1913
|
}
|
|
1880
1914
|
];
|
|
1881
|
-
var PLANS_WITH_OWN_TELEGRAM_BOT = [
|
|
1882
|
-
Plan.SELF_HOSTED,
|
|
1883
|
-
Plan.TEAM,
|
|
1884
|
-
Plan.ENTERPRISE
|
|
1885
|
-
];
|
|
1915
|
+
var PLANS_WITH_OWN_TELEGRAM_BOT = [Plan.TEAM, Plan.ENTERPRISE];
|
|
1886
1916
|
var BY_KEY = new Map(INTEGRATIONS.map((info) => [info.key, info]));
|
|
1887
1917
|
|
|
1888
1918
|
// ../shared/src/notifications/kinds.ts
|
|
@@ -3428,7 +3458,22 @@ var CurrentTaskSchema = z30.object({
|
|
|
3428
3458
|
* Необязательно: состояния, записанные прежними версиями клиента, поля
|
|
3429
3459
|
* не имеют — тогда `submit_work` работает как раньше, в режиме DIRECT.
|
|
3430
3460
|
*/
|
|
3431
|
-
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()
|
|
3432
3477
|
});
|
|
3433
3478
|
var TrustCacheSchema = z30.object({
|
|
3434
3479
|
profileKey: z30.string().min(1),
|
|
@@ -4180,7 +4225,14 @@ var createSocket = (url, options) => io(url, {
|
|
|
4180
4225
|
// src/hooks/audit-sender.ts
|
|
4181
4226
|
var DEFAULT_CONNECT_TIMEOUT_MS = 3e3;
|
|
4182
4227
|
var DEFAULT_FLUSH_GRACE_MS = 150;
|
|
4183
|
-
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
|
+
]);
|
|
4184
4236
|
function sendHookActions(actions, options = {}) {
|
|
4185
4237
|
const wireActions = actions.filter((action) => WIRE_EVENTS.has(action.type));
|
|
4186
4238
|
if (wireActions.length === 0) return;
|
|
@@ -4365,1296 +4417,1528 @@ function renderHookOutput(format, hook, output) {
|
|
|
4365
4417
|
return { stdout: "{}", stderr: "", exitCode: 0 };
|
|
4366
4418
|
}
|
|
4367
4419
|
|
|
4368
|
-
// src/
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
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
|
+
});
|
|
4373
4464
|
}
|
|
4374
|
-
function
|
|
4375
|
-
|
|
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}`;
|
|
4376
4469
|
}
|
|
4377
4470
|
|
|
4378
|
-
// src/
|
|
4379
|
-
function
|
|
4380
|
-
|
|
4381
|
-
const maxMs = options.maxMs ?? 3e4;
|
|
4382
|
-
const safeAttempt = Math.max(0, Math.trunc(attempt));
|
|
4383
|
-
const exponent = Math.min(safeAttempt, 30);
|
|
4384
|
-
const raw = baseMs * 2 ** exponent;
|
|
4385
|
-
return Math.min(raw, maxMs);
|
|
4471
|
+
// src/git/result.ts
|
|
4472
|
+
function gitOk(data) {
|
|
4473
|
+
return { ok: true, data };
|
|
4386
4474
|
}
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
import { EventEmitter } from "events";
|
|
4390
|
-
|
|
4391
|
-
// src/socket/emit-with-timeout.ts
|
|
4392
|
-
function toError(raw, context) {
|
|
4393
|
-
return raw instanceof Error ? raw : new Error(`${context}: ${String(raw)}`);
|
|
4475
|
+
function gitErr(message) {
|
|
4476
|
+
return { ok: false, message };
|
|
4394
4477
|
}
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
4401
|
-
socket.emitWithAck(event, payload).then((value) => {
|
|
4402
|
-
clearTimeout(timer);
|
|
4403
|
-
resolve4(value);
|
|
4404
|
-
}).catch((err) => {
|
|
4405
|
-
clearTimeout(timer);
|
|
4406
|
-
reject(toError(err, event));
|
|
4407
|
-
});
|
|
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
|
|
4408
4483
|
});
|
|
4484
|
+
return result.ok;
|
|
4409
4485
|
}
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
updatedAt: now().toISOString(),
|
|
4417
|
-
rules
|
|
4418
|
-
};
|
|
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());
|
|
4419
4492
|
}
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
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());
|
|
4424
4499
|
}
|
|
4425
4500
|
|
|
4426
|
-
// src/
|
|
4427
|
-
var
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
if (
|
|
4433
|
-
|
|
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 });
|
|
4434
4522
|
}
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
// По умолчанию — файл ИМЕННО этого агента (state/store.ts::getStateFilePath(agentId)),
|
|
4444
|
-
// а не общий `state.json`: не трогаем src/state/**, но обязаны использовать
|
|
4445
|
-
// его правильно (OQ-05 — состояние на агента, не на машину).
|
|
4446
|
-
stateFilePath: options.stateFilePath ?? getStateFilePath(options.agentId),
|
|
4447
|
-
heartbeatIntervalMs: options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
4448
|
-
ackTimeoutMs: options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS,
|
|
4449
|
-
backoffBaseMs: options.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,
|
|
4450
|
-
backoffMaxMs: options.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS,
|
|
4451
|
-
now: options.now ?? (() => /* @__PURE__ */ new Date())
|
|
4452
|
-
};
|
|
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
|
+
`;
|
|
4453
4531
|
}
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
stopped = true;
|
|
4459
|
-
reconnectAttempt = 0;
|
|
4460
|
-
reconnectTimer;
|
|
4461
|
-
heartbeatTimer;
|
|
4462
|
-
lastHelloAck;
|
|
4463
|
-
paused = false;
|
|
4464
|
-
pauseReasonText;
|
|
4465
|
-
leaseLostTasks = /* @__PURE__ */ new Map();
|
|
4466
|
-
constructor(options) {
|
|
4467
|
-
super();
|
|
4468
|
-
this.opts = resolveOptions(options);
|
|
4469
|
-
this.agentId = options.agentId;
|
|
4470
|
-
const factory = options.createSocket ?? createSocket;
|
|
4471
|
-
this.socket = factory(`${trimTrailingSlash2(options.url)}/agent`, {
|
|
4472
|
-
auth: { token: options.token },
|
|
4473
|
-
autoConnect: false,
|
|
4474
|
-
reconnection: false
|
|
4475
|
-
});
|
|
4476
|
-
this.registerSocketListeners();
|
|
4477
|
-
}
|
|
4478
|
-
// -- Публичное API -----------------------------------------------------
|
|
4479
|
-
on(event, listener) {
|
|
4480
|
-
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));
|
|
4481
4536
|
}
|
|
4482
|
-
|
|
4483
|
-
|
|
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 });
|
|
4484
4541
|
}
|
|
4485
|
-
|
|
4486
|
-
|
|
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));
|
|
4487
4546
|
}
|
|
4488
|
-
|
|
4489
|
-
|
|
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;
|
|
4490
4568
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
}
|
|
4499
|
-
/** Останавливает heartbeat, отменяет запланированное переподключение, закрывает сокет. После `stop()` клиент больше не переподключается сам. */
|
|
4500
|
-
stop() {
|
|
4501
|
-
this.stopped = true;
|
|
4502
|
-
if (this.reconnectTimer) {
|
|
4503
|
-
clearTimeout(this.reconnectTimer);
|
|
4504
|
-
this.reconnectTimer = void 0;
|
|
4505
|
-
}
|
|
4506
|
-
this.stopHeartbeatTimer();
|
|
4507
|
-
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
|
+
);
|
|
4508
4576
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
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);
|
|
4511
4602
|
}
|
|
4512
|
-
|
|
4513
|
-
|
|
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));
|
|
4514
4608
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
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);
|
|
4517
4624
|
}
|
|
4518
|
-
|
|
4519
|
-
|
|
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
|
+
);
|
|
4520
4646
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
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
|
+
);
|
|
4525
4651
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
* есть ВСЁ, что не `agent:hello`/`agent:heartbeat`/`agent:mode` (у
|
|
4533
|
-
* первых двух своя логика согласования состояния в этом классе, третий
|
|
4534
|
-
* — короткоживущий отдельный сокет, см. `send-agent-mode.ts`). Нарочно
|
|
4535
|
-
* НЕ отдельное соединение: `task:claim` — блокирующий долгий long-poll,
|
|
4536
|
-
* и держать его на том же сокете, где уже идёт heartbeat/presence этого
|
|
4537
|
-
* агента, а не поднимать вторую параллельную сессию — единственный
|
|
4538
|
-
* вариант, не удваивающий presence на сервере.
|
|
4539
|
-
*/
|
|
4540
|
-
callServer(event, payload, timeoutMs = this.opts.ackTimeoutMs) {
|
|
4541
|
-
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));
|
|
4542
4658
|
}
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
* это только ограничение по времени для вызывающей стороны (`connect.ts`).
|
|
4548
|
-
*/
|
|
4549
|
-
waitForFirstConnection(timeoutMs = 5e3) {
|
|
4550
|
-
return new Promise((resolve4) => {
|
|
4551
|
-
let settled = false;
|
|
4552
|
-
const onConnected = (ack) => {
|
|
4553
|
-
if (settled) return;
|
|
4554
|
-
settled = true;
|
|
4555
|
-
clearTimeout(timer);
|
|
4556
|
-
this.off("connected", onConnected);
|
|
4557
|
-
resolve4({ ok: true, ack });
|
|
4558
|
-
};
|
|
4559
|
-
const onAuthRejected = (message) => {
|
|
4560
|
-
if (settled) return;
|
|
4561
|
-
settled = true;
|
|
4562
|
-
clearTimeout(timer);
|
|
4563
|
-
this.off("connected", onConnected);
|
|
4564
|
-
this.off("auth-rejected", onAuthRejected);
|
|
4565
|
-
resolve4({ ok: false, message });
|
|
4566
|
-
};
|
|
4567
|
-
const timer = setTimeout(() => {
|
|
4568
|
-
if (settled) return;
|
|
4569
|
-
settled = true;
|
|
4570
|
-
this.off("connected", onConnected);
|
|
4571
|
-
this.off("auth-rejected", onAuthRejected);
|
|
4572
|
-
resolve4({
|
|
4573
|
-
ok: false,
|
|
4574
|
-
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).`
|
|
4575
|
-
});
|
|
4576
|
-
}, timeoutMs);
|
|
4577
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
4578
|
-
this.on("connected", onConnected);
|
|
4579
|
-
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
|
|
4580
4663
|
});
|
|
4664
|
+
if (continued.ok) startPoint = `${remote}/${params.continueFromBranch}`;
|
|
4581
4665
|
}
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
this.socket.on("mode:set", (raw) => this.handleModeSet(raw));
|
|
4589
|
-
this.socket.on("agent:pause", (raw) => this.handleAgentPause(raw));
|
|
4590
|
-
this.socket.on("trust:updated", (raw) => this.handleTrustUpdated(raw));
|
|
4591
|
-
this.socket.on("task:updated", (raw) => this.handleTaskUpdated(raw));
|
|
4592
|
-
this.socket.on("question:answered", (raw) => this.handleQuestionAnswered(raw));
|
|
4593
|
-
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));
|
|
4594
4672
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
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));
|
|
4599
4679
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
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));
|
|
4608
4693
|
}
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4694
|
+
const rebaseResult = await runGit(["rebase", `${remote}/${params.defaultBranch}`], {
|
|
4695
|
+
cwd: params.worktreeDir
|
|
4696
|
+
});
|
|
4697
|
+
if (rebaseResult.ok) {
|
|
4698
|
+
return gitOk({ outcome: "rebased", conflictedFiles: [] });
|
|
4613
4699
|
}
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
const
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
if (this.stopped) return;
|
|
4625
|
-
this.socket.connect();
|
|
4626
|
-
}, 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 });
|
|
4627
4710
|
}
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
// `claudeVersion` шлётся ТОЛЬКО для Claude Code — и только чтобы
|
|
4638
|
-
// клиент работал против ещё не обновлённого сервера, который знает
|
|
4639
|
-
// одно это поле. Для Codex поле с таким именем было бы неправдой.
|
|
4640
|
-
...this.opts.agentKind === AgentKind.CLAUDE_CODE ? { claudeVersion: this.opts.agentVersion } : {},
|
|
4641
|
-
roleKeys: [...this.opts.roleKeys],
|
|
4642
|
-
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
|
|
4643
|
-
});
|
|
4644
|
-
let raw;
|
|
4645
|
-
try {
|
|
4646
|
-
raw = await emitWithAckTimeout(this.socket, "agent:hello", payload, this.opts.ackTimeoutMs);
|
|
4647
|
-
} catch (err) {
|
|
4648
|
-
this.emit("client-error", toError2(err, "agent:hello"));
|
|
4649
|
-
return;
|
|
4650
|
-
}
|
|
4651
|
-
if (this.stopped) return;
|
|
4652
|
-
if (isAckError(raw)) {
|
|
4653
|
-
this.emit("client-error", new Error(`agent:hello: ${raw.error.code}: ${raw.error.message}`));
|
|
4654
|
-
return;
|
|
4655
|
-
}
|
|
4656
|
-
const parsed = agentHelloAckSchema.safeParse(raw);
|
|
4657
|
-
if (!parsed.success) {
|
|
4658
|
-
this.emit(
|
|
4659
|
-
"client-error",
|
|
4660
|
-
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}`)
|
|
4661
|
-
);
|
|
4662
|
-
return;
|
|
4663
|
-
}
|
|
4664
|
-
const ack = parsed.data;
|
|
4665
|
-
this.lastHelloAck = ack;
|
|
4666
|
-
this.paused = false;
|
|
4667
|
-
this.pauseReasonText = void 0;
|
|
4668
|
-
this.applyHelloAck(ack);
|
|
4669
|
-
this.emit("connected", ack);
|
|
4670
|
-
}
|
|
4671
|
-
applyHelloAck(ack) {
|
|
4672
|
-
const state = readState(this.opts.stateFilePath);
|
|
4673
|
-
const patch = {
|
|
4674
|
-
// OQ-06: сервер — источник правды, локальный режим перекрывается безусловно.
|
|
4675
|
-
mode: ack.mode,
|
|
4676
|
-
trust: toTrustCache(ack.trust, this.opts.now)
|
|
4677
|
-
};
|
|
4678
|
-
if (!ack.currentTask || ack.currentTask.id !== state.currentTask?.taskId) {
|
|
4679
|
-
patch.currentTask = null;
|
|
4680
|
-
}
|
|
4681
|
-
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
|
+
);
|
|
4682
4720
|
}
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
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
|
+
);
|
|
4689
4727
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
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
|
+
);
|
|
4694
4745
|
}
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
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
|
|
4702
4760
|
});
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
raw = await emitWithAckTimeout(
|
|
4706
|
-
this.socket,
|
|
4707
|
-
"agent:heartbeat",
|
|
4708
|
-
payload,
|
|
4709
|
-
this.opts.ackTimeoutMs
|
|
4710
|
-
);
|
|
4711
|
-
} catch (err) {
|
|
4712
|
-
this.emit("client-error", toError2(err, "agent:heartbeat"));
|
|
4713
|
-
return;
|
|
4714
|
-
}
|
|
4715
|
-
if (isAckError(raw)) {
|
|
4716
|
-
this.emit(
|
|
4717
|
-
"client-error",
|
|
4718
|
-
new Error(`agent:heartbeat: ${raw.error.code}: ${raw.error.message}`)
|
|
4719
|
-
);
|
|
4720
|
-
return;
|
|
4721
|
-
}
|
|
4722
|
-
const parsed = agentHeartbeatAckSchema.safeParse(raw);
|
|
4723
|
-
if (!parsed.success) {
|
|
4724
|
-
this.emit(
|
|
4725
|
-
"client-error",
|
|
4726
|
-
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}`)
|
|
4727
|
-
);
|
|
4728
|
-
return;
|
|
4729
|
-
}
|
|
4730
|
-
this.emit("heartbeat", parsed.data);
|
|
4731
|
-
}
|
|
4732
|
-
// -- Пуш-события сервера (§11.2 ТЗ) --------------------------------------
|
|
4733
|
-
handleLeaseLost(raw) {
|
|
4734
|
-
const parsed = leaseLostPayloadSchema.safeParse(raw);
|
|
4735
|
-
if (!parsed.success) {
|
|
4736
|
-
this.emit(
|
|
4737
|
-
"client-error",
|
|
4738
|
-
new Error(`lease:lost: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
4739
|
-
);
|
|
4740
|
-
return;
|
|
4741
|
-
}
|
|
4742
|
-
const { taskId, reason } = parsed.data;
|
|
4743
|
-
this.leaseLostTasks.set(taskId, reason);
|
|
4744
|
-
const state = readState(this.opts.stateFilePath);
|
|
4745
|
-
if (state.currentTask?.taskId === taskId) {
|
|
4746
|
-
updateState({ currentTask: null }, this.opts.stateFilePath);
|
|
4747
|
-
}
|
|
4748
|
-
this.emit("lease:lost", parsed.data);
|
|
4761
|
+
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
4762
|
+
worktreeDir = worktree.data.worktreeDir;
|
|
4749
4763
|
}
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
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
|
+
};
|
|
4758
4785
|
}
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
);
|
|
4766
|
-
return;
|
|
4767
|
-
}
|
|
4768
|
-
this.paused = true;
|
|
4769
|
-
this.pauseReasonText = parsed.data.reason;
|
|
4770
|
-
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
|
+
};
|
|
4771
4792
|
}
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
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;
|
|
4780
4807
|
}
|
|
4781
|
-
updateState({ trust: toTrustCache(parsed.data, this.opts.now) }, this.opts.stateFilePath);
|
|
4782
|
-
this.emit("trust:updated", parsed.data);
|
|
4783
4808
|
}
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
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
|
+
);
|
|
4791
4833
|
}
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
*/
|
|
4810
|
-
handleApprovalDecided(raw) {
|
|
4811
|
-
const parsed = approvalDecidedPayloadSchema.safeParse(raw);
|
|
4812
|
-
if (!parsed.success) {
|
|
4813
|
-
this.emit(
|
|
4814
|
-
"client-error",
|
|
4815
|
-
new Error(`approval:decided: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
4816
|
-
);
|
|
4817
|
-
return;
|
|
4818
|
-
}
|
|
4819
|
-
this.applyApprovalDecided(parsed.data);
|
|
4820
|
-
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;
|
|
4821
4851
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
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;
|
|
4841
4875
|
}
|
|
4842
|
-
updateState(clearPendingApproval(state, payload.approvalId), this.opts.stateFilePath);
|
|
4843
4876
|
}
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
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
|
+
};
|
|
4847
4887
|
}
|
|
4848
4888
|
|
|
4849
|
-
// src/
|
|
4850
|
-
var
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
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
|
|
4861
4912
|
});
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
return { ok: false, message: "\u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B agent:mode (ok:false)" };
|
|
4875
|
-
}
|
|
4876
|
-
return { ok: true, mode: parsed.data.mode ?? options.mode };
|
|
4877
|
-
} catch (err) {
|
|
4878
|
-
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
4879
|
-
} finally {
|
|
4880
|
-
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
|
+
);
|
|
4881
4925
|
}
|
|
4926
|
+
return gitOk({
|
|
4927
|
+
committed: true,
|
|
4928
|
+
sha: commit.data.sha,
|
|
4929
|
+
pushedTo: push.data.pushedTo
|
|
4930
|
+
});
|
|
4882
4931
|
}
|
|
4883
4932
|
|
|
4884
|
-
// src/
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
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
|
|
4894
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;
|
|
4895
4971
|
}
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
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;
|
|
4900
5016
|
}
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
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
|
+
});
|
|
4905
5044
|
}
|
|
4906
|
-
return {
|
|
5045
|
+
return { output: {}, serverActions: actions };
|
|
4907
5046
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
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" };
|
|
4917
5077
|
}
|
|
4918
|
-
|
|
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;
|
|
4919
5091
|
return {
|
|
4920
|
-
|
|
4921
|
-
|
|
5092
|
+
attempted: true,
|
|
5093
|
+
committed: result.data.committed,
|
|
5094
|
+
pushedTo: result.data.pushedTo,
|
|
5095
|
+
problem: null,
|
|
5096
|
+
report
|
|
4922
5097
|
};
|
|
4923
|
-
}
|
|
4924
|
-
const parsed = schema.safeParse(raw);
|
|
4925
|
-
if (!parsed.success) {
|
|
5098
|
+
} catch (error) {
|
|
4926
5099
|
return {
|
|
4927
|
-
|
|
4928
|
-
|
|
5100
|
+
...NOTHING,
|
|
5101
|
+
attempted: true,
|
|
5102
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
4929
5103
|
};
|
|
4930
5104
|
}
|
|
4931
|
-
|
|
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}` : ""}`;
|
|
4932
5111
|
}
|
|
4933
5112
|
|
|
4934
|
-
// src/
|
|
4935
|
-
var
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
)
|
|
4939
|
-
targetRole: questionAskPayloadSchema.shape.targetRole.describe(
|
|
4940
|
-
'\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.'
|
|
4941
|
-
),
|
|
4942
|
-
targetMemberId: questionAskPayloadSchema.shape.targetMemberId.describe(
|
|
4943
|
-
"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."
|
|
4944
|
-
),
|
|
4945
|
-
wantsFiles: questionAskPayloadSchema.shape.wantsFiles.describe(
|
|
4946
|
-
"\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."
|
|
4947
|
-
),
|
|
4948
|
-
options: questionAskPayloadSchema.shape.options.describe(
|
|
4949
|
-
"\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."
|
|
4950
|
-
),
|
|
4951
|
-
blocking: questionAskPayloadSchema.shape.blocking.describe(
|
|
4952
|
-
'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).'
|
|
4953
|
-
)
|
|
4954
|
-
};
|
|
4955
|
-
var BLOCKING_WAIT_SEC = 600;
|
|
4956
|
-
var ASK_ACK_BUFFER_MS = 15e3;
|
|
4957
|
-
async function askHumanHandler(args, runtime) {
|
|
4958
|
-
const state = readState(runtime.stateFilePath);
|
|
4959
|
-
const taskId = state.currentTask?.taskId;
|
|
4960
|
-
const waitSec = args.blocking ? BLOCKING_WAIT_SEC : 0;
|
|
4961
|
-
const parsedArgs = parseArgsOrError(questionAskPayloadSchema, {
|
|
4962
|
-
...taskId ? { taskId } : {},
|
|
4963
|
-
textMd: args.textMd,
|
|
4964
|
-
...args.targetRole ? { targetRole: args.targetRole } : {},
|
|
4965
|
-
...args.targetMemberId ? { targetMemberId: args.targetMemberId } : {},
|
|
4966
|
-
...args.options && args.options.length > 0 ? { options: args.options } : {},
|
|
4967
|
-
...args.wantsFiles ? { wantsFiles: true } : {},
|
|
4968
|
-
blocking: args.blocking,
|
|
4969
|
-
waitSec
|
|
4970
|
-
});
|
|
4971
|
-
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
4972
|
-
if (taskId) {
|
|
4973
|
-
const lease = runtime.client.isLeaseLost(taskId);
|
|
4974
|
-
if (lease.lost) return leaseLostResult(state.currentTask?.taskKey ?? taskId, lease.reason);
|
|
4975
|
-
}
|
|
4976
|
-
const result = await callServerOrError(
|
|
4977
|
-
runtime.client,
|
|
4978
|
-
"question:ask",
|
|
4979
|
-
parsedArgs.data,
|
|
4980
|
-
questionAskAckSchema,
|
|
4981
|
-
waitSec * 1e3 + ASK_ACK_BUFFER_MS
|
|
4982
|
-
);
|
|
4983
|
-
if (!result.ok) return errorResult(result.message);
|
|
4984
|
-
const settled = describeSettledQuestion(result.data.question);
|
|
4985
|
-
if (settled) return textResult(settled);
|
|
4986
|
-
if (args.blocking) {
|
|
4987
|
-
return textResult(
|
|
4988
|
-
`\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.`
|
|
4989
|
-
);
|
|
4990
|
-
}
|
|
4991
|
-
return textResult(
|
|
4992
|
-
`\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}".`
|
|
4993
|
-
);
|
|
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());
|
|
4994
5118
|
}
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
"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."
|
|
4998
|
-
),
|
|
4999
|
-
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.")
|
|
5000
|
-
};
|
|
5001
|
-
var DEFAULT_WAIT_ANSWER_SEC = 300;
|
|
5002
|
-
var WAIT_ANSWER_ACK_BUFFER_MS = 15e3;
|
|
5003
|
-
async function waitAnswerHandler(args, runtime) {
|
|
5004
|
-
const waitSec = args.waitSec ?? DEFAULT_WAIT_ANSWER_SEC;
|
|
5005
|
-
const parsedArgs = parseArgsOrError(questionWaitPayloadSchema, {
|
|
5006
|
-
questionId: args.questionId,
|
|
5007
|
-
waitSec
|
|
5008
|
-
});
|
|
5009
|
-
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5010
|
-
const result = await callServerOrError(
|
|
5011
|
-
runtime.client,
|
|
5012
|
-
"question:wait",
|
|
5013
|
-
parsedArgs.data,
|
|
5014
|
-
questionWaitAckSchema,
|
|
5015
|
-
waitSec * 1e3 + WAIT_ANSWER_ACK_BUFFER_MS
|
|
5016
|
-
);
|
|
5017
|
-
if (!result.ok) return errorResult(result.message);
|
|
5018
|
-
const settled = describeSettledQuestion(result.data.question);
|
|
5019
|
-
if (settled) return textResult(settled);
|
|
5020
|
-
return textResult(
|
|
5021
|
-
`\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.`
|
|
5022
|
-
);
|
|
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.`;
|
|
5023
5121
|
}
|
|
5024
|
-
function describeAnswerAttachments(question) {
|
|
5025
|
-
if (question.attachments.length === 0) return "";
|
|
5026
|
-
const lines = question.attachments.map(
|
|
5027
|
-
(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`
|
|
5028
|
-
);
|
|
5029
|
-
return `
|
|
5030
5122
|
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
if (question.status === QuestionStatus.DISMISSED) {
|
|
5040
|
-
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.`;
|
|
5041
|
-
}
|
|
5042
|
-
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);
|
|
5043
5131
|
}
|
|
5044
5132
|
|
|
5045
|
-
//
|
|
5046
|
-
|
|
5047
|
-
name: "@quiel/cli",
|
|
5048
|
-
version: "0.5.1",
|
|
5049
|
-
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",
|
|
5050
|
-
homepage: "https://quiel.app",
|
|
5051
|
-
repository: {
|
|
5052
|
-
type: "git",
|
|
5053
|
-
url: "git+https://github.com/Quiel-App/quiel-cli.git"
|
|
5054
|
-
},
|
|
5055
|
-
bugs: {
|
|
5056
|
-
url: "https://github.com/Quiel-App/quiel-cli/issues"
|
|
5057
|
-
},
|
|
5058
|
-
keywords: [
|
|
5059
|
-
"quiel",
|
|
5060
|
-
"claude-code",
|
|
5061
|
-
"codex",
|
|
5062
|
-
"cursor",
|
|
5063
|
-
"opencode",
|
|
5064
|
-
"mcp",
|
|
5065
|
-
"mcp-server",
|
|
5066
|
-
"ai-agents",
|
|
5067
|
-
"task-tracker"
|
|
5068
|
-
],
|
|
5069
|
-
license: "UNLICENSED",
|
|
5070
|
-
type: "module",
|
|
5071
|
-
bin: {
|
|
5072
|
-
quiel: "dist/cli.js"
|
|
5073
|
-
},
|
|
5074
|
-
files: [
|
|
5075
|
-
"dist",
|
|
5076
|
-
"README.md"
|
|
5077
|
-
],
|
|
5078
|
-
engines: {
|
|
5079
|
-
node: ">=22"
|
|
5080
|
-
},
|
|
5081
|
-
publishConfig: {
|
|
5082
|
-
access: "public"
|
|
5083
|
-
},
|
|
5084
|
-
scripts: {
|
|
5085
|
-
build: "tsup",
|
|
5086
|
-
dev: "tsup --watch",
|
|
5087
|
-
lint: "biome check .",
|
|
5088
|
-
typecheck: "tsc --noEmit",
|
|
5089
|
-
test: "vitest run --passWithNoTests"
|
|
5090
|
-
},
|
|
5091
|
-
dependencies: {
|
|
5092
|
-
"@modelcontextprotocol/sdk": "1.30.0",
|
|
5093
|
-
commander: "15.0.0",
|
|
5094
|
-
"socket.io-client": "4.8.3",
|
|
5095
|
-
zod: "4.5.4"
|
|
5096
|
-
},
|
|
5097
|
-
devDependencies: {
|
|
5098
|
-
"@quiel/config": "workspace:*",
|
|
5099
|
-
"@quiel/shared": "workspace:*",
|
|
5100
|
-
"@types/node": "20.19.43",
|
|
5101
|
-
tsup: "8.5.1",
|
|
5102
|
-
typescript: "7.0.2",
|
|
5103
|
-
vitest: "5.0.0"
|
|
5104
|
-
}
|
|
5105
|
-
};
|
|
5106
|
-
|
|
5107
|
-
// src/version.ts
|
|
5108
|
-
var VERSION = package_default.version;
|
|
5133
|
+
// src/socket/client.ts
|
|
5134
|
+
import { EventEmitter } from "events";
|
|
5109
5135
|
|
|
5110
|
-
// src/
|
|
5111
|
-
|
|
5112
|
-
|
|
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
|
+
}
|
|
5113
5155
|
|
|
5114
|
-
// src/
|
|
5115
|
-
var
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
"documents",
|
|
5123
|
-
"related",
|
|
5124
|
-
"repo"
|
|
5125
|
-
];
|
|
5126
|
-
var getContextInputShape = {
|
|
5127
|
-
taskId: contextGetPayloadSchema.shape.taskId.describe(
|
|
5128
|
-
"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)."
|
|
5129
|
-
),
|
|
5130
|
-
include: contextGetPayloadSchema.shape.include.optional().describe(
|
|
5131
|
-
"\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."
|
|
5132
|
-
)
|
|
5133
|
-
};
|
|
5134
|
-
async function getContextHandler(args, runtime) {
|
|
5135
|
-
const state = readState(runtime.stateFilePath);
|
|
5136
|
-
const taskId = args.taskId ?? state.currentTask?.taskId;
|
|
5137
|
-
if (taskId) {
|
|
5138
|
-
const lease = runtime.client.isLeaseLost(taskId);
|
|
5139
|
-
if (lease.lost) return leaseLostResult(taskId, lease.reason);
|
|
5140
|
-
}
|
|
5141
|
-
const include = args.include && args.include.length > 0 ? args.include : [...ALL_CONTEXT_INCLUDES];
|
|
5142
|
-
const payload = contextGetPayloadSchema.parse({ ...taskId ? { taskId } : {}, include });
|
|
5143
|
-
const result = await callServerOrError(
|
|
5144
|
-
runtime.client,
|
|
5145
|
-
"context:get",
|
|
5146
|
-
payload,
|
|
5147
|
-
contextPayloadSchema
|
|
5148
|
-
);
|
|
5149
|
-
if (!result.ok) return errorResult(result.message);
|
|
5150
|
-
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
|
+
};
|
|
5151
5164
|
}
|
|
5152
5165
|
|
|
5153
|
-
// src/
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
};
|
|
5157
|
-
async function getTaskHandler(args, runtime) {
|
|
5158
|
-
const lease = runtime.client.isLeaseLost(args.taskId);
|
|
5159
|
-
if (lease.lost) return leaseLostResult(args.taskId, lease.reason);
|
|
5160
|
-
const payload = taskGetPayloadSchema.parse({ taskId: args.taskId });
|
|
5161
|
-
const result = await callServerOrError(runtime.client, "task:get", payload, taskGetAckSchema);
|
|
5162
|
-
if (!result.ok) return errorResult(result.message);
|
|
5163
|
-
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";
|
|
5164
5169
|
}
|
|
5165
5170
|
|
|
5166
|
-
// src/
|
|
5167
|
-
var
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
return errorResult(
|
|
5175
|
-
"\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."
|
|
5176
|
-
);
|
|
5177
|
-
}
|
|
5178
|
-
const lease = runtime.client.isLeaseLost(state.currentTask.taskId);
|
|
5179
|
-
if (lease.lost) {
|
|
5180
|
-
return leaseLostResult(state.currentTask.taskKey ?? state.currentTask.taskId, lease.reason);
|
|
5181
|
-
}
|
|
5182
|
-
const payload = taskProgressPayloadSchema.parse({
|
|
5183
|
-
taskId: state.currentTask.taskId,
|
|
5184
|
-
note: args.message,
|
|
5185
|
-
...args.level ? { level: args.level } : {}
|
|
5186
|
-
});
|
|
5187
|
-
const result = await callServerOrError(
|
|
5188
|
-
runtime.client,
|
|
5189
|
-
"task:progress",
|
|
5190
|
-
payload,
|
|
5191
|
-
taskProgressAckSchema
|
|
5192
|
-
);
|
|
5193
|
-
if (!result.ok) return errorResult(result.message);
|
|
5194
|
-
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).");
|
|
5195
|
-
return textResult("\u0417\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438.");
|
|
5196
|
-
}
|
|
5197
|
-
|
|
5198
|
-
// src/git/result.ts
|
|
5199
|
-
function gitOk(data) {
|
|
5200
|
-
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)}`);
|
|
5201
5179
|
}
|
|
5202
|
-
function
|
|
5203
|
-
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
|
+
};
|
|
5204
5198
|
}
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
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();
|
|
5223
5222
|
}
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
if (!isValidGitBranchName(name)) {
|
|
5228
|
-
return gitErr(
|
|
5229
|
-
`${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.`
|
|
5230
|
-
);
|
|
5223
|
+
// -- Публичное API -----------------------------------------------------
|
|
5224
|
+
on(event, listener) {
|
|
5225
|
+
return super.on(event, listener);
|
|
5231
5226
|
}
|
|
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
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
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({
|
|
5267
5318
|
ok: false,
|
|
5268
|
-
|
|
5269
|
-
signal: error.signal ?? null,
|
|
5270
|
-
stdout,
|
|
5271
|
-
// Если git ничего не написал в stderr (например, сам процесс не
|
|
5272
|
-
// запустился — ENOENT), берём текст ошибки Node, чтобы вызывающий
|
|
5273
|
-
// код не остался с пустым сообщением.
|
|
5274
|
-
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).`
|
|
5275
5320
|
});
|
|
5276
|
-
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
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);
|
|
5297
5532
|
}
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreeDir });
|
|
5302
|
-
if (!result.ok) {
|
|
5303
|
-
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);
|
|
5304
5536
|
}
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
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);
|
|
5316
5566
|
}
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
if (
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
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);
|
|
5327
5588
|
}
|
|
5328
|
-
|
|
5589
|
+
};
|
|
5590
|
+
function trimTrailingSlash2(url) {
|
|
5591
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5329
5592
|
}
|
|
5330
5593
|
|
|
5331
|
-
// src/
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
return
|
|
5335
|
-
|
|
5336
|
-
${buildTaskTrailer(params.taskKey)}
|
|
5337
|
-
`;
|
|
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;
|
|
5338
5598
|
}
|
|
5339
|
-
async function
|
|
5340
|
-
const
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
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();
|
|
5353
5626
|
}
|
|
5354
|
-
const sha = await getHeadSha(params.worktreeDir);
|
|
5355
|
-
return gitOk({ committed: true, sha: sha.ok ? sha.data : null });
|
|
5356
5627
|
}
|
|
5357
5628
|
|
|
5358
|
-
// src/
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
import { dirname as dirname4, isAbsolute, join as join9, relative, resolve as resolve2 } from "path";
|
|
5362
|
-
var WORKTREES_RELATIVE_ROOT = join9(".claude", "worktrees");
|
|
5363
|
-
function getDefaultWorktreesRoot(repoRoot) {
|
|
5364
|
-
return join9(repoRoot, WORKTREES_RELATIVE_ROOT);
|
|
5365
|
-
}
|
|
5366
|
-
function resolveWorktreeDir(worktreesRoot, repoRoot, branchName) {
|
|
5367
|
-
if (isInsideRepo(worktreesRoot, repoRoot)) return join9(worktreesRoot, branchName);
|
|
5368
|
-
const repoHash = createHash2("sha1").update(resolve2(repoRoot)).digest("hex").slice(0, 12);
|
|
5369
|
-
return join9(worktreesRoot, repoHash, branchName);
|
|
5370
|
-
}
|
|
5371
|
-
function isInsideRepo(candidate, repoRoot) {
|
|
5372
|
-
const rel = relative(resolve2(repoRoot), resolve2(candidate));
|
|
5373
|
-
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
5629
|
+
// src/mcp/helpers.ts
|
|
5630
|
+
function textResult(text) {
|
|
5631
|
+
return { content: [{ type: "text", text }] };
|
|
5374
5632
|
}
|
|
5375
|
-
function
|
|
5376
|
-
|
|
5377
|
-
return realpathSync(path);
|
|
5378
|
-
} catch {
|
|
5379
|
-
return resolve2(path);
|
|
5380
|
-
}
|
|
5633
|
+
function errorResult(text) {
|
|
5634
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
5381
5635
|
}
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
}
|
|
5387
|
-
const entries = [];
|
|
5388
|
-
let path;
|
|
5389
|
-
let branch = null;
|
|
5390
|
-
const flush = () => {
|
|
5391
|
-
if (path) entries.push({ path, branch });
|
|
5392
|
-
path = void 0;
|
|
5393
|
-
branch = null;
|
|
5394
|
-
};
|
|
5395
|
-
for (const line of result.stdout.split("\n")) {
|
|
5396
|
-
if (line.length === 0) {
|
|
5397
|
-
flush();
|
|
5398
|
-
continue;
|
|
5399
|
-
}
|
|
5400
|
-
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5401
|
-
else if (line.startsWith("branch refs/heads/")) branch = line.slice("branch refs/heads/".length);
|
|
5402
|
-
}
|
|
5403
|
-
flush();
|
|
5404
|
-
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
|
+
);
|
|
5405
5640
|
}
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
5411
|
-
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
5412
|
-
const worktreesRoot = params.worktreesRoot ?? getDefaultWorktreesRoot(params.repoRoot);
|
|
5413
|
-
const worktreeDir = resolveWorktreeDir(worktreesRoot, params.repoRoot, params.branchName);
|
|
5414
|
-
const list = await listWorktrees(params.repoRoot);
|
|
5415
|
-
if (!list.ok) return list;
|
|
5416
|
-
const existing = list.data.find((wt) => canonicalPath(wt.path) === canonicalPath(worktreeDir));
|
|
5417
|
-
if (existing) {
|
|
5418
|
-
if (existing.branch === params.branchName) {
|
|
5419
|
-
return gitOk({ worktreeDir, reused: true });
|
|
5420
|
-
}
|
|
5421
|
-
return gitErr(
|
|
5422
|
-
`\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.`
|
|
5423
|
-
);
|
|
5424
|
-
}
|
|
5425
|
-
if (existsSync9(worktreeDir)) {
|
|
5426
|
-
return gitErr(
|
|
5427
|
-
`\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.`
|
|
5428
|
-
);
|
|
5429
|
-
}
|
|
5430
|
-
await runGit(["worktree", "prune"], { cwd: params.repoRoot });
|
|
5431
|
-
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
5432
|
-
cwd: params.repoRoot
|
|
5433
|
-
});
|
|
5434
|
-
if (!fetchResult.ok) {
|
|
5435
|
-
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
5436
|
-
}
|
|
5437
|
-
mkdirSync6(dirname4(worktreeDir), { recursive: true });
|
|
5438
|
-
const branchExists = await localBranchExists(params.repoRoot, params.branchName);
|
|
5439
|
-
const addArgs = branchExists ? ["worktree", "add", worktreeDir, params.branchName] : ["worktree", "add", "-b", params.branchName, worktreeDir, `${remote}/${params.defaultBranch}`];
|
|
5440
|
-
const addResult = await runGit(addArgs, { cwd: params.repoRoot });
|
|
5441
|
-
if (!addResult.ok) {
|
|
5442
|
-
return gitErr(describeGitFailure("git worktree add", addResult));
|
|
5443
|
-
}
|
|
5444
|
-
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("; ");
|
|
5445
5645
|
}
|
|
5446
|
-
|
|
5447
|
-
const
|
|
5448
|
-
if (!
|
|
5449
|
-
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)}` };
|
|
5450
5650
|
}
|
|
5451
|
-
return
|
|
5452
|
-
}
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
if (!fetchResult.ok) {
|
|
5463
|
-
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
5651
|
+
return { ok: true, data: parsed.data };
|
|
5652
|
+
}
|
|
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
|
+
};
|
|
5464
5662
|
}
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
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
|
+
};
|
|
5470
5668
|
}
|
|
5471
|
-
const
|
|
5472
|
-
if (
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
`\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.`
|
|
5478
|
-
);
|
|
5479
|
-
}
|
|
5480
|
-
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
|
+
};
|
|
5481
5675
|
}
|
|
5482
|
-
return
|
|
5676
|
+
return { ok: true, data: parsed.data };
|
|
5483
5677
|
}
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
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);
|
|
5491
5720
|
}
|
|
5492
|
-
const
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
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.`
|
|
5497
5734
|
);
|
|
5498
5735
|
}
|
|
5499
|
-
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
|
+
);
|
|
5500
5739
|
}
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
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
|
|
5511
5753
|
});
|
|
5512
|
-
if (!
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
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
|
+
);
|
|
5518
5768
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
}
|
|
5532
|
-
|
|
5533
|
-
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)}`;
|
|
5534
5783
|
}
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
commitPrefix: params.commitPrefix,
|
|
5538
|
-
title: params.taskTitle,
|
|
5539
|
-
taskKey: params.taskKey
|
|
5540
|
-
});
|
|
5541
|
-
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5542
|
-
const rebase = await rebaseOntoDefault({
|
|
5543
|
-
worktreeDir,
|
|
5544
|
-
defaultBranch: params.defaultBranch,
|
|
5545
|
-
remote
|
|
5546
|
-
});
|
|
5547
|
-
if (!rebase.ok) return { status: "refused", worktreeDir, message: rebase.message };
|
|
5548
|
-
if (rebase.data.outcome === "conflict") {
|
|
5549
|
-
const files = rebase.data.conflictedFiles;
|
|
5550
|
-
return {
|
|
5551
|
-
status: "conflict",
|
|
5552
|
-
worktreeDir,
|
|
5553
|
-
conflictedFiles: files,
|
|
5554
|
-
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.`
|
|
5555
|
-
};
|
|
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.`;
|
|
5556
5786
|
}
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
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"
|
|
5563
5850
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
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);
|
|
5579
5886
|
}
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
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));
|
|
5587
5897
|
}
|
|
5588
5898
|
|
|
5589
|
-
// src/
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
const
|
|
5595
|
-
if (
|
|
5596
|
-
const
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
);
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
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."
|
|
5603
5922
|
);
|
|
5604
5923
|
}
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
const remote = params.remote ?? "origin";
|
|
5609
|
-
let worktreeDir;
|
|
5610
|
-
if (params.worktreeDir) {
|
|
5611
|
-
worktreeDir = params.worktreeDir;
|
|
5612
|
-
} else {
|
|
5613
|
-
const worktree = await ensureWorktree({
|
|
5614
|
-
repoRoot: params.repoRoot,
|
|
5615
|
-
branchName: params.branchName,
|
|
5616
|
-
defaultBranch: params.defaultBranch,
|
|
5617
|
-
remote,
|
|
5618
|
-
worktreesRoot: params.worktreesRoot
|
|
5619
|
-
});
|
|
5620
|
-
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
5621
|
-
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);
|
|
5622
5927
|
}
|
|
5623
|
-
const
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
taskKey: params.taskKey
|
|
5628
|
-
});
|
|
5629
|
-
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5630
|
-
const rebase = await rebaseOntoDefault({
|
|
5631
|
-
worktreeDir,
|
|
5632
|
-
defaultBranch: params.defaultBranch,
|
|
5633
|
-
remote
|
|
5928
|
+
const payload = taskProgressPayloadSchema.parse({
|
|
5929
|
+
taskId: state.currentTask.taskId,
|
|
5930
|
+
note: args.message,
|
|
5931
|
+
...args.level ? { level: args.level } : {}
|
|
5634
5932
|
});
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
if (
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
5645
|
-
worktreeRemoved = true;
|
|
5646
|
-
}
|
|
5647
|
-
}
|
|
5648
|
-
return {
|
|
5649
|
-
status: "pushed",
|
|
5650
|
-
worktreeDir,
|
|
5651
|
-
branchName: params.branchName,
|
|
5652
|
-
commitSha: sha.ok ? sha.data : null,
|
|
5653
|
-
pushedTo: push.data.pushedTo,
|
|
5654
|
-
conflicted,
|
|
5655
|
-
conflictedFiles: rebase.data.conflictedFiles,
|
|
5656
|
-
worktreeRemoved
|
|
5657
|
-
};
|
|
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.");
|
|
5658
5942
|
}
|
|
5659
5943
|
|
|
5660
5944
|
// src/mcp/task-worktree.ts
|
|
@@ -5729,7 +6013,9 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5729
6013
|
taskId: task.task.id,
|
|
5730
6014
|
taskKey: task.task.key,
|
|
5731
6015
|
resumeToken: task.lease.resumeToken,
|
|
5732
|
-
gitMode: task.project.gitMode
|
|
6016
|
+
gitMode: task.project.gitMode,
|
|
6017
|
+
branchName: task.project.branchName,
|
|
6018
|
+
commitPrefix: task.project.commitPrefix
|
|
5733
6019
|
}
|
|
5734
6020
|
},
|
|
5735
6021
|
stateFilePath
|
|
@@ -5742,7 +6028,12 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5742
6028
|
const worktree = await ensureWorktree({
|
|
5743
6029
|
repoRoot: runtime.cwd,
|
|
5744
6030
|
branchName: plan.branchName,
|
|
5745
|
-
defaultBranch: plan.defaultBranch
|
|
6031
|
+
defaultBranch: plan.defaultBranch,
|
|
6032
|
+
// LIM-03: задачу уже начинали и прервали — рабочее дерево заводим
|
|
6033
|
+
// ОТ ТОЙ ВЕТКИ, а не от основной. Иначе спасённая работа осталась
|
|
6034
|
+
// бы лежать на origin никем не прочитанной, и агент сделал бы её
|
|
6035
|
+
// заново, потратив свой лимит подписки на чужую.
|
|
6036
|
+
continueFromBranch: task.continueFrom?.branchName
|
|
5746
6037
|
});
|
|
5747
6038
|
if (worktree.ok) {
|
|
5748
6039
|
updateState(
|
|
@@ -5752,6 +6043,8 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5752
6043
|
taskKey: task.task.key,
|
|
5753
6044
|
resumeToken: task.lease.resumeToken,
|
|
5754
6045
|
gitMode: task.project.gitMode,
|
|
6046
|
+
branchName: task.project.branchName,
|
|
6047
|
+
commitPrefix: task.project.commitPrefix,
|
|
5755
6048
|
worktreeDir: worktree.data.worktreeDir
|
|
5756
6049
|
}
|
|
5757
6050
|
},
|
|
@@ -5770,11 +6063,14 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5770
6063
|
const warning = !started.ok ? `
|
|
5771
6064
|
|
|
5772
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.` : "";
|
|
5773
6069
|
return textResult(
|
|
5774
6070
|
`\u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0430:
|
|
5775
6071
|
${JSON.stringify(task, null, 2)}
|
|
5776
6072
|
|
|
5777
|
-
${worktreeNote}${warning}`
|
|
6073
|
+
${worktreeNote}${continueNote}${warning}`
|
|
5778
6074
|
);
|
|
5779
6075
|
}
|
|
5780
6076
|
|
|
@@ -6014,7 +6310,7 @@ async function settleApproval(ack, commandCwd, runtime, waitSecUsed) {
|
|
|
6014
6310
|
}
|
|
6015
6311
|
|
|
6016
6312
|
// src/mcp/set-mode.ts
|
|
6017
|
-
import { z as
|
|
6313
|
+
import { z as z38 } from "zod";
|
|
6018
6314
|
|
|
6019
6315
|
// src/cli/mode.ts
|
|
6020
6316
|
function parseModeArg(raw) {
|
|
@@ -6055,7 +6351,7 @@ async function requestModeChange(modeArg, options = {}) {
|
|
|
6055
6351
|
|
|
6056
6352
|
// src/mcp/set-mode.ts
|
|
6057
6353
|
var setModeInputShape = {
|
|
6058
|
-
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)")
|
|
6059
6355
|
};
|
|
6060
6356
|
async function setModeHandler(args, runtime) {
|
|
6061
6357
|
const result = await requestModeChange(args.mode, {
|
|
@@ -6070,20 +6366,20 @@ async function setModeHandler(args, runtime) {
|
|
|
6070
6366
|
}
|
|
6071
6367
|
|
|
6072
6368
|
// src/mcp/submit-work.ts
|
|
6073
|
-
import { z as
|
|
6369
|
+
import { z as z39 } from "zod";
|
|
6074
6370
|
var submitWorkInputShape = {
|
|
6075
|
-
branchName:
|
|
6371
|
+
branchName: z39.string().min(1).describe(
|
|
6076
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."
|
|
6077
6373
|
),
|
|
6078
|
-
defaultBranch:
|
|
6079
|
-
commitPrefix:
|
|
6080
|
-
taskKey:
|
|
6081
|
-
taskTitle:
|
|
6082
|
-
repoRoot:
|
|
6083
|
-
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(
|
|
6084
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."
|
|
6085
6381
|
),
|
|
6086
|
-
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.")
|
|
6087
6383
|
};
|
|
6088
6384
|
async function submitWorkHandler(args, runtime) {
|
|
6089
6385
|
const state = readState(runtime.stateFilePath);
|
|
@@ -6557,13 +6853,13 @@ function formatConnectResult(result) {
|
|
|
6557
6853
|
}
|
|
6558
6854
|
|
|
6559
6855
|
// src/hooks/pre-compact.ts
|
|
6560
|
-
import { z as
|
|
6561
|
-
var PreCompactInputSchema =
|
|
6562
|
-
hook_event_name:
|
|
6563
|
-
session_id:
|
|
6564
|
-
cwd:
|
|
6565
|
-
trigger:
|
|
6566
|
-
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()
|
|
6567
6863
|
}).passthrough();
|
|
6568
6864
|
function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6569
6865
|
const lastPreCompact = state.currentTask ? { ...state.currentTask, savedAt: now() } : null;
|
|
@@ -6572,14 +6868,14 @@ function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).
|
|
|
6572
6868
|
|
|
6573
6869
|
// src/hooks/pre-tool-use.ts
|
|
6574
6870
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
|
|
6575
|
-
import { z as
|
|
6576
|
-
var PreToolUseInputSchema =
|
|
6577
|
-
hook_event_name:
|
|
6578
|
-
session_id:
|
|
6579
|
-
cwd:
|
|
6580
|
-
tool_name:
|
|
6581
|
-
tool_input:
|
|
6582
|
-
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()
|
|
6583
6879
|
}).passthrough();
|
|
6584
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.";
|
|
6585
6881
|
var BOOTSTRAP_COMMAND_RE = /^quiel\s+(connect|status)\b/;
|
|
@@ -6811,13 +7107,13 @@ function readNewUsage(transcriptPath, offset) {
|
|
|
6811
7107
|
}
|
|
6812
7108
|
|
|
6813
7109
|
// src/hooks/session-start.ts
|
|
6814
|
-
import { z as
|
|
6815
|
-
var SessionStartInputSchema =
|
|
6816
|
-
hook_event_name:
|
|
6817
|
-
session_id:
|
|
6818
|
-
cwd:
|
|
6819
|
-
source:
|
|
6820
|
-
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()
|
|
6821
7117
|
}).passthrough();
|
|
6822
7118
|
var SESSION_RESET_SOURCES = /* @__PURE__ */ new Set([
|
|
6823
7119
|
"startup",
|
|
@@ -6856,13 +7152,13 @@ function sessionStartHook(input, state) {
|
|
|
6856
7152
|
}
|
|
6857
7153
|
|
|
6858
7154
|
// src/hooks/stop.ts
|
|
6859
|
-
import { z as
|
|
6860
|
-
var StopInputSchema =
|
|
6861
|
-
hook_event_name:
|
|
6862
|
-
session_id:
|
|
6863
|
-
cwd:
|
|
6864
|
-
stop_hook_active:
|
|
6865
|
-
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()
|
|
6866
7162
|
}).passthrough();
|
|
6867
7163
|
function taskLabel(state) {
|
|
6868
7164
|
if (!state.currentTask) return "";
|
|
@@ -6905,55 +7201,6 @@ function stopHook(input, state) {
|
|
|
6905
7201
|
};
|
|
6906
7202
|
}
|
|
6907
7203
|
|
|
6908
|
-
// src/hooks/stop-failure.ts
|
|
6909
|
-
import { z as z43 } from "zod";
|
|
6910
|
-
var StopFailureErrorTypeSchema = z43.enum([
|
|
6911
|
-
"rate_limit",
|
|
6912
|
-
"overloaded",
|
|
6913
|
-
"authentication_failed",
|
|
6914
|
-
"oauth_org_not_allowed",
|
|
6915
|
-
"account_on_hold",
|
|
6916
|
-
"billing_error",
|
|
6917
|
-
"invalid_request",
|
|
6918
|
-
"model_not_found",
|
|
6919
|
-
"server_error",
|
|
6920
|
-
"max_output_tokens",
|
|
6921
|
-
"unknown"
|
|
6922
|
-
]);
|
|
6923
|
-
var StopFailureInputSchema = z43.object({
|
|
6924
|
-
hook_event_name: z43.literal("StopFailure"),
|
|
6925
|
-
session_id: z43.string(),
|
|
6926
|
-
cwd: z43.string(),
|
|
6927
|
-
error: StopFailureErrorTypeSchema,
|
|
6928
|
-
error_details: z43.unknown().optional(),
|
|
6929
|
-
last_assistant_message: z43.string().optional()
|
|
6930
|
-
}).passthrough();
|
|
6931
|
-
function extractRetryAfterSec(errorDetails) {
|
|
6932
|
-
if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
|
|
6933
|
-
return errorDetails;
|
|
6934
|
-
}
|
|
6935
|
-
if (errorDetails && typeof errorDetails === "object") {
|
|
6936
|
-
const record = errorDetails;
|
|
6937
|
-
for (const key of ["retryAfterSec", "retryAfter", "retry_after", "retry_after_seconds"]) {
|
|
6938
|
-
const value = record[key];
|
|
6939
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
6940
|
-
}
|
|
6941
|
-
}
|
|
6942
|
-
if (typeof errorDetails === "string") {
|
|
6943
|
-
const match = errorDetails.match(/retry[-_ ]?after[^0-9]{0,10}(\d+)/i);
|
|
6944
|
-
if (match?.[1] !== void 0) return Number(match[1]);
|
|
6945
|
-
}
|
|
6946
|
-
return void 0;
|
|
6947
|
-
}
|
|
6948
|
-
function stopFailureHook(input, _state) {
|
|
6949
|
-
const retryAfterSec = extractRetryAfterSec(input.error_details);
|
|
6950
|
-
const action = {
|
|
6951
|
-
type: "agent:rate_limited",
|
|
6952
|
-
payload: retryAfterSec === void 0 ? { error: input.error } : { error: input.error, retryAfterSec }
|
|
6953
|
-
};
|
|
6954
|
-
return { output: {}, serverActions: [action] };
|
|
6955
|
-
}
|
|
6956
|
-
|
|
6957
7204
|
// src/hooks/run.ts
|
|
6958
7205
|
var HookInputSchema = z44.discriminatedUnion("hook_event_name", [
|
|
6959
7206
|
SessionStartInputSchema,
|
|
@@ -7457,13 +7704,26 @@ function createProgram() {
|
|
|
7457
7704
|
if (result.stderr.length > 0) process.stderr.write(`${result.stderr}
|
|
7458
7705
|
`);
|
|
7459
7706
|
process.exitCode = result.exitCode;
|
|
7460
|
-
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
|
+
}
|
|
7461
7716
|
return;
|
|
7462
7717
|
}
|
|
7463
7718
|
const execution = runCliHook(raw);
|
|
7464
7719
|
process.stdout.write(`${execution.stdout}
|
|
7465
7720
|
`);
|
|
7466
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]);
|
|
7467
7727
|
sendHookActions(execution.serverActions);
|
|
7468
7728
|
});
|
|
7469
7729
|
program.command("mcp").description(
|