@quiel/cli 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/cli.js +2002 -1331
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -372,35 +372,7 @@ var attachmentSchema = z7.object({
|
|
|
372
372
|
|
|
373
373
|
// ../shared/src/dto/auth.ts
|
|
374
374
|
import { z as z8 } from "zod";
|
|
375
|
-
var PASSWORD_MIN_LENGTH = 10;
|
|
376
|
-
var PASSWORD_MAX_LENGTH = 128;
|
|
377
|
-
var PasswordSchema = z8.string().min(PASSWORD_MIN_LENGTH, `\u041F\u0430\u0440\u043E\u043B\u044C \u043A\u043E\u0440\u043E\u0447\u0435 ${PASSWORD_MIN_LENGTH} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432`).max(PASSWORD_MAX_LENGTH, `\u041F\u0430\u0440\u043E\u043B\u044C \u0434\u043B\u0438\u043D\u043D\u0435\u0435 ${PASSWORD_MAX_LENGTH} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432`);
|
|
378
375
|
var EmailSchema = z8.string().trim().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u043E\u0447\u0442\u0443").email("\u041F\u043E\u0445\u043E\u0436\u0435, \u0432 \u0430\u0434\u0440\u0435\u0441\u0435 \u043E\u043F\u0435\u0447\u0430\u0442\u043A\u0430").toLowerCase();
|
|
379
|
-
var RegisterSchema = z8.object({
|
|
380
|
-
name: z8.string().trim().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F").max(120),
|
|
381
|
-
email: EmailSchema,
|
|
382
|
-
password: PasswordSchema
|
|
383
|
-
}).refine(
|
|
384
|
-
(value) => {
|
|
385
|
-
const localPart = value.email.split("@")[0] ?? "";
|
|
386
|
-
return localPart.length < 3 || !value.password.toLowerCase().includes(localPart);
|
|
387
|
-
},
|
|
388
|
-
{ message: "\u041F\u0430\u0440\u043E\u043B\u044C \u043D\u0435 \u0434\u043E\u043B\u0436\u0435\u043D \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0442\u044C \u0430\u0434\u0440\u0435\u0441 \u043F\u043E\u0447\u0442\u044B", path: ["password"] }
|
|
389
|
-
);
|
|
390
|
-
var LoginSchema = z8.object({
|
|
391
|
-
email: EmailSchema,
|
|
392
|
-
// На входе НЕ применяем правила длины: пароль мог быть задан раньше, по
|
|
393
|
-
// другим правилам, и «слишком короткий» на форме входа — это отказ в
|
|
394
|
-
// доступе законному владельцу, а заодно подсказка перебирающему, что
|
|
395
|
-
// проверять длину не нужно.
|
|
396
|
-
password: z8.string().min(1, "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043F\u0430\u0440\u043E\u043B\u044C")
|
|
397
|
-
});
|
|
398
|
-
var ForgotPasswordSchema = z8.object({ email: EmailSchema });
|
|
399
|
-
var ResetPasswordSchema = z8.object({
|
|
400
|
-
token: z8.string().min(1),
|
|
401
|
-
password: PasswordSchema
|
|
402
|
-
});
|
|
403
|
-
var ResendVerificationSchema = z8.object({ email: EmailSchema });
|
|
404
376
|
|
|
405
377
|
// ../shared/src/dto/milestone.ts
|
|
406
378
|
import { z as z9 } from "zod";
|
|
@@ -499,14 +471,8 @@ var ProfileNameSchema = z11.string().trim().min(1, "\u0423\u043A\u0430\u0436\u04
|
|
|
499
471
|
var UpdateProfileSchema = z11.object({
|
|
500
472
|
name: ProfileNameSchema
|
|
501
473
|
});
|
|
502
|
-
var ChangePasswordSchema = z11.object({
|
|
503
|
-
currentPassword: z11.string().min(1).max(200).optional(),
|
|
504
|
-
newPassword: PasswordSchema
|
|
505
|
-
});
|
|
506
474
|
var RequestEmailChangeSchema = z11.object({
|
|
507
|
-
newEmail: EmailSchema
|
|
508
|
-
/** Нужен, если у аккаунта есть пароль — см. `ChangePasswordSchema`. */
|
|
509
|
-
currentPassword: z11.string().min(1).max(200).optional()
|
|
475
|
+
newEmail: EmailSchema
|
|
510
476
|
});
|
|
511
477
|
var ConfirmEmailChangeSchema = z11.object({
|
|
512
478
|
token: z11.string().min(1)
|
|
@@ -815,6 +781,26 @@ var ProjectSettingsSchema = z14.object({
|
|
|
815
781
|
* Пусто — задача заводится без роли и видна всей команде проекта.
|
|
816
782
|
*/
|
|
817
783
|
reviewRoleKey: z14.string().trim().min(1).max(60).optional(),
|
|
784
|
+
/**
|
|
785
|
+
* Канал Slack, куда уходят события проекта (SL-04, ADR-007).
|
|
786
|
+
*
|
|
787
|
+
* КАНАЛ НА ПРОЕКТ, А НЕ НА ОРГАНИЗАЦИЮ. Установка одна на компанию —
|
|
788
|
+
* так устроен Slack, — но лента у каждого проекта своя: в организации
|
|
789
|
+
* с пятью проектами общий канал превращается в шум, который выключают
|
|
790
|
+
* через неделю.
|
|
791
|
+
*
|
|
792
|
+
* ПУСТО — ПОДДЕРЖИВАЕМЫЙ РЕЖИМ, А НЕ ПОЛОМКА. Проект без канала
|
|
793
|
+
* работает на личных сообщениях: подтверждения и вопросы всё равно
|
|
794
|
+
* приходят названному человеку в личку. Канал добавляет ОБЩУЮ
|
|
795
|
+
* видимость, а не доставку.
|
|
796
|
+
*/
|
|
797
|
+
slackChannelId: z14.string().trim().min(1).max(40).optional(),
|
|
798
|
+
/**
|
|
799
|
+
* Имя канала на момент выбора — только чтобы показать человеку
|
|
800
|
+
* «#релизы», а не `C09ABCDEF`. Slack допускает переименование, поэтому
|
|
801
|
+
* истина — идентификатор, а это подпись к нему.
|
|
802
|
+
*/
|
|
803
|
+
slackChannelName: z14.string().trim().min(1).max(120).optional(),
|
|
818
804
|
maxParallelTasksPerAgent: z14.number().int().positive().default(1),
|
|
819
805
|
leaseTtlSec: z14.number().int().positive().default(120),
|
|
820
806
|
taskTimeoutMin: z14.number().int().positive().default(90),
|
|
@@ -1317,13 +1303,22 @@ var taskPayloadLeaseSchema = z23.object({
|
|
|
1317
1303
|
resumeToken: z23.string().min(1),
|
|
1318
1304
|
expiresAt: z23.string().datetime()
|
|
1319
1305
|
});
|
|
1306
|
+
var taskPayloadContinueFromSchema = z23.object({
|
|
1307
|
+
branchName: z23.string().min(1),
|
|
1308
|
+
sha: z23.string().optional(),
|
|
1309
|
+
/** Короткая записка прошлого исполнителя «что успел». */
|
|
1310
|
+
note: z23.string().optional(),
|
|
1311
|
+
savedAt: z23.string()
|
|
1312
|
+
});
|
|
1320
1313
|
var taskPayloadSchema = z23.object({
|
|
1321
1314
|
task: taskPayloadTaskSchema,
|
|
1322
1315
|
project: taskPayloadProjectSchema,
|
|
1323
1316
|
dependencies: z23.array(taskPayloadDependencySchema),
|
|
1324
1317
|
openQuestions: z23.array(taskPayloadOpenQuestionSchema),
|
|
1325
1318
|
trust: taskPayloadTrustSchema,
|
|
1326
|
-
lease: taskPayloadLeaseSchema
|
|
1319
|
+
lease: taskPayloadLeaseSchema,
|
|
1320
|
+
/** Есть только у задач, которые уже начинали и прервали. */
|
|
1321
|
+
continueFrom: taskPayloadContinueFromSchema.optional()
|
|
1327
1322
|
});
|
|
1328
1323
|
var taskClaimPayloadSchema = z23.object({
|
|
1329
1324
|
/**
|
|
@@ -1440,7 +1435,21 @@ var contextPayloadSchema = z23.object({
|
|
|
1440
1435
|
});
|
|
1441
1436
|
var agentRateLimitedPayloadSchema = z23.object({
|
|
1442
1437
|
retryAfterSec: z23.number().int().positive().optional(),
|
|
1443
|
-
taskId: z23.string().min(1).optional()
|
|
1438
|
+
taskId: z23.string().min(1).optional(),
|
|
1439
|
+
/**
|
|
1440
|
+
* Когда лимит сбросится (LIM-06) — разобрано клиентом из текста самой
|
|
1441
|
+
* ошибки Claude Code («resets 3:45pm»).
|
|
1442
|
+
*
|
|
1443
|
+
* НЕ ПУТАТЬ С `retryAfterSec`. Тот — сколько секунд подождать, как
|
|
1444
|
+
* посчитал сам Claude Code, и его часто нет вовсе. Этот — абсолютный
|
|
1445
|
+
* момент, уже приведённый к UTC клиентом: в сообщении время местное и
|
|
1446
|
+
* без пояса, и разобрать его может только тот, кто стоит на той же
|
|
1447
|
+
* машине.
|
|
1448
|
+
*
|
|
1449
|
+
* Необязательное: формат сообщения может смениться, и тогда клиент
|
|
1450
|
+
* честно ничего не присылает, а не гадает.
|
|
1451
|
+
*/
|
|
1452
|
+
resetsAt: z23.string().datetime().optional()
|
|
1444
1453
|
});
|
|
1445
1454
|
var agentRateLimitedAckSchema = z23.object({
|
|
1446
1455
|
ok: z23.boolean(),
|
|
@@ -1520,6 +1529,18 @@ var taskUsagePayloadSchema = z23.object({
|
|
|
1520
1529
|
entries: z23.array(taskUsageEntrySchema).min(1).max(10)
|
|
1521
1530
|
});
|
|
1522
1531
|
var taskUsageAckSchema = z23.object({ ok: z23.boolean() });
|
|
1532
|
+
var taskWorkSavedPayloadSchema = z23.object({
|
|
1533
|
+
taskId: z23.string().min(1),
|
|
1534
|
+
/** Ветка, в которую ушла незаконченная работа. */
|
|
1535
|
+
branchName: z23.string().min(1).max(255),
|
|
1536
|
+
sha: z23.string().min(7).max(64).optional(),
|
|
1537
|
+
/**
|
|
1538
|
+
* Короткая записка «что успел» — её увидит следующий исполнитель.
|
|
1539
|
+
* Потолок жёсткий: это подпись к ветке, а не отчёт о работе.
|
|
1540
|
+
*/
|
|
1541
|
+
note: z23.string().max(500).optional()
|
|
1542
|
+
});
|
|
1543
|
+
var taskWorkSavedAckSchema = z23.object({ ok: z23.boolean() });
|
|
1523
1544
|
var sessionLimitReachedPayloadSchema = z23.object({
|
|
1524
1545
|
tasksCompletedInSession: z23.number().int().nonnegative()
|
|
1525
1546
|
});
|
|
@@ -1533,7 +1554,17 @@ var taskCreatePayloadSchema = z23.object({
|
|
|
1533
1554
|
executor: z23.enum(executorValues2),
|
|
1534
1555
|
blocksCurrent: z23.boolean(),
|
|
1535
1556
|
acceptance: z23.string().optional(),
|
|
1536
|
-
labels: z23.array(z23.string().min(1)).optional()
|
|
1557
|
+
labels: z23.array(z23.string().min(1)).optional(),
|
|
1558
|
+
/**
|
|
1559
|
+
* Задачи, от которых новая зависит, — связи ставятся сразу.
|
|
1560
|
+
*
|
|
1561
|
+
* Без этого поля менеджеру, разложившему спецификацию на двадцать
|
|
1562
|
+
* пять задач, приходилось идти по списку вторым проходом. Связь,
|
|
1563
|
+
* замыкающая кольцо, молча пропускается: задача уже создана, и ронять
|
|
1564
|
+
* создание из-за одной плохой связи значило бы терять работу целиком.
|
|
1565
|
+
* Ответ честно говорит, сколько связей поставлено.
|
|
1566
|
+
*/
|
|
1567
|
+
dependsOn: z23.array(z23.string().min(1)).max(50).optional()
|
|
1537
1568
|
}).refine((v) => Boolean(v.roleKey) !== Boolean(v.assigneeId), {
|
|
1538
1569
|
message: "\u041D\u0443\u0436\u0435\u043D \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u0438\u043D \u0430\u0434\u0440\u0435\u0441\u0430\u0442: roleKey \u0418\u041B\u0418 assigneeId"
|
|
1539
1570
|
});
|
|
@@ -1552,7 +1583,16 @@ var taskBriefSchema = z23.object({
|
|
|
1552
1583
|
var taskCreateAckSchema = z23.object({
|
|
1553
1584
|
task: taskBriefSchema,
|
|
1554
1585
|
/** Во что ушла ТЕКУЩАЯ задача автора, если `blocksCurrent: true`. */
|
|
1555
|
-
currentTaskStatus: z23.enum(taskStatusValues3).optional()
|
|
1586
|
+
currentTaskStatus: z23.enum(taskStatusValues3).optional(),
|
|
1587
|
+
/**
|
|
1588
|
+
* Сколько связей из `dependsOn` поставлено.
|
|
1589
|
+
*
|
|
1590
|
+
* Может быть меньше запрошенного: связь, замыкающая кольцо, или
|
|
1591
|
+
* задача из чужого проекта пропускаются молча — задача уже создана, и
|
|
1592
|
+
* ронять создание из-за плохой связи значило бы терять работу.
|
|
1593
|
+
* Поэтому число, а не «ок»: по нему видно, что не всё прошло.
|
|
1594
|
+
*/
|
|
1595
|
+
dependenciesLinked: z23.number().int().nonnegative().optional()
|
|
1556
1596
|
});
|
|
1557
1597
|
var taskListPayloadSchema = z23.object({
|
|
1558
1598
|
status: z23.array(z23.enum(taskStatusValues3)).optional(),
|
|
@@ -1564,6 +1604,57 @@ var taskListPayloadSchema = z23.object({
|
|
|
1564
1604
|
var taskListAckSchema = z23.object({
|
|
1565
1605
|
tasks: z23.array(taskBriefSchema)
|
|
1566
1606
|
});
|
|
1607
|
+
var taskPlanPayloadSchema = z23.object({
|
|
1608
|
+
taskId: z23.string().min(1),
|
|
1609
|
+
patch: z23.object({
|
|
1610
|
+
title: z23.string().min(1).max(300).optional(),
|
|
1611
|
+
descriptionMd: z23.string().max(1e5).optional(),
|
|
1612
|
+
acceptance: z23.string().max(2e4).nullable().optional(),
|
|
1613
|
+
type: z23.enum(taskTypeValues2).optional(),
|
|
1614
|
+
priority: z23.enum(priorityValues2).optional(),
|
|
1615
|
+
roleKey: z23.string().min(1).max(60).nullable().optional(),
|
|
1616
|
+
assigneeId: z23.string().min(1).nullable().optional(),
|
|
1617
|
+
milestoneId: z23.string().min(1).nullable().optional(),
|
|
1618
|
+
labels: z23.array(z23.string().min(1)).max(30).optional(),
|
|
1619
|
+
estimateMin: z23.number().int().positive().nullable().optional()
|
|
1620
|
+
}).refine((patch) => Object.keys(patch).length > 0, {
|
|
1621
|
+
message: "\u041D\u0443\u0436\u043D\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0442\u044C \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u043D\u043E \u043F\u043E\u043B\u0435"
|
|
1622
|
+
})
|
|
1623
|
+
});
|
|
1624
|
+
var taskPlanAckSchema = z23.object({
|
|
1625
|
+
task: taskBriefSchema
|
|
1626
|
+
});
|
|
1627
|
+
var taskLinkPayloadSchema = z23.object({
|
|
1628
|
+
/** Задача, которая блокирует (её делают первой). */
|
|
1629
|
+
blockerId: z23.string().min(1),
|
|
1630
|
+
/** Задача, которая ждёт (§7.3: `fromId` — «эта задача зависит от…»). */
|
|
1631
|
+
blockedId: z23.string().min(1),
|
|
1632
|
+
/** `BLOCKS` держит очередь, `RELATES` — просто связь «см. также». */
|
|
1633
|
+
kind: z23.enum(["BLOCKS", "RELATES"]).default("BLOCKS")
|
|
1634
|
+
});
|
|
1635
|
+
var taskLinkAckSchema = z23.object({
|
|
1636
|
+
linked: z23.boolean(),
|
|
1637
|
+
/** Задача-ждущая ушла в BLOCKED прямо сейчас (была IN_PROGRESS). */
|
|
1638
|
+
blockedNow: z23.boolean()
|
|
1639
|
+
});
|
|
1640
|
+
var taskUnlinkPayloadSchema = z23.object({
|
|
1641
|
+
blockerId: z23.string().min(1),
|
|
1642
|
+
blockedId: z23.string().min(1)
|
|
1643
|
+
});
|
|
1644
|
+
var taskUnlinkAckSchema = z23.object({
|
|
1645
|
+
/** `false` — такой связи и не было; это не ошибка, а отчёт. */
|
|
1646
|
+
unlinked: z23.boolean()
|
|
1647
|
+
});
|
|
1648
|
+
var taskPeekPayloadSchema = z23.object({});
|
|
1649
|
+
var taskPeekAckSchema = z23.object({
|
|
1650
|
+
/**
|
|
1651
|
+
* Нашлась задача, которую диспетчер выдал бы прямо сейчас.
|
|
1652
|
+
*
|
|
1653
|
+
* Подсказка, а не обещание: пока агент просыпается, задачу может взять
|
|
1654
|
+
* другой. Цена ошибки — один пустой `next_task`.
|
|
1655
|
+
*/
|
|
1656
|
+
available: z23.boolean()
|
|
1657
|
+
});
|
|
1567
1658
|
var taskUpdatePayloadSchema = z23.object({
|
|
1568
1659
|
taskId: z23.string().min(1),
|
|
1569
1660
|
patch: z23.object({
|
|
@@ -1845,12 +1936,28 @@ var ImportTasksRequestSchema = z26.object({
|
|
|
1845
1936
|
var IntegrationStatus = {
|
|
1846
1937
|
/** Работает и проверена вживую. */
|
|
1847
1938
|
READY: "ready",
|
|
1939
|
+
/**
|
|
1940
|
+
* Сделана целиком, часть пути пройдена вживую, но не весь.
|
|
1941
|
+
*
|
|
1942
|
+
* Заведено для Slack 23 сентября 2026. «Скоро» про него стало враньём в
|
|
1943
|
+
* тот день, когда владелец установил приложение, привязал себя командой
|
|
1944
|
+
* `/quiel link` и выбрал каналы двум проектам — всё это прошло на
|
|
1945
|
+
* настоящем рабочем пространстве. «Работает и проверена вживую» тоже
|
|
1946
|
+
* неправда: ни одно сообщение в канал ещё не наблюдалось, потому что в
|
|
1947
|
+
* проектах не было событий.
|
|
1948
|
+
*
|
|
1949
|
+
* Тот же приём, что `support: 'documented'` у видов агентских программ
|
|
1950
|
+
* (`agents/kinds.ts`): пометка снимается после живого прогона, а не
|
|
1951
|
+
* после того, как код написан.
|
|
1952
|
+
*/
|
|
1953
|
+
BETA: "beta",
|
|
1848
1954
|
/** Запланирована, но ещё не сделана. Показываем, чтобы не обещать голосом. */
|
|
1849
1955
|
PLANNED: "planned"
|
|
1850
1956
|
};
|
|
1851
1957
|
var IntegrationKey = {
|
|
1852
1958
|
GITHUB: "github",
|
|
1853
1959
|
TELEGRAM: "telegram",
|
|
1960
|
+
SLACK: "slack",
|
|
1854
1961
|
WEBHOOKS: "webhooks"
|
|
1855
1962
|
};
|
|
1856
1963
|
var INTEGRATIONS = [
|
|
@@ -1868,6 +1975,22 @@ var INTEGRATIONS = [
|
|
|
1868
1975
|
scope: "both",
|
|
1869
1976
|
requiresOrgAdmin: false
|
|
1870
1977
|
},
|
|
1978
|
+
{
|
|
1979
|
+
// ПЕРВАЯ ОБЩАЯ поверхность продукта: канал проекта, где команда
|
|
1980
|
+
// видит работу агентов, не открывая дашборд (ADR-007). Остальные
|
|
1981
|
+
// каналы уведомлений личные.
|
|
1982
|
+
//
|
|
1983
|
+
// Статус `beta`, а не `planned` и не `ready`. Установка, привязка
|
|
1984
|
+
// человека командой `/quiel link` и выбор канала проекту прошли на
|
|
1985
|
+
// настоящем рабочем пространстве 23 сентября 2026 — «скоро» про это
|
|
1986
|
+
// было бы враньём. Но ни одно сообщение в канал ещё не наблюдалось:
|
|
1987
|
+
// в проектах не было событий. Пометка снимается после первого
|
|
1988
|
+
// дошедшего сообщения, а не после того, как код написан.
|
|
1989
|
+
key: IntegrationKey.SLACK,
|
|
1990
|
+
status: IntegrationStatus.BETA,
|
|
1991
|
+
scope: "org",
|
|
1992
|
+
requiresOrgAdmin: true
|
|
1993
|
+
},
|
|
1871
1994
|
{
|
|
1872
1995
|
// Одна механика вместо десяти интеграций: Zapier, n8n, Make и любой
|
|
1873
1996
|
// свой эндпоинт. Пока не сделана — и список об этом честно говорит.
|
|
@@ -1884,6 +2007,15 @@ var BY_KEY = new Map(INTEGRATIONS.map((info) => [info.key, info]));
|
|
|
1884
2007
|
var NotificationChannel = {
|
|
1885
2008
|
INAPP: "inapp",
|
|
1886
2009
|
TELEGRAM: "telegram",
|
|
2010
|
+
/**
|
|
2011
|
+
* Личное сообщение в Slack (SL-06).
|
|
2012
|
+
*
|
|
2013
|
+
* ИМЕННО ЛИЧНОЕ, а не канал проекта. Канал — общая видимость и живёт
|
|
2014
|
+
* отдельно (`slack-channel` в воркере): он не адресован никому и не
|
|
2015
|
+
* зависит от настроек человека. Смешай мы их в одном канале доставки —
|
|
2016
|
+
* выключив «Slack», человек выключил бы и ленту всей команды.
|
|
2017
|
+
*/
|
|
2018
|
+
SLACK: "slack",
|
|
1887
2019
|
EMAIL: "email",
|
|
1888
2020
|
PUSH: "push"
|
|
1889
2021
|
};
|
|
@@ -1897,18 +2029,21 @@ var NotificationUrgency = {
|
|
|
1897
2029
|
var ALL = [
|
|
1898
2030
|
NotificationChannel.INAPP,
|
|
1899
2031
|
NotificationChannel.TELEGRAM,
|
|
2032
|
+
NotificationChannel.SLACK,
|
|
1900
2033
|
NotificationChannel.EMAIL,
|
|
1901
2034
|
NotificationChannel.PUSH
|
|
1902
2035
|
];
|
|
1903
2036
|
var FAST = [
|
|
1904
2037
|
NotificationChannel.INAPP,
|
|
1905
2038
|
NotificationChannel.TELEGRAM,
|
|
2039
|
+
NotificationChannel.SLACK,
|
|
1906
2040
|
NotificationChannel.PUSH
|
|
1907
2041
|
];
|
|
1908
2042
|
var QUIET = [NotificationChannel.INAPP];
|
|
1909
2043
|
var SLOW = [
|
|
1910
2044
|
NotificationChannel.INAPP,
|
|
1911
2045
|
NotificationChannel.TELEGRAM,
|
|
2046
|
+
NotificationChannel.SLACK,
|
|
1912
2047
|
NotificationChannel.EMAIL
|
|
1913
2048
|
];
|
|
1914
2049
|
function defaultsFor(urgency) {
|
|
@@ -1972,8 +2107,20 @@ var NOTIFICATION_KINDS = [
|
|
|
1972
2107
|
kind(
|
|
1973
2108
|
"agent_session_limit",
|
|
1974
2109
|
"\u0421\u0435\u0441\u0441\u0438\u044F \u0430\u0433\u0435\u043D\u0442\u0430 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u0430",
|
|
1975
|
-
"\u0410\u0433\u0435\u043D\u0442 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043B \u043F\u043E\u0434\u0440\u044F\u0434 \u0441\u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u0434\u0430\u0447, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u043E \u0434\u043E \u043E\u0447\u0438\u0441\u0442\u043A\u0438 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430.",
|
|
1976
|
-
|
|
2110
|
+
"\u0410\u0433\u0435\u043D\u0442 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043B \u043F\u043E\u0434\u0440\u044F\u0434 \u0441\u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u0434\u0430\u0447, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u043E \u0434\u043E \u043E\u0447\u0438\u0441\u0442\u043A\u0438 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430, \u0438 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B\u0441\u044F.",
|
|
2111
|
+
// ACTIONABLE, А НЕ INFORMATIONAL, И РАЗНИЦА НЕ В СЛОВЕ.
|
|
2112
|
+
//
|
|
2113
|
+
// У informational push, почта и Telegram выключены по умолчанию —
|
|
2114
|
+
// уведомление оставалось в колокольчике. 24 сентября 2026 владелец
|
|
2115
|
+
// обнаружил, что агент простоял ночь: он остановился по лимиту и
|
|
2116
|
+
// ждал `/clear`, а узнать об этом было неоткуда. Проверено в базе:
|
|
2117
|
+
// у трёх таких записей стояло `{push: false, email: false,
|
|
2118
|
+
// telegram: false}`.
|
|
2119
|
+
//
|
|
2120
|
+
// Уведомление, которое ТРЕБУЕТ ДЕЙСТВИЯ человека, и есть
|
|
2121
|
+
// определение actionable: без `/clear` очередь стоит, даже если
|
|
2122
|
+
// режим проекта автономный.
|
|
2123
|
+
NotificationUrgency.ACTIONABLE
|
|
1977
2124
|
),
|
|
1978
2125
|
kind(
|
|
1979
2126
|
"task_auto_accepted",
|
|
@@ -1995,6 +2142,7 @@ import { z as z27 } from "zod";
|
|
|
1995
2142
|
var NotificationChannelFlagsSchema = z27.object({
|
|
1996
2143
|
inapp: z27.boolean().optional(),
|
|
1997
2144
|
telegram: z27.boolean().optional(),
|
|
2145
|
+
slack: z27.boolean().optional(),
|
|
1998
2146
|
email: z27.boolean().optional(),
|
|
1999
2147
|
push: z27.boolean().optional()
|
|
2000
2148
|
}).partial();
|
|
@@ -3271,7 +3419,8 @@ import { z as z29 } from "zod";
|
|
|
3271
3419
|
var HookCommandEntrySchema = z29.object({
|
|
3272
3420
|
type: z29.literal("command"),
|
|
3273
3421
|
command: z29.string().min(1),
|
|
3274
|
-
timeout: z29.number().int().positive().optional()
|
|
3422
|
+
timeout: z29.number().int().positive().optional(),
|
|
3423
|
+
asyncRewake: z29.boolean().optional()
|
|
3275
3424
|
}).passthrough();
|
|
3276
3425
|
var HookMatcherGroupSchema = z29.object({
|
|
3277
3426
|
matcher: z29.string().optional(),
|
|
@@ -3291,6 +3440,14 @@ var CLI_BIN = "quiel";
|
|
|
3291
3440
|
var HOOK_SPECS = [
|
|
3292
3441
|
{ event: "SessionStart", command: `${CLI_BIN} hook session-start` },
|
|
3293
3442
|
{ event: "Stop", command: `${CLI_BIN} hook stop`, timeout: 10 },
|
|
3443
|
+
// ВТОРАЯ КОМАНДА НА ТОМ ЖЕ СОБЫТИИ, И ОНА НЕ ДУБЛЬ. `hook stop` решает,
|
|
3444
|
+
// можно ли остановиться; эта — что будет, ПОКА агент стоит. Без неё
|
|
3445
|
+
// разрешённый простой означал бы молчание до прихода человека.
|
|
3446
|
+
//
|
|
3447
|
+
// Выходит мгновенно с кодом 0, если режим ручной, задача на руках или
|
|
3448
|
+
// другой ожидатель уже работает, — поэтому висит она только тогда,
|
|
3449
|
+
// когда агенту действительно нечего делать.
|
|
3450
|
+
{ event: "Stop", command: `${CLI_BIN} wait-for-task`, asyncRewake: true },
|
|
3294
3451
|
{
|
|
3295
3452
|
event: "StopFailure",
|
|
3296
3453
|
matcher: "rate_limit|overloaded",
|
|
@@ -3313,7 +3470,8 @@ function upsertHookSpec(groups, spec) {
|
|
|
3313
3470
|
const commandEntry = {
|
|
3314
3471
|
type: "command",
|
|
3315
3472
|
command: spec.command,
|
|
3316
|
-
...spec.timeout !== void 0 ? { timeout: spec.timeout } : {}
|
|
3473
|
+
...spec.timeout !== void 0 ? { timeout: spec.timeout } : {},
|
|
3474
|
+
...spec.asyncRewake !== void 0 ? { asyncRewake: spec.asyncRewake } : {}
|
|
3317
3475
|
};
|
|
3318
3476
|
const groupIndex = groups.findIndex((g) => sameMatcher(g.matcher, spec.matcher));
|
|
3319
3477
|
if (groupIndex === -1) {
|
|
@@ -3423,7 +3581,22 @@ var CurrentTaskSchema = z30.object({
|
|
|
3423
3581
|
* Необязательно: состояния, записанные прежними версиями клиента, поля
|
|
3424
3582
|
* не имеют — тогда `submit_work` работает как раньше, в режиме DIRECT.
|
|
3425
3583
|
*/
|
|
3426
|
-
gitMode: z30.enum([GitMode.DIRECT, GitMode.PR]).optional()
|
|
3584
|
+
gitMode: z30.enum([GitMode.DIRECT, GitMode.PR]).optional(),
|
|
3585
|
+
/**
|
|
3586
|
+
* Имя ветки задачи и префикс сообщения коммита — как их посчитал
|
|
3587
|
+
* СЕРВЕР (`project.branchName`, `project.commitPrefix`, §10.3 ТЗ).
|
|
3588
|
+
*
|
|
3589
|
+
* Добавлены для спасения незаконченной работы (LIM-01): когда агент
|
|
3590
|
+
* упирается в лимит подписки, коммитить и отправлять надо уже без
|
|
3591
|
+
* него — он в этот момент ничего не может. Значит и ветку, и префикс
|
|
3592
|
+
* надо помнить заранее, а не спрашивать в момент прерывания.
|
|
3593
|
+
*
|
|
3594
|
+
* Необязательны: у состояний, записанных прежними версиями клиента,
|
|
3595
|
+
* полей нет — тогда работа спасается только коммитом (без push) или
|
|
3596
|
+
* не спасается вовсе, и об этом честно сообщается.
|
|
3597
|
+
*/
|
|
3598
|
+
branchName: z30.string().min(1).optional(),
|
|
3599
|
+
commitPrefix: z30.string().min(1).optional()
|
|
3427
3600
|
});
|
|
3428
3601
|
var TrustCacheSchema = z30.object({
|
|
3429
3602
|
profileKey: z30.string().min(1),
|
|
@@ -3465,6 +3638,30 @@ var ClientStateSchema = z30.object({
|
|
|
3465
3638
|
tasksCompletedInSession: z30.number().int().nonnegative().default(0),
|
|
3466
3639
|
/** Кэш `ProjectSettings.contextResetAfterTasks` (§7.2 ТЗ) — сколько задач подряд можно сдать, прежде чем `Stop`-хук попросит `/clear`. */
|
|
3467
3640
|
contextResetAfterTasks: z30.number().int().positive().default(DEFAULT_CONTEXT_RESET_AFTER_TASKS),
|
|
3641
|
+
/**
|
|
3642
|
+
* Идентификаторы задач, сданных в этой сессии, — а не одно число.
|
|
3643
|
+
*
|
|
3644
|
+
* Счётчик `tasksCompletedInSession` увеличивается на КАЖДУЮ успешную
|
|
3645
|
+
* сдачу, и задача, возвращённая с ревью и сданная повторно, считалась
|
|
3646
|
+
* второй. Владелец увидел «Сессия выполнила 9 задач» при лимите 8 и
|
|
3647
|
+
* справедливо решил, что счётчик сломан: он не сломан, он считает
|
|
3648
|
+
* сдачи, а сообщение называет их задачами.
|
|
3649
|
+
*
|
|
3650
|
+
* Храним ключи, чтобы говорить правду в обоих числах.
|
|
3651
|
+
*/
|
|
3652
|
+
completedTaskIds: z30.array(z30.string()).default([]),
|
|
3653
|
+
/**
|
|
3654
|
+
* Когда `next_task` в последний раз ответил «задач нет».
|
|
3655
|
+
*
|
|
3656
|
+
* БЕЗ ЭТОГО ПОЛЯ `Stop`-ХУК НЕ ОТЛИЧАЕТ ПУСТУЮ ОЧЕРЕДЬ ОТ ЛЕНИ. Он
|
|
3657
|
+
* безусловно требовал «вызови next_task», и при пустой очереди агент
|
|
3658
|
+
* крутил этот цикл часами: в боевом проекте очередь пустовала по
|
|
3659
|
+
* 15–25 минут подряд, и каждый круг тратил ход модели впустую.
|
|
3660
|
+
*
|
|
3661
|
+
* `null` — очередь не была пуста с начала сессии либо последняя
|
|
3662
|
+
* попытка что-то принесла.
|
|
3663
|
+
*/
|
|
3664
|
+
queueEmptyAt: z30.string().nullable().default(null),
|
|
3468
3665
|
/** Кэш стоп-паттернов/правил доверия для `PreToolUse` фазы 4. */
|
|
3469
3666
|
trust: TrustCacheSchema.default(null),
|
|
3470
3667
|
/** Снимок задачи на момент последнего `PreCompact` — пишет src/hooks/pre-compact.ts, читает SessionStart(source="compact"), чтобы напомнить модели, чем она занималась. */
|
|
@@ -4175,7 +4372,14 @@ var createSocket = (url, options) => io(url, {
|
|
|
4175
4372
|
// src/hooks/audit-sender.ts
|
|
4176
4373
|
var DEFAULT_CONNECT_TIMEOUT_MS = 3e3;
|
|
4177
4374
|
var DEFAULT_FLUSH_GRACE_MS = 150;
|
|
4178
|
-
var WIRE_EVENTS = /* @__PURE__ */ new Set([
|
|
4375
|
+
var WIRE_EVENTS = /* @__PURE__ */ new Set([
|
|
4376
|
+
"audit:command",
|
|
4377
|
+
"task:usage",
|
|
4378
|
+
"session-limit-reached",
|
|
4379
|
+
"agent:rate_limited",
|
|
4380
|
+
// LIM-02: о спасённой ветке сервер узнаёт отсюда же.
|
|
4381
|
+
"task:work_saved"
|
|
4382
|
+
]);
|
|
4179
4383
|
function sendHookActions(actions, options = {}) {
|
|
4180
4384
|
const wireActions = actions.filter((action) => WIRE_EVENTS.has(action.type));
|
|
4181
4385
|
if (wireActions.length === 0) return;
|
|
@@ -4360,1297 +4564,1528 @@ function renderHookOutput(format, hook, output) {
|
|
|
4360
4564
|
return { stdout: "{}", stderr: "", exitCode: 0 };
|
|
4361
4565
|
}
|
|
4362
4566
|
|
|
4363
|
-
// src/
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4567
|
+
// src/git/exec.ts
|
|
4568
|
+
import { execFile } from "child_process";
|
|
4569
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
4570
|
+
var MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
4571
|
+
function runGit(args, options) {
|
|
4572
|
+
return new Promise((resolvePromise) => {
|
|
4573
|
+
execFile(
|
|
4574
|
+
"git",
|
|
4575
|
+
[...args],
|
|
4576
|
+
{
|
|
4577
|
+
cwd: options.cwd,
|
|
4578
|
+
shell: false,
|
|
4579
|
+
timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
4580
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
4581
|
+
env: {
|
|
4582
|
+
...process.env,
|
|
4583
|
+
...options.env,
|
|
4584
|
+
// Без человека за терминалом: если git вдруг решит спросить
|
|
4585
|
+
// логин/пароль, зависший prompt выглядел бы как неожиданное
|
|
4586
|
+
// поведение (требование подзадачи: «любая неожиданность —
|
|
4587
|
+
// понятный отказ, а не попробую починить»), а не как честный
|
|
4588
|
+
// отказ команды с кодом возврата.
|
|
4589
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
4590
|
+
}
|
|
4591
|
+
},
|
|
4592
|
+
(error, stdout, stderr) => {
|
|
4593
|
+
if (!error) {
|
|
4594
|
+
resolvePromise({ ok: true, code: 0, signal: null, stdout, stderr });
|
|
4595
|
+
return;
|
|
4596
|
+
}
|
|
4597
|
+
const code = typeof error.code === "number" ? error.code : null;
|
|
4598
|
+
resolvePromise({
|
|
4599
|
+
ok: false,
|
|
4600
|
+
code,
|
|
4601
|
+
signal: error.signal ?? null,
|
|
4602
|
+
stdout,
|
|
4603
|
+
// Если git ничего не написал в stderr (например, сам процесс не
|
|
4604
|
+
// запустился — ENOENT), берём текст ошибки Node, чтобы вызывающий
|
|
4605
|
+
// код не остался с пустым сообщением.
|
|
4606
|
+
stderr: stderr.length > 0 ? stderr : error.message
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
);
|
|
4610
|
+
});
|
|
4368
4611
|
}
|
|
4369
|
-
function
|
|
4370
|
-
|
|
4612
|
+
function describeGitFailure(commandLabel, result) {
|
|
4613
|
+
const stderr = result.stderr.trim();
|
|
4614
|
+
const codeText = result.code === null ? "" : ` (\u043A\u043E\u0434 ${result.code})`;
|
|
4615
|
+
return stderr.length > 0 ? `${commandLabel}${codeText}: ${stderr}` : `${commandLabel} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439${codeText}`;
|
|
4371
4616
|
}
|
|
4372
4617
|
|
|
4373
|
-
// src/
|
|
4374
|
-
function
|
|
4375
|
-
|
|
4376
|
-
const maxMs = options.maxMs ?? 3e4;
|
|
4377
|
-
const safeAttempt = Math.max(0, Math.trunc(attempt));
|
|
4378
|
-
const exponent = Math.min(safeAttempt, 30);
|
|
4379
|
-
const raw = baseMs * 2 ** exponent;
|
|
4380
|
-
return Math.min(raw, maxMs);
|
|
4618
|
+
// src/git/result.ts
|
|
4619
|
+
function gitOk(data) {
|
|
4620
|
+
return { ok: true, data };
|
|
4381
4621
|
}
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
import { EventEmitter } from "events";
|
|
4385
|
-
|
|
4386
|
-
// src/socket/emit-with-timeout.ts
|
|
4387
|
-
function toError(raw, context) {
|
|
4388
|
-
return raw instanceof Error ? raw : new Error(`${context}: ${String(raw)}`);
|
|
4622
|
+
function gitErr(message) {
|
|
4623
|
+
return { ok: false, message };
|
|
4389
4624
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
4396
|
-
socket.emitWithAck(event, payload).then((value) => {
|
|
4397
|
-
clearTimeout(timer);
|
|
4398
|
-
resolve4(value);
|
|
4399
|
-
}).catch((err) => {
|
|
4400
|
-
clearTimeout(timer);
|
|
4401
|
-
reject(toError(err, event));
|
|
4402
|
-
});
|
|
4625
|
+
|
|
4626
|
+
// src/git/repo.ts
|
|
4627
|
+
async function localBranchExists(repoRoot, branch) {
|
|
4628
|
+
const result = await runGit(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
4629
|
+
cwd: repoRoot
|
|
4403
4630
|
});
|
|
4631
|
+
return result.ok;
|
|
4404
4632
|
}
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
};
|
|
4633
|
+
async function getHeadSha(worktreeDir) {
|
|
4634
|
+
const result = await runGit(["rev-parse", "HEAD"], { cwd: worktreeDir });
|
|
4635
|
+
if (!result.ok) {
|
|
4636
|
+
return gitErr(describeGitFailure("git rev-parse HEAD", result));
|
|
4637
|
+
}
|
|
4638
|
+
return gitOk(result.stdout.trim());
|
|
4639
|
+
}
|
|
4640
|
+
async function currentBranch(worktreeDir) {
|
|
4641
|
+
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreeDir });
|
|
4642
|
+
if (!result.ok) {
|
|
4643
|
+
return gitErr(describeGitFailure("git rev-parse --abbrev-ref HEAD", result));
|
|
4644
|
+
}
|
|
4645
|
+
return gitOk(result.stdout.trim());
|
|
4414
4646
|
}
|
|
4415
4647
|
|
|
4416
|
-
// src/
|
|
4417
|
-
|
|
4418
|
-
|
|
4648
|
+
// src/git/status.ts
|
|
4649
|
+
var CONFLICT_CODES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
4650
|
+
async function getWorktreeStatus(worktreeDir) {
|
|
4651
|
+
const result = await runGit(["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
4652
|
+
cwd: worktreeDir
|
|
4653
|
+
});
|
|
4654
|
+
if (!result.ok) {
|
|
4655
|
+
return gitErr(describeGitFailure("git status", result));
|
|
4656
|
+
}
|
|
4657
|
+
const changedPaths = [];
|
|
4658
|
+
const conflictedPaths = [];
|
|
4659
|
+
for (const line of result.stdout.split("\n")) {
|
|
4660
|
+
if (line.length === 0) continue;
|
|
4661
|
+
const code = line.slice(0, 2);
|
|
4662
|
+
const rawPath = line.slice(3);
|
|
4663
|
+
const arrowIndex = rawPath.indexOf(" -> ");
|
|
4664
|
+
const path = arrowIndex >= 0 ? rawPath.slice(arrowIndex + 4) : rawPath;
|
|
4665
|
+
changedPaths.push(path);
|
|
4666
|
+
if (CONFLICT_CODES.has(code)) conflictedPaths.push(path);
|
|
4667
|
+
}
|
|
4668
|
+
return gitOk({ clean: changedPaths.length === 0, changedPaths, conflictedPaths });
|
|
4419
4669
|
}
|
|
4420
4670
|
|
|
4421
|
-
// src/
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
return new Error(`${context}: ${String(raw)}`);
|
|
4671
|
+
// src/git/commit.ts
|
|
4672
|
+
function buildCommitMessage(params) {
|
|
4673
|
+
const subject = `${params.commitPrefix} ${params.title}`.trim();
|
|
4674
|
+
return `${subject}
|
|
4675
|
+
|
|
4676
|
+
${buildTaskTrailer(params.taskKey)}
|
|
4677
|
+
`;
|
|
4429
4678
|
}
|
|
4430
|
-
function
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
};
|
|
4679
|
+
async function commitAll(params) {
|
|
4680
|
+
const addResult = await runGit(["add", "-A"], { cwd: params.worktreeDir });
|
|
4681
|
+
if (!addResult.ok) {
|
|
4682
|
+
return gitErr(describeGitFailure("git add -A", addResult));
|
|
4683
|
+
}
|
|
4684
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4685
|
+
if (!status.ok) return status;
|
|
4686
|
+
if (status.data.clean) {
|
|
4687
|
+
return gitOk({ committed: false, sha: null });
|
|
4688
|
+
}
|
|
4689
|
+
const message = buildCommitMessage(params);
|
|
4690
|
+
const commitResult = await runGit(["commit", "-m", message], { cwd: params.worktreeDir });
|
|
4691
|
+
if (!commitResult.ok) {
|
|
4692
|
+
return gitErr(describeGitFailure("git commit", commitResult));
|
|
4693
|
+
}
|
|
4694
|
+
const sha = await getHeadSha(params.worktreeDir);
|
|
4695
|
+
return gitOk({ committed: true, sha: sha.ok ? sha.data : null });
|
|
4448
4696
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
auth: { token: options.token },
|
|
4468
|
-
autoConnect: false,
|
|
4469
|
-
reconnection: false
|
|
4470
|
-
});
|
|
4471
|
-
this.registerSocketListeners();
|
|
4472
|
-
}
|
|
4473
|
-
// -- Публичное API -----------------------------------------------------
|
|
4474
|
-
on(event, listener) {
|
|
4475
|
-
return super.on(event, listener);
|
|
4476
|
-
}
|
|
4477
|
-
once(event, listener) {
|
|
4478
|
-
return super.once(event, listener);
|
|
4697
|
+
|
|
4698
|
+
// src/git/branch-name.ts
|
|
4699
|
+
var FORBIDDEN_CHARS = /[\s~^:?*[\\\x00-\x1f\x7f]/;
|
|
4700
|
+
function isValidGitBranchName(name) {
|
|
4701
|
+
if (name.length === 0 || name.length > 255) return false;
|
|
4702
|
+
if (name.startsWith("-")) return false;
|
|
4703
|
+
if (name.startsWith("/") || name.endsWith("/")) return false;
|
|
4704
|
+
if (name.endsWith(".")) return false;
|
|
4705
|
+
if (name.includes("..")) return false;
|
|
4706
|
+
if (name.includes("//")) return false;
|
|
4707
|
+
if (name.includes("@{")) return false;
|
|
4708
|
+
if (name === "@") return false;
|
|
4709
|
+
if (FORBIDDEN_CHARS.test(name)) return false;
|
|
4710
|
+
const segments = name.split("/");
|
|
4711
|
+
for (const segment of segments) {
|
|
4712
|
+
if (segment.length === 0) return false;
|
|
4713
|
+
if (segment.startsWith(".")) return false;
|
|
4714
|
+
if (segment.endsWith(".lock")) return false;
|
|
4479
4715
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4716
|
+
return true;
|
|
4717
|
+
}
|
|
4718
|
+
function requireValidBranchName(name, label) {
|
|
4719
|
+
if (!isValidGitBranchName(name)) {
|
|
4720
|
+
return gitErr(
|
|
4721
|
+
`${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.`
|
|
4722
|
+
);
|
|
4482
4723
|
}
|
|
4483
|
-
|
|
4484
|
-
|
|
4724
|
+
return gitOk(name);
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
// src/git/worktree.ts
|
|
4728
|
+
import { createHash as createHash2 } from "crypto";
|
|
4729
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, realpathSync } from "fs";
|
|
4730
|
+
import { dirname as dirname4, isAbsolute, join as join9, relative, resolve as resolve2 } from "path";
|
|
4731
|
+
var WORKTREES_RELATIVE_ROOT = join9(".claude", "worktrees");
|
|
4732
|
+
function getDefaultWorktreesRoot(repoRoot) {
|
|
4733
|
+
return join9(repoRoot, WORKTREES_RELATIVE_ROOT);
|
|
4734
|
+
}
|
|
4735
|
+
function resolveWorktreeDir(worktreesRoot, repoRoot, branchName) {
|
|
4736
|
+
if (isInsideRepo(worktreesRoot, repoRoot)) return join9(worktreesRoot, branchName);
|
|
4737
|
+
const repoHash = createHash2("sha1").update(resolve2(repoRoot)).digest("hex").slice(0, 12);
|
|
4738
|
+
return join9(worktreesRoot, repoHash, branchName);
|
|
4739
|
+
}
|
|
4740
|
+
function isInsideRepo(candidate, repoRoot) {
|
|
4741
|
+
const rel = relative(resolve2(repoRoot), resolve2(candidate));
|
|
4742
|
+
return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
4743
|
+
}
|
|
4744
|
+
function canonicalPath(path) {
|
|
4745
|
+
try {
|
|
4746
|
+
return realpathSync(path);
|
|
4747
|
+
} catch {
|
|
4748
|
+
return resolve2(path);
|
|
4485
4749
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
this.startHeartbeatTimer();
|
|
4492
|
-
this.socket.connect();
|
|
4750
|
+
}
|
|
4751
|
+
async function listWorktrees(repoRoot) {
|
|
4752
|
+
const result = await runGit(["worktree", "list", "--porcelain"], { cwd: repoRoot });
|
|
4753
|
+
if (!result.ok) {
|
|
4754
|
+
return gitErr(describeGitFailure("git worktree list", result));
|
|
4493
4755
|
}
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4756
|
+
const entries = [];
|
|
4757
|
+
let path;
|
|
4758
|
+
let branch = null;
|
|
4759
|
+
const flush = () => {
|
|
4760
|
+
if (path) entries.push({ path, branch });
|
|
4761
|
+
path = void 0;
|
|
4762
|
+
branch = null;
|
|
4763
|
+
};
|
|
4764
|
+
for (const line of result.stdout.split("\n")) {
|
|
4765
|
+
if (line.length === 0) {
|
|
4766
|
+
flush();
|
|
4767
|
+
continue;
|
|
4500
4768
|
}
|
|
4501
|
-
|
|
4502
|
-
|
|
4769
|
+
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
4770
|
+
else if (line.startsWith("branch refs/heads/")) branch = line.slice("branch refs/heads/".length);
|
|
4503
4771
|
}
|
|
4504
|
-
|
|
4505
|
-
|
|
4772
|
+
flush();
|
|
4773
|
+
return gitOk(entries);
|
|
4774
|
+
}
|
|
4775
|
+
async function ensureWorktree(params) {
|
|
4776
|
+
const remote = params.remote ?? "origin";
|
|
4777
|
+
const branchCheck = requireValidBranchName(params.branchName, "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438");
|
|
4778
|
+
if (!branchCheck.ok) return branchCheck;
|
|
4779
|
+
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
4780
|
+
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
4781
|
+
const worktreesRoot = params.worktreesRoot ?? getDefaultWorktreesRoot(params.repoRoot);
|
|
4782
|
+
const worktreeDir = resolveWorktreeDir(worktreesRoot, params.repoRoot, params.branchName);
|
|
4783
|
+
const list = await listWorktrees(params.repoRoot);
|
|
4784
|
+
if (!list.ok) return list;
|
|
4785
|
+
const existing = list.data.find((wt) => canonicalPath(wt.path) === canonicalPath(worktreeDir));
|
|
4786
|
+
if (existing) {
|
|
4787
|
+
if (existing.branch === params.branchName) {
|
|
4788
|
+
return gitOk({ worktreeDir, reused: true });
|
|
4789
|
+
}
|
|
4790
|
+
return gitErr(
|
|
4791
|
+
`\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.`
|
|
4792
|
+
);
|
|
4506
4793
|
}
|
|
4507
|
-
|
|
4508
|
-
return
|
|
4794
|
+
if (existsSync9(worktreeDir)) {
|
|
4795
|
+
return gitErr(
|
|
4796
|
+
`\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.`
|
|
4797
|
+
);
|
|
4509
4798
|
}
|
|
4510
|
-
|
|
4511
|
-
|
|
4799
|
+
await runGit(["worktree", "prune"], { cwd: params.repoRoot });
|
|
4800
|
+
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
4801
|
+
cwd: params.repoRoot
|
|
4802
|
+
});
|
|
4803
|
+
if (!fetchResult.ok) {
|
|
4804
|
+
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
4512
4805
|
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4806
|
+
let startPoint = `${remote}/${params.defaultBranch}`;
|
|
4807
|
+
if (params.continueFromBranch) {
|
|
4808
|
+
const continued = await runGit(["fetch", remote, params.continueFromBranch], {
|
|
4809
|
+
cwd: params.repoRoot
|
|
4810
|
+
});
|
|
4811
|
+
if (continued.ok) startPoint = `${remote}/${params.continueFromBranch}`;
|
|
4515
4812
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4813
|
+
mkdirSync6(dirname4(worktreeDir), { recursive: true });
|
|
4814
|
+
const branchExists = await localBranchExists(params.repoRoot, params.branchName);
|
|
4815
|
+
const addArgs = branchExists ? ["worktree", "add", worktreeDir, params.branchName] : ["worktree", "add", "-b", params.branchName, worktreeDir, startPoint];
|
|
4816
|
+
const addResult = await runGit(addArgs, { cwd: params.repoRoot });
|
|
4817
|
+
if (!addResult.ok) {
|
|
4818
|
+
return gitErr(describeGitFailure("git worktree add", addResult));
|
|
4520
4819
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
* есть ВСЁ, что не `agent:hello`/`agent:heartbeat`/`agent:mode` (у
|
|
4528
|
-
* первых двух своя логика согласования состояния в этом классе, третий
|
|
4529
|
-
* — короткоживущий отдельный сокет, см. `send-agent-mode.ts`). Нарочно
|
|
4530
|
-
* НЕ отдельное соединение: `task:claim` — блокирующий долгий long-poll,
|
|
4531
|
-
* и держать его на том же сокете, где уже идёт heartbeat/presence этого
|
|
4532
|
-
* агента, а не поднимать вторую параллельную сессию — единственный
|
|
4533
|
-
* вариант, не удваивающий presence на сервере.
|
|
4534
|
-
*/
|
|
4535
|
-
callServer(event, payload, timeoutMs = this.opts.ackTimeoutMs) {
|
|
4536
|
-
return emitWithAckTimeout(this.socket, event, payload, timeoutMs);
|
|
4820
|
+
return gitOk({ worktreeDir, reused: false });
|
|
4821
|
+
}
|
|
4822
|
+
async function removeWorktree(repoRoot, worktreeDir) {
|
|
4823
|
+
const result = await runGit(["worktree", "remove", worktreeDir], { cwd: repoRoot });
|
|
4824
|
+
if (!result.ok) {
|
|
4825
|
+
return gitErr(describeGitFailure("git worktree remove", result));
|
|
4537
4826
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
this.off("connected", onConnected);
|
|
4552
|
-
resolve4({ ok: true, ack });
|
|
4553
|
-
};
|
|
4554
|
-
const onAuthRejected = (message) => {
|
|
4555
|
-
if (settled) return;
|
|
4556
|
-
settled = true;
|
|
4557
|
-
clearTimeout(timer);
|
|
4558
|
-
this.off("connected", onConnected);
|
|
4559
|
-
this.off("auth-rejected", onAuthRejected);
|
|
4560
|
-
resolve4({ ok: false, message });
|
|
4561
|
-
};
|
|
4562
|
-
const timer = setTimeout(() => {
|
|
4563
|
-
if (settled) return;
|
|
4564
|
-
settled = true;
|
|
4565
|
-
this.off("connected", onConnected);
|
|
4566
|
-
this.off("auth-rejected", onAuthRejected);
|
|
4567
|
-
resolve4({
|
|
4568
|
-
ok: false,
|
|
4569
|
-
message: `\u041D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430 agent:hello \u0437\u0430 ${timeoutMs} \u043C\u0441 \u2014 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430. \u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0432 \u0444\u043E\u043D\u0435 (backoff \xA711.4 \u0422\u0417).`
|
|
4570
|
-
});
|
|
4571
|
-
}, timeoutMs);
|
|
4572
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
4573
|
-
this.on("connected", onConnected);
|
|
4574
|
-
this.on("auth-rejected", onAuthRejected);
|
|
4575
|
-
});
|
|
4827
|
+
return gitOk(void 0);
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
// src/git/submit.ts
|
|
4831
|
+
async function rebaseOntoDefault(params) {
|
|
4832
|
+
const remote = params.remote ?? "origin";
|
|
4833
|
+
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
4834
|
+
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
4835
|
+
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
4836
|
+
cwd: params.worktreeDir
|
|
4837
|
+
});
|
|
4838
|
+
if (!fetchResult.ok) {
|
|
4839
|
+
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
4576
4840
|
}
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
this.socket.on("lease:lost", (raw) => this.handleLeaseLost(raw));
|
|
4583
|
-
this.socket.on("mode:set", (raw) => this.handleModeSet(raw));
|
|
4584
|
-
this.socket.on("agent:pause", (raw) => this.handleAgentPause(raw));
|
|
4585
|
-
this.socket.on("trust:updated", (raw) => this.handleTrustUpdated(raw));
|
|
4586
|
-
this.socket.on("task:updated", (raw) => this.handleTaskUpdated(raw));
|
|
4587
|
-
this.socket.on("question:answered", (raw) => this.handleQuestionAnswered(raw));
|
|
4588
|
-
this.socket.on("approval:decided", (raw) => this.handleApprovalDecided(raw));
|
|
4841
|
+
const rebaseResult = await runGit(["rebase", `${remote}/${params.defaultBranch}`], {
|
|
4842
|
+
cwd: params.worktreeDir
|
|
4843
|
+
});
|
|
4844
|
+
if (rebaseResult.ok) {
|
|
4845
|
+
return gitOk({ outcome: "rebased", conflictedFiles: [] });
|
|
4589
4846
|
}
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
this.stop();
|
|
4598
|
-
this.emit("auth-rejected", describeAuthRejection(this.agentId));
|
|
4599
|
-
return;
|
|
4600
|
-
}
|
|
4601
|
-
this.emit("client-error", toError2(err, "connect_error"));
|
|
4602
|
-
this.scheduleReconnect();
|
|
4603
|
-
}
|
|
4604
|
-
handleDisconnect(reason) {
|
|
4605
|
-
this.emit("disconnected", reason);
|
|
4606
|
-
if (this.stopped) return;
|
|
4607
|
-
this.scheduleReconnect();
|
|
4608
|
-
}
|
|
4609
|
-
scheduleReconnect() {
|
|
4610
|
-
if (this.stopped || this.reconnectTimer) return;
|
|
4611
|
-
const delayMs = nextReconnectDelayMs(this.reconnectAttempt, {
|
|
4612
|
-
baseMs: this.opts.backoffBaseMs,
|
|
4613
|
-
maxMs: this.opts.backoffMaxMs
|
|
4614
|
-
});
|
|
4615
|
-
this.reconnectAttempt += 1;
|
|
4616
|
-
this.emit("reconnecting", { attempt: this.reconnectAttempt, delayMs });
|
|
4617
|
-
this.reconnectTimer = setTimeout(() => {
|
|
4618
|
-
this.reconnectTimer = void 0;
|
|
4619
|
-
if (this.stopped) return;
|
|
4620
|
-
this.socket.connect();
|
|
4621
|
-
}, delayMs);
|
|
4622
|
-
}
|
|
4623
|
-
// -- agent:hello ---------------------------------------------------------
|
|
4624
|
-
async sendHello() {
|
|
4625
|
-
const state = readState(this.opts.stateFilePath);
|
|
4626
|
-
const payload = agentHelloPayloadSchema.parse({
|
|
4627
|
-
clientVersion: this.opts.clientVersion,
|
|
4628
|
-
cwd: this.opts.cwd,
|
|
4629
|
-
os: this.opts.os,
|
|
4630
|
-
agentKind: this.opts.agentKind,
|
|
4631
|
-
agentVersion: this.opts.agentVersion,
|
|
4632
|
-
// `claudeVersion` шлётся ТОЛЬКО для Claude Code — и только чтобы
|
|
4633
|
-
// клиент работал против ещё не обновлённого сервера, который знает
|
|
4634
|
-
// одно это поле. Для Codex поле с таким именем было бы неправдой.
|
|
4635
|
-
...this.opts.agentKind === AgentKind.CLAUDE_CODE ? { claudeVersion: this.opts.agentVersion } : {},
|
|
4636
|
-
roleKeys: [...this.opts.roleKeys],
|
|
4637
|
-
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
|
|
4638
|
-
});
|
|
4639
|
-
let raw;
|
|
4640
|
-
try {
|
|
4641
|
-
raw = await emitWithAckTimeout(this.socket, "agent:hello", payload, this.opts.ackTimeoutMs);
|
|
4642
|
-
} catch (err) {
|
|
4643
|
-
this.emit("client-error", toError2(err, "agent:hello"));
|
|
4644
|
-
return;
|
|
4645
|
-
}
|
|
4646
|
-
if (this.stopped) return;
|
|
4647
|
-
if (isAckError(raw)) {
|
|
4648
|
-
this.emit("client-error", new Error(`agent:hello: ${raw.error.code}: ${raw.error.message}`));
|
|
4649
|
-
return;
|
|
4650
|
-
}
|
|
4651
|
-
const parsed = agentHelloAckSchema.safeParse(raw);
|
|
4652
|
-
if (!parsed.success) {
|
|
4653
|
-
this.emit(
|
|
4654
|
-
"client-error",
|
|
4655
|
-
new Error(`agent:hello: \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}`)
|
|
4847
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4848
|
+
if (status.ok && status.data.conflictedPaths.length > 0) {
|
|
4849
|
+
const conflictedFiles = status.data.conflictedPaths;
|
|
4850
|
+
const abortResult = await runGit(["rebase", "--abort"], { cwd: params.worktreeDir });
|
|
4851
|
+
if (!abortResult.ok) {
|
|
4852
|
+
return gitErr(
|
|
4853
|
+
`\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.`
|
|
4656
4854
|
);
|
|
4657
|
-
return;
|
|
4658
4855
|
}
|
|
4659
|
-
|
|
4660
|
-
this.lastHelloAck = ack;
|
|
4661
|
-
this.paused = false;
|
|
4662
|
-
this.pauseReasonText = void 0;
|
|
4663
|
-
this.applyHelloAck(ack);
|
|
4664
|
-
this.emit("connected", ack);
|
|
4856
|
+
return gitOk({ outcome: "conflict", conflictedFiles });
|
|
4665
4857
|
}
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
}
|
|
4676
|
-
updateState(patch, this.opts.stateFilePath);
|
|
4858
|
+
return gitErr(describeGitFailure("git rebase", rebaseResult));
|
|
4859
|
+
}
|
|
4860
|
+
async function assertReadyToPush(params) {
|
|
4861
|
+
const branch = await currentBranch(params.worktreeDir);
|
|
4862
|
+
if (!branch.ok) return branch;
|
|
4863
|
+
if (branch.data !== params.branchName) {
|
|
4864
|
+
return gitErr(
|
|
4865
|
+
`\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.`
|
|
4866
|
+
);
|
|
4677
4867
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4868
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
4869
|
+
if (!status.ok) return status;
|
|
4870
|
+
if (!status.data.clean) {
|
|
4871
|
+
return gitErr(
|
|
4872
|
+
`\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.`
|
|
4873
|
+
);
|
|
4684
4874
|
}
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4875
|
+
return gitOk(void 0);
|
|
4876
|
+
}
|
|
4877
|
+
async function pushFastForward(params) {
|
|
4878
|
+
const remote = params.remote ?? "origin";
|
|
4879
|
+
const branchCheck = requireValidBranchName(params.branchName, "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438");
|
|
4880
|
+
if (!branchCheck.ok) return branchCheck;
|
|
4881
|
+
const defaultBranchCheck = requireValidBranchName(params.defaultBranch, "\u0418\u043C\u044F \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438");
|
|
4882
|
+
if (!defaultBranchCheck.ok) return defaultBranchCheck;
|
|
4883
|
+
const ready = await assertReadyToPush(params);
|
|
4884
|
+
if (!ready.ok) return ready;
|
|
4885
|
+
const pushResult = await runGit(["push", remote, `HEAD:refs/heads/${params.defaultBranch}`], {
|
|
4886
|
+
cwd: params.worktreeDir
|
|
4887
|
+
});
|
|
4888
|
+
if (!pushResult.ok) {
|
|
4889
|
+
return gitErr(
|
|
4890
|
+
describeGitFailure(`git push ${remote} HEAD:refs/heads/${params.defaultBranch}`, pushResult)
|
|
4891
|
+
);
|
|
4689
4892
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4893
|
+
return gitOk({ pushedTo: `${remote}/${params.defaultBranch}` });
|
|
4894
|
+
}
|
|
4895
|
+
async function submitDirectTask(params) {
|
|
4896
|
+
const remote = params.remote ?? "origin";
|
|
4897
|
+
let worktreeDir;
|
|
4898
|
+
if (params.worktreeDir) {
|
|
4899
|
+
worktreeDir = params.worktreeDir;
|
|
4900
|
+
} else {
|
|
4901
|
+
const worktree = await ensureWorktree({
|
|
4902
|
+
repoRoot: params.repoRoot,
|
|
4903
|
+
branchName: params.branchName,
|
|
4904
|
+
defaultBranch: params.defaultBranch,
|
|
4905
|
+
remote,
|
|
4906
|
+
worktreesRoot: params.worktreesRoot
|
|
4697
4907
|
});
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
raw = await emitWithAckTimeout(
|
|
4701
|
-
this.socket,
|
|
4702
|
-
"agent:heartbeat",
|
|
4703
|
-
payload,
|
|
4704
|
-
this.opts.ackTimeoutMs
|
|
4705
|
-
);
|
|
4706
|
-
} catch (err) {
|
|
4707
|
-
this.emit("client-error", toError2(err, "agent:heartbeat"));
|
|
4708
|
-
return;
|
|
4709
|
-
}
|
|
4710
|
-
if (isAckError(raw)) {
|
|
4711
|
-
this.emit(
|
|
4712
|
-
"client-error",
|
|
4713
|
-
new Error(`agent:heartbeat: ${raw.error.code}: ${raw.error.message}`)
|
|
4714
|
-
);
|
|
4715
|
-
return;
|
|
4716
|
-
}
|
|
4717
|
-
const parsed = agentHeartbeatAckSchema.safeParse(raw);
|
|
4718
|
-
if (!parsed.success) {
|
|
4719
|
-
this.emit(
|
|
4720
|
-
"client-error",
|
|
4721
|
-
new Error(`agent:heartbeat: \u043D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 ack: ${parsed.error.message}`)
|
|
4722
|
-
);
|
|
4723
|
-
return;
|
|
4724
|
-
}
|
|
4725
|
-
this.emit("heartbeat", parsed.data);
|
|
4908
|
+
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
4909
|
+
worktreeDir = worktree.data.worktreeDir;
|
|
4726
4910
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4911
|
+
const commit = await commitAll({
|
|
4912
|
+
worktreeDir,
|
|
4913
|
+
commitPrefix: params.commitPrefix,
|
|
4914
|
+
title: params.taskTitle,
|
|
4915
|
+
taskKey: params.taskKey
|
|
4916
|
+
});
|
|
4917
|
+
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
4918
|
+
const rebase = await rebaseOntoDefault({
|
|
4919
|
+
worktreeDir,
|
|
4920
|
+
defaultBranch: params.defaultBranch,
|
|
4921
|
+
remote
|
|
4922
|
+
});
|
|
4923
|
+
if (!rebase.ok) return { status: "refused", worktreeDir, message: rebase.message };
|
|
4924
|
+
if (rebase.data.outcome === "conflict") {
|
|
4925
|
+
const files = rebase.data.conflictedFiles;
|
|
4926
|
+
return {
|
|
4927
|
+
status: "conflict",
|
|
4928
|
+
worktreeDir,
|
|
4929
|
+
conflictedFiles: files,
|
|
4930
|
+
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.`
|
|
4931
|
+
};
|
|
4744
4932
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4933
|
+
if (params.requireTestsPassed && !params.testsPassed) {
|
|
4934
|
+
return {
|
|
4935
|
+
status: "refused",
|
|
4936
|
+
worktreeDir,
|
|
4937
|
+
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."
|
|
4938
|
+
};
|
|
4939
|
+
}
|
|
4940
|
+
const push = await pushFastForward({
|
|
4941
|
+
worktreeDir,
|
|
4942
|
+
branchName: params.branchName,
|
|
4943
|
+
defaultBranch: params.defaultBranch,
|
|
4944
|
+
remote
|
|
4945
|
+
});
|
|
4946
|
+
if (!push.ok) return { status: "refused", worktreeDir, message: push.message };
|
|
4947
|
+
const sha = await getHeadSha(worktreeDir);
|
|
4948
|
+
let worktreeRemoved = false;
|
|
4949
|
+
if (params.removeWorktreeOnSuccess !== false) {
|
|
4950
|
+
const removed = await removeWorktree(params.repoRoot, worktreeDir);
|
|
4951
|
+
if (removed.ok) {
|
|
4952
|
+
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
4953
|
+
worktreeRemoved = true;
|
|
4750
4954
|
}
|
|
4751
|
-
updateState({ mode: parsed.data.mode }, this.opts.stateFilePath);
|
|
4752
|
-
this.emit("mode:set", parsed.data);
|
|
4753
4955
|
}
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4956
|
+
return {
|
|
4957
|
+
status: "pushed",
|
|
4958
|
+
worktreeDir,
|
|
4959
|
+
commitSha: sha.ok ? sha.data : null,
|
|
4960
|
+
pushedTo: push.data.pushedTo,
|
|
4961
|
+
worktreeRemoved
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4965
|
+
// src/git/submit-pr.ts
|
|
4966
|
+
async function pushTaskBranch(params) {
|
|
4967
|
+
const remote = params.remote ?? "origin";
|
|
4968
|
+
const branchCheck = requireValidBranchName(params.branchName, "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438");
|
|
4969
|
+
if (!branchCheck.ok) return branchCheck;
|
|
4970
|
+
const ready = await assertReadyToPush(params);
|
|
4971
|
+
if (!ready.ok) return ready;
|
|
4972
|
+
const pushResult = await runGit(
|
|
4973
|
+
["push", "--set-upstream", remote, `HEAD:refs/heads/${params.branchName}`],
|
|
4974
|
+
{ cwd: params.worktreeDir }
|
|
4975
|
+
);
|
|
4976
|
+
if (!pushResult.ok) {
|
|
4977
|
+
return gitErr(
|
|
4978
|
+
describeGitFailure(`git push ${remote} HEAD:refs/heads/${params.branchName}`, pushResult)
|
|
4979
|
+
);
|
|
4766
4980
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4981
|
+
return gitOk({ pushedTo: `${remote}/${params.branchName}` });
|
|
4982
|
+
}
|
|
4983
|
+
async function submitPullRequestTask(params) {
|
|
4984
|
+
const remote = params.remote ?? "origin";
|
|
4985
|
+
let worktreeDir;
|
|
4986
|
+
if (params.worktreeDir) {
|
|
4987
|
+
worktreeDir = params.worktreeDir;
|
|
4988
|
+
} else {
|
|
4989
|
+
const worktree = await ensureWorktree({
|
|
4990
|
+
repoRoot: params.repoRoot,
|
|
4991
|
+
branchName: params.branchName,
|
|
4992
|
+
defaultBranch: params.defaultBranch,
|
|
4993
|
+
remote,
|
|
4994
|
+
worktreesRoot: params.worktreesRoot
|
|
4995
|
+
});
|
|
4996
|
+
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
4997
|
+
worktreeDir = worktree.data.worktreeDir;
|
|
4998
|
+
}
|
|
4999
|
+
const commit = await commitAll({
|
|
5000
|
+
worktreeDir,
|
|
5001
|
+
commitPrefix: params.commitPrefix,
|
|
5002
|
+
title: params.taskTitle,
|
|
5003
|
+
taskKey: params.taskKey
|
|
5004
|
+
});
|
|
5005
|
+
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5006
|
+
const rebase = await rebaseOntoDefault({
|
|
5007
|
+
worktreeDir,
|
|
5008
|
+
defaultBranch: params.defaultBranch,
|
|
5009
|
+
remote
|
|
5010
|
+
});
|
|
5011
|
+
if (!rebase.ok) return { status: "refused", worktreeDir, message: rebase.message };
|
|
5012
|
+
const conflicted = rebase.data.outcome === "conflict";
|
|
5013
|
+
const push = await pushTaskBranch({ worktreeDir, branchName: params.branchName, remote });
|
|
5014
|
+
if (!push.ok) return { status: "refused", worktreeDir, message: push.message };
|
|
5015
|
+
const sha = await getHeadSha(worktreeDir);
|
|
5016
|
+
let worktreeRemoved = false;
|
|
5017
|
+
if (params.removeWorktreeOnSuccess !== false) {
|
|
5018
|
+
const removed = await removeWorktree(params.repoRoot, worktreeDir);
|
|
5019
|
+
if (removed.ok) {
|
|
5020
|
+
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
5021
|
+
worktreeRemoved = true;
|
|
4775
5022
|
}
|
|
4776
|
-
updateState({ trust: toTrustCache(parsed.data, this.opts.now) }, this.opts.stateFilePath);
|
|
4777
|
-
this.emit("trust:updated", parsed.data);
|
|
4778
5023
|
}
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
5024
|
+
return {
|
|
5025
|
+
status: "pushed",
|
|
5026
|
+
worktreeDir,
|
|
5027
|
+
branchName: params.branchName,
|
|
5028
|
+
commitSha: sha.ok ? sha.data : null,
|
|
5029
|
+
pushedTo: push.data.pushedTo,
|
|
5030
|
+
conflicted,
|
|
5031
|
+
conflictedFiles: rebase.data.conflictedFiles,
|
|
5032
|
+
worktreeRemoved
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
|
|
5036
|
+
// src/git/rescue.ts
|
|
5037
|
+
var RescueReason = {
|
|
5038
|
+
/** Модель упёрлась в лимит подписки (`StopFailure`, matcher `rate_limit`). */
|
|
5039
|
+
RATE_LIMIT: "rate_limit",
|
|
5040
|
+
/** Сервер забрал задачу: аренда истекла или её отдали другому. */
|
|
5041
|
+
LEASE_LOST: "lease_lost"
|
|
5042
|
+
};
|
|
5043
|
+
var REASON_TEXT = {
|
|
5044
|
+
[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",
|
|
5045
|
+
[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"
|
|
5046
|
+
};
|
|
5047
|
+
async function rescueUnfinishedWork(params) {
|
|
5048
|
+
const status = await getWorktreeStatus(params.worktreeDir);
|
|
5049
|
+
if (!status.ok) return status;
|
|
5050
|
+
if (status.data.clean) {
|
|
5051
|
+
return gitOk({ committed: false, sha: null, pushedTo: null });
|
|
4782
5052
|
}
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
5053
|
+
const what = params.title?.trim();
|
|
5054
|
+
const commit = await commitAll({
|
|
5055
|
+
worktreeDir: params.worktreeDir,
|
|
5056
|
+
commitPrefix: params.commitPrefix,
|
|
5057
|
+
title: what ? `${what} (${REASON_TEXT[params.reason]})` : REASON_TEXT[params.reason],
|
|
5058
|
+
taskKey: params.taskKey
|
|
5059
|
+
});
|
|
5060
|
+
if (!commit.ok) return commit;
|
|
5061
|
+
if (!commit.data.committed) {
|
|
5062
|
+
return gitOk({ committed: false, sha: null, pushedTo: null });
|
|
4786
5063
|
}
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
5064
|
+
const push = await pushTaskBranch({
|
|
5065
|
+
worktreeDir: params.worktreeDir,
|
|
5066
|
+
branchName: params.branchName
|
|
5067
|
+
});
|
|
5068
|
+
if (!push.ok) {
|
|
5069
|
+
return gitErr(
|
|
5070
|
+
`\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}`
|
|
5071
|
+
);
|
|
5072
|
+
}
|
|
5073
|
+
return gitOk({
|
|
5074
|
+
committed: true,
|
|
5075
|
+
sha: commit.data.sha,
|
|
5076
|
+
pushedTo: push.data.pushedTo
|
|
5077
|
+
});
|
|
5078
|
+
}
|
|
5079
|
+
|
|
5080
|
+
// src/hooks/stop-failure.ts
|
|
5081
|
+
import { z as z37 } from "zod";
|
|
5082
|
+
|
|
5083
|
+
// src/hooks/reset-time.ts
|
|
5084
|
+
var DAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
5085
|
+
function parseClock(hour, minute, meridiem) {
|
|
5086
|
+
const lower = meridiem.toLowerCase();
|
|
5087
|
+
let h = hour % 12;
|
|
5088
|
+
if (lower === "pm") h += 12;
|
|
5089
|
+
return { h, m: minute };
|
|
5090
|
+
}
|
|
5091
|
+
function parseResetAt(message, now) {
|
|
5092
|
+
if (!message) return void 0;
|
|
5093
|
+
const withDay = message.match(
|
|
5094
|
+
/resets\s+(sun|mon|tue|wed|thu|fri|sat)[a-z]*\s+(\d{1,2}):(\d{2})\s*(am|pm)/i
|
|
5095
|
+
);
|
|
5096
|
+
if (withDay) {
|
|
5097
|
+
const [, dayRaw = "", hourRaw = "", minuteRaw = "", meridiem = ""] = withDay;
|
|
5098
|
+
const targetDay = DAYS.indexOf(dayRaw.slice(0, 3).toLowerCase());
|
|
5099
|
+
if (targetDay === -1) return void 0;
|
|
5100
|
+
const { h, m } = parseClock(Number(hourRaw), Number(minuteRaw), meridiem);
|
|
5101
|
+
const result = new Date(now);
|
|
5102
|
+
result.setHours(h, m, 0, 0);
|
|
5103
|
+
let shift = (targetDay - result.getDay() + 7) % 7;
|
|
5104
|
+
if (shift === 0 && result.getTime() <= now.getTime()) shift = 7;
|
|
5105
|
+
result.setDate(result.getDate() + shift);
|
|
5106
|
+
return result;
|
|
5107
|
+
}
|
|
5108
|
+
const withTime = message.match(/resets\s+(\d{1,2}):(\d{2})\s*(am|pm)/i);
|
|
5109
|
+
if (withTime) {
|
|
5110
|
+
const [, hourRaw = "", minuteRaw = "", meridiem = ""] = withTime;
|
|
5111
|
+
const { h, m } = parseClock(Number(hourRaw), Number(minuteRaw), meridiem);
|
|
5112
|
+
const result = new Date(now);
|
|
5113
|
+
result.setHours(h, m, 0, 0);
|
|
5114
|
+
if (result.getTime() <= now.getTime()) result.setDate(result.getDate() + 1);
|
|
5115
|
+
return result;
|
|
5116
|
+
}
|
|
5117
|
+
return void 0;
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
// src/hooks/stop-failure.ts
|
|
5121
|
+
var StopFailureErrorTypeSchema = z37.enum([
|
|
5122
|
+
"rate_limit",
|
|
5123
|
+
"overloaded",
|
|
5124
|
+
"authentication_failed",
|
|
5125
|
+
"oauth_org_not_allowed",
|
|
5126
|
+
"account_on_hold",
|
|
5127
|
+
"billing_error",
|
|
5128
|
+
"invalid_request",
|
|
5129
|
+
"model_not_found",
|
|
5130
|
+
"server_error",
|
|
5131
|
+
"max_output_tokens",
|
|
5132
|
+
// Добавлен 21 сентября 2026 по живой документации: его в нашем
|
|
5133
|
+
// перечислении не было, и `StopFailureInputSchema` отклонила бы вход с
|
|
5134
|
+
// ним целиком — хук отработал бы вхолостую, а сервер не узнал бы, что
|
|
5135
|
+
// у агента что-то случилось.
|
|
5136
|
+
"cloud_credential_error",
|
|
5137
|
+
"unknown"
|
|
5138
|
+
]);
|
|
5139
|
+
var StopFailureInputSchema = z37.object({
|
|
5140
|
+
hook_event_name: z37.literal("StopFailure"),
|
|
5141
|
+
session_id: z37.string(),
|
|
5142
|
+
cwd: z37.string(),
|
|
5143
|
+
error: StopFailureErrorTypeSchema,
|
|
5144
|
+
error_details: z37.unknown().optional(),
|
|
5145
|
+
last_assistant_message: z37.string().optional()
|
|
5146
|
+
}).passthrough();
|
|
5147
|
+
function extractRetryAfterSec(errorDetails) {
|
|
5148
|
+
if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
|
|
5149
|
+
return errorDetails;
|
|
5150
|
+
}
|
|
5151
|
+
if (errorDetails && typeof errorDetails === "object") {
|
|
5152
|
+
const record = errorDetails;
|
|
5153
|
+
for (const key of ["retryAfterSec", "retryAfter", "retry_after", "retry_after_seconds"]) {
|
|
5154
|
+
const value = record[key];
|
|
5155
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
4813
5156
|
}
|
|
4814
|
-
this.applyApprovalDecided(parsed.data);
|
|
4815
|
-
this.emit("approval:decided", parsed.data);
|
|
4816
5157
|
}
|
|
4817
|
-
|
|
4818
|
-
const
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
},
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
return;
|
|
5158
|
+
if (typeof errorDetails === "string") {
|
|
5159
|
+
const match = errorDetails.match(/retry[-_ ]?after[^0-9]{0,10}(\d+)/i);
|
|
5160
|
+
if (match?.[1] !== void 0) return Number(match[1]);
|
|
5161
|
+
}
|
|
5162
|
+
return void 0;
|
|
5163
|
+
}
|
|
5164
|
+
var RESCUE_WORK_ACTION = "rescue-work";
|
|
5165
|
+
function stopFailureHook(input, state) {
|
|
5166
|
+
const retryAfterSec = extractRetryAfterSec(input.error_details);
|
|
5167
|
+
const resetAt = parseResetAt(input.last_assistant_message, /* @__PURE__ */ new Date());
|
|
5168
|
+
const actions = [
|
|
5169
|
+
{
|
|
5170
|
+
type: "agent:rate_limited",
|
|
5171
|
+
payload: {
|
|
5172
|
+
error: input.error,
|
|
5173
|
+
...retryAfterSec === void 0 ? {} : { retryAfterSec },
|
|
5174
|
+
...resetAt ? { resetsAt: resetAt.toISOString() } : {}
|
|
5175
|
+
}
|
|
4836
5176
|
}
|
|
4837
|
-
|
|
5177
|
+
];
|
|
5178
|
+
const task = state.currentTask;
|
|
5179
|
+
if (task?.worktreeDir && task.branchName && task.commitPrefix) {
|
|
5180
|
+
actions.push({
|
|
5181
|
+
type: RESCUE_WORK_ACTION,
|
|
5182
|
+
payload: {
|
|
5183
|
+
reason: "rate_limit",
|
|
5184
|
+
taskId: task.taskId,
|
|
5185
|
+
worktreeDir: task.worktreeDir,
|
|
5186
|
+
branchName: task.branchName,
|
|
5187
|
+
commitPrefix: task.commitPrefix,
|
|
5188
|
+
taskKey: task.taskKey ?? task.taskId
|
|
5189
|
+
}
|
|
5190
|
+
});
|
|
4838
5191
|
}
|
|
4839
|
-
};
|
|
4840
|
-
function trimTrailingSlash2(url) {
|
|
4841
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5192
|
+
return { output: {}, serverActions: actions };
|
|
4842
5193
|
}
|
|
4843
5194
|
|
|
4844
|
-
// src/
|
|
4845
|
-
var
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
5195
|
+
// src/hooks/rescue-runner.ts
|
|
5196
|
+
var NOTHING = {
|
|
5197
|
+
attempted: false,
|
|
5198
|
+
committed: false,
|
|
5199
|
+
pushedTo: null,
|
|
5200
|
+
problem: null,
|
|
5201
|
+
report: null
|
|
5202
|
+
};
|
|
5203
|
+
function readParams(action) {
|
|
5204
|
+
const p = action.payload ?? {};
|
|
5205
|
+
const str = (key) => {
|
|
5206
|
+
const value = p[key];
|
|
5207
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
5208
|
+
};
|
|
5209
|
+
const worktreeDir = str("worktreeDir");
|
|
5210
|
+
const branchName = str("branchName");
|
|
5211
|
+
const commitPrefix = str("commitPrefix");
|
|
5212
|
+
const taskKey = str("taskKey");
|
|
5213
|
+
const taskId = str("taskId");
|
|
5214
|
+
if (!worktreeDir || !branchName || !commitPrefix || !taskKey || !taskId) return null;
|
|
5215
|
+
const reason = p.reason === RescueReason.LEASE_LOST ? RescueReason.LEASE_LOST : RescueReason.RATE_LIMIT;
|
|
5216
|
+
return { worktreeDir, branchName, commitPrefix, taskKey, taskId, reason };
|
|
5217
|
+
}
|
|
5218
|
+
async function runRescueAction(actions) {
|
|
5219
|
+
const action = actions.find((candidate) => candidate.type === RESCUE_WORK_ACTION);
|
|
5220
|
+
if (!action) return NOTHING;
|
|
5221
|
+
const params = readParams(action);
|
|
5222
|
+
if (!params) {
|
|
5223
|
+
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" };
|
|
5224
|
+
}
|
|
4857
5225
|
try {
|
|
4858
|
-
await
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
if (isAckError(raw)) {
|
|
4862
|
-
return { ok: false, message: `${raw.error.code}: ${raw.error.message}` };
|
|
4863
|
-
}
|
|
4864
|
-
const parsed = agentModeAckSchema.safeParse(raw);
|
|
4865
|
-
if (!parsed.success) {
|
|
4866
|
-
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}` };
|
|
5226
|
+
const result = await rescueUnfinishedWork(params);
|
|
5227
|
+
if (!result.ok) {
|
|
5228
|
+
return { ...NOTHING, attempted: true, problem: result.message };
|
|
4867
5229
|
}
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
5230
|
+
const report = result.data.committed ? {
|
|
5231
|
+
type: "task:work_saved",
|
|
5232
|
+
payload: {
|
|
5233
|
+
taskId: params.taskId,
|
|
5234
|
+
branchName: params.branchName,
|
|
5235
|
+
...result.data.sha ? { sha: result.data.sha } : {}
|
|
5236
|
+
}
|
|
5237
|
+
} : null;
|
|
5238
|
+
return {
|
|
5239
|
+
attempted: true,
|
|
5240
|
+
committed: result.data.committed,
|
|
5241
|
+
pushedTo: result.data.pushedTo,
|
|
5242
|
+
problem: null,
|
|
5243
|
+
report
|
|
5244
|
+
};
|
|
5245
|
+
} catch (error) {
|
|
5246
|
+
return {
|
|
5247
|
+
...NOTHING,
|
|
5248
|
+
attempted: true,
|
|
5249
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
5250
|
+
};
|
|
4876
5251
|
}
|
|
4877
5252
|
}
|
|
5253
|
+
function describeRescue(result) {
|
|
5254
|
+
if (!result.attempted) return null;
|
|
5255
|
+
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}`;
|
|
5256
|
+
if (!result.committed) return null;
|
|
5257
|
+
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}` : ""}`;
|
|
5258
|
+
}
|
|
4878
5259
|
|
|
4879
|
-
// src/
|
|
4880
|
-
|
|
4881
|
-
|
|
5260
|
+
// src/socket/auth-reject.ts
|
|
5261
|
+
var AUTH_REJECT_REASONS = /* @__PURE__ */ new Set(["missing", "invalid"]);
|
|
5262
|
+
function isAuthRejection(error) {
|
|
5263
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5264
|
+
return AUTH_REJECT_REASONS.has(message.trim());
|
|
4882
5265
|
}
|
|
4883
|
-
function
|
|
4884
|
-
return
|
|
4885
|
-
}
|
|
4886
|
-
function leaseLostResult(taskLabel2, reason) {
|
|
4887
|
-
return errorResult(
|
|
4888
|
-
`\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.`
|
|
4889
|
-
);
|
|
5266
|
+
function describeAuthRejection(agentId) {
|
|
5267
|
+
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.`;
|
|
4890
5268
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
5269
|
+
|
|
5270
|
+
// src/socket/backoff.ts
|
|
5271
|
+
function nextReconnectDelayMs(attempt, options = {}) {
|
|
5272
|
+
const baseMs = options.baseMs ?? 1e3;
|
|
5273
|
+
const maxMs = options.maxMs ?? 3e4;
|
|
5274
|
+
const safeAttempt = Math.max(0, Math.trunc(attempt));
|
|
5275
|
+
const exponent = Math.min(safeAttempt, 30);
|
|
5276
|
+
const raw = baseMs * 2 ** exponent;
|
|
5277
|
+
return Math.min(raw, maxMs);
|
|
4895
5278
|
}
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
5279
|
+
|
|
5280
|
+
// src/socket/client.ts
|
|
5281
|
+
import { EventEmitter } from "events";
|
|
5282
|
+
|
|
5283
|
+
// src/socket/emit-with-timeout.ts
|
|
5284
|
+
function toError(raw, context) {
|
|
5285
|
+
return raw instanceof Error ? raw : new Error(`${context}: ${String(raw)}`);
|
|
4902
5286
|
}
|
|
4903
|
-
async function
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
};
|
|
4918
|
-
}
|
|
4919
|
-
const parsed = schema.safeParse(raw);
|
|
4920
|
-
if (!parsed.success) {
|
|
4921
|
-
return {
|
|
4922
|
-
ok: false,
|
|
4923
|
-
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}`
|
|
4924
|
-
};
|
|
4925
|
-
}
|
|
4926
|
-
return { ok: true, data: parsed.data };
|
|
5287
|
+
async function emitWithAckTimeout(socket, event, payload, timeoutMs) {
|
|
5288
|
+
return await new Promise((resolve4, reject) => {
|
|
5289
|
+
const timer = setTimeout(() => {
|
|
5290
|
+
reject(new Error(`${event}: \u043D\u0435\u0442 ack \u043E\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0437\u0430 ${timeoutMs} \u043C\u0441`));
|
|
5291
|
+
}, timeoutMs);
|
|
5292
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5293
|
+
socket.emitWithAck(event, payload).then((value) => {
|
|
5294
|
+
clearTimeout(timer);
|
|
5295
|
+
resolve4(value);
|
|
5296
|
+
}).catch((err) => {
|
|
5297
|
+
clearTimeout(timer);
|
|
5298
|
+
reject(toError(err, event));
|
|
5299
|
+
});
|
|
5300
|
+
});
|
|
4927
5301
|
}
|
|
4928
5302
|
|
|
4929
|
-
// src/
|
|
4930
|
-
var
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
targetMemberId: questionAskPayloadSchema.shape.targetMemberId.describe(
|
|
4938
|
-
"ID \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u0433\u043E \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0430\u0434\u0440\u0435\u0441\u0430\u0442 \u0432\u043E\u043F\u0440\u043E\u0441\u0430. \u041D\u0443\u0436\u0435\u043D \u0420\u041E\u0412\u041D\u041E \u041E\u0414\u0418\u041D \u0430\u0434\u0440\u0435\u0441\u0430\u0442: targetRole \u0418\u041B\u0418 targetMemberId, \u043D\u0435 \u043E\u0431\u0430 \u0441\u0440\u0430\u0437\u0443 \u0438 \u043D\u0435 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E."
|
|
4939
|
-
),
|
|
4940
|
-
wantsFiles: questionAskPayloadSchema.shape.wantsFiles.describe(
|
|
4941
|
-
"\u041F\u043E\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043F\u0440\u0438\u043B\u043E\u0436\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u043A \u043E\u0442\u0432\u0435\u0442\u0443 (\u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442, \u043B\u043E\u0433, \u0432\u044B\u0433\u0440\u0443\u0437\u043A\u0443). \u0424\u043E\u0440\u043C\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u0441 \u0432\u044B\u0431\u043E\u0440\u043E\u043C \u0444\u0430\u0439\u043B\u0430. \u042D\u0442\u043E \u041F\u0420\u041E\u0421\u042C\u0411\u0410, \u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u0435: \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0432\u043F\u0440\u0430\u0432\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u0431\u0435\u0437 \u0444\u0430\u0439\u043B\u043E\u0432, \u0438 \u0442\u043E\u0433\u0434\u0430 `attachments` \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043F\u0443\u0441\u0442\u044B\u043C."
|
|
4942
|
-
),
|
|
4943
|
-
options: questionAskPayloadSchema.shape.options.describe(
|
|
4944
|
-
"\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u043E\u0442\u0432\u0435\u0442\u0430 (\u043F\u043E\u043A\u0430\u0436\u0443\u0442\u0441\u044F \u043A\u043D\u043E\u043F\u043A\u0430\u043C\u0438 \u0432 UI/Telegram). \u041D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439 \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043E\u0442\u0432\u0435\u0442\u0438\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C."
|
|
4945
|
-
),
|
|
4946
|
-
blocking: questionAskPayloadSchema.shape.blocking.describe(
|
|
4947
|
-
'true \u2014 \u0442\u0432\u043E\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441 WAITING_HUMAN, \u0438 \u044D\u0442\u043E\u0442 \u0432\u044B\u0437\u043E\u0432 \u0441\u0430\u043C \u0436\u0434\u0451\u0442 \u043E\u0442\u0432\u0435\u0442 \u0434\u043E 600 \u0441; \u0435\u0441\u043B\u0438 \u0437\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435\u0442, \u0432\u044B\u0437\u043E\u0432 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0441 \u043F\u043E\u043C\u0435\u0442\u043A\u043E\u0439 "pending" \u2014 \u0442\u043E\u0433\u0434\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId \u0438\u0437 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430, \u043D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430. false \u2014 \u0432\u043E\u043F\u0440\u043E\u0441 \u043F\u0440\u043E\u0441\u0442\u043E \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F, \u0431\u0435\u0437 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0438 \u0431\u0435\u0437 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 (\u0434\u043B\u044F \u043D\u0435 \u0441\u0440\u043E\u0447\u043D\u044B\u0445 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0439).'
|
|
4948
|
-
)
|
|
4949
|
-
};
|
|
4950
|
-
var BLOCKING_WAIT_SEC = 600;
|
|
4951
|
-
var ASK_ACK_BUFFER_MS = 15e3;
|
|
4952
|
-
async function askHumanHandler(args, runtime) {
|
|
4953
|
-
const state = readState(runtime.stateFilePath);
|
|
4954
|
-
const taskId = state.currentTask?.taskId;
|
|
4955
|
-
const waitSec = args.blocking ? BLOCKING_WAIT_SEC : 0;
|
|
4956
|
-
const parsedArgs = parseArgsOrError(questionAskPayloadSchema, {
|
|
4957
|
-
...taskId ? { taskId } : {},
|
|
4958
|
-
textMd: args.textMd,
|
|
4959
|
-
...args.targetRole ? { targetRole: args.targetRole } : {},
|
|
4960
|
-
...args.targetMemberId ? { targetMemberId: args.targetMemberId } : {},
|
|
4961
|
-
...args.options && args.options.length > 0 ? { options: args.options } : {},
|
|
4962
|
-
...args.wantsFiles ? { wantsFiles: true } : {},
|
|
4963
|
-
blocking: args.blocking,
|
|
4964
|
-
waitSec
|
|
4965
|
-
});
|
|
4966
|
-
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
4967
|
-
if (taskId) {
|
|
4968
|
-
const lease = runtime.client.isLeaseLost(taskId);
|
|
4969
|
-
if (lease.lost) return leaseLostResult(state.currentTask?.taskKey ?? taskId, lease.reason);
|
|
4970
|
-
}
|
|
4971
|
-
const result = await callServerOrError(
|
|
4972
|
-
runtime.client,
|
|
4973
|
-
"question:ask",
|
|
4974
|
-
parsedArgs.data,
|
|
4975
|
-
questionAskAckSchema,
|
|
4976
|
-
waitSec * 1e3 + ASK_ACK_BUFFER_MS
|
|
4977
|
-
);
|
|
4978
|
-
if (!result.ok) return errorResult(result.message);
|
|
4979
|
-
const settled = describeSettledQuestion(result.data.question);
|
|
4980
|
-
if (settled) return textResult(settled);
|
|
4981
|
-
if (args.blocking) {
|
|
4982
|
-
return textResult(
|
|
4983
|
-
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u0437\u0430\u0434\u0430\u043D, \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u0430 \u0432 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 (WAITING_HUMAN). \u041E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E ${BLOCKING_WAIT_SEC} \u0441 (pending) \u2014 \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId="${result.data.question.id}", \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0436\u0434\u0430\u0442\u044C. \u041D\u0435 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443 \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435, \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u0448\u044C \u043E\u0442\u0432\u0435\u0442.`
|
|
4984
|
-
);
|
|
4985
|
-
}
|
|
4986
|
-
return textResult(
|
|
4987
|
-
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D (\u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u041E\u0442\u0432\u0435\u0442\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u2014 \u0435\u0441\u043B\u0438 \u043E\u043D \u043D\u0443\u0436\u0435\u043D, \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C, \u0432\u044B\u0437\u043E\u0432\u0438 wait_answer \u0441 questionId="${result.data.question.id}".`
|
|
4988
|
-
);
|
|
5303
|
+
// src/socket/trust-cache.ts
|
|
5304
|
+
var UNKNOWN_TRUST_PROFILE_KEY = "unknown";
|
|
5305
|
+
function toTrustCache(rules, now = () => /* @__PURE__ */ new Date()) {
|
|
5306
|
+
return {
|
|
5307
|
+
profileKey: UNKNOWN_TRUST_PROFILE_KEY,
|
|
5308
|
+
updatedAt: now().toISOString(),
|
|
5309
|
+
rules
|
|
5310
|
+
};
|
|
4989
5311
|
}
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
waitSec: questionWaitPayloadSchema.shape.waitSec.optional().describe("\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C \u043E\u0442\u0432\u0435\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u044B\u0437\u043E\u0432\u0435, 0..600. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 300.")
|
|
4995
|
-
};
|
|
4996
|
-
var DEFAULT_WAIT_ANSWER_SEC = 300;
|
|
4997
|
-
var WAIT_ANSWER_ACK_BUFFER_MS = 15e3;
|
|
4998
|
-
async function waitAnswerHandler(args, runtime) {
|
|
4999
|
-
const waitSec = args.waitSec ?? DEFAULT_WAIT_ANSWER_SEC;
|
|
5000
|
-
const parsedArgs = parseArgsOrError(questionWaitPayloadSchema, {
|
|
5001
|
-
questionId: args.questionId,
|
|
5002
|
-
waitSec
|
|
5003
|
-
});
|
|
5004
|
-
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5005
|
-
const result = await callServerOrError(
|
|
5006
|
-
runtime.client,
|
|
5007
|
-
"question:wait",
|
|
5008
|
-
parsedArgs.data,
|
|
5009
|
-
questionWaitAckSchema,
|
|
5010
|
-
waitSec * 1e3 + WAIT_ANSWER_ACK_BUFFER_MS
|
|
5011
|
-
);
|
|
5012
|
-
if (!result.ok) return errorResult(result.message);
|
|
5013
|
-
const settled = describeSettledQuestion(result.data.question);
|
|
5014
|
-
if (settled) return textResult(settled);
|
|
5015
|
-
return textResult(
|
|
5016
|
-
`\u0412\u043E\u043F\u0440\u043E\u0441 ${result.data.question.id} \u0432\u0441\u0451 \u0435\u0449\u0451 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 (\u0436\u0434\u0430\u043B ${waitSec} \u0441). \u0412\u044B\u0437\u043E\u0432\u0438 wait_answer \u0435\u0449\u0451 \u0440\u0430\u0437 \u0441 \u0442\u0435\u043C \u0436\u0435 questionId, \u043A\u043E\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0443\u043C\u0435\u0441\u0442\u043D\u043E.`
|
|
5017
|
-
);
|
|
5312
|
+
|
|
5313
|
+
// src/socket/types.ts
|
|
5314
|
+
function isAckError(raw) {
|
|
5315
|
+
return typeof raw === "object" && raw !== null && "error" in raw && typeof raw.error === "object";
|
|
5018
5316
|
}
|
|
5019
|
-
function describeAnswerAttachments(question) {
|
|
5020
|
-
if (question.attachments.length === 0) return "";
|
|
5021
|
-
const lines = question.attachments.map(
|
|
5022
|
-
(file) => file.downloadUrl ? `- ${file.fileName} (${file.mimeType}) \u2014 \u0441\u043A\u0430\u0447\u0430\u0439: ${file.downloadUrl}` : `- ${file.fileName} (${file.mimeType}) \u2014 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430, \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442`
|
|
5023
|
-
);
|
|
5024
|
-
return `
|
|
5025
5317
|
|
|
5026
|
-
|
|
5027
|
-
|
|
5318
|
+
// src/socket/client.ts
|
|
5319
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
5320
|
+
var DEFAULT_ACK_TIMEOUT_MS = 1e4;
|
|
5321
|
+
var DEFAULT_BACKOFF_BASE_MS = 1e3;
|
|
5322
|
+
var DEFAULT_BACKOFF_MAX_MS = 3e4;
|
|
5323
|
+
function toError2(raw, context) {
|
|
5324
|
+
if (raw instanceof Error) return raw;
|
|
5325
|
+
return new Error(`${context}: ${String(raw)}`);
|
|
5028
5326
|
}
|
|
5029
|
-
function
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5327
|
+
function resolveOptions(options) {
|
|
5328
|
+
return {
|
|
5329
|
+
roleKeys: options.roleKeys,
|
|
5330
|
+
cwd: options.cwd,
|
|
5331
|
+
clientVersion: options.clientVersion,
|
|
5332
|
+
agentVersion: options.agentVersion ?? "unknown",
|
|
5333
|
+
agentKind: options.agentKind ?? DEFAULT_AGENT_KIND,
|
|
5334
|
+
os: options.os ?? process.platform,
|
|
5335
|
+
// По умолчанию — файл ИМЕННО этого агента (state/store.ts::getStateFilePath(agentId)),
|
|
5336
|
+
// а не общий `state.json`: не трогаем src/state/**, но обязаны использовать
|
|
5337
|
+
// его правильно (OQ-05 — состояние на агента, не на машину).
|
|
5338
|
+
stateFilePath: options.stateFilePath ?? getStateFilePath(options.agentId),
|
|
5339
|
+
heartbeatIntervalMs: options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
5340
|
+
ackTimeoutMs: options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS,
|
|
5341
|
+
backoffBaseMs: options.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,
|
|
5342
|
+
backoffMaxMs: options.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS,
|
|
5343
|
+
now: options.now ?? (() => /* @__PURE__ */ new Date())
|
|
5344
|
+
};
|
|
5345
|
+
}
|
|
5346
|
+
var AgentSocketClient = class extends EventEmitter {
|
|
5347
|
+
socket;
|
|
5348
|
+
opts;
|
|
5349
|
+
agentId;
|
|
5350
|
+
stopped = true;
|
|
5351
|
+
reconnectAttempt = 0;
|
|
5352
|
+
reconnectTimer;
|
|
5353
|
+
heartbeatTimer;
|
|
5354
|
+
lastHelloAck;
|
|
5355
|
+
paused = false;
|
|
5356
|
+
pauseReasonText;
|
|
5357
|
+
leaseLostTasks = /* @__PURE__ */ new Map();
|
|
5358
|
+
constructor(options) {
|
|
5359
|
+
super();
|
|
5360
|
+
this.opts = resolveOptions(options);
|
|
5361
|
+
this.agentId = options.agentId;
|
|
5362
|
+
const factory = options.createSocket ?? createSocket;
|
|
5363
|
+
this.socket = factory(`${trimTrailingSlash2(options.url)}/agent`, {
|
|
5364
|
+
auth: { token: options.token },
|
|
5365
|
+
autoConnect: false,
|
|
5366
|
+
reconnection: false
|
|
5367
|
+
});
|
|
5368
|
+
this.registerSocketListeners();
|
|
5033
5369
|
}
|
|
5034
|
-
|
|
5035
|
-
|
|
5370
|
+
// -- Публичное API -----------------------------------------------------
|
|
5371
|
+
on(event, listener) {
|
|
5372
|
+
return super.on(event, listener);
|
|
5036
5373
|
}
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
// package.json
|
|
5041
|
-
var package_default = {
|
|
5042
|
-
name: "@quiel/cli",
|
|
5043
|
-
version: "0.5.2",
|
|
5044
|
-
description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 (Claude Code, Codex, Cursor, OpenCode) \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
|
|
5045
|
-
homepage: "https://quiel.app",
|
|
5046
|
-
repository: {
|
|
5047
|
-
type: "git",
|
|
5048
|
-
url: "git+https://github.com/Quiel-App/quiel-cli.git"
|
|
5049
|
-
},
|
|
5050
|
-
bugs: {
|
|
5051
|
-
url: "https://github.com/Quiel-App/quiel-cli/issues"
|
|
5052
|
-
},
|
|
5053
|
-
keywords: [
|
|
5054
|
-
"quiel",
|
|
5055
|
-
"claude-code",
|
|
5056
|
-
"codex",
|
|
5057
|
-
"cursor",
|
|
5058
|
-
"opencode",
|
|
5059
|
-
"mcp",
|
|
5060
|
-
"mcp-server",
|
|
5061
|
-
"ai-agents",
|
|
5062
|
-
"task-tracker"
|
|
5063
|
-
],
|
|
5064
|
-
license: "Apache-2.0",
|
|
5065
|
-
type: "module",
|
|
5066
|
-
bin: {
|
|
5067
|
-
quiel: "dist/cli.js"
|
|
5068
|
-
},
|
|
5069
|
-
files: [
|
|
5070
|
-
"dist",
|
|
5071
|
-
"README.md",
|
|
5072
|
-
"LICENSE"
|
|
5073
|
-
],
|
|
5074
|
-
engines: {
|
|
5075
|
-
node: ">=22"
|
|
5076
|
-
},
|
|
5077
|
-
publishConfig: {
|
|
5078
|
-
access: "public"
|
|
5079
|
-
},
|
|
5080
|
-
scripts: {
|
|
5081
|
-
build: "tsup",
|
|
5082
|
-
dev: "tsup --watch",
|
|
5083
|
-
lint: "biome check .",
|
|
5084
|
-
typecheck: "tsc --noEmit",
|
|
5085
|
-
test: "vitest run --passWithNoTests"
|
|
5086
|
-
},
|
|
5087
|
-
dependencies: {
|
|
5088
|
-
"@modelcontextprotocol/sdk": "1.30.0",
|
|
5089
|
-
commander: "15.0.0",
|
|
5090
|
-
"socket.io-client": "4.8.3",
|
|
5091
|
-
zod: "4.5.4"
|
|
5092
|
-
},
|
|
5093
|
-
devDependencies: {
|
|
5094
|
-
"@quiel/config": "workspace:*",
|
|
5095
|
-
"@quiel/shared": "workspace:*",
|
|
5096
|
-
"@types/node": "20.19.43",
|
|
5097
|
-
tsup: "8.5.1",
|
|
5098
|
-
typescript: "7.0.2",
|
|
5099
|
-
vitest: "5.0.0"
|
|
5374
|
+
once(event, listener) {
|
|
5375
|
+
return super.once(event, listener);
|
|
5100
5376
|
}
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
// src/version.ts
|
|
5104
|
-
var VERSION = package_default.version;
|
|
5105
|
-
|
|
5106
|
-
// src/mcp/constants.ts
|
|
5107
|
-
var MCP_SERVER_NAME = "quiel";
|
|
5108
|
-
var MCP_SERVER_VERSION = VERSION;
|
|
5109
|
-
|
|
5110
|
-
// src/mcp/get-context.ts
|
|
5111
|
-
var ALL_CONTEXT_INCLUDES = [
|
|
5112
|
-
"spec",
|
|
5113
|
-
"decisions",
|
|
5114
|
-
// ВСЕ документы проекта — по умолчанию, а не по особому запросу.
|
|
5115
|
-
// `spec`/`decisions` покрывают два типа из пяти, и человек, положивший
|
|
5116
|
-
// контекст под типом `DATA` или `PLAN`, вправе ожидать, что агент его
|
|
5117
|
-
// увидит. 14 сентября 2026 ровно этого и не случилось.
|
|
5118
|
-
"documents",
|
|
5119
|
-
"related",
|
|
5120
|
-
"repo"
|
|
5121
|
-
];
|
|
5122
|
-
var getContextInputShape = {
|
|
5123
|
-
taskId: contextGetPayloadSchema.shape.taskId.describe(
|
|
5124
|
-
"ID \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u043B\u044F \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043D\u0443\u0436\u0435\u043D \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442. \u0415\u0441\u043B\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0430\u0433\u0435\u043D\u0442\u0430 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C)."
|
|
5125
|
-
),
|
|
5126
|
-
include: contextGetPayloadSchema.shape.include.optional().describe(
|
|
5127
|
-
"\u0427\u0442\u043E \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C: spec | decisions | related | repo. \u041D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u0438\u043B\u0438 \u043F\u0443\u0441\u0442 \u2014 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0432\u0441\u0451 \u0441\u0440\u0430\u0437\u0443."
|
|
5128
|
-
)
|
|
5129
|
-
};
|
|
5130
|
-
async function getContextHandler(args, runtime) {
|
|
5131
|
-
const state = readState(runtime.stateFilePath);
|
|
5132
|
-
const taskId = args.taskId ?? state.currentTask?.taskId;
|
|
5133
|
-
if (taskId) {
|
|
5134
|
-
const lease = runtime.client.isLeaseLost(taskId);
|
|
5135
|
-
if (lease.lost) return leaseLostResult(taskId, lease.reason);
|
|
5377
|
+
off(event, listener) {
|
|
5378
|
+
return super.off(event, listener);
|
|
5136
5379
|
}
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
const result = await callServerOrError(
|
|
5140
|
-
runtime.client,
|
|
5141
|
-
"context:get",
|
|
5142
|
-
payload,
|
|
5143
|
-
contextPayloadSchema
|
|
5144
|
-
);
|
|
5145
|
-
if (!result.ok) return errorResult(result.message);
|
|
5146
|
-
return textResult(JSON.stringify(result.data, null, 2));
|
|
5147
|
-
}
|
|
5148
|
-
|
|
5149
|
-
// src/mcp/get-task.ts
|
|
5150
|
-
var getTaskInputShape = {
|
|
5151
|
-
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")
|
|
5152
|
-
};
|
|
5153
|
-
async function getTaskHandler(args, runtime) {
|
|
5154
|
-
const lease = runtime.client.isLeaseLost(args.taskId);
|
|
5155
|
-
if (lease.lost) return leaseLostResult(args.taskId, lease.reason);
|
|
5156
|
-
const payload = taskGetPayloadSchema.parse({ taskId: args.taskId });
|
|
5157
|
-
const result = await callServerOrError(runtime.client, "task:get", payload, taskGetAckSchema);
|
|
5158
|
-
if (!result.ok) return errorResult(result.message);
|
|
5159
|
-
return textResult(JSON.stringify(result.data, null, 2));
|
|
5160
|
-
}
|
|
5161
|
-
|
|
5162
|
-
// src/mcp/log.ts
|
|
5163
|
-
var logInputShape = {
|
|
5164
|
-
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"),
|
|
5165
|
-
level: taskProgressPayloadSchema.shape.level.optional().describe("debug | info | warn | error, \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E info")
|
|
5166
|
-
};
|
|
5167
|
-
async function logHandler(args, runtime) {
|
|
5168
|
-
const state = readState(runtime.stateFilePath);
|
|
5169
|
-
if (!state.currentTask) {
|
|
5170
|
-
return errorResult(
|
|
5171
|
-
"\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430 \u0432 \u0435\u0451 \u043B\u0435\u043D\u0442\u0443. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0437\u043E\u0432\u0438 next_task."
|
|
5172
|
-
);
|
|
5380
|
+
emit(event, ...args) {
|
|
5381
|
+
return super.emit(event, ...args);
|
|
5173
5382
|
}
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5383
|
+
/** Поднимает соединение и heartbeat-таймер. Идемпотентно — повторный вызов на уже запущенном клиенте не делает ничего. */
|
|
5384
|
+
start() {
|
|
5385
|
+
if (!this.stopped) return;
|
|
5386
|
+
this.stopped = false;
|
|
5387
|
+
this.reconnectAttempt = 0;
|
|
5388
|
+
this.startHeartbeatTimer();
|
|
5389
|
+
this.socket.connect();
|
|
5390
|
+
}
|
|
5391
|
+
/** Останавливает heartbeat, отменяет запланированное переподключение, закрывает сокет. После `stop()` клиент больше не переподключается сам. */
|
|
5392
|
+
stop() {
|
|
5393
|
+
this.stopped = true;
|
|
5394
|
+
if (this.reconnectTimer) {
|
|
5395
|
+
clearTimeout(this.reconnectTimer);
|
|
5396
|
+
this.reconnectTimer = void 0;
|
|
5397
|
+
}
|
|
5398
|
+
this.stopHeartbeatTimer();
|
|
5399
|
+
this.socket.disconnect();
|
|
5400
|
+
}
|
|
5401
|
+
get connected() {
|
|
5402
|
+
return this.socket.connected;
|
|
5403
|
+
}
|
|
5404
|
+
get lastHello() {
|
|
5405
|
+
return this.lastHelloAck;
|
|
5406
|
+
}
|
|
5407
|
+
get isPaused() {
|
|
5408
|
+
return this.paused;
|
|
5409
|
+
}
|
|
5410
|
+
get pauseReason() {
|
|
5411
|
+
return this.pauseReasonText;
|
|
5412
|
+
}
|
|
5413
|
+
/** §11.2 ТЗ: «следующий вызов любого тула по этой задаче должен сообщить модели, что работу надо прекратить» — тулы (P2-12) сверяются с этим геттером. */
|
|
5414
|
+
isLeaseLost(taskId) {
|
|
5415
|
+
const reason = this.leaseLostTasks.get(taskId);
|
|
5416
|
+
return reason === void 0 ? { lost: false } : { lost: true, reason };
|
|
5417
|
+
}
|
|
5418
|
+
/**
|
|
5419
|
+
* Общий emit+ack поверх ЭТОГО ЖЕ постоянного сокета — тем же приёмом
|
|
5420
|
+
* (`emitWithAckTimeout`), которым `sendHello`/`sendHeartbeat` уже
|
|
5421
|
+
* пользуются внутри класса, но публично: MCP-тулы (P2-12/13,
|
|
5422
|
+
* `../mcp/**`) шлют через него `task:claim`/`task:start`/`task:progress`/
|
|
5423
|
+
* `task:get`/`task:report`/`task:release`/`context:get` (§11.1 ТЗ) — то
|
|
5424
|
+
* есть ВСЁ, что не `agent:hello`/`agent:heartbeat`/`agent:mode` (у
|
|
5425
|
+
* первых двух своя логика согласования состояния в этом классе, третий
|
|
5426
|
+
* — короткоживущий отдельный сокет, см. `send-agent-mode.ts`). Нарочно
|
|
5427
|
+
* НЕ отдельное соединение: `task:claim` — блокирующий долгий long-poll,
|
|
5428
|
+
* и держать его на том же сокете, где уже идёт heartbeat/presence этого
|
|
5429
|
+
* агента, а не поднимать вторую параллельную сессию — единственный
|
|
5430
|
+
* вариант, не удваивающий presence на сервере.
|
|
5431
|
+
*/
|
|
5432
|
+
callServer(event, payload, timeoutMs = this.opts.ackTimeoutMs) {
|
|
5433
|
+
return emitWithAckTimeout(this.socket, event, payload, timeoutMs);
|
|
5434
|
+
}
|
|
5435
|
+
/**
|
|
5436
|
+
* Ждёт первого успешного `agent:hello` не дольше `timeoutMs` (по
|
|
5437
|
+
* умолчанию 5 с — как раньше HTTP-пинг в `network.ts`). Таймаут НЕ
|
|
5438
|
+
* останавливает клиент: переподключение по backoff продолжается в фоне,
|
|
5439
|
+
* это только ограничение по времени для вызывающей стороны (`connect.ts`).
|
|
5440
|
+
*/
|
|
5441
|
+
waitForFirstConnection(timeoutMs = 5e3) {
|
|
5442
|
+
return new Promise((resolve4) => {
|
|
5443
|
+
let settled = false;
|
|
5444
|
+
const onConnected = (ack) => {
|
|
5445
|
+
if (settled) return;
|
|
5446
|
+
settled = true;
|
|
5447
|
+
clearTimeout(timer);
|
|
5448
|
+
this.off("connected", onConnected);
|
|
5449
|
+
resolve4({ ok: true, ack });
|
|
5450
|
+
};
|
|
5451
|
+
const onAuthRejected = (message) => {
|
|
5452
|
+
if (settled) return;
|
|
5453
|
+
settled = true;
|
|
5454
|
+
clearTimeout(timer);
|
|
5455
|
+
this.off("connected", onConnected);
|
|
5456
|
+
this.off("auth-rejected", onAuthRejected);
|
|
5457
|
+
resolve4({ ok: false, message });
|
|
5458
|
+
};
|
|
5459
|
+
const timer = setTimeout(() => {
|
|
5460
|
+
if (settled) return;
|
|
5461
|
+
settled = true;
|
|
5462
|
+
this.off("connected", onConnected);
|
|
5463
|
+
this.off("auth-rejected", onAuthRejected);
|
|
5464
|
+
resolve4({
|
|
5465
|
+
ok: false,
|
|
5466
|
+
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).`
|
|
5467
|
+
});
|
|
5468
|
+
}, timeoutMs);
|
|
5469
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5470
|
+
this.on("connected", onConnected);
|
|
5471
|
+
this.on("auth-rejected", onAuthRejected);
|
|
5472
|
+
});
|
|
5473
|
+
}
|
|
5474
|
+
// -- Подключение --------------------------------------------------------
|
|
5475
|
+
registerSocketListeners() {
|
|
5476
|
+
this.socket.on("connect", () => this.handleConnect());
|
|
5477
|
+
this.socket.on("connect_error", (err) => this.handleConnectError(err));
|
|
5478
|
+
this.socket.on("disconnect", (reason) => this.handleDisconnect(String(reason)));
|
|
5479
|
+
this.socket.on("lease:lost", (raw) => this.handleLeaseLost(raw));
|
|
5480
|
+
this.socket.on("mode:set", (raw) => this.handleModeSet(raw));
|
|
5481
|
+
this.socket.on("agent:pause", (raw) => this.handleAgentPause(raw));
|
|
5482
|
+
this.socket.on("trust:updated", (raw) => this.handleTrustUpdated(raw));
|
|
5483
|
+
this.socket.on("task:updated", (raw) => this.handleTaskUpdated(raw));
|
|
5484
|
+
this.socket.on("question:answered", (raw) => this.handleQuestionAnswered(raw));
|
|
5485
|
+
this.socket.on("approval:decided", (raw) => this.handleApprovalDecided(raw));
|
|
5486
|
+
}
|
|
5487
|
+
handleConnect() {
|
|
5488
|
+
if (this.stopped) return;
|
|
5489
|
+
this.reconnectAttempt = 0;
|
|
5490
|
+
void this.sendHello();
|
|
5491
|
+
}
|
|
5492
|
+
handleConnectError(err) {
|
|
5493
|
+
if (isAuthRejection(err)) {
|
|
5494
|
+
this.stop();
|
|
5495
|
+
this.emit("auth-rejected", describeAuthRejection(this.agentId));
|
|
5496
|
+
return;
|
|
5497
|
+
}
|
|
5498
|
+
this.emit("client-error", toError2(err, "connect_error"));
|
|
5499
|
+
this.scheduleReconnect();
|
|
5500
|
+
}
|
|
5501
|
+
handleDisconnect(reason) {
|
|
5502
|
+
this.emit("disconnected", reason);
|
|
5503
|
+
if (this.stopped) return;
|
|
5504
|
+
this.scheduleReconnect();
|
|
5505
|
+
}
|
|
5506
|
+
scheduleReconnect() {
|
|
5507
|
+
if (this.stopped || this.reconnectTimer) return;
|
|
5508
|
+
const delayMs = nextReconnectDelayMs(this.reconnectAttempt, {
|
|
5509
|
+
baseMs: this.opts.backoffBaseMs,
|
|
5510
|
+
maxMs: this.opts.backoffMaxMs
|
|
5511
|
+
});
|
|
5512
|
+
this.reconnectAttempt += 1;
|
|
5513
|
+
this.emit("reconnecting", { attempt: this.reconnectAttempt, delayMs });
|
|
5514
|
+
this.reconnectTimer = setTimeout(() => {
|
|
5515
|
+
this.reconnectTimer = void 0;
|
|
5516
|
+
if (this.stopped) return;
|
|
5517
|
+
this.socket.connect();
|
|
5518
|
+
}, delayMs);
|
|
5519
|
+
}
|
|
5520
|
+
// -- agent:hello ---------------------------------------------------------
|
|
5521
|
+
async sendHello() {
|
|
5522
|
+
const state = readState(this.opts.stateFilePath);
|
|
5523
|
+
const payload = agentHelloPayloadSchema.parse({
|
|
5524
|
+
clientVersion: this.opts.clientVersion,
|
|
5525
|
+
cwd: this.opts.cwd,
|
|
5526
|
+
os: this.opts.os,
|
|
5527
|
+
agentKind: this.opts.agentKind,
|
|
5528
|
+
agentVersion: this.opts.agentVersion,
|
|
5529
|
+
// `claudeVersion` шлётся ТОЛЬКО для Claude Code — и только чтобы
|
|
5530
|
+
// клиент работал против ещё не обновлённого сервера, который знает
|
|
5531
|
+
// одно это поле. Для Codex поле с таким именем было бы неправдой.
|
|
5532
|
+
...this.opts.agentKind === AgentKind.CLAUDE_CODE ? { claudeVersion: this.opts.agentVersion } : {},
|
|
5533
|
+
roleKeys: [...this.opts.roleKeys],
|
|
5534
|
+
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
|
|
5535
|
+
});
|
|
5536
|
+
let raw;
|
|
5537
|
+
try {
|
|
5538
|
+
raw = await emitWithAckTimeout(this.socket, "agent:hello", payload, this.opts.ackTimeoutMs);
|
|
5539
|
+
} catch (err) {
|
|
5540
|
+
this.emit("client-error", toError2(err, "agent:hello"));
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
if (this.stopped) return;
|
|
5544
|
+
if (isAckError(raw)) {
|
|
5545
|
+
this.emit("client-error", new Error(`agent:hello: ${raw.error.code}: ${raw.error.message}`));
|
|
5546
|
+
return;
|
|
5547
|
+
}
|
|
5548
|
+
const parsed = agentHelloAckSchema.safeParse(raw);
|
|
5549
|
+
if (!parsed.success) {
|
|
5550
|
+
this.emit(
|
|
5551
|
+
"client-error",
|
|
5552
|
+
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}`)
|
|
5553
|
+
);
|
|
5554
|
+
return;
|
|
5555
|
+
}
|
|
5556
|
+
const ack = parsed.data;
|
|
5557
|
+
this.lastHelloAck = ack;
|
|
5558
|
+
this.paused = false;
|
|
5559
|
+
this.pauseReasonText = void 0;
|
|
5560
|
+
this.applyHelloAck(ack);
|
|
5561
|
+
this.emit("connected", ack);
|
|
5562
|
+
}
|
|
5563
|
+
applyHelloAck(ack) {
|
|
5564
|
+
const state = readState(this.opts.stateFilePath);
|
|
5565
|
+
const patch = {
|
|
5566
|
+
// OQ-06: сервер — источник правды, локальный режим перекрывается безусловно.
|
|
5567
|
+
mode: ack.mode,
|
|
5568
|
+
trust: toTrustCache(ack.trust, this.opts.now)
|
|
5569
|
+
};
|
|
5570
|
+
if (!ack.currentTask || ack.currentTask.id !== state.currentTask?.taskId) {
|
|
5571
|
+
patch.currentTask = null;
|
|
5572
|
+
}
|
|
5573
|
+
updateState(patch, this.opts.stateFilePath);
|
|
5574
|
+
}
|
|
5575
|
+
// -- agent:heartbeat -------------------------------------------------------
|
|
5576
|
+
startHeartbeatTimer() {
|
|
5577
|
+
if (this.heartbeatTimer) return;
|
|
5578
|
+
this.heartbeatTimer = setInterval(() => {
|
|
5579
|
+
void this.sendHeartbeat();
|
|
5580
|
+
}, this.opts.heartbeatIntervalMs);
|
|
5581
|
+
}
|
|
5582
|
+
stopHeartbeatTimer() {
|
|
5583
|
+
if (!this.heartbeatTimer) return;
|
|
5584
|
+
clearInterval(this.heartbeatTimer);
|
|
5585
|
+
this.heartbeatTimer = void 0;
|
|
5586
|
+
}
|
|
5587
|
+
async sendHeartbeat() {
|
|
5588
|
+
if (!this.socket.connected) return;
|
|
5589
|
+
const state = readState(this.opts.stateFilePath);
|
|
5590
|
+
const payload = agentHeartbeatPayloadSchema.parse({
|
|
5591
|
+
...state.currentTask?.taskId ? { currentTaskId: state.currentTask.taskId } : {},
|
|
5592
|
+
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {},
|
|
5593
|
+
status: state.currentTask ? AgentStatus.BUSY : AgentStatus.IDLE
|
|
5594
|
+
});
|
|
5595
|
+
let raw;
|
|
5596
|
+
try {
|
|
5597
|
+
raw = await emitWithAckTimeout(
|
|
5598
|
+
this.socket,
|
|
5599
|
+
"agent:heartbeat",
|
|
5600
|
+
payload,
|
|
5601
|
+
this.opts.ackTimeoutMs
|
|
5602
|
+
);
|
|
5603
|
+
} catch (err) {
|
|
5604
|
+
this.emit("client-error", toError2(err, "agent:heartbeat"));
|
|
5605
|
+
return;
|
|
5606
|
+
}
|
|
5607
|
+
if (isAckError(raw)) {
|
|
5608
|
+
this.emit(
|
|
5609
|
+
"client-error",
|
|
5610
|
+
new Error(`agent:heartbeat: ${raw.error.code}: ${raw.error.message}`)
|
|
5611
|
+
);
|
|
5612
|
+
return;
|
|
5613
|
+
}
|
|
5614
|
+
const parsed = agentHeartbeatAckSchema.safeParse(raw);
|
|
5615
|
+
if (!parsed.success) {
|
|
5616
|
+
this.emit(
|
|
5617
|
+
"client-error",
|
|
5618
|
+
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}`)
|
|
5619
|
+
);
|
|
5620
|
+
return;
|
|
5621
|
+
}
|
|
5622
|
+
this.emit("heartbeat", parsed.data);
|
|
5623
|
+
}
|
|
5624
|
+
// -- Пуш-события сервера (§11.2 ТЗ) --------------------------------------
|
|
5625
|
+
handleLeaseLost(raw) {
|
|
5626
|
+
const parsed = leaseLostPayloadSchema.safeParse(raw);
|
|
5627
|
+
if (!parsed.success) {
|
|
5628
|
+
this.emit(
|
|
5629
|
+
"client-error",
|
|
5630
|
+
new Error(`lease:lost: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5631
|
+
);
|
|
5632
|
+
return;
|
|
5633
|
+
}
|
|
5634
|
+
const { taskId, reason } = parsed.data;
|
|
5635
|
+
this.leaseLostTasks.set(taskId, reason);
|
|
5636
|
+
const state = readState(this.opts.stateFilePath);
|
|
5637
|
+
if (state.currentTask?.taskId === taskId) {
|
|
5638
|
+
updateState({ currentTask: null }, this.opts.stateFilePath);
|
|
5639
|
+
}
|
|
5640
|
+
this.emit("lease:lost", parsed.data);
|
|
5177
5641
|
}
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
"
|
|
5186
|
-
payload,
|
|
5187
|
-
taskProgressAckSchema
|
|
5188
|
-
);
|
|
5189
|
-
if (!result.ok) return errorResult(result.message);
|
|
5190
|
-
if (!result.data.ok) return errorResult("\u0421\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u0437\u0430\u043F\u0438\u0441\u044C \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 (ok:false).");
|
|
5191
|
-
return textResult("\u0417\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438.");
|
|
5192
|
-
}
|
|
5193
|
-
|
|
5194
|
-
// src/git/result.ts
|
|
5195
|
-
function gitOk(data) {
|
|
5196
|
-
return { ok: true, data };
|
|
5197
|
-
}
|
|
5198
|
-
function gitErr(message) {
|
|
5199
|
-
return { ok: false, message };
|
|
5200
|
-
}
|
|
5201
|
-
|
|
5202
|
-
// src/git/branch-name.ts
|
|
5203
|
-
var FORBIDDEN_CHARS = /[\s~^:?*[\\\x00-\x1f\x7f]/;
|
|
5204
|
-
function isValidGitBranchName(name) {
|
|
5205
|
-
if (name.length === 0 || name.length > 255) return false;
|
|
5206
|
-
if (name.startsWith("-")) return false;
|
|
5207
|
-
if (name.startsWith("/") || name.endsWith("/")) return false;
|
|
5208
|
-
if (name.endsWith(".")) return false;
|
|
5209
|
-
if (name.includes("..")) return false;
|
|
5210
|
-
if (name.includes("//")) return false;
|
|
5211
|
-
if (name.includes("@{")) return false;
|
|
5212
|
-
if (name === "@") return false;
|
|
5213
|
-
if (FORBIDDEN_CHARS.test(name)) return false;
|
|
5214
|
-
const segments = name.split("/");
|
|
5215
|
-
for (const segment of segments) {
|
|
5216
|
-
if (segment.length === 0) return false;
|
|
5217
|
-
if (segment.startsWith(".")) return false;
|
|
5218
|
-
if (segment.endsWith(".lock")) return false;
|
|
5642
|
+
handleModeSet(raw) {
|
|
5643
|
+
const parsed = modeSetPayloadSchema.safeParse(raw);
|
|
5644
|
+
if (!parsed.success) {
|
|
5645
|
+
this.emit("client-error", new Error(`mode:set: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`));
|
|
5646
|
+
return;
|
|
5647
|
+
}
|
|
5648
|
+
updateState({ mode: parsed.data.mode }, this.opts.stateFilePath);
|
|
5649
|
+
this.emit("mode:set", parsed.data);
|
|
5219
5650
|
}
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5651
|
+
handleAgentPause(raw) {
|
|
5652
|
+
const parsed = agentPausePayloadSchema.safeParse(raw);
|
|
5653
|
+
if (!parsed.success) {
|
|
5654
|
+
this.emit(
|
|
5655
|
+
"client-error",
|
|
5656
|
+
new Error(`agent:pause: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5657
|
+
);
|
|
5658
|
+
return;
|
|
5659
|
+
}
|
|
5660
|
+
this.paused = true;
|
|
5661
|
+
this.pauseReasonText = parsed.data.reason;
|
|
5662
|
+
this.emit("pause", parsed.data);
|
|
5227
5663
|
}
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
[...args],
|
|
5240
|
-
{
|
|
5241
|
-
cwd: options.cwd,
|
|
5242
|
-
shell: false,
|
|
5243
|
-
timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
5244
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
5245
|
-
env: {
|
|
5246
|
-
...process.env,
|
|
5247
|
-
...options.env,
|
|
5248
|
-
// Без человека за терминалом: если git вдруг решит спросить
|
|
5249
|
-
// логин/пароль, зависший prompt выглядел бы как неожиданное
|
|
5250
|
-
// поведение (требование подзадачи: «любая неожиданность —
|
|
5251
|
-
// понятный отказ, а не попробую починить»), а не как честный
|
|
5252
|
-
// отказ команды с кодом возврата.
|
|
5253
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
5254
|
-
}
|
|
5255
|
-
},
|
|
5256
|
-
(error, stdout, stderr) => {
|
|
5257
|
-
if (!error) {
|
|
5258
|
-
resolvePromise({ ok: true, code: 0, signal: null, stdout, stderr });
|
|
5259
|
-
return;
|
|
5260
|
-
}
|
|
5261
|
-
const code = typeof error.code === "number" ? error.code : null;
|
|
5262
|
-
resolvePromise({
|
|
5263
|
-
ok: false,
|
|
5264
|
-
code,
|
|
5265
|
-
signal: error.signal ?? null,
|
|
5266
|
-
stdout,
|
|
5267
|
-
// Если git ничего не написал в stderr (например, сам процесс не
|
|
5268
|
-
// запустился — ENOENT), берём текст ошибки Node, чтобы вызывающий
|
|
5269
|
-
// код не остался с пустым сообщением.
|
|
5270
|
-
stderr: stderr.length > 0 ? stderr : error.message
|
|
5271
|
-
});
|
|
5272
|
-
}
|
|
5273
|
-
);
|
|
5274
|
-
});
|
|
5275
|
-
}
|
|
5276
|
-
function describeGitFailure(commandLabel, result) {
|
|
5277
|
-
const stderr = result.stderr.trim();
|
|
5278
|
-
const codeText = result.code === null ? "" : ` (\u043A\u043E\u0434 ${result.code})`;
|
|
5279
|
-
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}`;
|
|
5280
|
-
}
|
|
5281
|
-
|
|
5282
|
-
// src/git/repo.ts
|
|
5283
|
-
async function localBranchExists(repoRoot, branch) {
|
|
5284
|
-
const result = await runGit(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
5285
|
-
cwd: repoRoot
|
|
5286
|
-
});
|
|
5287
|
-
return result.ok;
|
|
5288
|
-
}
|
|
5289
|
-
async function getHeadSha(worktreeDir) {
|
|
5290
|
-
const result = await runGit(["rev-parse", "HEAD"], { cwd: worktreeDir });
|
|
5291
|
-
if (!result.ok) {
|
|
5292
|
-
return gitErr(describeGitFailure("git rev-parse HEAD", result));
|
|
5664
|
+
handleTrustUpdated(raw) {
|
|
5665
|
+
const parsed = trustUpdatedPayloadSchema.safeParse(raw);
|
|
5666
|
+
if (!parsed.success) {
|
|
5667
|
+
this.emit(
|
|
5668
|
+
"client-error",
|
|
5669
|
+
new Error(`trust:updated: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5670
|
+
);
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5673
|
+
updateState({ trust: toTrustCache(parsed.data, this.opts.now) }, this.opts.stateFilePath);
|
|
5674
|
+
this.emit("trust:updated", parsed.data);
|
|
5293
5675
|
}
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: worktreeDir });
|
|
5298
|
-
if (!result.ok) {
|
|
5299
|
-
return gitErr(describeGitFailure("git rev-parse --abbrev-ref HEAD", result));
|
|
5676
|
+
handleTaskUpdated(raw) {
|
|
5677
|
+
const parsed = taskUpdatedAgentPayloadSchema.safeParse(raw);
|
|
5678
|
+
if (parsed.success) this.emit("task:updated", parsed.data);
|
|
5300
5679
|
}
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
// src/git/status.ts
|
|
5305
|
-
var CONFLICT_CODES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
5306
|
-
async function getWorktreeStatus(worktreeDir) {
|
|
5307
|
-
const result = await runGit(["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
5308
|
-
cwd: worktreeDir
|
|
5309
|
-
});
|
|
5310
|
-
if (!result.ok) {
|
|
5311
|
-
return gitErr(describeGitFailure("git status", result));
|
|
5680
|
+
handleQuestionAnswered(raw) {
|
|
5681
|
+
const parsed = questionAnsweredPayloadSchema.safeParse(raw);
|
|
5682
|
+
if (parsed.success) this.emit("question:answered", parsed.data);
|
|
5312
5683
|
}
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5684
|
+
/**
|
|
5685
|
+
* §14.4 ТЗ, п.4-5 — асинхронный путь (в дополнение к синхронному в
|
|
5686
|
+
* `mcp/request-approval.ts::settleApproval`, который срабатывает, когда
|
|
5687
|
+
* решение приходит в рамках ОДНОГО вызова `request_approval`/
|
|
5688
|
+
* `wait_approval`). Этот пуш ловит случай, когда решение пришло уже
|
|
5689
|
+
* ПОСЛЕ того, как собственный long-poll тула истёк (вернул `PENDING`),
|
|
5690
|
+
* а модель ещё не успела/не стала звать `wait_approval` снова — на
|
|
5691
|
+
* постоянном соединении (`quiel mcp`, единственный держатель ЭТОГО
|
|
5692
|
+
* класса) `approval:decided` долетит и без явного повторного вызова.
|
|
5693
|
+
*
|
|
5694
|
+
* `approvalDecidedPayloadSchema` несёт только `approvalId`+`status`+
|
|
5695
|
+
* `oneTimeToken?` — НЕ `command`/`cwd` (провод их не отдаёт, см.
|
|
5696
|
+
* `state/schema.ts::PendingApprovalSchema`), поэтому command+cwd берутся
|
|
5697
|
+
* из `state.pendingApprovals[approvalId]`, куда их кладёт
|
|
5698
|
+
* `request_approval` сразу после создания запроса. Если записи там нет
|
|
5699
|
+
* (approval создан не этим клиентом/до перезапуска процесса, либо уже
|
|
5700
|
+
* обработан) — писать пока нечего, `pendingApprovals` и так пуст.
|
|
5701
|
+
*/
|
|
5702
|
+
handleApprovalDecided(raw) {
|
|
5703
|
+
const parsed = approvalDecidedPayloadSchema.safeParse(raw);
|
|
5704
|
+
if (!parsed.success) {
|
|
5705
|
+
this.emit(
|
|
5706
|
+
"client-error",
|
|
5707
|
+
new Error(`approval:decided: \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 payload: ${parsed.error.message}`)
|
|
5708
|
+
);
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
this.applyApprovalDecided(parsed.data);
|
|
5712
|
+
this.emit("approval:decided", parsed.data);
|
|
5323
5713
|
}
|
|
5324
|
-
|
|
5714
|
+
applyApprovalDecided(payload) {
|
|
5715
|
+
const state = readState(this.opts.stateFilePath);
|
|
5716
|
+
const pending = state.pendingApprovals[payload.approvalId];
|
|
5717
|
+
if (!pending) return;
|
|
5718
|
+
if (payload.status === ApprovalStatus.APPROVED && payload.oneTimeToken) {
|
|
5719
|
+
const approvalTtlMin = state.trust?.rules.approvalTtlMin ?? 60;
|
|
5720
|
+
const expiresAt = new Date(this.opts.now().getTime() + approvalTtlMin * 6e4).toISOString();
|
|
5721
|
+
updateState(
|
|
5722
|
+
{
|
|
5723
|
+
...storeApprovedCommand(
|
|
5724
|
+
state,
|
|
5725
|
+
{ command: pending.command, cwd: pending.cwd, token: payload.oneTimeToken, expiresAt },
|
|
5726
|
+
this.opts.now
|
|
5727
|
+
),
|
|
5728
|
+
...clearPendingApproval(state, payload.approvalId)
|
|
5729
|
+
},
|
|
5730
|
+
this.opts.stateFilePath
|
|
5731
|
+
);
|
|
5732
|
+
return;
|
|
5733
|
+
}
|
|
5734
|
+
updateState(clearPendingApproval(state, payload.approvalId), this.opts.stateFilePath);
|
|
5735
|
+
}
|
|
5736
|
+
};
|
|
5737
|
+
function trimTrailingSlash2(url) {
|
|
5738
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5325
5739
|
}
|
|
5326
5740
|
|
|
5327
|
-
// src/
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
return
|
|
5331
|
-
|
|
5332
|
-
${buildTaskTrailer(params.taskKey)}
|
|
5333
|
-
`;
|
|
5741
|
+
// src/socket/send-agent-mode.ts
|
|
5742
|
+
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
5743
|
+
function trimTrailingSlash3(url) {
|
|
5744
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
5334
5745
|
}
|
|
5335
|
-
async function
|
|
5336
|
-
const
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5746
|
+
async function sendAgentMode(options) {
|
|
5747
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
5748
|
+
const factory = options.createSocket ?? createSocket;
|
|
5749
|
+
const socket = factory(`${trimTrailingSlash3(options.url)}/agent`, {
|
|
5750
|
+
auth: { token: options.token },
|
|
5751
|
+
autoConnect: false,
|
|
5752
|
+
reconnection: false
|
|
5753
|
+
});
|
|
5754
|
+
try {
|
|
5755
|
+
await connectOnce(socket, timeoutMs);
|
|
5756
|
+
const payload = agentModePayloadSchema.parse({ mode: options.mode });
|
|
5757
|
+
const raw = await emitWithAckTimeout(socket, "agent:mode", payload, timeoutMs);
|
|
5758
|
+
if (isAckError(raw)) {
|
|
5759
|
+
return { ok: false, message: `${raw.error.code}: ${raw.error.message}` };
|
|
5760
|
+
}
|
|
5761
|
+
const parsed = agentModeAckSchema.safeParse(raw);
|
|
5762
|
+
if (!parsed.success) {
|
|
5763
|
+
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}` };
|
|
5764
|
+
}
|
|
5765
|
+
if (!parsed.data.ok) {
|
|
5766
|
+
return { ok: false, message: "\u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B agent:mode (ok:false)" };
|
|
5767
|
+
}
|
|
5768
|
+
return { ok: true, mode: parsed.data.mode ?? options.mode };
|
|
5769
|
+
} catch (err) {
|
|
5770
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
5771
|
+
} finally {
|
|
5772
|
+
socket.disconnect();
|
|
5349
5773
|
}
|
|
5350
|
-
const sha = await getHeadSha(params.worktreeDir);
|
|
5351
|
-
return gitOk({ committed: true, sha: sha.ok ? sha.data : null });
|
|
5352
5774
|
}
|
|
5353
5775
|
|
|
5354
|
-
// src/
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
import { dirname as dirname4, isAbsolute, join as join9, relative, resolve as resolve2 } from "path";
|
|
5358
|
-
var WORKTREES_RELATIVE_ROOT = join9(".claude", "worktrees");
|
|
5359
|
-
function getDefaultWorktreesRoot(repoRoot) {
|
|
5360
|
-
return join9(repoRoot, WORKTREES_RELATIVE_ROOT);
|
|
5776
|
+
// src/mcp/helpers.ts
|
|
5777
|
+
function textResult(text) {
|
|
5778
|
+
return { content: [{ type: "text", text }] };
|
|
5361
5779
|
}
|
|
5362
|
-
function
|
|
5363
|
-
|
|
5364
|
-
const repoHash = createHash2("sha1").update(resolve2(repoRoot)).digest("hex").slice(0, 12);
|
|
5365
|
-
return join9(worktreesRoot, repoHash, branchName);
|
|
5780
|
+
function errorResult(text) {
|
|
5781
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
5366
5782
|
}
|
|
5367
|
-
function
|
|
5368
|
-
|
|
5369
|
-
|
|
5783
|
+
function leaseLostResult(taskLabel2, reason) {
|
|
5784
|
+
return errorResult(
|
|
5785
|
+
`\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.`
|
|
5786
|
+
);
|
|
5370
5787
|
}
|
|
5371
|
-
function
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
return resolve2(path);
|
|
5376
|
-
}
|
|
5788
|
+
function describeZodIssues(error) {
|
|
5789
|
+
return error.issues.map(
|
|
5790
|
+
(issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message
|
|
5791
|
+
).join("; ");
|
|
5377
5792
|
}
|
|
5378
|
-
|
|
5379
|
-
const
|
|
5380
|
-
if (!
|
|
5381
|
-
return
|
|
5382
|
-
}
|
|
5383
|
-
const entries = [];
|
|
5384
|
-
let path;
|
|
5385
|
-
let branch = null;
|
|
5386
|
-
const flush = () => {
|
|
5387
|
-
if (path) entries.push({ path, branch });
|
|
5388
|
-
path = void 0;
|
|
5389
|
-
branch = null;
|
|
5390
|
-
};
|
|
5391
|
-
for (const line of result.stdout.split("\n")) {
|
|
5392
|
-
if (line.length === 0) {
|
|
5393
|
-
flush();
|
|
5394
|
-
continue;
|
|
5395
|
-
}
|
|
5396
|
-
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5397
|
-
else if (line.startsWith("branch refs/heads/")) branch = line.slice("branch refs/heads/".length);
|
|
5793
|
+
function parseArgsOrError(schema, raw) {
|
|
5794
|
+
const parsed = schema.safeParse(raw);
|
|
5795
|
+
if (!parsed.success) {
|
|
5796
|
+
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)}` };
|
|
5398
5797
|
}
|
|
5399
|
-
|
|
5400
|
-
return gitOk(entries);
|
|
5798
|
+
return { ok: true, data: parsed.data };
|
|
5401
5799
|
}
|
|
5402
|
-
async function
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
if (!list.ok) return list;
|
|
5412
|
-
const existing = list.data.find((wt) => canonicalPath(wt.path) === canonicalPath(worktreeDir));
|
|
5413
|
-
if (existing) {
|
|
5414
|
-
if (existing.branch === params.branchName) {
|
|
5415
|
-
return gitOk({ worktreeDir, reused: true });
|
|
5416
|
-
}
|
|
5417
|
-
return gitErr(
|
|
5418
|
-
`\u041F\u043E \u043F\u0443\u0442\u0438 ${worktreeDir} \u0443\u0436\u0435 \u0435\u0441\u0442\u044C git worktree \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 "${existing.branch ?? "(\u043E\u0442\u0441\u043E\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u044B\u0439 HEAD)"}", \u0430 \u043E\u0436\u0438\u0434\u0430\u043B\u0430\u0441\u044C "${params.branchName}". \u0420\u0430\u0437\u0431\u0435\u0440\u0438\u0441\u044C \u0440\u0443\u043A\u0430\u043C\u0438: "git -C ${params.repoRoot} worktree remove ${worktreeDir}" (\u0434\u043E\u0431\u0430\u0432\u044C "--force", \u0435\u0441\u043B\u0438 \u0442\u0430\u043C \u043D\u0435\u0442 \u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u043E\u0433\u043E \u0432\u0430\u0436\u043D\u043E\u0433\u043E), \u0437\u0430\u0442\u0435\u043C \u043F\u043E\u0432\u0442\u043E\u0440\u0438.`
|
|
5419
|
-
);
|
|
5420
|
-
}
|
|
5421
|
-
if (existsSync9(worktreeDir)) {
|
|
5422
|
-
return gitErr(
|
|
5423
|
-
`\u041F\u0443\u0442\u044C ${worktreeDir} \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E git \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u0435\u0433\u043E \u0441\u0432\u043E\u0435\u0439 worktree. \u0423\u0431\u0435\u0440\u0438 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u2014 \u0442\u0440\u043E\u0433\u0430\u0442\u044C \u0447\u0443\u0436\u043E\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E.`
|
|
5424
|
-
);
|
|
5425
|
-
}
|
|
5426
|
-
await runGit(["worktree", "prune"], { cwd: params.repoRoot });
|
|
5427
|
-
const fetchResult = await runGit(["fetch", remote, params.defaultBranch], {
|
|
5428
|
-
cwd: params.repoRoot
|
|
5429
|
-
});
|
|
5430
|
-
if (!fetchResult.ok) {
|
|
5431
|
-
return gitErr(describeGitFailure(`git fetch ${remote} ${params.defaultBranch}`, fetchResult));
|
|
5800
|
+
async function callServerOrError(client, event, payload, schema, timeoutMs) {
|
|
5801
|
+
let raw;
|
|
5802
|
+
try {
|
|
5803
|
+
raw = await client.callServer(event, payload, timeoutMs);
|
|
5804
|
+
} catch (err) {
|
|
5805
|
+
return {
|
|
5806
|
+
ok: false,
|
|
5807
|
+
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.`
|
|
5808
|
+
};
|
|
5432
5809
|
}
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
return gitErr(describeGitFailure("git worktree add", addResult));
|
|
5810
|
+
if (isAckError(raw)) {
|
|
5811
|
+
return {
|
|
5812
|
+
ok: false,
|
|
5813
|
+
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}`
|
|
5814
|
+
};
|
|
5439
5815
|
}
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5816
|
+
const parsed = schema.safeParse(raw);
|
|
5817
|
+
if (!parsed.success) {
|
|
5818
|
+
return {
|
|
5819
|
+
ok: false,
|
|
5820
|
+
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}`
|
|
5821
|
+
};
|
|
5446
5822
|
}
|
|
5447
|
-
return
|
|
5823
|
+
return { ok: true, data: parsed.data };
|
|
5448
5824
|
}
|
|
5449
5825
|
|
|
5450
|
-
// src/
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5826
|
+
// src/mcp/ask.ts
|
|
5827
|
+
var askHumanInputShape = {
|
|
5828
|
+
textMd: questionAskPayloadSchema.shape.textMd.describe(
|
|
5829
|
+
"\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."
|
|
5830
|
+
),
|
|
5831
|
+
targetRole: questionAskPayloadSchema.shape.targetRole.describe(
|
|
5832
|
+
'\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.'
|
|
5833
|
+
),
|
|
5834
|
+
targetMemberId: questionAskPayloadSchema.shape.targetMemberId.describe(
|
|
5835
|
+
"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."
|
|
5836
|
+
),
|
|
5837
|
+
wantsFiles: questionAskPayloadSchema.shape.wantsFiles.describe(
|
|
5838
|
+
"\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."
|
|
5839
|
+
),
|
|
5840
|
+
options: questionAskPayloadSchema.shape.options.describe(
|
|
5841
|
+
"\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."
|
|
5842
|
+
),
|
|
5843
|
+
blocking: questionAskPayloadSchema.shape.blocking.describe(
|
|
5844
|
+
'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).'
|
|
5845
|
+
)
|
|
5846
|
+
};
|
|
5847
|
+
var BLOCKING_WAIT_SEC = 600;
|
|
5848
|
+
var ASK_ACK_BUFFER_MS = 15e3;
|
|
5849
|
+
async function askHumanHandler(args, runtime) {
|
|
5850
|
+
const state = readState(runtime.stateFilePath);
|
|
5851
|
+
const taskId = state.currentTask?.taskId;
|
|
5852
|
+
const waitSec = args.blocking ? BLOCKING_WAIT_SEC : 0;
|
|
5853
|
+
const parsedArgs = parseArgsOrError(questionAskPayloadSchema, {
|
|
5854
|
+
...taskId ? { taskId } : {},
|
|
5855
|
+
textMd: args.textMd,
|
|
5856
|
+
...args.targetRole ? { targetRole: args.targetRole } : {},
|
|
5857
|
+
...args.targetMemberId ? { targetMemberId: args.targetMemberId } : {},
|
|
5858
|
+
...args.options && args.options.length > 0 ? { options: args.options } : {},
|
|
5859
|
+
...args.wantsFiles ? { wantsFiles: true } : {},
|
|
5860
|
+
blocking: args.blocking,
|
|
5861
|
+
waitSec
|
|
5463
5862
|
});
|
|
5464
|
-
if (
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
if (status.ok && status.data.conflictedPaths.length > 0) {
|
|
5469
|
-
const conflictedFiles = status.data.conflictedPaths;
|
|
5470
|
-
const abortResult = await runGit(["rebase", "--abort"], { cwd: params.worktreeDir });
|
|
5471
|
-
if (!abortResult.ok) {
|
|
5472
|
-
return gitErr(
|
|
5473
|
-
`\u041A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043F\u0440\u0438 rebase (${conflictedFiles.join(", ")}), \u0438 "git rebase --abort" \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u043B: ${describeGitFailure("git rebase --abort", abortResult)}. Worktree \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u043D\u0443\u0436\u043D\u043E \u0440\u0443\u0447\u043D\u043E\u0435 \u0432\u043C\u0435\u0448\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E.`
|
|
5474
|
-
);
|
|
5475
|
-
}
|
|
5476
|
-
return gitOk({ outcome: "conflict", conflictedFiles });
|
|
5477
|
-
}
|
|
5478
|
-
return gitErr(describeGitFailure("git rebase", rebaseResult));
|
|
5479
|
-
}
|
|
5480
|
-
async function assertReadyToPush(params) {
|
|
5481
|
-
const branch = await currentBranch(params.worktreeDir);
|
|
5482
|
-
if (!branch.ok) return branch;
|
|
5483
|
-
if (branch.data !== params.branchName) {
|
|
5484
|
-
return gitErr(
|
|
5485
|
-
`\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.`
|
|
5486
|
-
);
|
|
5487
|
-
}
|
|
5488
|
-
const status = await getWorktreeStatus(params.worktreeDir);
|
|
5489
|
-
if (!status.ok) return status;
|
|
5490
|
-
if (!status.data.clean) {
|
|
5491
|
-
return gitErr(
|
|
5492
|
-
`\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.`
|
|
5493
|
-
);
|
|
5863
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5864
|
+
if (taskId) {
|
|
5865
|
+
const lease = runtime.client.isLeaseLost(taskId);
|
|
5866
|
+
if (lease.lost) return leaseLostResult(state.currentTask?.taskKey ?? taskId, lease.reason);
|
|
5494
5867
|
}
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
if (!
|
|
5503
|
-
const
|
|
5504
|
-
if (
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
if (!pushResult.ok) {
|
|
5509
|
-
return gitErr(
|
|
5510
|
-
describeGitFailure(`git push ${remote} HEAD:refs/heads/${params.defaultBranch}`, pushResult)
|
|
5868
|
+
const result = await callServerOrError(
|
|
5869
|
+
runtime.client,
|
|
5870
|
+
"question:ask",
|
|
5871
|
+
parsedArgs.data,
|
|
5872
|
+
questionAskAckSchema,
|
|
5873
|
+
waitSec * 1e3 + ASK_ACK_BUFFER_MS
|
|
5874
|
+
);
|
|
5875
|
+
if (!result.ok) return errorResult(result.message);
|
|
5876
|
+
const settled = describeSettledQuestion(result.data.question);
|
|
5877
|
+
if (settled) return textResult(settled);
|
|
5878
|
+
if (args.blocking) {
|
|
5879
|
+
return textResult(
|
|
5880
|
+
`\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.`
|
|
5511
5881
|
);
|
|
5512
5882
|
}
|
|
5513
|
-
return
|
|
5883
|
+
return textResult(
|
|
5884
|
+
`\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}".`
|
|
5885
|
+
);
|
|
5514
5886
|
}
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
5529
|
-
worktreeDir = worktree.data.worktreeDir;
|
|
5530
|
-
}
|
|
5531
|
-
const commit = await commitAll({
|
|
5532
|
-
worktreeDir,
|
|
5533
|
-
commitPrefix: params.commitPrefix,
|
|
5534
|
-
title: params.taskTitle,
|
|
5535
|
-
taskKey: params.taskKey
|
|
5536
|
-
});
|
|
5537
|
-
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5538
|
-
const rebase = await rebaseOntoDefault({
|
|
5539
|
-
worktreeDir,
|
|
5540
|
-
defaultBranch: params.defaultBranch,
|
|
5541
|
-
remote
|
|
5887
|
+
var waitAnswerInputShape = {
|
|
5888
|
+
questionId: questionWaitPayloadSchema.shape.questionId.describe(
|
|
5889
|
+
"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."
|
|
5890
|
+
),
|
|
5891
|
+
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.")
|
|
5892
|
+
};
|
|
5893
|
+
var DEFAULT_WAIT_ANSWER_SEC = 300;
|
|
5894
|
+
var WAIT_ANSWER_ACK_BUFFER_MS = 15e3;
|
|
5895
|
+
async function waitAnswerHandler(args, runtime) {
|
|
5896
|
+
const waitSec = args.waitSec ?? DEFAULT_WAIT_ANSWER_SEC;
|
|
5897
|
+
const parsedArgs = parseArgsOrError(questionWaitPayloadSchema, {
|
|
5898
|
+
questionId: args.questionId,
|
|
5899
|
+
waitSec
|
|
5542
5900
|
});
|
|
5543
|
-
if (!
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
if (
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5901
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
5902
|
+
const result = await callServerOrError(
|
|
5903
|
+
runtime.client,
|
|
5904
|
+
"question:wait",
|
|
5905
|
+
parsedArgs.data,
|
|
5906
|
+
questionWaitAckSchema,
|
|
5907
|
+
waitSec * 1e3 + WAIT_ANSWER_ACK_BUFFER_MS
|
|
5908
|
+
);
|
|
5909
|
+
if (!result.ok) return errorResult(result.message);
|
|
5910
|
+
const settled = describeSettledQuestion(result.data.question);
|
|
5911
|
+
if (settled) return textResult(settled);
|
|
5912
|
+
return textResult(
|
|
5913
|
+
`\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.`
|
|
5914
|
+
);
|
|
5915
|
+
}
|
|
5916
|
+
function describeAnswerAttachments(question) {
|
|
5917
|
+
if (question.attachments.length === 0) return "";
|
|
5918
|
+
const lines = question.attachments.map(
|
|
5919
|
+
(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`
|
|
5920
|
+
);
|
|
5921
|
+
return `
|
|
5922
|
+
|
|
5923
|
+
\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.
|
|
5924
|
+
` + lines.join("\n");
|
|
5925
|
+
}
|
|
5926
|
+
function describeSettledQuestion(question) {
|
|
5927
|
+
if (question.status === QuestionStatus.ANSWERED) {
|
|
5928
|
+
return `\u0412\u043E\u043F\u0440\u043E\u0441 ${question.id} \u043F\u043E\u043B\u0443\u0447\u0438\u043B \u043E\u0442\u0432\u0435\u0442:
|
|
5929
|
+
${question.answerMd ?? ""}${describeAnswerAttachments(question)}`;
|
|
5559
5930
|
}
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
branchName: params.branchName,
|
|
5563
|
-
defaultBranch: params.defaultBranch,
|
|
5564
|
-
remote
|
|
5565
|
-
});
|
|
5566
|
-
if (!push.ok) return { status: "refused", worktreeDir, message: push.message };
|
|
5567
|
-
const sha = await getHeadSha(worktreeDir);
|
|
5568
|
-
let worktreeRemoved = false;
|
|
5569
|
-
if (params.removeWorktreeOnSuccess !== false) {
|
|
5570
|
-
const removed = await removeWorktree(params.repoRoot, worktreeDir);
|
|
5571
|
-
if (removed.ok) {
|
|
5572
|
-
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
5573
|
-
worktreeRemoved = true;
|
|
5574
|
-
}
|
|
5931
|
+
if (question.status === QuestionStatus.DISMISSED) {
|
|
5932
|
+
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.`;
|
|
5575
5933
|
}
|
|
5576
|
-
return
|
|
5577
|
-
status: "pushed",
|
|
5578
|
-
worktreeDir,
|
|
5579
|
-
commitSha: sha.ok ? sha.data : null,
|
|
5580
|
-
pushedTo: push.data.pushedTo,
|
|
5581
|
-
worktreeRemoved
|
|
5582
|
-
};
|
|
5934
|
+
return void 0;
|
|
5583
5935
|
}
|
|
5584
5936
|
|
|
5585
|
-
//
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5937
|
+
// package.json
|
|
5938
|
+
var package_default = {
|
|
5939
|
+
name: "@quiel/cli",
|
|
5940
|
+
version: "0.6.0",
|
|
5941
|
+
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",
|
|
5942
|
+
homepage: "https://quiel.app",
|
|
5943
|
+
repository: {
|
|
5944
|
+
type: "git",
|
|
5945
|
+
url: "git+https://github.com/Quiel-App/quiel-cli.git"
|
|
5946
|
+
},
|
|
5947
|
+
bugs: {
|
|
5948
|
+
url: "https://github.com/Quiel-App/quiel-cli/issues"
|
|
5949
|
+
},
|
|
5950
|
+
keywords: [
|
|
5951
|
+
"quiel",
|
|
5952
|
+
"claude-code",
|
|
5953
|
+
"codex",
|
|
5954
|
+
"cursor",
|
|
5955
|
+
"opencode",
|
|
5956
|
+
"mcp",
|
|
5957
|
+
"mcp-server",
|
|
5958
|
+
"ai-agents",
|
|
5959
|
+
"task-tracker"
|
|
5960
|
+
],
|
|
5961
|
+
license: "Apache-2.0",
|
|
5962
|
+
type: "module",
|
|
5963
|
+
bin: {
|
|
5964
|
+
quiel: "dist/cli.js"
|
|
5965
|
+
},
|
|
5966
|
+
files: [
|
|
5967
|
+
"dist",
|
|
5968
|
+
"README.md",
|
|
5969
|
+
"LICENSE"
|
|
5970
|
+
],
|
|
5971
|
+
engines: {
|
|
5972
|
+
node: ">=22"
|
|
5973
|
+
},
|
|
5974
|
+
publishConfig: {
|
|
5975
|
+
access: "public"
|
|
5976
|
+
},
|
|
5977
|
+
scripts: {
|
|
5978
|
+
build: "tsup",
|
|
5979
|
+
dev: "tsup --watch",
|
|
5980
|
+
lint: "biome check .",
|
|
5981
|
+
typecheck: "tsc --noEmit",
|
|
5982
|
+
test: "vitest run --passWithNoTests"
|
|
5983
|
+
},
|
|
5984
|
+
dependencies: {
|
|
5985
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
5986
|
+
commander: "15.0.0",
|
|
5987
|
+
"socket.io-client": "4.8.3",
|
|
5988
|
+
zod: "4.5.4"
|
|
5989
|
+
},
|
|
5990
|
+
devDependencies: {
|
|
5991
|
+
"@quiel/config": "workspace:*",
|
|
5992
|
+
"@quiel/shared": "workspace:*",
|
|
5993
|
+
"@types/node": "20.19.43",
|
|
5994
|
+
tsup: "8.5.1",
|
|
5995
|
+
typescript: "7.0.2",
|
|
5996
|
+
vitest: "5.0.0"
|
|
5997
|
+
}
|
|
5998
|
+
};
|
|
5999
|
+
|
|
6000
|
+
// src/version.ts
|
|
6001
|
+
var VERSION = package_default.version;
|
|
6002
|
+
|
|
6003
|
+
// src/mcp/constants.ts
|
|
6004
|
+
var MCP_SERVER_NAME = "quiel";
|
|
6005
|
+
var MCP_SERVER_VERSION = VERSION;
|
|
6006
|
+
|
|
6007
|
+
// src/mcp/get-context.ts
|
|
6008
|
+
var ALL_CONTEXT_INCLUDES = [
|
|
6009
|
+
"spec",
|
|
6010
|
+
"decisions",
|
|
6011
|
+
// ВСЕ документы проекта — по умолчанию, а не по особому запросу.
|
|
6012
|
+
// `spec`/`decisions` покрывают два типа из пяти, и человек, положивший
|
|
6013
|
+
// контекст под типом `DATA` или `PLAN`, вправе ожидать, что агент его
|
|
6014
|
+
// увидит. 14 сентября 2026 ровно этого и не случилось.
|
|
6015
|
+
"documents",
|
|
6016
|
+
"related",
|
|
6017
|
+
"repo"
|
|
6018
|
+
];
|
|
6019
|
+
var getContextInputShape = {
|
|
6020
|
+
taskId: contextGetPayloadSchema.shape.taskId.describe(
|
|
6021
|
+
"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)."
|
|
6022
|
+
),
|
|
6023
|
+
include: contextGetPayloadSchema.shape.include.optional().describe(
|
|
6024
|
+
"\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."
|
|
6025
|
+
)
|
|
6026
|
+
};
|
|
6027
|
+
async function getContextHandler(args, runtime) {
|
|
6028
|
+
const state = readState(runtime.stateFilePath);
|
|
6029
|
+
const taskId = args.taskId ?? state.currentTask?.taskId;
|
|
6030
|
+
if (taskId) {
|
|
6031
|
+
const lease = runtime.client.isLeaseLost(taskId);
|
|
6032
|
+
if (lease.lost) return leaseLostResult(taskId, lease.reason);
|
|
6033
|
+
}
|
|
6034
|
+
const include = args.include && args.include.length > 0 ? args.include : [...ALL_CONTEXT_INCLUDES];
|
|
6035
|
+
const payload = contextGetPayloadSchema.parse({ ...taskId ? { taskId } : {}, include });
|
|
6036
|
+
const result = await callServerOrError(
|
|
6037
|
+
runtime.client,
|
|
6038
|
+
"context:get",
|
|
6039
|
+
payload,
|
|
6040
|
+
contextPayloadSchema
|
|
5595
6041
|
);
|
|
5596
|
-
if (!
|
|
5597
|
-
|
|
5598
|
-
|
|
6042
|
+
if (!result.ok) return errorResult(result.message);
|
|
6043
|
+
return textResult(JSON.stringify(result.data, null, 2));
|
|
6044
|
+
}
|
|
6045
|
+
|
|
6046
|
+
// src/mcp/get-task.ts
|
|
6047
|
+
var getTaskInputShape = {
|
|
6048
|
+
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")
|
|
6049
|
+
};
|
|
6050
|
+
async function getTaskHandler(args, runtime) {
|
|
6051
|
+
const lease = runtime.client.isLeaseLost(args.taskId);
|
|
6052
|
+
if (lease.lost) return leaseLostResult(args.taskId, lease.reason);
|
|
6053
|
+
const payload = taskGetPayloadSchema.parse({ taskId: args.taskId });
|
|
6054
|
+
const result = await callServerOrError(runtime.client, "task:get", payload, taskGetAckSchema);
|
|
6055
|
+
if (!result.ok) return errorResult(result.message);
|
|
6056
|
+
return textResult(JSON.stringify(result.data, null, 2));
|
|
6057
|
+
}
|
|
6058
|
+
|
|
6059
|
+
// src/mcp/log.ts
|
|
6060
|
+
var logInputShape = {
|
|
6061
|
+
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"),
|
|
6062
|
+
level: taskProgressPayloadSchema.shape.level.optional().describe("debug | info | warn | error, \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E info")
|
|
6063
|
+
};
|
|
6064
|
+
async function logHandler(args, runtime) {
|
|
6065
|
+
const state = readState(runtime.stateFilePath);
|
|
6066
|
+
if (!state.currentTask) {
|
|
6067
|
+
return errorResult(
|
|
6068
|
+
"\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430 \u0432 \u0435\u0451 \u043B\u0435\u043D\u0442\u0443. \u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0437\u043E\u0432\u0438 next_task."
|
|
5599
6069
|
);
|
|
5600
6070
|
}
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
const remote = params.remote ?? "origin";
|
|
5605
|
-
let worktreeDir;
|
|
5606
|
-
if (params.worktreeDir) {
|
|
5607
|
-
worktreeDir = params.worktreeDir;
|
|
5608
|
-
} else {
|
|
5609
|
-
const worktree = await ensureWorktree({
|
|
5610
|
-
repoRoot: params.repoRoot,
|
|
5611
|
-
branchName: params.branchName,
|
|
5612
|
-
defaultBranch: params.defaultBranch,
|
|
5613
|
-
remote,
|
|
5614
|
-
worktreesRoot: params.worktreesRoot
|
|
5615
|
-
});
|
|
5616
|
-
if (!worktree.ok) return { status: "refused", message: worktree.message };
|
|
5617
|
-
worktreeDir = worktree.data.worktreeDir;
|
|
6071
|
+
const lease = runtime.client.isLeaseLost(state.currentTask.taskId);
|
|
6072
|
+
if (lease.lost) {
|
|
6073
|
+
return leaseLostResult(state.currentTask.taskKey ?? state.currentTask.taskId, lease.reason);
|
|
5618
6074
|
}
|
|
5619
|
-
const
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
taskKey: params.taskKey
|
|
5624
|
-
});
|
|
5625
|
-
if (!commit.ok) return { status: "refused", worktreeDir, message: commit.message };
|
|
5626
|
-
const rebase = await rebaseOntoDefault({
|
|
5627
|
-
worktreeDir,
|
|
5628
|
-
defaultBranch: params.defaultBranch,
|
|
5629
|
-
remote
|
|
6075
|
+
const payload = taskProgressPayloadSchema.parse({
|
|
6076
|
+
taskId: state.currentTask.taskId,
|
|
6077
|
+
note: args.message,
|
|
6078
|
+
...args.level ? { level: args.level } : {}
|
|
5630
6079
|
});
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
if (
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
await runGit(["branch", "-D", params.branchName], { cwd: params.repoRoot });
|
|
5641
|
-
worktreeRemoved = true;
|
|
5642
|
-
}
|
|
5643
|
-
}
|
|
5644
|
-
return {
|
|
5645
|
-
status: "pushed",
|
|
5646
|
-
worktreeDir,
|
|
5647
|
-
branchName: params.branchName,
|
|
5648
|
-
commitSha: sha.ok ? sha.data : null,
|
|
5649
|
-
pushedTo: push.data.pushedTo,
|
|
5650
|
-
conflicted,
|
|
5651
|
-
conflictedFiles: rebase.data.conflictedFiles,
|
|
5652
|
-
worktreeRemoved
|
|
5653
|
-
};
|
|
6080
|
+
const result = await callServerOrError(
|
|
6081
|
+
runtime.client,
|
|
6082
|
+
"task:progress",
|
|
6083
|
+
payload,
|
|
6084
|
+
taskProgressAckSchema
|
|
6085
|
+
);
|
|
6086
|
+
if (!result.ok) return errorResult(result.message);
|
|
6087
|
+
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).");
|
|
6088
|
+
return textResult("\u0417\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u043B\u0435\u043D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0438.");
|
|
5654
6089
|
}
|
|
5655
6090
|
|
|
5656
6091
|
// src/mcp/task-worktree.ts
|
|
@@ -5688,7 +6123,7 @@ var nextTaskInputShape = {
|
|
|
5688
6123
|
"\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438, 0..600 (\xA711.1 \u0422\u0417). \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 300 \u2014 \u043A\u0430\u043A claimTimeoutSec \u0434\u0438\u0441\u043F\u0435\u0442\u0447\u0435\u0440\u0430 (\xA79.1 \u0422\u0417)."
|
|
5689
6124
|
)
|
|
5690
6125
|
};
|
|
5691
|
-
var DEFAULT_WAIT_SEC =
|
|
6126
|
+
var DEFAULT_WAIT_SEC = 90;
|
|
5692
6127
|
var CLAIM_ACK_BUFFER_MS = 15e3;
|
|
5693
6128
|
async function nextTaskHandler(args, runtime) {
|
|
5694
6129
|
const { client, stateFilePath } = runtime;
|
|
@@ -5714,8 +6149,9 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5714
6149
|
);
|
|
5715
6150
|
if (!claimed.ok) return errorResult(claimed.message);
|
|
5716
6151
|
if ("empty" in claimed.data) {
|
|
6152
|
+
updateState({ queueEmptyAt: (/* @__PURE__ */ new Date()).toISOString() }, stateFilePath);
|
|
5717
6153
|
return textResult(
|
|
5718
|
-
`\u0417\u0430\u0434\u0430\u0447 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 (\u0436\u0434\u0430\u043B ${waitSec} \u0441). \u042D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434, \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430 \
|
|
6154
|
+
`\u0417\u0430\u0434\u0430\u0447 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 (\u0436\u0434\u0430\u043B ${waitSec} \u0441). \u042D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434, \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430. \u041C\u043E\u0436\u0435\u0448\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0445\u043E\u0434 \u2014 \u0445\u0443\u043A \u0442\u0435\u0431\u044F \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442, \u043F\u043E\u043A\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u0443\u0441\u0442\u0430.`
|
|
5719
6155
|
);
|
|
5720
6156
|
}
|
|
5721
6157
|
const task = claimed.data;
|
|
@@ -5725,8 +6161,13 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5725
6161
|
taskId: task.task.id,
|
|
5726
6162
|
taskKey: task.task.key,
|
|
5727
6163
|
resumeToken: task.lease.resumeToken,
|
|
5728
|
-
gitMode: task.project.gitMode
|
|
5729
|
-
|
|
6164
|
+
gitMode: task.project.gitMode,
|
|
6165
|
+
branchName: task.project.branchName,
|
|
6166
|
+
commitPrefix: task.project.commitPrefix
|
|
6167
|
+
},
|
|
6168
|
+
// Очередь оказалась не пуста — пометка снимается, иначе хук
|
|
6169
|
+
// продолжал бы считать, что работы нет.
|
|
6170
|
+
queueEmptyAt: null
|
|
5730
6171
|
},
|
|
5731
6172
|
stateFilePath
|
|
5732
6173
|
);
|
|
@@ -5738,7 +6179,12 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5738
6179
|
const worktree = await ensureWorktree({
|
|
5739
6180
|
repoRoot: runtime.cwd,
|
|
5740
6181
|
branchName: plan.branchName,
|
|
5741
|
-
defaultBranch: plan.defaultBranch
|
|
6182
|
+
defaultBranch: plan.defaultBranch,
|
|
6183
|
+
// LIM-03: задачу уже начинали и прервали — рабочее дерево заводим
|
|
6184
|
+
// ОТ ТОЙ ВЕТКИ, а не от основной. Иначе спасённая работа осталась
|
|
6185
|
+
// бы лежать на origin никем не прочитанной, и агент сделал бы её
|
|
6186
|
+
// заново, потратив свой лимит подписки на чужую.
|
|
6187
|
+
continueFromBranch: task.continueFrom?.branchName
|
|
5742
6188
|
});
|
|
5743
6189
|
if (worktree.ok) {
|
|
5744
6190
|
updateState(
|
|
@@ -5748,6 +6194,8 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5748
6194
|
taskKey: task.task.key,
|
|
5749
6195
|
resumeToken: task.lease.resumeToken,
|
|
5750
6196
|
gitMode: task.project.gitMode,
|
|
6197
|
+
branchName: task.project.branchName,
|
|
6198
|
+
commitPrefix: task.project.commitPrefix,
|
|
5751
6199
|
worktreeDir: worktree.data.worktreeDir
|
|
5752
6200
|
}
|
|
5753
6201
|
},
|
|
@@ -5766,11 +6214,84 @@ async function nextTaskHandler(args, runtime) {
|
|
|
5766
6214
|
const warning = !started.ok ? `
|
|
5767
6215
|
|
|
5768
6216
|
\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." : "";
|
|
6217
|
+
const continueNote = task.continueFrom ? `
|
|
6218
|
+
|
|
6219
|
+
\u042D\u0422\u0423 \u0417\u0410\u0414\u0410\u0427\u0423 \u0423\u0416\u0415 \u041D\u0410\u0427\u0418\u041D\u0410\u041B\u0418. \u041D\u0435\u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u043B\u0435\u0436\u0438\u0442 \u0432 \u0432\u0435\u0442\u043A\u0435 "${task.continueFrom.branchName}"${task.continueFrom.sha ? ` (\u043A\u043E\u043C\u043C\u0438\u0442 ${task.continueFrom.sha.slice(0, 8)})` : ""}${task.continueFrom.note ? `: ${task.continueFrom.note}` : ""}. \u0420\u0430\u0431\u043E\u0447\u0435\u0435 \u0434\u0435\u0440\u0435\u0432\u043E \u0437\u0430\u0432\u0435\u0434\u0435\u043D\u043E \u043E\u0442 \u043D\u0435\u0451 \u2014 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u0438 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439, \u0430 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0439 \u0437\u0430\u043D\u043E\u0432\u043E. \u0420\u0435\u0448\u0438\u0448\u044C, \u0447\u0442\u043E \u043F\u0440\u043E\u0449\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u0430\u0442\u044C, \u2014 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439, \u043D\u043E \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438.` : "";
|
|
5769
6220
|
return textResult(
|
|
5770
6221
|
`\u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0430:
|
|
5771
6222
|
${JSON.stringify(task, null, 2)}
|
|
5772
6223
|
|
|
5773
|
-
${worktreeNote}${warning}`
|
|
6224
|
+
${worktreeNote}${continueNote}${warning}`
|
|
6225
|
+
);
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
// src/mcp/link.ts
|
|
6229
|
+
var linkTasksInputShape = {
|
|
6230
|
+
blockerId: taskLinkPayloadSchema.shape.blockerId.describe(
|
|
6231
|
+
"ID \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043D\u0430\u0434\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u041F\u0415\u0420\u0412\u041E\u0419 \u2014 \u043E\u043D\u0430 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0432\u0442\u043E\u0440\u0443\u044E."
|
|
6232
|
+
),
|
|
6233
|
+
blockedId: taskLinkPayloadSchema.shape.blockedId.describe(
|
|
6234
|
+
"ID \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0416\u0414\u0401\u0422: \u0435\u0451 \u043D\u0435\u043B\u044C\u0437\u044F \u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u043A\u0430 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0430 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0430\u044F."
|
|
6235
|
+
),
|
|
6236
|
+
kind: taskLinkPayloadSchema.shape.kind.describe(
|
|
6237
|
+
"BLOCKS \u2014 \u0434\u0435\u0440\u0436\u0438\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E). RELATES \u2014 \u043F\u0440\u043E\u0441\u0442\u043E \u0441\u0432\u044F\u0437\u044C \xAB\u0441\u043C. \u0442\u0430\u043A\u0436\u0435\xBB, \u043D\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043D\u0435 \u0432\u043B\u0438\u044F\u0435\u0442."
|
|
6238
|
+
)
|
|
6239
|
+
};
|
|
6240
|
+
async function linkTasksHandler(args, runtime) {
|
|
6241
|
+
const parsedArgs = parseArgsOrError(taskLinkPayloadSchema, args);
|
|
6242
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6243
|
+
const result = await callServerOrError(
|
|
6244
|
+
runtime.client,
|
|
6245
|
+
"task:link",
|
|
6246
|
+
parsedArgs.data,
|
|
6247
|
+
taskLinkAckSchema
|
|
6248
|
+
);
|
|
6249
|
+
if (!result.ok) return errorResult(result.message);
|
|
6250
|
+
if (!result.data.linked) {
|
|
6251
|
+
return textResult(`\u0421\u0432\u044F\u0437\u044C \u0443\u0436\u0435 \u0431\u044B\u043B\u0430: ${args.blockerId} \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId}.`);
|
|
6252
|
+
}
|
|
6253
|
+
const stopped = result.data.blockedNow ? " \u0417\u0430\u0434\u0430\u0447\u0430 \u0431\u044B\u043B\u0430 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435 \u0438 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u043E\u043D\u0430 \u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0443\u044E." : "";
|
|
6254
|
+
return textResult(`\u0413\u043E\u0442\u043E\u0432\u043E: ${args.blockerId} \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId}.${stopped}`);
|
|
6255
|
+
}
|
|
6256
|
+
var unlinkTasksInputShape = {
|
|
6257
|
+
blockerId: taskUnlinkPayloadSchema.shape.blockerId.describe("ID \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438."),
|
|
6258
|
+
blockedId: taskUnlinkPayloadSchema.shape.blockedId.describe("ID \u0436\u0434\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438.")
|
|
6259
|
+
};
|
|
6260
|
+
async function unlinkTasksHandler(args, runtime) {
|
|
6261
|
+
const parsedArgs = parseArgsOrError(taskUnlinkPayloadSchema, args);
|
|
6262
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6263
|
+
const result = await callServerOrError(
|
|
6264
|
+
runtime.client,
|
|
6265
|
+
"task:unlink",
|
|
6266
|
+
parsedArgs.data,
|
|
6267
|
+
taskUnlinkAckSchema
|
|
6268
|
+
);
|
|
6269
|
+
if (!result.ok) return errorResult(result.message);
|
|
6270
|
+
return textResult(
|
|
6271
|
+
result.data.unlinked ? `\u0421\u0432\u044F\u0437\u044C \u0441\u043D\u044F\u0442\u0430: ${args.blockerId} \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId}.` : `\u0422\u0430\u043A\u043E\u0439 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u0431\u044B\u043B\u043E: ${args.blockerId} \u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B ${args.blockedId}.`
|
|
6272
|
+
);
|
|
6273
|
+
}
|
|
6274
|
+
var planTaskInputShape = {
|
|
6275
|
+
taskId: taskPlanPayloadSchema.shape.taskId.describe(
|
|
6276
|
+
"ID \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u043B\u044E\u0431\u043E\u0439, \u0430\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430."
|
|
6277
|
+
),
|
|
6278
|
+
patch: taskPlanPayloadSchema.shape.patch.describe(
|
|
6279
|
+
"\u041F\u043E\u043B\u044F \u043F\u043B\u0430\u043D\u0430: title, descriptionMd, acceptance, type, priority, roleKey, assigneeId, milestoneId, labels, estimateMin. \u0421\u0442\u0430\u0442\u0443\u0441 \u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0440\u0430\u0431\u043E\u0442\u044B \u043C\u0435\u043D\u044F\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u0438\u0437 report."
|
|
6280
|
+
)
|
|
6281
|
+
};
|
|
6282
|
+
async function planTaskHandler(args, runtime) {
|
|
6283
|
+
const parsedArgs = parseArgsOrError(taskPlanPayloadSchema, args);
|
|
6284
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6285
|
+
const result = await callServerOrError(
|
|
6286
|
+
runtime.client,
|
|
6287
|
+
"task:plan",
|
|
6288
|
+
parsedArgs.data,
|
|
6289
|
+
taskPlanAckSchema
|
|
6290
|
+
);
|
|
6291
|
+
if (!result.ok) return errorResult(result.message);
|
|
6292
|
+
return textResult(
|
|
6293
|
+
`\u0417\u0430\u0434\u0430\u0447\u0430 ${result.data.task.key} \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430:
|
|
6294
|
+
${JSON.stringify(result.data.task, null, 2)}`
|
|
5774
6295
|
);
|
|
5775
6296
|
}
|
|
5776
6297
|
|
|
@@ -5850,8 +6371,15 @@ async function reportHandler(args, runtime) {
|
|
|
5850
6371
|
);
|
|
5851
6372
|
}
|
|
5852
6373
|
const taskLabel2 = state.currentTask.taskKey ?? state.currentTask.taskId;
|
|
6374
|
+
const completedTaskIds = state.completedTaskIds.includes(state.currentTask.taskId) ? state.completedTaskIds : [...state.completedTaskIds, state.currentTask.taskId];
|
|
5853
6375
|
updateState(
|
|
5854
|
-
{
|
|
6376
|
+
{
|
|
6377
|
+
currentTask: null,
|
|
6378
|
+
tasksCompletedInSession: state.tasksCompletedInSession + 1,
|
|
6379
|
+
completedTaskIds,
|
|
6380
|
+
// Сдали задачу — значит очередь только что была не пуста.
|
|
6381
|
+
queueEmptyAt: null
|
|
6382
|
+
},
|
|
5855
6383
|
runtime.stateFilePath
|
|
5856
6384
|
);
|
|
5857
6385
|
const duplicateNote = result.data.duplicate ? " (\u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043C\u0435\u0442\u0438\u043B \u044D\u0442\u043E \u043A\u0430\u043A \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u0436\u0435 \u043F\u0440\u0438\u043D\u044F\u0442\u043E\u0439 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0437\u0430\u0434\u0432\u0430\u0438\u0432\u0430\u043D\u0438\u044F \u043D\u0435\u0442)" : "";
|
|
@@ -6010,7 +6538,7 @@ async function settleApproval(ack, commandCwd, runtime, waitSecUsed) {
|
|
|
6010
6538
|
}
|
|
6011
6539
|
|
|
6012
6540
|
// src/mcp/set-mode.ts
|
|
6013
|
-
import { z as
|
|
6541
|
+
import { z as z38 } from "zod";
|
|
6014
6542
|
|
|
6015
6543
|
// src/cli/mode.ts
|
|
6016
6544
|
function parseModeArg(raw) {
|
|
@@ -6051,7 +6579,7 @@ async function requestModeChange(modeArg, options = {}) {
|
|
|
6051
6579
|
|
|
6052
6580
|
// src/mcp/set-mode.ts
|
|
6053
6581
|
var setModeInputShape = {
|
|
6054
|
-
mode:
|
|
6582
|
+
mode: z38.enum(["auto", "manual"]).describe("auto \u2014 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430, manual \u2014 \u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C (\xA710.2 \u0422\u0417)")
|
|
6055
6583
|
};
|
|
6056
6584
|
async function setModeHandler(args, runtime) {
|
|
6057
6585
|
const result = await requestModeChange(args.mode, {
|
|
@@ -6066,20 +6594,20 @@ async function setModeHandler(args, runtime) {
|
|
|
6066
6594
|
}
|
|
6067
6595
|
|
|
6068
6596
|
// src/mcp/submit-work.ts
|
|
6069
|
-
import { z as
|
|
6597
|
+
import { z as z39 } from "zod";
|
|
6070
6598
|
var submitWorkInputShape = {
|
|
6071
|
-
branchName:
|
|
6599
|
+
branchName: z39.string().min(1).describe(
|
|
6072
6600
|
"\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0431\u0435\u0440\u0438 \u0440\u043E\u0432\u043D\u043E \u0438\u0437 project.branchName \u0432 TaskPayload, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0432\u0435\u0440\u043D\u0443\u043B next_task. \u041D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0441\u0432\u043E\u0451."
|
|
6073
6601
|
),
|
|
6074
|
-
defaultBranch:
|
|
6075
|
-
commitPrefix:
|
|
6076
|
-
taskKey:
|
|
6077
|
-
taskTitle:
|
|
6078
|
-
repoRoot:
|
|
6079
|
-
testsPassed:
|
|
6602
|
+
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."),
|
|
6603
|
+
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:".'),
|
|
6604
|
+
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."),
|
|
6605
|
+
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."),
|
|
6606
|
+
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."),
|
|
6607
|
+
testsPassed: z39.boolean().optional().describe(
|
|
6080
6608
|
"\u041F\u0440\u043E\u0448\u043B\u0438 \u043B\u0438 \u0442\u0435\u0441\u0442\u044B. \u0415\u0441\u043B\u0438 \u043F\u0440\u043E\u0435\u043A\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0437\u0435\u043B\u0451\u043D\u044B\u0445 \u0442\u0435\u0441\u0442\u043E\u0432, \u0430 \u0437\u0434\u0435\u0441\u044C false \u2014 \u0441\u0434\u0430\u0447\u0430 \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442\u0441\u044F, \u0438 \u044D\u0442\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E."
|
|
6081
6609
|
),
|
|
6082
|
-
requireTestsPassed:
|
|
6610
|
+
requireTestsPassed: z39.boolean().optional().describe("ProjectSettings.requireTests \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0438\u0437 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0438.")
|
|
6083
6611
|
};
|
|
6084
6612
|
async function submitWorkHandler(args, runtime) {
|
|
6085
6613
|
const state = readState(runtime.stateFilePath);
|
|
@@ -6171,6 +6699,9 @@ async function submitViaPullRequest(args, runtime, currentTask) {
|
|
|
6171
6699
|
|
|
6172
6700
|
// src/mcp/tasks.ts
|
|
6173
6701
|
var createTaskInputShape = {
|
|
6702
|
+
dependsOn: taskCreatePayloadSchema.shape.dependsOn.describe(
|
|
6703
|
+
"ID \u0437\u0430\u0434\u0430\u0447, \u043E\u0442 \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u043D\u043E\u0432\u0430\u044F \u0437\u0430\u0432\u0438\u0441\u0438\u0442: \u0435\u0451 \u043D\u0435 \u0432\u044B\u0434\u0430\u0434\u0443\u0442, \u043F\u043E\u043A\u0430 \u043E\u043D\u0438 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u044B. \u0421\u0432\u044F\u0437\u0438 \u0441\u0442\u0430\u0432\u044F\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u2014 \u0432\u0442\u043E\u0440\u043E\u0439 \u043F\u0440\u043E\u0445\u043E\u0434 \u043F\u043E \u0441\u043F\u0438\u0441\u043A\u0443 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D. \u041D\u0435\u043F\u043E\u0434\u0445\u043E\u0434\u044F\u0449\u0430\u044F \u0441\u0441\u044B\u043B\u043A\u0430 (\u0447\u0443\u0436\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442, \u043D\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430) \u043C\u043E\u043B\u0447\u0430 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F: \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0436\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430, \u0438 \u0442\u0435\u0440\u044F\u0442\u044C \u0435\u0451 \u0438\u0437-\u0437\u0430 \u043E\u0434\u043D\u043E\u0439 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435\u043B\u044C\u0437\u044F."
|
|
6704
|
+
),
|
|
6174
6705
|
title: taskCreatePayloadSchema.shape.title.describe("\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u043D\u043E\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0440\u043E\u0442\u043A\u043E."),
|
|
6175
6706
|
descriptionMd: taskCreatePayloadSchema.shape.descriptionMd.describe(
|
|
6176
6707
|
"Markdown-\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043D\u043E\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u0438 \u0437\u0430\u0447\u0435\u043C."
|
|
@@ -6209,7 +6740,8 @@ async function createTaskHandler(args, runtime) {
|
|
|
6209
6740
|
executor: args.executor,
|
|
6210
6741
|
blocksCurrent: args.blocksCurrent,
|
|
6211
6742
|
...args.acceptance ? { acceptance: args.acceptance } : {},
|
|
6212
|
-
...args.labels && args.labels.length > 0 ? { labels: args.labels } : {}
|
|
6743
|
+
...args.labels && args.labels.length > 0 ? { labels: args.labels } : {},
|
|
6744
|
+
...args.dependsOn && args.dependsOn.length > 0 ? { dependsOn: args.dependsOn } : {}
|
|
6213
6745
|
});
|
|
6214
6746
|
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6215
6747
|
const state = readState(runtime.stateFilePath);
|
|
@@ -6235,8 +6767,15 @@ async function createTaskHandler(args, runtime) {
|
|
|
6235
6767
|
const blockNote = result.data.currentTaskStatus ? `
|
|
6236
6768
|
|
|
6237
6769
|
\u0422\u0432\u043E\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 ${state.currentTask?.taskKey ?? state.currentTask?.taskId ?? ""} \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430: \u043D\u043E\u0432\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u2014 ${result.data.currentTaskStatus}. \u041F\u0440\u0435\u043A\u0440\u0430\u0442\u0438 \u0430\u043A\u0442\u0438\u0432\u043D\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430\u0434 \u043D\u0435\u0439 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441; \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u0439\u0434\u0451\u0448\u044C \u0434\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0441\u0442\u043E\u0438\u0442 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u0442\u044C, \u0441\u0434\u0430\u0439 report(partial) \u0441 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435\u043C \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0433\u043E \u0438 \u0443\u043A\u0430\u0436\u0438 \u0432 nextSteps, \u0447\u0442\u043E \u0436\u0434\u0451\u0448\u044C \u043D\u043E\u0432\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443 ${result.data.task.key}.` : "";
|
|
6770
|
+
const requested = args.dependsOn?.length ?? 0;
|
|
6771
|
+
const linked = result.data.dependenciesLinked ?? 0;
|
|
6772
|
+
const depsNote = requested === 0 ? "" : linked === requested ? `
|
|
6773
|
+
|
|
6774
|
+
\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E: ${linked}.` : `
|
|
6775
|
+
|
|
6776
|
+
\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E ${linked} \u0438\u0437 ${requested}: \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0435 \u043F\u043E\u0434\u043E\u0448\u043B\u0438 (\u043D\u0435\u0442 \u0442\u0430\u043A\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u043B\u0438 \u043E\u043D\u0430 \u0438\u0437 \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430). \u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0438\u0445 \u0438 \u043F\u0440\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0441\u0442\u0438 \u0441\u0432\u044F\u0436\u0438 \u0447\u0435\u0440\u0435\u0437 link_tasks.`;
|
|
6238
6777
|
return textResult(
|
|
6239
|
-
`\u0421\u043E\u0437\u0434\u0430\u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 ${result.data.task.key} (${result.data.task.status}) \u0434\u043B\u044F ${addressee}.${blockNote}`
|
|
6778
|
+
`\u0421\u043E\u0437\u0434\u0430\u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 ${result.data.task.key} (${result.data.task.status}) \u0434\u043B\u044F ${addressee}.${depsNote}${blockNote}`
|
|
6240
6779
|
);
|
|
6241
6780
|
}
|
|
6242
6781
|
var listTasksInputShape = {
|
|
@@ -6415,6 +6954,33 @@ function registerTools(server, runtime) {
|
|
|
6415
6954
|
},
|
|
6416
6955
|
(args) => updateTaskHandler(args, runtime)
|
|
6417
6956
|
);
|
|
6957
|
+
server.registerTool(
|
|
6958
|
+
"link_tasks",
|
|
6959
|
+
{
|
|
6960
|
+
title: "\u0421\u0432\u044F\u0437\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438",
|
|
6961
|
+
description: "\u0417\u0430\u0434\u0430\u0442\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \xABA \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 B\xBB: \u043F\u043E\u043A\u0430 A \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0434\u0438\u0441\u043F\u0435\u0442\u0447\u0435\u0440 \u043D\u0435 \u0432\u044B\u0434\u0430\u0441\u0442 B. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0441 \u041B\u042E\u0411\u042B\u041C\u0418 \u0437\u0430\u0434\u0430\u0447\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0430\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430 \u2014 \u0432 \u043E\u0442\u043B\u0438\u0447\u0438\u0435 \u043E\u0442 update_task. \u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u043E\u043B\u044C \xAB\u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\xBB \u043B\u0438\u0431\u043E \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430/\u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u0432\u044F\u0437\u044C, \u0437\u0430\u043C\u044B\u043A\u0430\u044E\u0449\u0430\u044F \u043A\u043E\u043B\u044C\u0446\u043E, \u0431\u0443\u0434\u0435\u0442 \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442\u0430: \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u043D\u0451\u043C \u0436\u0434\u0430\u043B\u0438 \u0431\u044B \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430 \u0432\u0435\u0447\u043D\u043E.",
|
|
6962
|
+
inputSchema: linkTasksInputShape
|
|
6963
|
+
},
|
|
6964
|
+
(args) => linkTasksHandler(args, runtime)
|
|
6965
|
+
);
|
|
6966
|
+
server.registerTool(
|
|
6967
|
+
"plan_task",
|
|
6968
|
+
{
|
|
6969
|
+
title: "\u0421\u043F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443",
|
|
6970
|
+
description: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043F\u043E\u043B\u044F \u041B\u042E\u0411\u041E\u0419 \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A, \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435, \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043F\u0440\u0438\u0451\u043C\u043A\u0438, \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442, \u0442\u0438\u043F, \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430, \u043C\u0438\u043B\u0441\u0442\u043E\u0443\u043D, \u043C\u0435\u0442\u043A\u0438, \u043E\u0446\u0435\u043D\u043A\u0443. \u0410\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430 \u2014 \u0432 \u043E\u0442\u043B\u0438\u0447\u0438\u0435 \u043E\u0442 update_task. \u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u043E\u043B\u044C \xAB\u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\xBB \u043B\u0438\u0431\u043E \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430/\u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u0422\u0410\u0422\u0423\u0421 \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F: \u0432 \u0440\u0430\u0431\u043E\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A. \u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0440\u0430\u0431\u043E\u0442\u044B \u0442\u043E\u0436\u0435 \u2014 \u043E\u043D\u0438 \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u0438\u0437 report.",
|
|
6971
|
+
inputSchema: planTaskInputShape
|
|
6972
|
+
},
|
|
6973
|
+
(args) => planTaskHandler(args, runtime)
|
|
6974
|
+
);
|
|
6975
|
+
server.registerTool(
|
|
6976
|
+
"unlink_tasks",
|
|
6977
|
+
{
|
|
6978
|
+
title: "\u0421\u043D\u044F\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043C\u0435\u0436\u0434\u0443 \u0437\u0430\u0434\u0430\u0447\u0430\u043C\u0438",
|
|
6979
|
+
description: "\u0423\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \xABA \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 B\xBB. \u0415\u0441\u043B\u0438 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u044D\u0442\u043E \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430, \u0430 \u043E\u0442\u0447\u0451\u0442.",
|
|
6980
|
+
inputSchema: unlinkTasksInputShape
|
|
6981
|
+
},
|
|
6982
|
+
(args) => unlinkTasksHandler(args, runtime)
|
|
6983
|
+
);
|
|
6418
6984
|
server.registerTool(
|
|
6419
6985
|
"ask_human",
|
|
6420
6986
|
{
|
|
@@ -6553,13 +7119,13 @@ function formatConnectResult(result) {
|
|
|
6553
7119
|
}
|
|
6554
7120
|
|
|
6555
7121
|
// src/hooks/pre-compact.ts
|
|
6556
|
-
import { z as
|
|
6557
|
-
var PreCompactInputSchema =
|
|
6558
|
-
hook_event_name:
|
|
6559
|
-
session_id:
|
|
6560
|
-
cwd:
|
|
6561
|
-
trigger:
|
|
6562
|
-
custom_instructions:
|
|
7122
|
+
import { z as z40 } from "zod";
|
|
7123
|
+
var PreCompactInputSchema = z40.object({
|
|
7124
|
+
hook_event_name: z40.literal("PreCompact"),
|
|
7125
|
+
session_id: z40.string(),
|
|
7126
|
+
cwd: z40.string(),
|
|
7127
|
+
trigger: z40.enum(["manual", "auto"]),
|
|
7128
|
+
custom_instructions: z40.string().nullable()
|
|
6563
7129
|
}).passthrough();
|
|
6564
7130
|
function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6565
7131
|
const lastPreCompact = state.currentTask ? { ...state.currentTask, savedAt: now() } : null;
|
|
@@ -6568,14 +7134,14 @@ function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).
|
|
|
6568
7134
|
|
|
6569
7135
|
// src/hooks/pre-tool-use.ts
|
|
6570
7136
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
|
|
6571
|
-
import { z as
|
|
6572
|
-
var PreToolUseInputSchema =
|
|
6573
|
-
hook_event_name:
|
|
6574
|
-
session_id:
|
|
6575
|
-
cwd:
|
|
6576
|
-
tool_name:
|
|
6577
|
-
tool_input:
|
|
6578
|
-
tool_use_id:
|
|
7137
|
+
import { z as z41 } from "zod";
|
|
7138
|
+
var PreToolUseInputSchema = z41.object({
|
|
7139
|
+
hook_event_name: z41.literal("PreToolUse"),
|
|
7140
|
+
session_id: z41.string(),
|
|
7141
|
+
cwd: z41.string(),
|
|
7142
|
+
tool_name: z41.string(),
|
|
7143
|
+
tool_input: z41.record(z41.string(), z41.unknown()),
|
|
7144
|
+
tool_use_id: z41.string()
|
|
6579
7145
|
}).passthrough();
|
|
6580
7146
|
var NO_TRUST_CACHE_REASON = "\u041A\u044D\u0448 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043E\u0432\u0435\u0440\u0438\u044F (TrustRules) \u043F\u0443\u0441\u0442 \u2014 \u043A\u043B\u0438\u0435\u043D\u0442 \u043D\u0438 \u0440\u0430\u0437\u0443 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0430\u043B \u0438\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u043D\u0435\u0442 agent:hello/trust:updated). \u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 \u043F\u0440\u043E\u0444\u0438\u043B\u044C (full/guarded/approve-all) \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (fail-closed \u043F\u0440\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \xA710.4 \u0422\u0417) \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435: `quiel connect` (\u044D\u0442\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0430 \u0438\u043C\u0435\u043D\u043D\u043E \u0434\u043B\u044F \u0442\u0430\u043A\u043E\u0433\u043E \u0441\u043B\u0443\u0447\u0430\u044F, \u0441\u043C. isBootstrapCommand) \u2014 \u043F\u043E\u0441\u043B\u0435 \u0440\u0443\u043A\u043E\u043F\u043E\u0436\u0430\u0442\u0438\u044F \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043F\u0440\u0438\u0434\u0443\u0442, \u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043D\u0430\u0447\u043D\u0443\u0442 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442\u044C. \u0415\u0441\u043B\u0438 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C, \u0432\u044B\u0437\u043E\u0432\u0438 request_approval.";
|
|
6581
7147
|
var BOOTSTRAP_COMMAND_RE = /^quiel\s+(connect|status)\b/;
|
|
@@ -6807,13 +7373,13 @@ function readNewUsage(transcriptPath, offset) {
|
|
|
6807
7373
|
}
|
|
6808
7374
|
|
|
6809
7375
|
// src/hooks/session-start.ts
|
|
6810
|
-
import { z as
|
|
6811
|
-
var SessionStartInputSchema =
|
|
6812
|
-
hook_event_name:
|
|
6813
|
-
session_id:
|
|
6814
|
-
cwd:
|
|
6815
|
-
source:
|
|
6816
|
-
model:
|
|
7376
|
+
import { z as z42 } from "zod";
|
|
7377
|
+
var SessionStartInputSchema = z42.object({
|
|
7378
|
+
hook_event_name: z42.literal("SessionStart"),
|
|
7379
|
+
session_id: z42.string(),
|
|
7380
|
+
cwd: z42.string(),
|
|
7381
|
+
source: z42.enum(["startup", "resume", "clear", "compact", "fork"]),
|
|
7382
|
+
model: z42.string().optional()
|
|
6817
7383
|
}).passthrough();
|
|
6818
7384
|
var SESSION_RESET_SOURCES = /* @__PURE__ */ new Set([
|
|
6819
7385
|
"startup",
|
|
@@ -6852,13 +7418,26 @@ function sessionStartHook(input, state) {
|
|
|
6852
7418
|
}
|
|
6853
7419
|
|
|
6854
7420
|
// src/hooks/stop.ts
|
|
6855
|
-
import { z as
|
|
6856
|
-
var StopInputSchema =
|
|
6857
|
-
hook_event_name:
|
|
6858
|
-
session_id:
|
|
6859
|
-
cwd:
|
|
6860
|
-
stop_hook_active:
|
|
6861
|
-
last_assistant_message:
|
|
7421
|
+
import { z as z43 } from "zod";
|
|
7422
|
+
var StopInputSchema = z43.object({
|
|
7423
|
+
hook_event_name: z43.literal("Stop"),
|
|
7424
|
+
session_id: z43.string(),
|
|
7425
|
+
cwd: z43.string(),
|
|
7426
|
+
stop_hook_active: z43.boolean(),
|
|
7427
|
+
last_assistant_message: z43.string().optional(),
|
|
7428
|
+
/**
|
|
7429
|
+
* Незавершённая фоновая работа сессии.
|
|
7430
|
+
*
|
|
7431
|
+
* ЧИТАЕМ ТОЛЬКО ДЛИНУ, И ЭТО НАМЕРЕННО. Внутренняя форма элементов в
|
|
7432
|
+
* документации не описана, а строить решение на угаданных полях —
|
|
7433
|
+
* ровно то, что §22.8 ТЗ запрещает. Нам достаточно ответа на вопрос
|
|
7434
|
+
* «есть ли незакрытая фоновая работа»: он не зависит от того, что
|
|
7435
|
+
* внутри.
|
|
7436
|
+
*
|
|
7437
|
+
* В §10.4 ТЗ этих полей нет — они появились в Claude Code позже.
|
|
7438
|
+
*/
|
|
7439
|
+
background_tasks: z43.array(z43.unknown()).optional(),
|
|
7440
|
+
session_crons: z43.array(z43.unknown()).optional()
|
|
6862
7441
|
}).passthrough();
|
|
6863
7442
|
function taskLabel(state) {
|
|
6864
7443
|
if (!state.currentTask) return "";
|
|
@@ -6871,6 +7450,13 @@ function stopHook(input, state) {
|
|
|
6871
7450
|
if (state.mode !== AgentMode.AUTONOMOUS) {
|
|
6872
7451
|
return { output: {} };
|
|
6873
7452
|
}
|
|
7453
|
+
if (hasUnfinishedBackgroundWork(input)) {
|
|
7454
|
+
return {
|
|
7455
|
+
output: {
|
|
7456
|
+
systemMessage: "\u0410\u0433\u0435\u043D\u0442 \u0436\u0434\u0451\u0442 \u0444\u043E\u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u2014 \u0445\u0443\u043A \u043D\u0435 \u043C\u0435\u0448\u0430\u0435\u0442. \u0425\u043E\u0434 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u0441\u044F, \u043A\u043E\u0433\u0434\u0430 \u043E\u043D\u0430 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u0441\u044F."
|
|
7457
|
+
}
|
|
7458
|
+
};
|
|
7459
|
+
}
|
|
6874
7460
|
if (state.currentTask) {
|
|
6875
7461
|
return {
|
|
6876
7462
|
output: {
|
|
@@ -6879,77 +7465,44 @@ function stopHook(input, state) {
|
|
|
6879
7465
|
}
|
|
6880
7466
|
};
|
|
6881
7467
|
}
|
|
6882
|
-
if (state.tasksCompletedInSession
|
|
7468
|
+
if (state.tasksCompletedInSession >= state.contextResetAfterTasks) {
|
|
7469
|
+
return limitReached(state);
|
|
7470
|
+
}
|
|
7471
|
+
if (state.queueEmptyAt) {
|
|
6883
7472
|
return {
|
|
6884
7473
|
output: {
|
|
6885
|
-
|
|
6886
|
-
reason: "\u0412\u044B\u0437\u043E\u0432\u0438 next_task \u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438."
|
|
7474
|
+
systemMessage: "\u041E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u0443\u0441\u0442\u0430 \u2014 \u0430\u0433\u0435\u043D\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D. \u041F\u043E\u0437\u043E\u0432\u0438\u0442\u0435 \u0435\u0433\u043E \u0441\u043D\u043E\u0432\u0430, \u043A\u043E\u0433\u0434\u0430 \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u0437\u0430\u0434\u0430\u0447\u0438: \u043E\u043D \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0438\u0445 \u0441 \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0436\u0435 next_task."
|
|
6887
7475
|
}
|
|
6888
7476
|
};
|
|
6889
7477
|
}
|
|
7478
|
+
return {
|
|
7479
|
+
output: {
|
|
7480
|
+
decision: "block",
|
|
7481
|
+
reason: "\u0412\u044B\u0437\u043E\u0432\u0438 next_task \u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438."
|
|
7482
|
+
}
|
|
7483
|
+
};
|
|
7484
|
+
}
|
|
7485
|
+
function hasUnfinishedBackgroundWork(input) {
|
|
7486
|
+
return (input.background_tasks?.length ?? 0) > 0 || (input.session_crons?.length ?? 0) > 0;
|
|
7487
|
+
}
|
|
7488
|
+
function limitReached(state) {
|
|
6890
7489
|
const serverActions = [
|
|
6891
7490
|
{
|
|
6892
7491
|
type: "session-limit-reached",
|
|
6893
7492
|
payload: { tasksCompletedInSession: state.tasksCompletedInSession }
|
|
6894
7493
|
}
|
|
6895
7494
|
];
|
|
7495
|
+
const tasks = state.completedTaskIds.length;
|
|
7496
|
+
const submissions = state.tasksCompletedInSession;
|
|
7497
|
+
const counted = tasks > 0 && tasks !== submissions ? `${tasks} \u0437\u0430\u0434\u0430\u0447(\u0438) \u0437\u0430 ${submissions} \u0441\u0434\u0430\u0447(\u0438)` : `${submissions} \u0437\u0430\u0434\u0430\u0447(\u0438)`;
|
|
6896
7498
|
return {
|
|
6897
7499
|
output: {
|
|
6898
|
-
systemMessage: `\u0421\u0435\u0441\u0441\u0438\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043B\u0430 ${
|
|
7500
|
+
systemMessage: `\u0421\u0435\u0441\u0441\u0438\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043B\u0430 ${counted}. \u0421\u0434\u0435\u043B\u0430\u0439\u0442\u0435 /clear \u0438 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430 \u0441\u043D\u043E\u0432\u0430.`
|
|
6899
7501
|
},
|
|
6900
7502
|
serverActions
|
|
6901
7503
|
};
|
|
6902
7504
|
}
|
|
6903
7505
|
|
|
6904
|
-
// src/hooks/stop-failure.ts
|
|
6905
|
-
import { z as z43 } from "zod";
|
|
6906
|
-
var StopFailureErrorTypeSchema = z43.enum([
|
|
6907
|
-
"rate_limit",
|
|
6908
|
-
"overloaded",
|
|
6909
|
-
"authentication_failed",
|
|
6910
|
-
"oauth_org_not_allowed",
|
|
6911
|
-
"account_on_hold",
|
|
6912
|
-
"billing_error",
|
|
6913
|
-
"invalid_request",
|
|
6914
|
-
"model_not_found",
|
|
6915
|
-
"server_error",
|
|
6916
|
-
"max_output_tokens",
|
|
6917
|
-
"unknown"
|
|
6918
|
-
]);
|
|
6919
|
-
var StopFailureInputSchema = z43.object({
|
|
6920
|
-
hook_event_name: z43.literal("StopFailure"),
|
|
6921
|
-
session_id: z43.string(),
|
|
6922
|
-
cwd: z43.string(),
|
|
6923
|
-
error: StopFailureErrorTypeSchema,
|
|
6924
|
-
error_details: z43.unknown().optional(),
|
|
6925
|
-
last_assistant_message: z43.string().optional()
|
|
6926
|
-
}).passthrough();
|
|
6927
|
-
function extractRetryAfterSec(errorDetails) {
|
|
6928
|
-
if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
|
|
6929
|
-
return errorDetails;
|
|
6930
|
-
}
|
|
6931
|
-
if (errorDetails && typeof errorDetails === "object") {
|
|
6932
|
-
const record = errorDetails;
|
|
6933
|
-
for (const key of ["retryAfterSec", "retryAfter", "retry_after", "retry_after_seconds"]) {
|
|
6934
|
-
const value = record[key];
|
|
6935
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
6936
|
-
}
|
|
6937
|
-
}
|
|
6938
|
-
if (typeof errorDetails === "string") {
|
|
6939
|
-
const match = errorDetails.match(/retry[-_ ]?after[^0-9]{0,10}(\d+)/i);
|
|
6940
|
-
if (match?.[1] !== void 0) return Number(match[1]);
|
|
6941
|
-
}
|
|
6942
|
-
return void 0;
|
|
6943
|
-
}
|
|
6944
|
-
function stopFailureHook(input, _state) {
|
|
6945
|
-
const retryAfterSec = extractRetryAfterSec(input.error_details);
|
|
6946
|
-
const action = {
|
|
6947
|
-
type: "agent:rate_limited",
|
|
6948
|
-
payload: retryAfterSec === void 0 ? { error: input.error } : { error: input.error, retryAfterSec }
|
|
6949
|
-
};
|
|
6950
|
-
return { output: {}, serverActions: [action] };
|
|
6951
|
-
}
|
|
6952
|
-
|
|
6953
7506
|
// src/hooks/run.ts
|
|
6954
7507
|
var HookInputSchema = z44.discriminatedUnion("hook_event_name", [
|
|
6955
7508
|
SessionStartInputSchema,
|
|
@@ -7344,6 +7897,101 @@ function formatStatus(report) {
|
|
|
7344
7897
|
return lines.join("\n");
|
|
7345
7898
|
}
|
|
7346
7899
|
|
|
7900
|
+
// src/cli/wait-for-task.ts
|
|
7901
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, rmSync, writeFileSync as writeFileSync10 } from "fs";
|
|
7902
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
7903
|
+
var DEFAULT_POLL_INTERVAL_MS = 15e3;
|
|
7904
|
+
var DEFAULT_MAX_WAIT_MS = 8 * 60 * 60 * 1e3;
|
|
7905
|
+
var CONNECT_TIMEOUT_MS = 8e3;
|
|
7906
|
+
var PEEK_TIMEOUT_MS = 8e3;
|
|
7907
|
+
var WAIT_EXIT_IDLE = 0;
|
|
7908
|
+
var WAIT_EXIT_WAKE = 2;
|
|
7909
|
+
function skipReason(state) {
|
|
7910
|
+
if (state.mode !== AgentMode.AUTONOMOUS) {
|
|
7911
|
+
return "\u0440\u0435\u0436\u0438\u043C \u043D\u0435 \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439";
|
|
7912
|
+
}
|
|
7913
|
+
if (state.currentTask) {
|
|
7914
|
+
return "\u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0436\u0435 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435";
|
|
7915
|
+
}
|
|
7916
|
+
return null;
|
|
7917
|
+
}
|
|
7918
|
+
function lockPath(stateFilePath) {
|
|
7919
|
+
return join11(dirname6(stateFilePath), "wait-for-task.lock");
|
|
7920
|
+
}
|
|
7921
|
+
function acquireLock(stateFilePath) {
|
|
7922
|
+
const path = lockPath(stateFilePath);
|
|
7923
|
+
if (existsSync11(path)) {
|
|
7924
|
+
const pid = Number.parseInt(readFileSync9(path, "utf8").trim(), 10);
|
|
7925
|
+
if (Number.isFinite(pid) && pid > 0 && isAlive(pid)) return false;
|
|
7926
|
+
}
|
|
7927
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
7928
|
+
writeFileSync10(path, String(process.pid), "utf8");
|
|
7929
|
+
return true;
|
|
7930
|
+
}
|
|
7931
|
+
function releaseLock(stateFilePath) {
|
|
7932
|
+
rmSync(lockPath(stateFilePath), { force: true });
|
|
7933
|
+
}
|
|
7934
|
+
function isAlive(pid) {
|
|
7935
|
+
try {
|
|
7936
|
+
process.kill(pid, 0);
|
|
7937
|
+
return true;
|
|
7938
|
+
} catch {
|
|
7939
|
+
return false;
|
|
7940
|
+
}
|
|
7941
|
+
}
|
|
7942
|
+
function trimTrailingSlash4(url) {
|
|
7943
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
7944
|
+
}
|
|
7945
|
+
async function peekOnce(url, token, createSocket2) {
|
|
7946
|
+
const socket = createSocket2(`${trimTrailingSlash4(url)}/agent`, {
|
|
7947
|
+
auth: { token },
|
|
7948
|
+
autoConnect: false,
|
|
7949
|
+
reconnection: false
|
|
7950
|
+
});
|
|
7951
|
+
try {
|
|
7952
|
+
await connectOnce(socket, CONNECT_TIMEOUT_MS);
|
|
7953
|
+
const raw = await emitWithAckTimeout(socket, "task:peek", {}, PEEK_TIMEOUT_MS);
|
|
7954
|
+
const parsed = taskPeekAckSchema.safeParse(raw);
|
|
7955
|
+
return parsed.success ? parsed.data.available : null;
|
|
7956
|
+
} catch {
|
|
7957
|
+
return null;
|
|
7958
|
+
} finally {
|
|
7959
|
+
socket.disconnect();
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
async function waitForTask(options = {}) {
|
|
7963
|
+
const context = resolveProjectContext(options.cwd ?? process.cwd(), options.credentialsFilePath);
|
|
7964
|
+
if (!context.ok) return { outcome: "idle", reason: "\u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D" };
|
|
7965
|
+
const stateFilePath = options.stateFilePath ?? getStateFilePath();
|
|
7966
|
+
const skip = skipReason(readState(stateFilePath));
|
|
7967
|
+
if (skip) return { outcome: "idle", reason: skip };
|
|
7968
|
+
if (!acquireLock(stateFilePath)) {
|
|
7969
|
+
return { outcome: "idle", reason: "\u043E\u0436\u0438\u0434\u0430\u0442\u0435\u043B\u044C \u0443\u0436\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D" };
|
|
7970
|
+
}
|
|
7971
|
+
const now = options.now ?? (() => Date.now());
|
|
7972
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
|
|
7973
|
+
const createSocket2 = options.createSocket ?? createSocket;
|
|
7974
|
+
const deadline = now() + (options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS);
|
|
7975
|
+
const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
7976
|
+
try {
|
|
7977
|
+
while (now() < deadline) {
|
|
7978
|
+
const reason = skipReason(readState(stateFilePath));
|
|
7979
|
+
if (reason) return { outcome: "idle", reason };
|
|
7980
|
+
const available = await peekOnce(context.config.url, context.credentials.token, createSocket2);
|
|
7981
|
+
if (available === true) {
|
|
7982
|
+
return {
|
|
7983
|
+
outcome: "wake",
|
|
7984
|
+
message: "\u0412 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043F\u043E\u044F\u0432\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u0434\u0430\u0447\u0430. \u0412\u044B\u0437\u043E\u0432\u0438 next_task, \u0447\u0442\u043E\u0431\u044B \u0432\u0437\u044F\u0442\u044C \u0435\u0451."
|
|
7985
|
+
};
|
|
7986
|
+
}
|
|
7987
|
+
await sleep2(interval);
|
|
7988
|
+
}
|
|
7989
|
+
return { outcome: "idle", reason: "\u0438\u0441\u0442\u0435\u043A\u043B\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F" };
|
|
7990
|
+
} finally {
|
|
7991
|
+
releaseLock(stateFilePath);
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
|
|
7347
7995
|
// src/cli/program.ts
|
|
7348
7996
|
var CLI_VERSION = VERSION;
|
|
7349
7997
|
function printError(error) {
|
|
@@ -7426,6 +8074,16 @@ function createProgram() {
|
|
|
7426
8074
|
printError(result.message);
|
|
7427
8075
|
process.exitCode = 1;
|
|
7428
8076
|
});
|
|
8077
|
+
program.command("wait-for-task").description("\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E (\u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F \u0445\u0443\u043A\u043E\u043C Stop \u0441 asyncRewake)").option("--cwd <dir>", "\u0420\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (\u0434\u043B\u044F \u0442\u0435\u0441\u0442\u043E\u0432/\u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438)").action(async (opts) => {
|
|
8078
|
+
const result = await waitForTask({ cwd: opts.cwd });
|
|
8079
|
+
if (result.outcome === "wake") {
|
|
8080
|
+
process.stderr.write(`${result.message}
|
|
8081
|
+
`);
|
|
8082
|
+
process.exitCode = WAIT_EXIT_WAKE;
|
|
8083
|
+
return;
|
|
8084
|
+
}
|
|
8085
|
+
process.exitCode = WAIT_EXIT_IDLE;
|
|
8086
|
+
});
|
|
7429
8087
|
program.command("hook").description(
|
|
7430
8088
|
"\u0412\u044B\u0437\u0432\u0430\u0442\u044C \u0445\u0443\u043A \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0439 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B (JSON \u043D\u0430 stdin) \u2014 \u043F\u0440\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043A\u043E\u043D\u0444\u0438\u0433\u043E\u043C \u043F\u0440\u0438 `quiel init`"
|
|
7431
8089
|
).argument("<name>", `\u0438\u043C\u044F \u0445\u0443\u043A\u0430: ${KNOWN_HOOK_NAMES.join(", ")}`).option(
|
|
@@ -7453,13 +8111,26 @@ function createProgram() {
|
|
|
7453
8111
|
if (result.stderr.length > 0) process.stderr.write(`${result.stderr}
|
|
7454
8112
|
`);
|
|
7455
8113
|
process.exitCode = result.exitCode;
|
|
7456
|
-
if (result.execution)
|
|
8114
|
+
if (result.execution) {
|
|
8115
|
+
const rescued = await runRescueAction(result.execution.serverActions);
|
|
8116
|
+
const rescuedNote = describeRescue(rescued);
|
|
8117
|
+
if (rescuedNote) process.stderr.write(`${rescuedNote}
|
|
8118
|
+
`);
|
|
8119
|
+
sendHookActions(
|
|
8120
|
+
rescued.report ? [...result.execution.serverActions, rescued.report] : result.execution.serverActions
|
|
8121
|
+
);
|
|
8122
|
+
}
|
|
7457
8123
|
return;
|
|
7458
8124
|
}
|
|
7459
8125
|
const execution = runCliHook(raw);
|
|
7460
8126
|
process.stdout.write(`${execution.stdout}
|
|
7461
8127
|
`);
|
|
7462
8128
|
process.exitCode = execution.exitCode;
|
|
8129
|
+
const rescue = await runRescueAction(execution.serverActions);
|
|
8130
|
+
const note = describeRescue(rescue);
|
|
8131
|
+
if (note) process.stderr.write(`${note}
|
|
8132
|
+
`);
|
|
8133
|
+
if (rescue.report) sendHookActions([rescue.report]);
|
|
7463
8134
|
sendHookActions(execution.serverActions);
|
|
7464
8135
|
});
|
|
7465
8136
|
program.command("mcp").description(
|