@quiel/cli 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/dist/cli.js +460 -53
  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,6 +107,8 @@ 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
 
111
114
  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.
@@ -118,6 +121,7 @@ Existing configuration is merged, not overwritten: other MCP servers, other hook
118
121
  | `quiel connect` | Hold the connection to the platform |
119
122
  | `quiel status [--check]` | Show the project, the configured agent program, mode and current task |
120
123
  | `quiel mode auto\|manual` | Switch between autonomous and manual |
124
+ | `quiel wait-for-task` | Wait for a task to appear and wake the sleeping session (started by a hook, not by you) |
121
125
  | `quiel mcp` | Run the MCP server over stdio (your agent starts this itself) |
122
126
  | `quiel hook <name> [--format …]` | Run a hook (your agent's configuration calls this) |
123
127
 
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(),
@@ -3326,6 +3440,14 @@ var CLI_BIN = "quiel";
3326
3440
  var HOOK_SPECS = [
3327
3441
  { event: "SessionStart", command: `${CLI_BIN} hook session-start` },
3328
3442
  { event: "Stop", command: `${CLI_BIN} hook stop`, timeout: 10 },
3443
+ // ВТОРАЯ КОМАНДА НА ТОМ ЖЕ СОБЫТИИ, И ОНА НЕ ДУБЛЬ. `hook stop` решает,
3444
+ // можно ли остановиться; эта — что будет, ПОКА агент стоит. Без неё
3445
+ // разрешённый простой означал бы молчание до прихода человека.
3446
+ //
3447
+ // Выходит мгновенно с кодом 0, если режим ручной, задача на руках или
3448
+ // другой ожидатель уже работает, — поэтому висит она только тогда,
3449
+ // когда агенту действительно нечего делать.
3450
+ { event: "Stop", command: `${CLI_BIN} wait-for-task`, asyncRewake: true },
3329
3451
  {
3330
3452
  event: "StopFailure",
3331
3453
  matcher: "rate_limit|overloaded",
@@ -3348,7 +3470,8 @@ function upsertHookSpec(groups, spec) {
3348
3470
  const commandEntry = {
3349
3471
  type: "command",
3350
3472
  command: spec.command,
3351
- ...spec.timeout !== void 0 ? { timeout: spec.timeout } : {}
3473
+ ...spec.timeout !== void 0 ? { timeout: spec.timeout } : {},
3474
+ ...spec.asyncRewake !== void 0 ? { asyncRewake: spec.asyncRewake } : {}
3352
3475
  };
3353
3476
  const groupIndex = groups.findIndex((g) => sameMatcher(g.matcher, spec.matcher));
3354
3477
  if (groupIndex === -1) {
@@ -3515,6 +3638,30 @@ var ClientStateSchema = z30.object({
3515
3638
  tasksCompletedInSession: z30.number().int().nonnegative().default(0),
3516
3639
  /** Кэш `ProjectSettings.contextResetAfterTasks` (§7.2 ТЗ) — сколько задач подряд можно сдать, прежде чем `Stop`-хук попросит `/clear`. */
3517
3640
  contextResetAfterTasks: z30.number().int().positive().default(DEFAULT_CONTEXT_RESET_AFTER_TASKS),
3641
+ /**
3642
+ * Идентификаторы задач, сданных в этой сессии, — а не одно число.
3643
+ *
3644
+ * Счётчик `tasksCompletedInSession` увеличивается на КАЖДУЮ успешную
3645
+ * сдачу, и задача, возвращённая с ревью и сданная повторно, считалась
3646
+ * второй. Владелец увидел «Сессия выполнила 9 задач» при лимите 8 и
3647
+ * справедливо решил, что счётчик сломан: он не сломан, он считает
3648
+ * сдачи, а сообщение называет их задачами.
3649
+ *
3650
+ * Храним ключи, чтобы говорить правду в обоих числах.
3651
+ */
3652
+ completedTaskIds: z30.array(z30.string()).default([]),
3653
+ /**
3654
+ * Когда `next_task` в последний раз ответил «задач нет».
3655
+ *
3656
+ * БЕЗ ЭТОГО ПОЛЯ `Stop`-ХУК НЕ ОТЛИЧАЕТ ПУСТУЮ ОЧЕРЕДЬ ОТ ЛЕНИ. Он
3657
+ * безусловно требовал «вызови next_task», и при пустой очереди агент
3658
+ * крутил этот цикл часами: в боевом проекте очередь пустовала по
3659
+ * 15–25 минут подряд, и каждый круг тратил ход модели впустую.
3660
+ *
3661
+ * `null` — очередь не была пуста с начала сессии либо последняя
3662
+ * попытка что-то принесла.
3663
+ */
3664
+ queueEmptyAt: z30.string().nullable().default(null),
3518
3665
  /** Кэш стоп-паттернов/правил доверия для `PreToolUse` фазы 4. */
3519
3666
  trust: TrustCacheSchema.default(null),
3520
3667
  /** Снимок задачи на момент последнего `PreCompact` — пишет src/hooks/pre-compact.ts, читает SessionStart(source="compact"), чтобы напомнить модели, чем она занималась. */
@@ -5790,7 +5937,7 @@ ${question.answerMd ?? ""}${describeAnswerAttachments(question)}`;
5790
5937
  // package.json
5791
5938
  var package_default = {
5792
5939
  name: "@quiel/cli",
5793
- version: "0.5.3",
5940
+ version: "0.6.0",
5794
5941
  description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 (Claude Code, Codex, Cursor, OpenCode) \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
5795
5942
  homepage: "https://quiel.app",
5796
5943
  repository: {
@@ -5976,7 +6123,7 @@ var nextTaskInputShape = {
5976
6123
  "\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0435\u043A\u0443\u043D\u0434 \u0436\u0434\u0430\u0442\u044C \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438, 0..600 (\xA711.1 \u0422\u0417). \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 300 \u2014 \u043A\u0430\u043A claimTimeoutSec \u0434\u0438\u0441\u043F\u0435\u0442\u0447\u0435\u0440\u0430 (\xA79.1 \u0422\u0417)."
5977
6124
  )
5978
6125
  };
5979
- var DEFAULT_WAIT_SEC = 300;
6126
+ var DEFAULT_WAIT_SEC = 90;
5980
6127
  var CLAIM_ACK_BUFFER_MS = 15e3;
5981
6128
  async function nextTaskHandler(args, runtime) {
5982
6129
  const { client, stateFilePath } = runtime;
@@ -6002,8 +6149,9 @@ async function nextTaskHandler(args, runtime) {
6002
6149
  );
6003
6150
  if (!claimed.ok) return errorResult(claimed.message);
6004
6151
  if ("empty" in claimed.data) {
6152
+ updateState({ queueEmptyAt: (/* @__PURE__ */ new Date()).toISOString() }, stateFilePath);
6005
6153
  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.`
6154
+ `\u0417\u0430\u0434\u0430\u0447 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442 (\u0436\u0434\u0430\u043B ${waitSec} \u0441). \u042D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434, \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430. \u041C\u043E\u0436\u0435\u0448\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0445\u043E\u0434 \u2014 \u0445\u0443\u043A \u0442\u0435\u0431\u044F \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442, \u043F\u043E\u043A\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u0443\u0441\u0442\u0430.`
6007
6155
  );
6008
6156
  }
6009
6157
  const task = claimed.data;
@@ -6016,7 +6164,10 @@ async function nextTaskHandler(args, runtime) {
6016
6164
  gitMode: task.project.gitMode,
6017
6165
  branchName: task.project.branchName,
6018
6166
  commitPrefix: task.project.commitPrefix
6019
- }
6167
+ },
6168
+ // Очередь оказалась не пуста — пометка снимается, иначе хук
6169
+ // продолжал бы считать, что работы нет.
6170
+ queueEmptyAt: null
6020
6171
  },
6021
6172
  stateFilePath
6022
6173
  );
@@ -6074,6 +6225,76 @@ ${worktreeNote}${continueNote}${warning}`
6074
6225
  );
6075
6226
  }
6076
6227
 
6228
+ // src/mcp/link.ts
6229
+ var linkTasksInputShape = {
6230
+ blockerId: taskLinkPayloadSchema.shape.blockerId.describe(
6231
+ "ID \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043D\u0430\u0434\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u041F\u0415\u0420\u0412\u041E\u0419 \u2014 \u043E\u043D\u0430 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0432\u0442\u043E\u0440\u0443\u044E."
6232
+ ),
6233
+ blockedId: taskLinkPayloadSchema.shape.blockedId.describe(
6234
+ "ID \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0416\u0414\u0401\u0422: \u0435\u0451 \u043D\u0435\u043B\u044C\u0437\u044F \u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u043A\u0430 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0430 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0430\u044F."
6235
+ ),
6236
+ kind: taskLinkPayloadSchema.shape.kind.describe(
6237
+ "BLOCKS \u2014 \u0434\u0435\u0440\u0436\u0438\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E). RELATES \u2014 \u043F\u0440\u043E\u0441\u0442\u043E \u0441\u0432\u044F\u0437\u044C \xAB\u0441\u043C. \u0442\u0430\u043A\u0436\u0435\xBB, \u043D\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043D\u0435 \u0432\u043B\u0438\u044F\u0435\u0442."
6238
+ )
6239
+ };
6240
+ async function linkTasksHandler(args, runtime) {
6241
+ const parsedArgs = parseArgsOrError(taskLinkPayloadSchema, args);
6242
+ if (!parsedArgs.ok) return errorResult(parsedArgs.message);
6243
+ const result = await callServerOrError(
6244
+ runtime.client,
6245
+ "task:link",
6246
+ parsedArgs.data,
6247
+ taskLinkAckSchema
6248
+ );
6249
+ if (!result.ok) return errorResult(result.message);
6250
+ if (!result.data.linked) {
6251
+ return textResult(`\u0421\u0432\u044F\u0437\u044C \u0443\u0436\u0435 \u0431\u044B\u043B\u0430: ${args.blockerId} \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId}.`);
6252
+ }
6253
+ const stopped = result.data.blockedNow ? " \u0417\u0430\u0434\u0430\u0447\u0430 \u0431\u044B\u043B\u0430 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435 \u0438 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u043E\u043D\u0430 \u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0443\u044E." : "";
6254
+ return textResult(`\u0413\u043E\u0442\u043E\u0432\u043E: ${args.blockerId} \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId}.${stopped}`);
6255
+ }
6256
+ var unlinkTasksInputShape = {
6257
+ blockerId: taskUnlinkPayloadSchema.shape.blockerId.describe("ID \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438."),
6258
+ blockedId: taskUnlinkPayloadSchema.shape.blockedId.describe("ID \u0436\u0434\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438.")
6259
+ };
6260
+ async function unlinkTasksHandler(args, runtime) {
6261
+ const parsedArgs = parseArgsOrError(taskUnlinkPayloadSchema, args);
6262
+ if (!parsedArgs.ok) return errorResult(parsedArgs.message);
6263
+ const result = await callServerOrError(
6264
+ runtime.client,
6265
+ "task:unlink",
6266
+ parsedArgs.data,
6267
+ taskUnlinkAckSchema
6268
+ );
6269
+ if (!result.ok) return errorResult(result.message);
6270
+ return textResult(
6271
+ result.data.unlinked ? `\u0421\u0432\u044F\u0437\u044C \u0441\u043D\u044F\u0442\u0430: ${args.blockerId} \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 ${args.blockedId}.` : `\u0422\u0430\u043A\u043E\u0439 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u0431\u044B\u043B\u043E: ${args.blockerId} \u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043B ${args.blockedId}.`
6272
+ );
6273
+ }
6274
+ var planTaskInputShape = {
6275
+ taskId: taskPlanPayloadSchema.shape.taskId.describe(
6276
+ "ID \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u043B\u044E\u0431\u043E\u0439, \u0430\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430."
6277
+ ),
6278
+ patch: taskPlanPayloadSchema.shape.patch.describe(
6279
+ "\u041F\u043E\u043B\u044F \u043F\u043B\u0430\u043D\u0430: title, descriptionMd, acceptance, type, priority, roleKey, assigneeId, milestoneId, labels, estimateMin. \u0421\u0442\u0430\u0442\u0443\u0441 \u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0440\u0430\u0431\u043E\u0442\u044B \u043C\u0435\u043D\u044F\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u0438\u0437 report."
6280
+ )
6281
+ };
6282
+ async function planTaskHandler(args, runtime) {
6283
+ const parsedArgs = parseArgsOrError(taskPlanPayloadSchema, args);
6284
+ if (!parsedArgs.ok) return errorResult(parsedArgs.message);
6285
+ const result = await callServerOrError(
6286
+ runtime.client,
6287
+ "task:plan",
6288
+ parsedArgs.data,
6289
+ taskPlanAckSchema
6290
+ );
6291
+ if (!result.ok) return errorResult(result.message);
6292
+ return textResult(
6293
+ `\u0417\u0430\u0434\u0430\u0447\u0430 ${result.data.task.key} \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430:
6294
+ ${JSON.stringify(result.data.task, null, 2)}`
6295
+ );
6296
+ }
6297
+
6077
6298
  // src/mcp/release-task.ts
6078
6299
  var releaseTaskInputShape = {
6079
6300
  reason: taskReleasePayloadSchema.shape.reason.describe(
@@ -6150,8 +6371,15 @@ async function reportHandler(args, runtime) {
6150
6371
  );
6151
6372
  }
6152
6373
  const taskLabel2 = state.currentTask.taskKey ?? state.currentTask.taskId;
6374
+ const completedTaskIds = state.completedTaskIds.includes(state.currentTask.taskId) ? state.completedTaskIds : [...state.completedTaskIds, state.currentTask.taskId];
6153
6375
  updateState(
6154
- { currentTask: null, tasksCompletedInSession: state.tasksCompletedInSession + 1 },
6376
+ {
6377
+ currentTask: null,
6378
+ tasksCompletedInSession: state.tasksCompletedInSession + 1,
6379
+ completedTaskIds,
6380
+ // Сдали задачу — значит очередь только что была не пуста.
6381
+ queueEmptyAt: null
6382
+ },
6155
6383
  runtime.stateFilePath
6156
6384
  );
6157
6385
  const duplicateNote = result.data.duplicate ? " (\u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u043C\u0435\u0442\u0438\u043B \u044D\u0442\u043E \u043A\u0430\u043A \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u0436\u0435 \u043F\u0440\u0438\u043D\u044F\u0442\u043E\u0439 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0437\u0430\u0434\u0432\u0430\u0438\u0432\u0430\u043D\u0438\u044F \u043D\u0435\u0442)" : "";
@@ -6471,6 +6699,9 @@ async function submitViaPullRequest(args, runtime, currentTask) {
6471
6699
 
6472
6700
  // src/mcp/tasks.ts
6473
6701
  var createTaskInputShape = {
6702
+ dependsOn: taskCreatePayloadSchema.shape.dependsOn.describe(
6703
+ "ID \u0437\u0430\u0434\u0430\u0447, \u043E\u0442 \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u043D\u043E\u0432\u0430\u044F \u0437\u0430\u0432\u0438\u0441\u0438\u0442: \u0435\u0451 \u043D\u0435 \u0432\u044B\u0434\u0430\u0434\u0443\u0442, \u043F\u043E\u043A\u0430 \u043E\u043D\u0438 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u044B. \u0421\u0432\u044F\u0437\u0438 \u0441\u0442\u0430\u0432\u044F\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u2014 \u0432\u0442\u043E\u0440\u043E\u0439 \u043F\u0440\u043E\u0445\u043E\u0434 \u043F\u043E \u0441\u043F\u0438\u0441\u043A\u0443 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D. \u041D\u0435\u043F\u043E\u0434\u0445\u043E\u0434\u044F\u0449\u0430\u044F \u0441\u0441\u044B\u043B\u043A\u0430 (\u0447\u0443\u0436\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442, \u043D\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430) \u043C\u043E\u043B\u0447\u0430 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F: \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0436\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430, \u0438 \u0442\u0435\u0440\u044F\u0442\u044C \u0435\u0451 \u0438\u0437-\u0437\u0430 \u043E\u0434\u043D\u043E\u0439 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435\u043B\u044C\u0437\u044F."
6704
+ ),
6474
6705
  title: taskCreatePayloadSchema.shape.title.describe("\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u043D\u043E\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0440\u043E\u0442\u043A\u043E."),
6475
6706
  descriptionMd: taskCreatePayloadSchema.shape.descriptionMd.describe(
6476
6707
  "Markdown-\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043D\u043E\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u0438 \u0437\u0430\u0447\u0435\u043C."
@@ -6509,7 +6740,8 @@ async function createTaskHandler(args, runtime) {
6509
6740
  executor: args.executor,
6510
6741
  blocksCurrent: args.blocksCurrent,
6511
6742
  ...args.acceptance ? { acceptance: args.acceptance } : {},
6512
- ...args.labels && args.labels.length > 0 ? { labels: args.labels } : {}
6743
+ ...args.labels && args.labels.length > 0 ? { labels: args.labels } : {},
6744
+ ...args.dependsOn && args.dependsOn.length > 0 ? { dependsOn: args.dependsOn } : {}
6513
6745
  });
6514
6746
  if (!parsedArgs.ok) return errorResult(parsedArgs.message);
6515
6747
  const state = readState(runtime.stateFilePath);
@@ -6535,8 +6767,15 @@ async function createTaskHandler(args, runtime) {
6535
6767
  const blockNote = result.data.currentTaskStatus ? `
6536
6768
 
6537
6769
  \u0422\u0432\u043E\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 ${state.currentTask?.taskKey ?? state.currentTask?.taskId ?? ""} \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430: \u043D\u043E\u0432\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u2014 ${result.data.currentTaskStatus}. \u041F\u0440\u0435\u043A\u0440\u0430\u0442\u0438 \u0430\u043A\u0442\u0438\u0432\u043D\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430\u0434 \u043D\u0435\u0439 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441; \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u0439\u0434\u0451\u0448\u044C \u0434\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0441\u0442\u043E\u0438\u0442 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u0442\u044C, \u0441\u0434\u0430\u0439 report(partial) \u0441 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435\u043C \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0433\u043E \u0438 \u0443\u043A\u0430\u0436\u0438 \u0432 nextSteps, \u0447\u0442\u043E \u0436\u0434\u0451\u0448\u044C \u043D\u043E\u0432\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443 ${result.data.task.key}.` : "";
6770
+ const requested = args.dependsOn?.length ?? 0;
6771
+ const linked = result.data.dependenciesLinked ?? 0;
6772
+ const depsNote = requested === 0 ? "" : linked === requested ? `
6773
+
6774
+ \u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E: ${linked}.` : `
6775
+
6776
+ \u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E ${linked} \u0438\u0437 ${requested}: \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0435 \u043F\u043E\u0434\u043E\u0448\u043B\u0438 (\u043D\u0435\u0442 \u0442\u0430\u043A\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u043B\u0438 \u043E\u043D\u0430 \u0438\u0437 \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430). \u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0438\u0445 \u0438 \u043F\u0440\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0441\u0442\u0438 \u0441\u0432\u044F\u0436\u0438 \u0447\u0435\u0440\u0435\u0437 link_tasks.`;
6538
6777
  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}`
6778
+ `\u0421\u043E\u0437\u0434\u0430\u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 ${result.data.task.key} (${result.data.task.status}) \u0434\u043B\u044F ${addressee}.${depsNote}${blockNote}`
6540
6779
  );
6541
6780
  }
6542
6781
  var listTasksInputShape = {
@@ -6715,6 +6954,33 @@ function registerTools(server, runtime) {
6715
6954
  },
6716
6955
  (args) => updateTaskHandler(args, runtime)
6717
6956
  );
6957
+ server.registerTool(
6958
+ "link_tasks",
6959
+ {
6960
+ title: "\u0421\u0432\u044F\u0437\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438",
6961
+ description: "\u0417\u0430\u0434\u0430\u0442\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \xABA \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 B\xBB: \u043F\u043E\u043A\u0430 A \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u0430, \u0434\u0438\u0441\u043F\u0435\u0442\u0447\u0435\u0440 \u043D\u0435 \u0432\u044B\u0434\u0430\u0441\u0442 B. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0441 \u041B\u042E\u0411\u042B\u041C\u0418 \u0437\u0430\u0434\u0430\u0447\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0430\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430 \u2014 \u0432 \u043E\u0442\u043B\u0438\u0447\u0438\u0435 \u043E\u0442 update_task. \u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u043E\u043B\u044C \xAB\u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\xBB \u043B\u0438\u0431\u043E \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430/\u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u0432\u044F\u0437\u044C, \u0437\u0430\u043C\u044B\u043A\u0430\u044E\u0449\u0430\u044F \u043A\u043E\u043B\u044C\u0446\u043E, \u0431\u0443\u0434\u0435\u0442 \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442\u0430: \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u043D\u0451\u043C \u0436\u0434\u0430\u043B\u0438 \u0431\u044B \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430 \u0432\u0435\u0447\u043D\u043E.",
6962
+ inputSchema: linkTasksInputShape
6963
+ },
6964
+ (args) => linkTasksHandler(args, runtime)
6965
+ );
6966
+ server.registerTool(
6967
+ "plan_task",
6968
+ {
6969
+ title: "\u0421\u043F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443",
6970
+ description: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043F\u043E\u043B\u044F \u041B\u042E\u0411\u041E\u0419 \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A, \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435, \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043F\u0440\u0438\u0451\u043C\u043A\u0438, \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442, \u0442\u0438\u043F, \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430, \u043C\u0438\u043B\u0441\u0442\u043E\u0443\u043D, \u043C\u0435\u0442\u043A\u0438, \u043E\u0446\u0435\u043D\u043A\u0443. \u0410\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430 \u2014 \u0432 \u043E\u0442\u043B\u0438\u0447\u0438\u0435 \u043E\u0442 update_task. \u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u043E\u043B\u044C \xAB\u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\xBB \u043B\u0438\u0431\u043E \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430/\u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u0422\u0410\u0422\u0423\u0421 \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F: \u0432 \u0440\u0430\u0431\u043E\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u043F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A. \u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0440\u0430\u0431\u043E\u0442\u044B \u0442\u043E\u0436\u0435 \u2014 \u043E\u043D\u0438 \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u0438\u0437 report.",
6971
+ inputSchema: planTaskInputShape
6972
+ },
6973
+ (args) => planTaskHandler(args, runtime)
6974
+ );
6975
+ server.registerTool(
6976
+ "unlink_tasks",
6977
+ {
6978
+ title: "\u0421\u043D\u044F\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043C\u0435\u0436\u0434\u0443 \u0437\u0430\u0434\u0430\u0447\u0430\u043C\u0438",
6979
+ description: "\u0423\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \xABA \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 B\xBB. \u0415\u0441\u043B\u0438 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u044D\u0442\u043E \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430, \u0430 \u043E\u0442\u0447\u0451\u0442.",
6980
+ inputSchema: unlinkTasksInputShape
6981
+ },
6982
+ (args) => unlinkTasksHandler(args, runtime)
6983
+ );
6718
6984
  server.registerTool(
6719
6985
  "ask_human",
6720
6986
  {
@@ -7158,7 +7424,20 @@ var StopInputSchema = z43.object({
7158
7424
  session_id: z43.string(),
7159
7425
  cwd: z43.string(),
7160
7426
  stop_hook_active: z43.boolean(),
7161
- last_assistant_message: z43.string().optional()
7427
+ last_assistant_message: z43.string().optional(),
7428
+ /**
7429
+ * Незавершённая фоновая работа сессии.
7430
+ *
7431
+ * ЧИТАЕМ ТОЛЬКО ДЛИНУ, И ЭТО НАМЕРЕННО. Внутренняя форма элементов в
7432
+ * документации не описана, а строить решение на угаданных полях —
7433
+ * ровно то, что §22.8 ТЗ запрещает. Нам достаточно ответа на вопрос
7434
+ * «есть ли незакрытая фоновая работа»: он не зависит от того, что
7435
+ * внутри.
7436
+ *
7437
+ * В §10.4 ТЗ этих полей нет — они появились в Claude Code позже.
7438
+ */
7439
+ background_tasks: z43.array(z43.unknown()).optional(),
7440
+ session_crons: z43.array(z43.unknown()).optional()
7162
7441
  }).passthrough();
7163
7442
  function taskLabel(state) {
7164
7443
  if (!state.currentTask) return "";
@@ -7171,6 +7450,13 @@ function stopHook(input, state) {
7171
7450
  if (state.mode !== AgentMode.AUTONOMOUS) {
7172
7451
  return { output: {} };
7173
7452
  }
7453
+ if (hasUnfinishedBackgroundWork(input)) {
7454
+ return {
7455
+ output: {
7456
+ systemMessage: "\u0410\u0433\u0435\u043D\u0442 \u0436\u0434\u0451\u0442 \u0444\u043E\u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u2014 \u0445\u0443\u043A \u043D\u0435 \u043C\u0435\u0448\u0430\u0435\u0442. \u0425\u043E\u0434 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u0441\u044F, \u043A\u043E\u0433\u0434\u0430 \u043E\u043D\u0430 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u0441\u044F."
7457
+ }
7458
+ };
7459
+ }
7174
7460
  if (state.currentTask) {
7175
7461
  return {
7176
7462
  output: {
@@ -7179,23 +7465,39 @@ function stopHook(input, state) {
7179
7465
  }
7180
7466
  };
7181
7467
  }
7182
- if (state.tasksCompletedInSession < state.contextResetAfterTasks) {
7468
+ if (state.tasksCompletedInSession >= state.contextResetAfterTasks) {
7469
+ return limitReached(state);
7470
+ }
7471
+ if (state.queueEmptyAt) {
7183
7472
  return {
7184
7473
  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."
7474
+ systemMessage: "\u041E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u0443\u0441\u0442\u0430 \u2014 \u0430\u0433\u0435\u043D\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D. \u041F\u043E\u0437\u043E\u0432\u0438\u0442\u0435 \u0435\u0433\u043E \u0441\u043D\u043E\u0432\u0430, \u043A\u043E\u0433\u0434\u0430 \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u0437\u0430\u0434\u0430\u0447\u0438: \u043E\u043D \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0438\u0445 \u0441 \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0436\u0435 next_task."
7187
7475
  }
7188
7476
  };
7189
7477
  }
7478
+ return {
7479
+ output: {
7480
+ decision: "block",
7481
+ reason: "\u0412\u044B\u0437\u043E\u0432\u0438 next_task \u0434\u043B\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438."
7482
+ }
7483
+ };
7484
+ }
7485
+ function hasUnfinishedBackgroundWork(input) {
7486
+ return (input.background_tasks?.length ?? 0) > 0 || (input.session_crons?.length ?? 0) > 0;
7487
+ }
7488
+ function limitReached(state) {
7190
7489
  const serverActions = [
7191
7490
  {
7192
7491
  type: "session-limit-reached",
7193
7492
  payload: { tasksCompletedInSession: state.tasksCompletedInSession }
7194
7493
  }
7195
7494
  ];
7495
+ const tasks = state.completedTaskIds.length;
7496
+ const submissions = state.tasksCompletedInSession;
7497
+ const counted = tasks > 0 && tasks !== submissions ? `${tasks} \u0437\u0430\u0434\u0430\u0447(\u0438) \u0437\u0430 ${submissions} \u0441\u0434\u0430\u0447(\u0438)` : `${submissions} \u0437\u0430\u0434\u0430\u0447(\u0438)`;
7196
7498
  return {
7197
7499
  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.`
7500
+ systemMessage: `\u0421\u0435\u0441\u0441\u0438\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043B\u0430 ${counted}. \u0421\u0434\u0435\u043B\u0430\u0439\u0442\u0435 /clear \u0438 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430 \u0441\u043D\u043E\u0432\u0430.`
7199
7501
  },
7200
7502
  serverActions
7201
7503
  };
@@ -7595,6 +7897,101 @@ function formatStatus(report) {
7595
7897
  return lines.join("\n");
7596
7898
  }
7597
7899
 
7900
+ // src/cli/wait-for-task.ts
7901
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, rmSync, writeFileSync as writeFileSync10 } from "fs";
7902
+ import { dirname as dirname6, join as join11 } from "path";
7903
+ var DEFAULT_POLL_INTERVAL_MS = 15e3;
7904
+ var DEFAULT_MAX_WAIT_MS = 8 * 60 * 60 * 1e3;
7905
+ var CONNECT_TIMEOUT_MS = 8e3;
7906
+ var PEEK_TIMEOUT_MS = 8e3;
7907
+ var WAIT_EXIT_IDLE = 0;
7908
+ var WAIT_EXIT_WAKE = 2;
7909
+ function skipReason(state) {
7910
+ if (state.mode !== AgentMode.AUTONOMOUS) {
7911
+ return "\u0440\u0435\u0436\u0438\u043C \u043D\u0435 \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439";
7912
+ }
7913
+ if (state.currentTask) {
7914
+ return "\u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0436\u0435 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435";
7915
+ }
7916
+ return null;
7917
+ }
7918
+ function lockPath(stateFilePath) {
7919
+ return join11(dirname6(stateFilePath), "wait-for-task.lock");
7920
+ }
7921
+ function acquireLock(stateFilePath) {
7922
+ const path = lockPath(stateFilePath);
7923
+ if (existsSync11(path)) {
7924
+ const pid = Number.parseInt(readFileSync9(path, "utf8").trim(), 10);
7925
+ if (Number.isFinite(pid) && pid > 0 && isAlive(pid)) return false;
7926
+ }
7927
+ mkdirSync8(dirname6(path), { recursive: true });
7928
+ writeFileSync10(path, String(process.pid), "utf8");
7929
+ return true;
7930
+ }
7931
+ function releaseLock(stateFilePath) {
7932
+ rmSync(lockPath(stateFilePath), { force: true });
7933
+ }
7934
+ function isAlive(pid) {
7935
+ try {
7936
+ process.kill(pid, 0);
7937
+ return true;
7938
+ } catch {
7939
+ return false;
7940
+ }
7941
+ }
7942
+ function trimTrailingSlash4(url) {
7943
+ return url.endsWith("/") ? url.slice(0, -1) : url;
7944
+ }
7945
+ async function peekOnce(url, token, createSocket2) {
7946
+ const socket = createSocket2(`${trimTrailingSlash4(url)}/agent`, {
7947
+ auth: { token },
7948
+ autoConnect: false,
7949
+ reconnection: false
7950
+ });
7951
+ try {
7952
+ await connectOnce(socket, CONNECT_TIMEOUT_MS);
7953
+ const raw = await emitWithAckTimeout(socket, "task:peek", {}, PEEK_TIMEOUT_MS);
7954
+ const parsed = taskPeekAckSchema.safeParse(raw);
7955
+ return parsed.success ? parsed.data.available : null;
7956
+ } catch {
7957
+ return null;
7958
+ } finally {
7959
+ socket.disconnect();
7960
+ }
7961
+ }
7962
+ async function waitForTask(options = {}) {
7963
+ const context = resolveProjectContext(options.cwd ?? process.cwd(), options.credentialsFilePath);
7964
+ if (!context.ok) return { outcome: "idle", reason: "\u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D" };
7965
+ const stateFilePath = options.stateFilePath ?? getStateFilePath();
7966
+ const skip = skipReason(readState(stateFilePath));
7967
+ if (skip) return { outcome: "idle", reason: skip };
7968
+ if (!acquireLock(stateFilePath)) {
7969
+ return { outcome: "idle", reason: "\u043E\u0436\u0438\u0434\u0430\u0442\u0435\u043B\u044C \u0443\u0436\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D" };
7970
+ }
7971
+ const now = options.now ?? (() => Date.now());
7972
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
7973
+ const createSocket2 = options.createSocket ?? createSocket;
7974
+ const deadline = now() + (options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS);
7975
+ const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
7976
+ try {
7977
+ while (now() < deadline) {
7978
+ const reason = skipReason(readState(stateFilePath));
7979
+ if (reason) return { outcome: "idle", reason };
7980
+ const available = await peekOnce(context.config.url, context.credentials.token, createSocket2);
7981
+ if (available === true) {
7982
+ return {
7983
+ outcome: "wake",
7984
+ message: "\u0412 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043F\u043E\u044F\u0432\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u0434\u0430\u0447\u0430. \u0412\u044B\u0437\u043E\u0432\u0438 next_task, \u0447\u0442\u043E\u0431\u044B \u0432\u0437\u044F\u0442\u044C \u0435\u0451."
7985
+ };
7986
+ }
7987
+ await sleep2(interval);
7988
+ }
7989
+ return { outcome: "idle", reason: "\u0438\u0441\u0442\u0435\u043A\u043B\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F" };
7990
+ } finally {
7991
+ releaseLock(stateFilePath);
7992
+ }
7993
+ }
7994
+
7598
7995
  // src/cli/program.ts
7599
7996
  var CLI_VERSION = VERSION;
7600
7997
  function printError(error) {
@@ -7677,6 +8074,16 @@ function createProgram() {
7677
8074
  printError(result.message);
7678
8075
  process.exitCode = 1;
7679
8076
  });
8077
+ program.command("wait-for-task").description("\u0416\u0434\u0430\u0442\u044C \u043F\u043E\u044F\u0432\u043B\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E (\u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F \u0445\u0443\u043A\u043E\u043C Stop \u0441 asyncRewake)").option("--cwd <dir>", "\u0420\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (\u0434\u043B\u044F \u0442\u0435\u0441\u0442\u043E\u0432/\u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438)").action(async (opts) => {
8078
+ const result = await waitForTask({ cwd: opts.cwd });
8079
+ if (result.outcome === "wake") {
8080
+ process.stderr.write(`${result.message}
8081
+ `);
8082
+ process.exitCode = WAIT_EXIT_WAKE;
8083
+ return;
8084
+ }
8085
+ process.exitCode = WAIT_EXIT_IDLE;
8086
+ });
7680
8087
  program.command("hook").description(
7681
8088
  "\u0412\u044B\u0437\u0432\u0430\u0442\u044C \u0445\u0443\u043A \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0439 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B (JSON \u043D\u0430 stdin) \u2014 \u043F\u0440\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043A\u043E\u043D\u0444\u0438\u0433\u043E\u043C \u043F\u0440\u0438 `quiel init`"
7682
8089
  ).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.0",
4
4
  "description": "CLI и MCP-сервер Quiel: подключает агента участника (Claude Code, Codex, Cursor, OpenCode) к платформе",
5
5
  "homepage": "https://quiel.app",
6
6
  "repository": {