@quiel/cli 0.5.3 → 0.6.2
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 +6 -0
- package/dist/cli.js +551 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ This package is the part that runs **on your machine**.
|
|
|
15
15
|
- **Gives your agent the platform's tools over MCP** — take the next task, read project context and documents, read the files a human attached to the task, ask a human (and ask them to attach a file), request approval for a dangerous action, submit work.
|
|
16
16
|
- **Installs hooks into your agent** so a shell command is checked against the project's trust profile *before* it runs. A command that matches a stop pattern is refused; a command that needs a human gets one.
|
|
17
17
|
- **Keeps the connection and the task lease alive**, so the platform knows the task is being worked on and not abandoned.
|
|
18
|
+
- **Lets the agent idle, and wakes it when work appears.** An empty queue is a normal state, not a failure: the agent stops its turn instead of asking for a task over and over. A background watcher notices when something is queued for it and wakes the session. Waiting costs a socket and a few bytes — not model turns, not context.
|
|
18
19
|
- **Counts the tokens each task costs** and reports the totals, so a project can be looked at afterwards.
|
|
19
20
|
- **Rescues unfinished work when your subscription limit runs out.** It commits what the agent managed and pushes it to the task's own branch, marked as interrupted, so the next person or agent continues from there instead of starting over. It never pushes to your main branch.
|
|
20
21
|
|
|
@@ -106,8 +107,12 @@ Everything goes into your project directory, so it is visible in `git status` an
|
|
|
106
107
|
|
|
107
108
|
Plus one line in `.gitignore` for the directory the agent works in.
|
|
108
109
|
|
|
110
|
+
For Claude Code, `Stop` gets **two** commands, and they are not duplicates: one decides whether the agent may stop, the other waits for work while it is stopped and wakes the session when something appears. The second is declared with `asyncRewake`, so it runs in the background and never blocks you.
|
|
111
|
+
|
|
109
112
|
**Your token goes into none of them.** It is stored separately in `~/.quiel/credentials.json` with `0600` permissions, outside the repository. `.quiel.json` — the file that *is* committed — holds only the platform address, the project key, the agent id, its roles and which agent program you chose.
|
|
110
113
|
|
|
114
|
+
**After upgrading the package, run `quiel init` again.** `npm i -g` replaces the binary but does not touch your agent's configuration — and a new release may add a hook. Version 0.6.0 added the task watcher this way, and an agent upgraded without re-running `init` went quiet instead of idling. The client now notices and keeps working the old way, but `init` is what turns the new behaviour on. Running the MCP server at the time? Reconnect it (`/mcp` in Claude Code) — the old process keeps answering until you do.
|
|
115
|
+
|
|
111
116
|
Existing configuration is merged, not overwritten: other MCP servers, other hooks and your own comments survive. Running `init` again is the normal way to reconnect an agent, and it does not duplicate anything.
|
|
112
117
|
|
|
113
118
|
## Commands
|
|
@@ -118,6 +123,7 @@ Existing configuration is merged, not overwritten: other MCP servers, other hook
|
|
|
118
123
|
| `quiel connect` | Hold the connection to the platform |
|
|
119
124
|
| `quiel status [--check]` | Show the project, the configured agent program, mode and current task |
|
|
120
125
|
| `quiel mode auto\|manual` | Switch between autonomous and manual |
|
|
126
|
+
| `quiel wait-for-task` | Wait for a task to appear and wake the sleeping session (started by a hook, not by you) |
|
|
121
127
|
| `quiel mcp` | Run the MCP server over stdio (your agent starts this itself) |
|
|
122
128
|
| `quiel hook <name> [--format …]` | Run a hook (your agent's configuration calls this) |
|
|
123
129
|
|
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),
|
|
@@ -1568,7 +1554,17 @@ var taskCreatePayloadSchema = z23.object({
|
|
|
1568
1554
|
executor: z23.enum(executorValues2),
|
|
1569
1555
|
blocksCurrent: z23.boolean(),
|
|
1570
1556
|
acceptance: z23.string().optional(),
|
|
1571
|
-
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()
|
|
1572
1568
|
}).refine((v) => Boolean(v.roleKey) !== Boolean(v.assigneeId), {
|
|
1573
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"
|
|
1574
1570
|
});
|
|
@@ -1587,7 +1583,16 @@ var taskBriefSchema = z23.object({
|
|
|
1587
1583
|
var taskCreateAckSchema = z23.object({
|
|
1588
1584
|
task: taskBriefSchema,
|
|
1589
1585
|
/** Во что ушла ТЕКУЩАЯ задача автора, если `blocksCurrent: true`. */
|
|
1590
|
-
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()
|
|
1591
1596
|
});
|
|
1592
1597
|
var taskListPayloadSchema = z23.object({
|
|
1593
1598
|
status: z23.array(z23.enum(taskStatusValues3)).optional(),
|
|
@@ -1599,6 +1604,57 @@ var taskListPayloadSchema = z23.object({
|
|
|
1599
1604
|
var taskListAckSchema = z23.object({
|
|
1600
1605
|
tasks: z23.array(taskBriefSchema)
|
|
1601
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
|
+
});
|
|
1602
1658
|
var taskUpdatePayloadSchema = z23.object({
|
|
1603
1659
|
taskId: z23.string().min(1),
|
|
1604
1660
|
patch: z23.object({
|
|
@@ -1880,12 +1936,28 @@ var ImportTasksRequestSchema = z26.object({
|
|
|
1880
1936
|
var IntegrationStatus = {
|
|
1881
1937
|
/** Работает и проверена вживую. */
|
|
1882
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",
|
|
1883
1954
|
/** Запланирована, но ещё не сделана. Показываем, чтобы не обещать голосом. */
|
|
1884
1955
|
PLANNED: "planned"
|
|
1885
1956
|
};
|
|
1886
1957
|
var IntegrationKey = {
|
|
1887
1958
|
GITHUB: "github",
|
|
1888
1959
|
TELEGRAM: "telegram",
|
|
1960
|
+
SLACK: "slack",
|
|
1889
1961
|
WEBHOOKS: "webhooks"
|
|
1890
1962
|
};
|
|
1891
1963
|
var INTEGRATIONS = [
|
|
@@ -1903,6 +1975,22 @@ var INTEGRATIONS = [
|
|
|
1903
1975
|
scope: "both",
|
|
1904
1976
|
requiresOrgAdmin: false
|
|
1905
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
|
+
},
|
|
1906
1994
|
{
|
|
1907
1995
|
// Одна механика вместо десяти интеграций: Zapier, n8n, Make и любой
|
|
1908
1996
|
// свой эндпоинт. Пока не сделана — и список об этом честно говорит.
|
|
@@ -1919,6 +2007,15 @@ var BY_KEY = new Map(INTEGRATIONS.map((info) => [info.key, info]));
|
|
|
1919
2007
|
var NotificationChannel = {
|
|
1920
2008
|
INAPP: "inapp",
|
|
1921
2009
|
TELEGRAM: "telegram",
|
|
2010
|
+
/**
|
|
2011
|
+
* Личное сообщение в Slack (SL-06).
|
|
2012
|
+
*
|
|
2013
|
+
* ИМЕННО ЛИЧНОЕ, а не канал проекта. Канал — общая видимость и живёт
|
|
2014
|
+
* отдельно (`slack-channel` в воркере): он не адресован никому и не
|
|
2015
|
+
* зависит от настроек человека. Смешай мы их в одном канале доставки —
|
|
2016
|
+
* выключив «Slack», человек выключил бы и ленту всей команды.
|
|
2017
|
+
*/
|
|
2018
|
+
SLACK: "slack",
|
|
1922
2019
|
EMAIL: "email",
|
|
1923
2020
|
PUSH: "push"
|
|
1924
2021
|
};
|
|
@@ -1932,18 +2029,21 @@ var NotificationUrgency = {
|
|
|
1932
2029
|
var ALL = [
|
|
1933
2030
|
NotificationChannel.INAPP,
|
|
1934
2031
|
NotificationChannel.TELEGRAM,
|
|
2032
|
+
NotificationChannel.SLACK,
|
|
1935
2033
|
NotificationChannel.EMAIL,
|
|
1936
2034
|
NotificationChannel.PUSH
|
|
1937
2035
|
];
|
|
1938
2036
|
var FAST = [
|
|
1939
2037
|
NotificationChannel.INAPP,
|
|
1940
2038
|
NotificationChannel.TELEGRAM,
|
|
2039
|
+
NotificationChannel.SLACK,
|
|
1941
2040
|
NotificationChannel.PUSH
|
|
1942
2041
|
];
|
|
1943
2042
|
var QUIET = [NotificationChannel.INAPP];
|
|
1944
2043
|
var SLOW = [
|
|
1945
2044
|
NotificationChannel.INAPP,
|
|
1946
2045
|
NotificationChannel.TELEGRAM,
|
|
2046
|
+
NotificationChannel.SLACK,
|
|
1947
2047
|
NotificationChannel.EMAIL
|
|
1948
2048
|
];
|
|
1949
2049
|
function defaultsFor(urgency) {
|
|
@@ -2007,8 +2107,20 @@ var NOTIFICATION_KINDS = [
|
|
|
2007
2107
|
kind(
|
|
2008
2108
|
"agent_session_limit",
|
|
2009
2109
|
"\u0421\u0435\u0441\u0441\u0438\u044F \u0430\u0433\u0435\u043D\u0442\u0430 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u0430",
|
|
2010
|
-
"\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.",
|
|
2011
|
-
|
|
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
|
|
2012
2124
|
),
|
|
2013
2125
|
kind(
|
|
2014
2126
|
"task_auto_accepted",
|
|
@@ -2030,6 +2142,7 @@ import { z as z27 } from "zod";
|
|
|
2030
2142
|
var NotificationChannelFlagsSchema = z27.object({
|
|
2031
2143
|
inapp: z27.boolean().optional(),
|
|
2032
2144
|
telegram: z27.boolean().optional(),
|
|
2145
|
+
slack: z27.boolean().optional(),
|
|
2033
2146
|
email: z27.boolean().optional(),
|
|
2034
2147
|
push: z27.boolean().optional()
|
|
2035
2148
|
}).partial();
|
|
@@ -3306,7 +3419,8 @@ import { z as z29 } from "zod";
|
|
|
3306
3419
|
var HookCommandEntrySchema = z29.object({
|
|
3307
3420
|
type: z29.literal("command"),
|
|
3308
3421
|
command: z29.string().min(1),
|
|
3309
|
-
timeout: z29.number().int().positive().optional()
|
|
3422
|
+
timeout: z29.number().int().positive().optional(),
|
|
3423
|
+
asyncRewake: z29.boolean().optional()
|
|
3310
3424
|
}).passthrough();
|
|
3311
3425
|
var HookMatcherGroupSchema = z29.object({
|
|
3312
3426
|
matcher: z29.string().optional(),
|
|
@@ -3322,10 +3436,48 @@ var ClaudeSettingsSchema = z29.object({
|
|
|
3322
3436
|
env: z29.record(z29.string(), z29.string()).optional()
|
|
3323
3437
|
}).passthrough();
|
|
3324
3438
|
var CLAUDE_SETTINGS_RELATIVE_PATH = join2(".claude", "settings.json");
|
|
3439
|
+
function getClaudeSettingsPath(dir) {
|
|
3440
|
+
return join2(dir, ".claude", "settings.json");
|
|
3441
|
+
}
|
|
3442
|
+
function readClaudeSettings(dir) {
|
|
3443
|
+
const path = getClaudeSettingsPath(dir);
|
|
3444
|
+
if (!existsSync2(path)) return { ok: true, settings: {} };
|
|
3445
|
+
let raw;
|
|
3446
|
+
try {
|
|
3447
|
+
raw = readFileSync2(path, "utf8");
|
|
3448
|
+
} catch (error) {
|
|
3449
|
+
return { ok: false, message: `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${path}: ${String(error)}` };
|
|
3450
|
+
}
|
|
3451
|
+
let parsedJson;
|
|
3452
|
+
try {
|
|
3453
|
+
parsedJson = raw.trim().length === 0 ? {} : JSON.parse(raw);
|
|
3454
|
+
} catch {
|
|
3455
|
+
return {
|
|
3456
|
+
ok: false,
|
|
3457
|
+
message: `${path} \u2014 \u043D\u0435 \u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON. \u041F\u043E\u043F\u0440\u0430\u0432\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E (\u044D\u0442\u043E \u0444\u0430\u0439\u043B Claude Code, \u043D\u0435 \u043D\u0430\u0448) \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 "quiel init".`
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
const parsed = ClaudeSettingsSchema.safeParse(parsedJson);
|
|
3461
|
+
if (!parsed.success) {
|
|
3462
|
+
return {
|
|
3463
|
+
ok: false,
|
|
3464
|
+
message: `${path} \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u0443\u044E \u0444\u043E\u0440\u043C\u0443: ${parsed.error.issues.map((i) => i.message).join("; ")}`
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
return { ok: true, settings: parsed.data };
|
|
3468
|
+
}
|
|
3325
3469
|
var CLI_BIN = "quiel";
|
|
3326
3470
|
var HOOK_SPECS = [
|
|
3327
3471
|
{ event: "SessionStart", command: `${CLI_BIN} hook session-start` },
|
|
3328
3472
|
{ event: "Stop", command: `${CLI_BIN} hook stop`, timeout: 10 },
|
|
3473
|
+
// ВТОРАЯ КОМАНДА НА ТОМ ЖЕ СОБЫТИИ, И ОНА НЕ ДУБЛЬ. `hook stop` решает,
|
|
3474
|
+
// можно ли остановиться; эта — что будет, ПОКА агент стоит. Без неё
|
|
3475
|
+
// разрешённый простой означал бы молчание до прихода человека.
|
|
3476
|
+
//
|
|
3477
|
+
// Выходит мгновенно с кодом 0, если режим ручной, задача на руках или
|
|
3478
|
+
// другой ожидатель уже работает, — поэтому висит она только тогда,
|
|
3479
|
+
// когда агенту действительно нечего делать.
|
|
3480
|
+
{ event: "Stop", command: `${CLI_BIN} wait-for-task`, asyncRewake: true },
|
|
3329
3481
|
{
|
|
3330
3482
|
event: "StopFailure",
|
|
3331
3483
|
matcher: "rate_limit|overloaded",
|
|
@@ -3348,7 +3500,8 @@ function upsertHookSpec(groups, spec) {
|
|
|
3348
3500
|
const commandEntry = {
|
|
3349
3501
|
type: "command",
|
|
3350
3502
|
command: spec.command,
|
|
3351
|
-
...spec.timeout !== void 0 ? { timeout: spec.timeout } : {}
|
|
3503
|
+
...spec.timeout !== void 0 ? { timeout: spec.timeout } : {},
|
|
3504
|
+
...spec.asyncRewake !== void 0 ? { asyncRewake: spec.asyncRewake } : {}
|
|
3352
3505
|
};
|
|
3353
3506
|
const groupIndex = groups.findIndex((g) => sameMatcher(g.matcher, spec.matcher));
|
|
3354
3507
|
if (groupIndex === -1) {
|
|
@@ -3515,6 +3668,40 @@ var ClientStateSchema = z30.object({
|
|
|
3515
3668
|
tasksCompletedInSession: z30.number().int().nonnegative().default(0),
|
|
3516
3669
|
/** Кэш `ProjectSettings.contextResetAfterTasks` (§7.2 ТЗ) — сколько задач подряд можно сдать, прежде чем `Stop`-хук попросит `/clear`. */
|
|
3517
3670
|
contextResetAfterTasks: z30.number().int().positive().default(DEFAULT_CONTEXT_RESET_AFTER_TASKS),
|
|
3671
|
+
/**
|
|
3672
|
+
* Идентификаторы задач, сданных в этой сессии, — а не одно число.
|
|
3673
|
+
*
|
|
3674
|
+
* Счётчик `tasksCompletedInSession` увеличивается на КАЖДУЮ успешную
|
|
3675
|
+
* сдачу, и задача, возвращённая с ревью и сданная повторно, считалась
|
|
3676
|
+
* второй. Владелец увидел «Сессия выполнила 9 задач» при лимите 8 и
|
|
3677
|
+
* справедливо решил, что счётчик сломан: он не сломан, он считает
|
|
3678
|
+
* сдачи, а сообщение называет их задачами.
|
|
3679
|
+
*
|
|
3680
|
+
* Храним ключи, чтобы говорить правду в обоих числах.
|
|
3681
|
+
*/
|
|
3682
|
+
completedTaskIds: z30.array(z30.string()).default([]),
|
|
3683
|
+
/**
|
|
3684
|
+
* Когда `next_task` в последний раз ответил «задач нет».
|
|
3685
|
+
*
|
|
3686
|
+
* БЕЗ ЭТОГО ПОЛЯ `Stop`-ХУК НЕ ОТЛИЧАЕТ ПУСТУЮ ОЧЕРЕДЬ ОТ ЛЕНИ. Он
|
|
3687
|
+
* безусловно требовал «вызови next_task», и при пустой очереди агент
|
|
3688
|
+
* крутил этот цикл часами: в боевом проекте очередь пустовала по
|
|
3689
|
+
* 15–25 минут подряд, и каждый круг тратил ход модели впустую.
|
|
3690
|
+
*
|
|
3691
|
+
* `null` — очередь не была пуста с начала сессии либо последняя
|
|
3692
|
+
* попытка что-то принесла.
|
|
3693
|
+
*/
|
|
3694
|
+
queueEmptyAt: z30.string().nullable().default(null),
|
|
3695
|
+
/**
|
|
3696
|
+
* Когда `next_task` в последний раз получил ОТКАЗ сервера.
|
|
3697
|
+
*
|
|
3698
|
+
* Отличается от пустой очереди принципиально: там работы нет, здесь её
|
|
3699
|
+
* не удалось спросить. Без этого поля хук видел «задачи не сдана нет,
|
|
3700
|
+
* очередь не пуста» и гнал звать `next_task` снова — а тот снова
|
|
3701
|
+
* падал. Агент не мог ни работать, ни остановиться: девять отказов
|
|
3702
|
+
* подряд за пятнадцать минут на боевом проекте 24 сентября 2026.
|
|
3703
|
+
*/
|
|
3704
|
+
claimFailedAt: z30.string().nullable().default(null),
|
|
3518
3705
|
/** Кэш стоп-паттернов/правил доверия для `PreToolUse` фазы 4. */
|
|
3519
3706
|
trust: TrustCacheSchema.default(null),
|
|
3520
3707
|
/** Снимок задачи на момент последнего `PreCompact` — пишет src/hooks/pre-compact.ts, читает SessionStart(source="compact"), чтобы напомнить модели, чем она занималась. */
|
|
@@ -5790,7 +5977,7 @@ ${question.answerMd ?? ""}${describeAnswerAttachments(question)}`;
|
|
|
5790
5977
|
// package.json
|
|
5791
5978
|
var package_default = {
|
|
5792
5979
|
name: "@quiel/cli",
|
|
5793
|
-
version: "0.
|
|
5980
|
+
version: "0.6.2",
|
|
5794
5981
|
description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 (Claude Code, Codex, Cursor, OpenCode) \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
|
|
5795
5982
|
homepage: "https://quiel.app",
|
|
5796
5983
|
repository: {
|
|
@@ -5971,12 +6158,22 @@ function describeNoWorktree(reason) {
|
|
|
5971
6158
|
}
|
|
5972
6159
|
|
|
5973
6160
|
// src/mcp/next-task.ts
|
|
6161
|
+
var DEFAULT_WAIT_SEC = 90;
|
|
6162
|
+
var MAX_WAIT_SEC = 110;
|
|
5974
6163
|
var nextTaskInputShape = {
|
|
5975
|
-
|
|
5976
|
-
|
|
6164
|
+
// ПОТОЛОК УЖЕ, ЧЕМ У СЕРВЕРА, И ЭТО НАМЕРЕННО. Схема события
|
|
6165
|
+
// допускает до 600 с — столько держит long-poll диспетчер. Но у
|
|
6166
|
+
// Claude Code вызов MCP умирает по таймауту на 600 с, а ожидание ack
|
|
6167
|
+
// здесь ещё на 15 с длиннее. Попроси агент 600 — и есть окно, в
|
|
6168
|
+
// котором сервер ВЫДАЛ задачу, а вызов уже убит: задача ушла в
|
|
6169
|
+
// IN_PROGRESS, агент об этом не знает, аренда висит до истечения.
|
|
6170
|
+
// Найдено владельцем на боевом проекте 24 сентября 2026.
|
|
6171
|
+
waitSec: taskClaimPayloadSchema.shape.waitSec.refine((value) => value <= MAX_WAIT_SEC, {
|
|
6172
|
+
message: `\u0411\u043E\u043B\u044C\u0448\u0435 ${MAX_WAIT_SEC} \u0441 \u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F: \u0432\u044B\u0437\u043E\u0432 MCP \u043D\u0435 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0451\u0442 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435. \u0417\u0430\u0432\u0435\u0440\u0448\u0438 \u0445\u043E\u0434 \u2014 \u0431\u0443\u0434\u0438\u043B\u044C\u043D\u0438\u043A \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442, \u043A\u043E\u0433\u0434\u0430 \u043F\u043E\u044F\u0432\u0438\u0442\u0441\u044F \u0437\u0430\u0434\u0430\u0447\u0430.`
|
|
6173
|
+
}).optional().describe(
|
|
6174
|
+
"\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..110. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 90. \u0411\u043E\u043B\u044C\u0448\u0435 \u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F \u0438 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E: Claude Code \u0443\u0432\u043E\u0434\u0438\u0442 \u0432\u044B\u0437\u043E\u0432 MCP \u0432 \u0444\u043E\u043D \u043F\u0440\u0438\u043C\u0435\u0440\u043D\u043E \u0447\u0435\u0440\u0435\u0437 120 \u0441\u0435\u043A\u0443\u043D\u0434, \u0430 \u0434\u043E\u043B\u0433\u043E\u0435 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430 \u0431\u0443\u0434\u0438\u043B\u044C\u043D\u0438\u043A\u0430 (quiel wait-for-task), \u0430 \u043D\u0435 \u044D\u0442\u043E\u0433\u043E \u0442\u0443\u043B\u0430. \u041F\u0443\u0441\u0442\u043E\u0439 \u043E\u0442\u0432\u0435\u0442 \u2014 \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434: \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0445\u043E\u0434, \u0442\u0435\u0431\u044F \u0440\u0430\u0437\u0431\u0443\u0434\u044F\u0442."
|
|
5977
6175
|
)
|
|
5978
6176
|
};
|
|
5979
|
-
var DEFAULT_WAIT_SEC = 300;
|
|
5980
6177
|
var CLAIM_ACK_BUFFER_MS = 15e3;
|
|
5981
6178
|
async function nextTaskHandler(args, runtime) {
|
|
5982
6179
|
const { client, stateFilePath } = runtime;
|
|
@@ -6000,10 +6197,14 @@ async function nextTaskHandler(args, runtime) {
|
|
|
6000
6197
|
taskClaimAckSchema,
|
|
6001
6198
|
waitSec * 1e3 + CLAIM_ACK_BUFFER_MS
|
|
6002
6199
|
);
|
|
6003
|
-
if (!claimed.ok)
|
|
6200
|
+
if (!claimed.ok) {
|
|
6201
|
+
updateState({ claimFailedAt: (/* @__PURE__ */ new Date()).toISOString() }, stateFilePath);
|
|
6202
|
+
return errorResult(claimed.message);
|
|
6203
|
+
}
|
|
6004
6204
|
if ("empty" in claimed.data) {
|
|
6205
|
+
updateState({ queueEmptyAt: (/* @__PURE__ */ new Date()).toISOString(), claimFailedAt: null }, stateFilePath);
|
|
6005
6206
|
return textResult(
|
|
6006
|
-
`\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 \
|
|
6207
|
+
`\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.`
|
|
6007
6208
|
);
|
|
6008
6209
|
}
|
|
6009
6210
|
const task = claimed.data;
|
|
@@ -6016,7 +6217,11 @@ async function nextTaskHandler(args, runtime) {
|
|
|
6016
6217
|
gitMode: task.project.gitMode,
|
|
6017
6218
|
branchName: task.project.branchName,
|
|
6018
6219
|
commitPrefix: task.project.commitPrefix
|
|
6019
|
-
}
|
|
6220
|
+
},
|
|
6221
|
+
// Очередь оказалась не пуста — пометки снимаются, иначе хук
|
|
6222
|
+
// продолжал бы считать, что работы нет или что сервер лежит.
|
|
6223
|
+
queueEmptyAt: null,
|
|
6224
|
+
claimFailedAt: null
|
|
6020
6225
|
},
|
|
6021
6226
|
stateFilePath
|
|
6022
6227
|
);
|
|
@@ -6074,6 +6279,77 @@ ${worktreeNote}${continueNote}${warning}`
|
|
|
6074
6279
|
);
|
|
6075
6280
|
}
|
|
6076
6281
|
|
|
6282
|
+
// src/mcp/link.ts
|
|
6283
|
+
var linkTasksInputShape = {
|
|
6284
|
+
blockerId: taskLinkPayloadSchema.shape.blockerId.describe(
|
|
6285
|
+
"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."
|
|
6286
|
+
),
|
|
6287
|
+
blockedId: taskLinkPayloadSchema.shape.blockedId.describe(
|
|
6288
|
+
"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."
|
|
6289
|
+
),
|
|
6290
|
+
kind: taskLinkPayloadSchema.shape.kind.describe(
|
|
6291
|
+
"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."
|
|
6292
|
+
)
|
|
6293
|
+
};
|
|
6294
|
+
async function linkTasksHandler(args, runtime) {
|
|
6295
|
+
const parsedArgs = parseArgsOrError(taskLinkPayloadSchema, args);
|
|
6296
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6297
|
+
const result = await callServerOrError(
|
|
6298
|
+
runtime.client,
|
|
6299
|
+
"task:link",
|
|
6300
|
+
parsedArgs.data,
|
|
6301
|
+
taskLinkAckSchema
|
|
6302
|
+
);
|
|
6303
|
+
if (!result.ok) return errorResult(result.message);
|
|
6304
|
+
const relation = parsedArgs.data.kind === "RELATES" ? `\u0441\u0432\u044F\u0437\u0430\u043D\u0430 \u0441 ${args.blockedId} (RELATES, \u043D\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043D\u0435 \u0432\u043B\u0438\u044F\u0435\u0442)` : `\u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId} (BLOCKS)`;
|
|
6305
|
+
if (!result.data.linked) {
|
|
6306
|
+
return textResult(`\u0421\u0432\u044F\u0437\u044C \u0443\u0436\u0435 \u0431\u044B\u043B\u0430: ${args.blockerId} ${relation}.`);
|
|
6307
|
+
}
|
|
6308
|
+
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." : "";
|
|
6309
|
+
return textResult(`\u0413\u043E\u0442\u043E\u0432\u043E: ${args.blockerId} ${relation}.${stopped}`);
|
|
6310
|
+
}
|
|
6311
|
+
var unlinkTasksInputShape = {
|
|
6312
|
+
blockerId: taskUnlinkPayloadSchema.shape.blockerId.describe("ID \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438."),
|
|
6313
|
+
blockedId: taskUnlinkPayloadSchema.shape.blockedId.describe("ID \u0436\u0434\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438.")
|
|
6314
|
+
};
|
|
6315
|
+
async function unlinkTasksHandler(args, runtime) {
|
|
6316
|
+
const parsedArgs = parseArgsOrError(taskUnlinkPayloadSchema, args);
|
|
6317
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6318
|
+
const result = await callServerOrError(
|
|
6319
|
+
runtime.client,
|
|
6320
|
+
"task:unlink",
|
|
6321
|
+
parsedArgs.data,
|
|
6322
|
+
taskUnlinkAckSchema
|
|
6323
|
+
);
|
|
6324
|
+
if (!result.ok) return errorResult(result.message);
|
|
6325
|
+
return textResult(
|
|
6326
|
+
result.data.unlinked ? `\u0421\u0432\u044F\u0437\u044C \u0441\u043D\u044F\u0442\u0430: \u043C\u0435\u0436\u0434\u0443 ${args.blockerId} \u0438 ${args.blockedId} \u0435\u0451 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442.` : `\u0422\u0430\u043A\u043E\u0439 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u0431\u044B\u043B\u043E: \u043C\u0435\u0436\u0434\u0443 ${args.blockerId} \u0438 ${args.blockedId}.`
|
|
6327
|
+
);
|
|
6328
|
+
}
|
|
6329
|
+
var planTaskInputShape = {
|
|
6330
|
+
taskId: taskPlanPayloadSchema.shape.taskId.describe(
|
|
6331
|
+
"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."
|
|
6332
|
+
),
|
|
6333
|
+
patch: taskPlanPayloadSchema.shape.patch.describe(
|
|
6334
|
+
"\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."
|
|
6335
|
+
)
|
|
6336
|
+
};
|
|
6337
|
+
async function planTaskHandler(args, runtime) {
|
|
6338
|
+
const parsedArgs = parseArgsOrError(taskPlanPayloadSchema, args);
|
|
6339
|
+
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6340
|
+
const result = await callServerOrError(
|
|
6341
|
+
runtime.client,
|
|
6342
|
+
"task:plan",
|
|
6343
|
+
parsedArgs.data,
|
|
6344
|
+
taskPlanAckSchema
|
|
6345
|
+
);
|
|
6346
|
+
if (!result.ok) return errorResult(result.message);
|
|
6347
|
+
return textResult(
|
|
6348
|
+
`\u0417\u0430\u0434\u0430\u0447\u0430 ${result.data.task.key} \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430:
|
|
6349
|
+
${JSON.stringify(result.data.task, null, 2)}`
|
|
6350
|
+
);
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6077
6353
|
// src/mcp/release-task.ts
|
|
6078
6354
|
var releaseTaskInputShape = {
|
|
6079
6355
|
reason: taskReleasePayloadSchema.shape.reason.describe(
|
|
@@ -6150,8 +6426,15 @@ async function reportHandler(args, runtime) {
|
|
|
6150
6426
|
);
|
|
6151
6427
|
}
|
|
6152
6428
|
const taskLabel2 = state.currentTask.taskKey ?? state.currentTask.taskId;
|
|
6429
|
+
const completedTaskIds = state.completedTaskIds.includes(state.currentTask.taskId) ? state.completedTaskIds : [...state.completedTaskIds, state.currentTask.taskId];
|
|
6153
6430
|
updateState(
|
|
6154
|
-
{
|
|
6431
|
+
{
|
|
6432
|
+
currentTask: null,
|
|
6433
|
+
tasksCompletedInSession: state.tasksCompletedInSession + 1,
|
|
6434
|
+
completedTaskIds,
|
|
6435
|
+
// Сдали задачу — значит очередь только что была не пуста.
|
|
6436
|
+
queueEmptyAt: null
|
|
6437
|
+
},
|
|
6155
6438
|
runtime.stateFilePath
|
|
6156
6439
|
);
|
|
6157
6440
|
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)" : "";
|
|
@@ -6471,6 +6754,9 @@ async function submitViaPullRequest(args, runtime, currentTask) {
|
|
|
6471
6754
|
|
|
6472
6755
|
// src/mcp/tasks.ts
|
|
6473
6756
|
var createTaskInputShape = {
|
|
6757
|
+
dependsOn: taskCreatePayloadSchema.shape.dependsOn.describe(
|
|
6758
|
+
"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."
|
|
6759
|
+
),
|
|
6474
6760
|
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."),
|
|
6475
6761
|
descriptionMd: taskCreatePayloadSchema.shape.descriptionMd.describe(
|
|
6476
6762
|
"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."
|
|
@@ -6509,7 +6795,8 @@ async function createTaskHandler(args, runtime) {
|
|
|
6509
6795
|
executor: args.executor,
|
|
6510
6796
|
blocksCurrent: args.blocksCurrent,
|
|
6511
6797
|
...args.acceptance ? { acceptance: args.acceptance } : {},
|
|
6512
|
-
...args.labels && args.labels.length > 0 ? { labels: args.labels } : {}
|
|
6798
|
+
...args.labels && args.labels.length > 0 ? { labels: args.labels } : {},
|
|
6799
|
+
...args.dependsOn && args.dependsOn.length > 0 ? { dependsOn: args.dependsOn } : {}
|
|
6513
6800
|
});
|
|
6514
6801
|
if (!parsedArgs.ok) return errorResult(parsedArgs.message);
|
|
6515
6802
|
const state = readState(runtime.stateFilePath);
|
|
@@ -6535,8 +6822,15 @@ async function createTaskHandler(args, runtime) {
|
|
|
6535
6822
|
const blockNote = result.data.currentTaskStatus ? `
|
|
6536
6823
|
|
|
6537
6824
|
\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}.` : "";
|
|
6825
|
+
const requested = args.dependsOn?.length ?? 0;
|
|
6826
|
+
const linked = result.data.dependenciesLinked ?? 0;
|
|
6827
|
+
const depsNote = requested === 0 ? "" : linked === requested ? `
|
|
6828
|
+
|
|
6829
|
+
\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E: ${linked}.` : `
|
|
6830
|
+
|
|
6831
|
+
\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.`;
|
|
6538
6832
|
return textResult(
|
|
6539
|
-
`\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}`
|
|
6833
|
+
`\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}`
|
|
6540
6834
|
);
|
|
6541
6835
|
}
|
|
6542
6836
|
var listTasksInputShape = {
|
|
@@ -6715,6 +7009,33 @@ function registerTools(server, runtime) {
|
|
|
6715
7009
|
},
|
|
6716
7010
|
(args) => updateTaskHandler(args, runtime)
|
|
6717
7011
|
);
|
|
7012
|
+
server.registerTool(
|
|
7013
|
+
"link_tasks",
|
|
7014
|
+
{
|
|
7015
|
+
title: "\u0421\u0432\u044F\u0437\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438",
|
|
7016
|
+
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.",
|
|
7017
|
+
inputSchema: linkTasksInputShape
|
|
7018
|
+
},
|
|
7019
|
+
(args) => linkTasksHandler(args, runtime)
|
|
7020
|
+
);
|
|
7021
|
+
server.registerTool(
|
|
7022
|
+
"plan_task",
|
|
7023
|
+
{
|
|
7024
|
+
title: "\u0421\u043F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443",
|
|
7025
|
+
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.",
|
|
7026
|
+
inputSchema: planTaskInputShape
|
|
7027
|
+
},
|
|
7028
|
+
(args) => planTaskHandler(args, runtime)
|
|
7029
|
+
);
|
|
7030
|
+
server.registerTool(
|
|
7031
|
+
"unlink_tasks",
|
|
7032
|
+
{
|
|
7033
|
+
title: "\u0421\u043D\u044F\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043C\u0435\u0436\u0434\u0443 \u0437\u0430\u0434\u0430\u0447\u0430\u043C\u0438",
|
|
7034
|
+
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.",
|
|
7035
|
+
inputSchema: unlinkTasksInputShape
|
|
7036
|
+
},
|
|
7037
|
+
(args) => unlinkTasksHandler(args, runtime)
|
|
7038
|
+
);
|
|
6718
7039
|
server.registerTool(
|
|
6719
7040
|
"ask_human",
|
|
6720
7041
|
{
|
|
@@ -7158,19 +7479,39 @@ var StopInputSchema = z43.object({
|
|
|
7158
7479
|
session_id: z43.string(),
|
|
7159
7480
|
cwd: z43.string(),
|
|
7160
7481
|
stop_hook_active: z43.boolean(),
|
|
7161
|
-
last_assistant_message: z43.string().optional()
|
|
7482
|
+
last_assistant_message: z43.string().optional(),
|
|
7483
|
+
/**
|
|
7484
|
+
* Незавершённая фоновая работа сессии.
|
|
7485
|
+
*
|
|
7486
|
+
* ЧИТАЕМ ТОЛЬКО ДЛИНУ, И ЭТО НАМЕРЕННО. Внутренняя форма элементов в
|
|
7487
|
+
* документации не описана, а строить решение на угаданных полях —
|
|
7488
|
+
* ровно то, что §22.8 ТЗ запрещает. Нам достаточно ответа на вопрос
|
|
7489
|
+
* «есть ли незакрытая фоновая работа»: он не зависит от того, что
|
|
7490
|
+
* внутри.
|
|
7491
|
+
*
|
|
7492
|
+
* В §10.4 ТЗ этих полей нет — они появились в Claude Code позже.
|
|
7493
|
+
*/
|
|
7494
|
+
background_tasks: z43.array(z43.unknown()).optional(),
|
|
7495
|
+
session_crons: z43.array(z43.unknown()).optional()
|
|
7162
7496
|
}).passthrough();
|
|
7163
7497
|
function taskLabel(state) {
|
|
7164
7498
|
if (!state.currentTask) return "";
|
|
7165
7499
|
return state.currentTask.taskKey ?? state.currentTask.taskId;
|
|
7166
7500
|
}
|
|
7167
|
-
function stopHook(input, state) {
|
|
7501
|
+
function stopHook(input, state, env = { wakeupAvailable: false }) {
|
|
7168
7502
|
if (input.stop_hook_active) {
|
|
7169
7503
|
return { output: {} };
|
|
7170
7504
|
}
|
|
7171
7505
|
if (state.mode !== AgentMode.AUTONOMOUS) {
|
|
7172
7506
|
return { output: {} };
|
|
7173
7507
|
}
|
|
7508
|
+
if (hasUnfinishedBackgroundWork(input)) {
|
|
7509
|
+
return {
|
|
7510
|
+
output: {
|
|
7511
|
+
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."
|
|
7512
|
+
}
|
|
7513
|
+
};
|
|
7514
|
+
}
|
|
7174
7515
|
if (state.currentTask) {
|
|
7175
7516
|
return {
|
|
7176
7517
|
output: {
|
|
@@ -7179,23 +7520,55 @@ function stopHook(input, state) {
|
|
|
7179
7520
|
}
|
|
7180
7521
|
};
|
|
7181
7522
|
}
|
|
7182
|
-
if (state.tasksCompletedInSession
|
|
7523
|
+
if (state.tasksCompletedInSession >= state.contextResetAfterTasks) {
|
|
7524
|
+
return limitReached(state);
|
|
7525
|
+
}
|
|
7526
|
+
if (state.claimFailedAt) {
|
|
7183
7527
|
return {
|
|
7184
7528
|
output: {
|
|
7185
|
-
|
|
7186
|
-
|
|
7529
|
+
systemMessage: "\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u043E\u0442\u043A\u0430\u0437\u0430\u043B\u0430 \u043D\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u0435 \u0437\u0430\u0434\u0430\u0447\u0438. \u0410\u0433\u0435\u043D\u0442 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0437\u0432\u0430\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443 \u0442\u043E, \u0447\u0442\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u043E\u0439 \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."
|
|
7530
|
+
}
|
|
7531
|
+
};
|
|
7532
|
+
}
|
|
7533
|
+
if (state.queueEmptyAt) {
|
|
7534
|
+
if (!env.wakeupAvailable) {
|
|
7535
|
+
return {
|
|
7536
|
+
output: {
|
|
7537
|
+
decision: "block",
|
|
7538
|
+
reason: "\u041E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u0443\u0441\u0442\u0430. \u041F\u043E\u0434\u043E\u0436\u0434\u0438 \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 next_task \u0435\u0449\u0451 \u0440\u0430\u0437 \u2014 \u0431\u0443\u0434\u0438\u043B\u044C\u043D\u0438\u043A \u0432 \u044D\u0442\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D, \u0442\u0430\u043A \u0447\u0442\u043E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C\u0441\u044F \u043D\u0435\u043B\u044C\u0437\u044F: \u0437\u0430\u0434\u0430\u0447\u0443 \u0438\u043D\u0430\u0447\u0435 \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442.",
|
|
7539
|
+
systemMessage: "\u0410\u0433\u0435\u043D\u0442 \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u0431\u0443\u0434\u0438\u043B\u044C\u043D\u0438\u043A \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D. \u0412\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \xABquiel init\xBB \u0432 \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u043E\u043D \u043F\u0440\u043E\u043F\u0438\u0448\u0435\u0442 \u043E\u0436\u0438\u0434\u0430\u0442\u0435\u043B\u044C \u0437\u0430\u0434\u0430\u0447, \u0438 \u0430\u0433\u0435\u043D\u0442 \u0441\u043C\u043E\u0436\u0435\u0442 \u0441\u043F\u043E\u043A\u043E\u0439\u043D\u043E \u043F\u0440\u043E\u0441\u0442\u0430\u0438\u0432\u0430\u0442\u044C, \u043F\u0440\u043E\u0441\u044B\u043F\u0430\u044F\u0441\u044C \u0441\u0430\u043C."
|
|
7540
|
+
}
|
|
7541
|
+
};
|
|
7542
|
+
}
|
|
7543
|
+
return {
|
|
7544
|
+
output: {
|
|
7545
|
+
systemMessage: "\u041E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u0443\u0441\u0442\u0430 \u2014 \u0430\u0433\u0435\u043D\u0442 \u0436\u0434\u0451\u0442. \u041E\u043D \u043F\u0440\u043E\u0441\u043D\u0451\u0442\u0441\u044F \u0441\u0430\u043C, \u043A\u0430\u043A \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u044F\u0432\u0438\u0442\u0441\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0435\u0433\u043E \u0440\u043E\u043B\u0438."
|
|
7187
7546
|
}
|
|
7188
7547
|
};
|
|
7189
7548
|
}
|
|
7549
|
+
return {
|
|
7550
|
+
output: {
|
|
7551
|
+
decision: "block",
|
|
7552
|
+
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."
|
|
7553
|
+
}
|
|
7554
|
+
};
|
|
7555
|
+
}
|
|
7556
|
+
function hasUnfinishedBackgroundWork(input) {
|
|
7557
|
+
return (input.background_tasks?.length ?? 0) > 0 || (input.session_crons?.length ?? 0) > 0;
|
|
7558
|
+
}
|
|
7559
|
+
function limitReached(state) {
|
|
7190
7560
|
const serverActions = [
|
|
7191
7561
|
{
|
|
7192
7562
|
type: "session-limit-reached",
|
|
7193
7563
|
payload: { tasksCompletedInSession: state.tasksCompletedInSession }
|
|
7194
7564
|
}
|
|
7195
7565
|
];
|
|
7566
|
+
const tasks = state.completedTaskIds.length;
|
|
7567
|
+
const submissions = state.tasksCompletedInSession;
|
|
7568
|
+
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)`;
|
|
7196
7569
|
return {
|
|
7197
7570
|
output: {
|
|
7198
|
-
systemMessage: `\u0421\u0435\u0441\u0441\u0438\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043B\u0430 ${
|
|
7571
|
+
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.`
|
|
7199
7572
|
},
|
|
7200
7573
|
serverActions
|
|
7201
7574
|
};
|
|
@@ -7209,6 +7582,20 @@ var HookInputSchema = z44.discriminatedUnion("hook_event_name", [
|
|
|
7209
7582
|
PreCompactInputSchema,
|
|
7210
7583
|
PreToolUseInputSchema
|
|
7211
7584
|
]);
|
|
7585
|
+
function isWakeupConfigured(cwd) {
|
|
7586
|
+
try {
|
|
7587
|
+
const root = findProjectRoot(cwd);
|
|
7588
|
+
if (!root) return false;
|
|
7589
|
+
const result = readClaudeSettings(root);
|
|
7590
|
+
if (!result.ok) return false;
|
|
7591
|
+
const groups = result.settings.hooks?.Stop ?? [];
|
|
7592
|
+
return groups.some(
|
|
7593
|
+
(group) => group.hooks.some((entry) => entry.command.includes("wait-for-task"))
|
|
7594
|
+
);
|
|
7595
|
+
} catch {
|
|
7596
|
+
return false;
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7212
7599
|
function runHook(rawInput, stateFilePath) {
|
|
7213
7600
|
const parsedInput = HookInputSchema.safeParse(rawInput);
|
|
7214
7601
|
if (!parsedInput.success) {
|
|
@@ -7248,7 +7635,7 @@ function dispatch2(input, state) {
|
|
|
7248
7635
|
case "SessionStart":
|
|
7249
7636
|
return sessionStartHook(input, state);
|
|
7250
7637
|
case "Stop":
|
|
7251
|
-
return stopHook(input, state);
|
|
7638
|
+
return stopHook(input, state, { wakeupAvailable: isWakeupConfigured(input.cwd) });
|
|
7252
7639
|
case "StopFailure":
|
|
7253
7640
|
return stopFailureHook(input, state);
|
|
7254
7641
|
case "PreCompact":
|
|
@@ -7427,7 +7814,7 @@ function missingNonInteractiveFields(options) {
|
|
|
7427
7814
|
return REQUIRED_FIELDS.filter((field) => !options[field] || options[field]?.trim().length === 0);
|
|
7428
7815
|
}
|
|
7429
7816
|
function parseRoles(raw) {
|
|
7430
|
-
return raw.split(
|
|
7817
|
+
return raw.split(/[,\s]+/).map((r) => r.trim()).filter((r) => r.length > 0);
|
|
7431
7818
|
}
|
|
7432
7819
|
function createNoopPrompter() {
|
|
7433
7820
|
const fail = async () => {
|
|
@@ -7595,6 +7982,101 @@ function formatStatus(report) {
|
|
|
7595
7982
|
return lines.join("\n");
|
|
7596
7983
|
}
|
|
7597
7984
|
|
|
7985
|
+
// src/cli/wait-for-task.ts
|
|
7986
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, rmSync, writeFileSync as writeFileSync10 } from "fs";
|
|
7987
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
7988
|
+
var DEFAULT_POLL_INTERVAL_MS = 15e3;
|
|
7989
|
+
var DEFAULT_MAX_WAIT_MS = 8 * 60 * 60 * 1e3;
|
|
7990
|
+
var CONNECT_TIMEOUT_MS = 8e3;
|
|
7991
|
+
var PEEK_TIMEOUT_MS = 8e3;
|
|
7992
|
+
var WAIT_EXIT_IDLE = 0;
|
|
7993
|
+
var WAIT_EXIT_WAKE = 2;
|
|
7994
|
+
function skipReason(state) {
|
|
7995
|
+
if (state.mode !== AgentMode.AUTONOMOUS) {
|
|
7996
|
+
return "\u0440\u0435\u0436\u0438\u043C \u043D\u0435 \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439";
|
|
7997
|
+
}
|
|
7998
|
+
if (state.currentTask) {
|
|
7999
|
+
return "\u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0436\u0435 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435";
|
|
8000
|
+
}
|
|
8001
|
+
return null;
|
|
8002
|
+
}
|
|
8003
|
+
function lockPath(stateFilePath) {
|
|
8004
|
+
return join11(dirname6(stateFilePath), "wait-for-task.lock");
|
|
8005
|
+
}
|
|
8006
|
+
function acquireLock(stateFilePath) {
|
|
8007
|
+
const path = lockPath(stateFilePath);
|
|
8008
|
+
if (existsSync11(path)) {
|
|
8009
|
+
const pid = Number.parseInt(readFileSync9(path, "utf8").trim(), 10);
|
|
8010
|
+
if (Number.isFinite(pid) && pid > 0 && isAlive(pid)) return false;
|
|
8011
|
+
}
|
|
8012
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
8013
|
+
writeFileSync10(path, String(process.pid), "utf8");
|
|
8014
|
+
return true;
|
|
8015
|
+
}
|
|
8016
|
+
function releaseLock(stateFilePath) {
|
|
8017
|
+
rmSync(lockPath(stateFilePath), { force: true });
|
|
8018
|
+
}
|
|
8019
|
+
function isAlive(pid) {
|
|
8020
|
+
try {
|
|
8021
|
+
process.kill(pid, 0);
|
|
8022
|
+
return true;
|
|
8023
|
+
} catch {
|
|
8024
|
+
return false;
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
function trimTrailingSlash4(url) {
|
|
8028
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
8029
|
+
}
|
|
8030
|
+
async function peekOnce(url, token, createSocket2) {
|
|
8031
|
+
const socket = createSocket2(`${trimTrailingSlash4(url)}/agent`, {
|
|
8032
|
+
auth: { token },
|
|
8033
|
+
autoConnect: false,
|
|
8034
|
+
reconnection: false
|
|
8035
|
+
});
|
|
8036
|
+
try {
|
|
8037
|
+
await connectOnce(socket, CONNECT_TIMEOUT_MS);
|
|
8038
|
+
const raw = await emitWithAckTimeout(socket, "task:peek", {}, PEEK_TIMEOUT_MS);
|
|
8039
|
+
const parsed = taskPeekAckSchema.safeParse(raw);
|
|
8040
|
+
return parsed.success ? parsed.data.available : null;
|
|
8041
|
+
} catch {
|
|
8042
|
+
return null;
|
|
8043
|
+
} finally {
|
|
8044
|
+
socket.disconnect();
|
|
8045
|
+
}
|
|
8046
|
+
}
|
|
8047
|
+
async function waitForTask(options = {}) {
|
|
8048
|
+
const context = resolveProjectContext(options.cwd ?? process.cwd(), options.credentialsFilePath);
|
|
8049
|
+
if (!context.ok) return { outcome: "idle", reason: "\u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D" };
|
|
8050
|
+
const stateFilePath = options.stateFilePath ?? getStateFilePath();
|
|
8051
|
+
const skip = skipReason(readState(stateFilePath));
|
|
8052
|
+
if (skip) return { outcome: "idle", reason: skip };
|
|
8053
|
+
if (!acquireLock(stateFilePath)) {
|
|
8054
|
+
return { outcome: "idle", reason: "\u043E\u0436\u0438\u0434\u0430\u0442\u0435\u043B\u044C \u0443\u0436\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D" };
|
|
8055
|
+
}
|
|
8056
|
+
const now = options.now ?? (() => Date.now());
|
|
8057
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
|
|
8058
|
+
const createSocket2 = options.createSocket ?? createSocket;
|
|
8059
|
+
const deadline = now() + (options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS);
|
|
8060
|
+
const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
8061
|
+
try {
|
|
8062
|
+
while (now() < deadline) {
|
|
8063
|
+
const reason = skipReason(readState(stateFilePath));
|
|
8064
|
+
if (reason) return { outcome: "idle", reason };
|
|
8065
|
+
const available = await peekOnce(context.config.url, context.credentials.token, createSocket2);
|
|
8066
|
+
if (available === true) {
|
|
8067
|
+
return {
|
|
8068
|
+
outcome: "wake",
|
|
8069
|
+
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."
|
|
8070
|
+
};
|
|
8071
|
+
}
|
|
8072
|
+
await sleep2(interval);
|
|
8073
|
+
}
|
|
8074
|
+
return { outcome: "idle", reason: "\u0438\u0441\u0442\u0435\u043A\u043B\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F" };
|
|
8075
|
+
} finally {
|
|
8076
|
+
releaseLock(stateFilePath);
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
|
|
7598
8080
|
// src/cli/program.ts
|
|
7599
8081
|
var CLI_VERSION = VERSION;
|
|
7600
8082
|
function printError(error) {
|
|
@@ -7677,6 +8159,16 @@ function createProgram() {
|
|
|
7677
8159
|
printError(result.message);
|
|
7678
8160
|
process.exitCode = 1;
|
|
7679
8161
|
});
|
|
8162
|
+
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) => {
|
|
8163
|
+
const result = await waitForTask({ cwd: opts.cwd });
|
|
8164
|
+
if (result.outcome === "wake") {
|
|
8165
|
+
process.stderr.write(`${result.message}
|
|
8166
|
+
`);
|
|
8167
|
+
process.exitCode = WAIT_EXIT_WAKE;
|
|
8168
|
+
return;
|
|
8169
|
+
}
|
|
8170
|
+
process.exitCode = WAIT_EXIT_IDLE;
|
|
8171
|
+
});
|
|
7680
8172
|
program.command("hook").description(
|
|
7681
8173
|
"\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`"
|
|
7682
8174
|
).argument("<name>", `\u0438\u043C\u044F \u0445\u0443\u043A\u0430: ${KNOWN_HOOK_NAMES.join(", ")}`).option(
|
package/package.json
CHANGED