@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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +551 -59
  3. 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
- NotificationUrgency.INFORMATIONAL
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.5.3",
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
- waitSec: taskClaimPayloadSchema.shape.waitSec.optional().describe(
5976
- "\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)."
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) return errorResult(claimed.message);
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 \u2014 \u0432\u044B\u0437\u043E\u0432\u0438 next_task \u0435\u0449\u0451 \u0440\u0430\u0437, \u043A\u043E\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0443\u043C\u0435\u0441\u0442\u043D\u043E.`
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
- { currentTask: null, tasksCompletedInSession: state.tasksCompletedInSession + 1 },
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 < state.contextResetAfterTasks) {
7523
+ if (state.tasksCompletedInSession >= state.contextResetAfterTasks) {
7524
+ return limitReached(state);
7525
+ }
7526
+ if (state.claimFailedAt) {
7183
7527
  return {
7184
7528
  output: {
7185
- decision: "block",
7186
- 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."
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 ${state.tasksCompletedInSession} \u0437\u0430\u0434\u0430\u0447(\u0438). \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.`
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(",").map((r) => r.trim()).filter((r) => r.length > 0);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiel/cli",
3
- "version": "0.5.3",
3
+ "version": "0.6.2",
4
4
  "description": "CLI и MCP-сервер Quiel: подключает агента участника (Claude Code, Codex, Cursor, OpenCode) к платформе",
5
5
  "homepage": "https://quiel.app",
6
6
  "repository": {