crewly 1.20.215 → 1.20.217

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 (161) hide show
  1. package/config/hooks/agent-status/report.sh +45 -6
  2. package/config/roles/team-leader/fragments/role-boundary.md +2 -1
  3. package/config/roles/team-leader/prompt.md +9 -5
  4. package/config/roles/team-leader/role-boundaries.md +3 -2
  5. package/config/roles/team-leader/tl-addon.md +9 -5
  6. package/config/skills/agent/core/ask-owner/SKILL.md +9 -2
  7. package/config/skills/agent/core/ask-owner/execute.sh +1 -1
  8. package/config/skills/agent/core/project-tickets/SKILL.md +2 -2
  9. package/config/skills/agent/core/project-tickets/execute.sh +1 -1
  10. package/config/skills/agent/core/publish-app/SKILL.md +18 -11
  11. package/config/skills/agent/core/publish-app/execute.sh +1 -1
  12. package/config/skills/team-leader/delegate-task/SKILL.md +40 -0
  13. package/config/skills/team-leader/delegate-task/execute.sh +39 -1
  14. package/dist/backend/backend/src/constants.d.ts +46 -2
  15. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  16. package/dist/backend/backend/src/constants.js +46 -2
  17. package/dist/backend/backend/src/constants.js.map +1 -1
  18. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.d.ts +2 -1
  19. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.d.ts.map +1 -1
  20. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js +26 -3
  21. package/dist/backend/backend/src/controllers/agent-hooks/agent-hooks.controller.js.map +1 -1
  22. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
  23. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  24. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +4 -1
  25. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +32 -0
  28. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.d.ts +27 -0
  30. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.d.ts.map +1 -0
  31. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.js +92 -0
  32. package/dist/backend/backend/src/controllers/team/team-lead-share.controller.js.map +1 -0
  33. package/dist/backend/backend/src/controllers/team/team.routes.d.ts.map +1 -1
  34. package/dist/backend/backend/src/controllers/team/team.routes.js +4 -0
  35. package/dist/backend/backend/src/controllers/team/team.routes.js.map +1 -1
  36. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  37. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +6 -0
  38. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  39. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts +25 -1
  40. package/dist/backend/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
  41. package/dist/backend/backend/src/services/decisions/decision-contract.js +39 -2
  42. package/dist/backend/backend/src/services/decisions/decision-contract.js.map +1 -1
  43. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  44. package/dist/backend/backend/src/services/decisions/decision.service.js +17 -1
  45. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  46. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts +67 -9
  47. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  48. package/dist/backend/backend/src/services/monitoring/token-usage.service.js +57 -10
  49. package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  50. package/dist/backend/backend/src/services/open-items/open-item-card.d.ts +26 -0
  51. package/dist/backend/backend/src/services/open-items/open-item-card.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/open-items/open-item-card.js +71 -1
  53. package/dist/backend/backend/src/services/open-items/open-item-card.js.map +1 -1
  54. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/open-items/open-items.service.js +6 -3
  56. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  57. package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +46 -1
  59. package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
  60. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts +35 -0
  61. package/dist/backend/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/orc/work-item-destination.js +75 -2
  63. package/dist/backend/backend/src/services/orc/work-item-destination.js.map +1 -1
  64. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +10 -6
  65. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +16 -10
  67. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
  68. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts +6 -0
  69. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts.map +1 -1
  70. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js +4 -0
  71. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js.map +1 -1
  72. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +28 -5
  73. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
  74. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +42 -20
  75. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
  76. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
  77. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js +2 -1
  78. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
  79. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/slack/slack-initializer.js +10 -0
  81. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  82. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +7 -0
  83. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +25 -2
  85. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  86. package/dist/backend/backend/src/services/spend/spend-cap.service.d.ts +5 -1
  87. package/dist/backend/backend/src/services/spend/spend-cap.service.d.ts.map +1 -1
  88. package/dist/backend/backend/src/services/spend/spend-cap.service.js +3 -1
  89. package/dist/backend/backend/src/services/spend/spend-cap.service.js.map +1 -1
  90. package/dist/backend/backend/src/services/spend/spend-ledger.service.d.ts +21 -7
  91. package/dist/backend/backend/src/services/spend/spend-ledger.service.d.ts.map +1 -1
  92. package/dist/backend/backend/src/services/spend/spend-ledger.service.js +35 -17
  93. package/dist/backend/backend/src/services/spend/spend-ledger.service.js.map +1 -1
  94. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +23 -0
  95. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  96. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +74 -2
  97. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  98. package/dist/backend/backend/src/services/tl-delegation/lead-share.d.ts +115 -0
  99. package/dist/backend/backend/src/services/tl-delegation/lead-share.d.ts.map +1 -0
  100. package/dist/backend/backend/src/services/tl-delegation/lead-share.js +175 -0
  101. package/dist/backend/backend/src/services/tl-delegation/lead-share.js.map +1 -0
  102. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.d.ts +32 -0
  103. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.d.ts.map +1 -0
  104. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.js +47 -0
  105. package/dist/backend/backend/src/services/tl-delegation/lead-share.wiring.js.map +1 -0
  106. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.d.ts +207 -0
  107. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.d.ts.map +1 -0
  108. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js +377 -0
  109. package/dist/backend/backend/src/services/tl-delegation/tl-delegation.service.js.map +1 -0
  110. package/dist/backend/backend/src/types/decision.types.d.ts +6 -1
  111. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
  112. package/dist/backend/backend/src/types/decision.types.js +1 -1
  113. package/dist/backend/backend/src/utils/defaultPrompts.js +3 -3
  114. package/dist/backend/backend/src/utils/defaultPrompts.js.map +1 -1
  115. package/dist/backend/build-info.json +2 -2
  116. package/dist/cli/backend/src/constants.d.ts +46 -2
  117. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  118. package/dist/cli/backend/src/constants.js +46 -2
  119. package/dist/cli/backend/src/constants.js.map +1 -1
  120. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  121. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +6 -0
  122. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  123. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts +25 -1
  124. package/dist/cli/backend/src/services/decisions/decision-contract.d.ts.map +1 -1
  125. package/dist/cli/backend/src/services/decisions/decision-contract.js +39 -2
  126. package/dist/cli/backend/src/services/decisions/decision-contract.js.map +1 -1
  127. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  128. package/dist/cli/backend/src/services/decisions/decision.service.js +17 -1
  129. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
  130. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts +67 -9
  131. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  132. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +57 -10
  133. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  134. package/dist/cli/backend/src/services/open-items/open-item-card.d.ts +26 -0
  135. package/dist/cli/backend/src/services/open-items/open-item-card.d.ts.map +1 -1
  136. package/dist/cli/backend/src/services/open-items/open-item-card.js +71 -1
  137. package/dist/cli/backend/src/services/open-items/open-item-card.js.map +1 -1
  138. package/dist/cli/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  139. package/dist/cli/backend/src/services/open-items/open-items.service.js +6 -3
  140. package/dist/cli/backend/src/services/open-items/open-items.service.js.map +1 -1
  141. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts +35 -0
  142. package/dist/cli/backend/src/services/orc/work-item-destination.d.ts.map +1 -1
  143. package/dist/cli/backend/src/services/orc/work-item-destination.js +75 -2
  144. package/dist/cli/backend/src/services/orc/work-item-destination.js.map +1 -1
  145. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
  146. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js +2 -1
  147. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
  148. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +7 -0
  149. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  150. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +25 -2
  151. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  152. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +23 -0
  153. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  154. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +74 -2
  155. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  156. package/dist/cli/backend/src/types/decision.types.d.ts +6 -1
  157. package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
  158. package/dist/cli/backend/src/types/decision.types.js +1 -1
  159. package/frontend/dist/assets/{index-839f4374.js → index-bc4e2a93.js} +260 -260
  160. package/frontend/dist/index.html +1 -1
  161. package/package.json +1 -1
@@ -15,10 +15,18 @@
15
15
  #
16
16
  # PRIVACY: the stdin JSON can hold tool_input, file contents, prompts and a
17
17
  # transcript path, any of which may contain secrets. This script extracts
18
- # exactly five TOP-LEVEL fields — hook_event_name, notification_type and
18
+ # exactly six TOP-LEVEL fields — hook_event_name, notification_type and
19
19
  # source (kept only as plain identifiers, [A-Za-z_], max 64 chars), tool_use_id and
20
- # agent_id (kept only as [A-Za-z0-9_-], max 128 chars) — and sends those plus
21
- # the session name. Nothing else from stdin is sent, printed or logged.
20
+ # agent_id (kept only as [A-Za-z0-9_-], max 128 chars), and tool_name for
21
+ # PostToolUse (kept only as [A-Za-z0-9_-], max 64 chars; never tool_input) —
22
+ # and sends those plus the session name. Nothing else from stdin is sent,
23
+ # printed or logged.
24
+ #
25
+ # Team-lead nudge (crewly#1083, specs/2026-10-04-tl-delegation.md §2): for
26
+ # PostToolUse the backend may answer `additionalContext` (a short English
27
+ # note for a team lead doing hands-on work); it is printed as Claude Code's
28
+ # hookSpecificOutput.additionalContext. Only that one string field of the
29
+ # response is used.
22
30
  #
23
31
  # It never blocks or slows the agent: every path exits 0, and the POST has a
24
32
  # 2-second ceiling. A failed POST is dropped silently (the backend's screen
@@ -68,8 +76,13 @@ ID_PATTERN='^[A-Za-z0-9_-]{1,128}$'
68
76
  TOOL_USE_ID=""
69
77
  AGENT_ID=""
70
78
  SOURCE=""
79
+ TOOL_NAME=""
71
80
  case "$EVENT" in
72
- PreToolUse|PostToolUse) TOOL_USE_ID="$(field tool_use_id "$ID_PATTERN")" ;;
81
+ PreToolUse) TOOL_USE_ID="$(field tool_use_id "$ID_PATTERN")" ;;
82
+ PostToolUse)
83
+ TOOL_USE_ID="$(field tool_use_id "$ID_PATTERN")"
84
+ TOOL_NAME="$(field tool_name '^[A-Za-z0-9_-]{1,64}$')"
85
+ ;;
73
86
  SubagentStart|SubagentStop) AGENT_ID="$(field agent_id "$ID_PATTERN")" ;;
74
87
  SessionStart) SOURCE="$(field source)" ;;
75
88
  esac
@@ -79,9 +92,10 @@ BODY="{\"event\":\"$EVENT\""
79
92
  [ -n "$TOOL_USE_ID" ] && BODY="$BODY,\"toolUseId\":\"$TOOL_USE_ID\""
80
93
  [ -n "$AGENT_ID" ] && BODY="$BODY,\"agentId\":\"$AGENT_ID\""
81
94
  [ -n "$SOURCE" ] && BODY="$BODY,\"source\":\"$SOURCE\""
95
+ [ -n "$TOOL_NAME" ] && BODY="$BODY,\"toolName\":\"$TOOL_NAME\""
82
96
  BODY="$BODY}"
83
97
 
84
- ARGS=(-s -o /dev/null --max-time 2 -X POST "$API_URL/api/agent-hooks"
98
+ ARGS=(-s --max-time 2 -X POST "$API_URL/api/agent-hooks"
85
99
  -H "Content-Type: application/json"
86
100
  -H "User-Agent: crewly-agent-status-hook/1"
87
101
  -H "X-Agent-Session: $SESSION")
@@ -93,5 +107,30 @@ if [ -n "${CREWLY_AGENT_AUTHORIZATION:-}" ]; then
93
107
  ARGS+=(-H "X-Agent-Authorization: b64:$(printf '%s' "$CREWLY_AGENT_AUTHORIZATION" | base64 | tr -d '\n')")
94
108
  fi
95
109
 
96
- curl "${ARGS[@]}" --data "$BODY" >/dev/null 2>&1 || true
110
+ # Only a PostToolUse with a tool name can get a note back; everything else
111
+ # discards the response.
112
+ if [ "$EVENT" != "PostToolUse" ] || [ -z "$TOOL_NAME" ]; then
113
+ curl "${ARGS[@]}" -o /dev/null --data "$BODY" >/dev/null 2>&1 || true
114
+ exit 0
115
+ fi
116
+
117
+ RESPONSE="$(curl "${ARGS[@]}" --data "$BODY" 2>/dev/null || true)"
118
+ [ -z "$RESPONSE" ] && exit 0
119
+ # Print {"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":<note>}}
120
+ # when the response carries a string additionalContext; nothing otherwise.
121
+ if command -v jq >/dev/null 2>&1; then
122
+ printf '%s' "$RESPONSE" | jq -c 'if type == "object" and (.additionalContext | type) == "string" and (.additionalContext | length) > 0 then {hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: .additionalContext}} else empty end' 2>/dev/null || true
123
+ elif command -v node >/dev/null 2>&1; then
124
+ printf '%s' "$RESPONSE" | node -e '
125
+ let s = "";
126
+ process.stdin.on("data", (c) => { s += c; });
127
+ process.stdin.on("end", () => {
128
+ try {
129
+ const o = JSON.parse(s);
130
+ if (o && typeof o.additionalContext === "string" && o.additionalContext.length > 0) {
131
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: o.additionalContext } }));
132
+ }
133
+ } catch { /* not JSON: no note */ }
134
+ });' 2>/dev/null || true
135
+ fi
97
136
  exit 0
@@ -8,7 +8,8 @@
8
8
  ### What You Are NOT
9
9
  - Direct user communicator (route through orchestrator)
10
10
  - Orchestration-level router
11
- - Implementation executor (delegate to your team)
11
+ - Implementation executor (delegate to your team — any member can code, write and research; role is a preference, not a limit)
12
+ - The relay for a member's answer to the owner (delegate owner requests with `--thread <key>`; the member answers in that thread)
12
13
 
13
14
  ### Try-Before-Refuse Protocol
14
15
  Before refusing any task:
@@ -111,12 +111,16 @@ Each project keeps its own backlog in `<project>/.crewly/tickets/` (one markdown
111
111
  When the owner switched the ticket autopilot on for a project, Crewly sends you a `ticket_triage` WorkItem while someone on your team is idle and backlog tickets wait. Decide every ticket it lists, then complete the WorkItem (one line per ticket):
112
112
  - **ready + assign** (`assign-ticket`, or `project-tickets update --status ready` for the next idle member) — at most the stated number of in-progress tickets per member;
113
113
  - **split** into smaller ready tickets and cancel the original with a note;
114
- - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–3 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
114
+ - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–5 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
115
115
  - **cancel** with a reason.
116
116
 
117
117
  Tickets marked *worker-created — review first* were filed by a team member: check they are wanted before making them ready.
118
118
 
119
- **Who does what.** Delegate by role: give each ticket to the member whose role fits the work. A member shown *stopped* is available — assigning starts them; stopped is never "busy". Take a ticket yourself only for lead-level work (review, decisions, owner communication, cross-team coordination) or when no member fits. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by current fit and availability, and split a mixed ticket so each part goes to the right role.
119
+ **Who does what.** Role is a preference, not a limit: every member runs the same runtime and can code, write and research. Any member can take any work that needs no special account, tool or permission. Prefer an idle member over doing it yourself; a member shown *stopped* is available too — assigning starts them; stopped is never "busy". Do hands-on work yourself only for lead-level work (review, decisions, owner communication, cross-team coordination), when every member is busy, or when the work truly needs your own judgment. "No member fits" is rare — a role mismatch is not a reason. When it happens, record what is missing (access, tool, permission): `delegate-task --no-member-fits "<what is missing>" --task "<the work>"`. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by availability, and split a mixed ticket so the parts run in parallel.
120
+
121
+ **Owner requests from Slack.** When the owner asks you something in Slack that a member can do, delegate it with `--thread <key>` (the key from `[SLACK-THREAD:<key>]`). The member answers the owner in that thread itself, under its own name — you do not keep work to keep the owner informed, and you do not relay its result. A short "Sage is on it" from you is enough.
122
+
123
+ **Execution nudge.** If you edit files for a while, Crewly adds a `[CREWLY-NUDGE]` note naming members who can take the work. Delegate, or record why you keep it (`--no-member-fits`).
120
124
 
121
125
  **The autopilot does not lift the approval boundary.** Even with it on, these need the owner's explicit OK: sending email or messages to outside people; publishing content publicly; deploying to production; spending money. Work such a ticket up to a draft or a PR, then `ask-owner … --sensitive <kind>` for the final step (never auto-applied). The answer reaches the asking agent as a `[DECISION …]` message and the mark clears itself; act on it. Withdraw a question that is no longer needed with `ask-owner --clear`.
122
126
 
@@ -157,12 +161,12 @@ When you receive an Objective from the Orchestrator:
157
161
  ```bash
158
162
  bash {{AGENT_SKILLS_PATH}}/core/get-team-status/execute.sh '{}'
159
163
  ```
160
- 4. **Match task to worker** — Consider role, current workload, and past performance. Don't overload a busy worker when an idle one is available.
164
+ 4. **Match task to worker** — Prefer an idle member; role is a preference, not a limit (any member can code, write and research). Don't overload a busy worker when an idle one is available.
161
165
  5. **Delegate immediately** — Don't analyze endlessly. Once you have a clear task and an available worker, delegate. Speed matters.
162
166
 
163
167
  ### Step 3: Task Delegation
164
- 1. Evaluate each worker's role and current workload
165
- 2. Use **delegate-task** to assign tasks to the best-matched workers
168
+ 1. Evaluate each worker's current workload (idle first), then role as a tie-breaker
169
+ 2. Use **delegate-task** to assign tasks to available workers (idle first; pass `--thread <key>` for an owner request)
166
170
  3. **Rule**: Never give the same worker more than 2 concurrent tasks (prevents PTY blocking)
167
171
  4. Include clear acceptance criteria in every delegation
168
172
 
@@ -3,7 +3,7 @@
3
3
  ### What You ARE
4
4
  - **Objective owner**: you carry the team's deliverable from brief to acceptance
5
5
  - **Planner & decomposer**: you turn requests into worker-sized subtasks (Goal + Outcome + Eval)
6
- - **Delegator**: you assign each subtask to the most-suitable available worker — this is your default action
6
+ - **Delegator**: you assign each subtask to an available worker (idle first) — this is your default action. Role is a preference, not a limit: any member can code, write and research
7
7
  - **Unblocker**: you clear obstacles, answer questions, and escalate upward when scope or risk changes
8
8
  - **Verifier**: you review worker output against the Eval criteria before declaring done
9
9
  - **Status surface**: you report progress and outcomes upstream so the owner never has to chase
@@ -11,7 +11,8 @@
11
11
  ### What You Are NOT
12
12
  - **Not a direct user communicator**: route user-facing replies through the orchestrator unless explicitly tasked otherwise
13
13
  - **Not the orchestration-level router**: you own one team's deliverable, not cross-team scheduling
14
- - **Not the default implementer**: implementation is a worker's job — see the Self-Implementation Exception Rule before opening an editor
14
+ - **Not the default implementer**: implementation is a worker's job. Do hands-on work only for lead-level work, when every member is busy, or when it truly needs your judgment — and record "no member fits" with what is missing (`delegate-task --no-member-fits`)
15
+ - **Not the relay for a member's answer**: delegate owner requests with `--thread <key>`; the member answers the owner in that thread
15
16
  - **Not a rubber-stamp reviewer**: an "LGTM" without checking against Eval is a failure mode, not a shortcut
16
17
 
17
18
  ### Try-Before-Refuse Protocol
@@ -97,12 +97,16 @@ Each project keeps its own backlog in `<project>/.crewly/tickets/` (one markdown
97
97
  When the owner switched the ticket autopilot on for a project, Crewly sends you a `ticket_triage` WorkItem while someone on your team is idle and backlog tickets wait. Decide every ticket it lists, then complete the WorkItem (one line per ticket):
98
98
  - **ready + assign** (`assign-ticket`, or `project-tickets update --status ready` for the next idle member) — at most the stated number of in-progress tickets per member;
99
99
  - **split** into smaller ready tickets and cancel the original with a note;
100
- - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–3 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
100
+ - **needs the owner** — `project-tickets ask-owner --project <P> --id <ID> --question "<one line>" --option "<choice>" --option "<choice>" --default "<choice or wait>"` (2–5 options; `--sensitive email|publish|deploy|spend` for the boundaries below). The ticket's assignee (or you) posts it as a card in the ticket's Slack thread; you do not message the owner about it;
101
101
  - **cancel** with a reason.
102
102
 
103
103
  Tickets marked *worker-created — review first* were filed by a team member: check they are wanted before making them ready.
104
104
 
105
- **Who does what.** Delegate by role: give each ticket to the member whose role fits the work. A member shown *stopped* is available — assigning starts them; stopped is never "busy". Take a ticket yourself only for lead-level work (review, decisions, owner communication, cross-team coordination) or when no member fits. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by current fit and availability, and split a mixed ticket so each part goes to the right role.
105
+ **Who does what.** Role is a preference, not a limit: every member runs the same runtime and can code, write and research. Any member can take any work that needs no special account, tool or permission. Prefer an idle member over doing it yourself; a member shown *stopped* is available too — assigning starts them; stopped is never "busy". Do hands-on work yourself only for lead-level work (review, decisions, owner communication, cross-team coordination), when every member is busy, or when the work truly needs your own judgment. "No member fits" is rare — a role mismatch is not a reason. When it happens, record what is missing (access, tool, permission): `delegate-task --no-member-fits "<what is missing>" --task "<the work>"`. A split written in an old ticket ("Owen writes, Nova does the images") is only a hint: decide by availability, and split a mixed ticket so the parts run in parallel.
106
+
107
+ **Owner requests from Slack.** When the owner asks you something in Slack that a member can do, delegate it with `--thread <key>` (the key from `[SLACK-THREAD:<key>]`). The member answers the owner in that thread itself, under its own name — you do not keep work to keep the owner informed, and you do not relay its result. A short "Sage is on it" from you is enough.
108
+
109
+ **Execution nudge.** If you edit files for a while, Crewly adds a `[CREWLY-NUDGE]` note naming members who can take the work. Delegate, or record why you keep it (`--no-member-fits`).
106
110
 
107
111
  **The autopilot does not lift the approval boundary.** Even with it on, these need the owner's explicit OK: sending email or messages to outside people; publishing content publicly; deploying to production; spending money. Work such a ticket up to a draft or a PR, then `ask-owner … --sensitive <kind>` for the final step (never auto-applied). The answer reaches the asking agent as a `[DECISION …]` message and the mark clears itself; act on it. Withdraw a question that is no longer needed with `ask-owner --clear`.
108
112
 
@@ -126,12 +130,12 @@ When you receive an Objective from the Orchestrator:
126
130
  ```bash
127
131
  bash {{AGENT_SKILLS_PATH}}/core/get-team-status/execute.sh '{}'
128
132
  ```
129
- 4. **Match task to worker** — Consider role, current workload, and past performance. Don't overload a busy worker when an idle one is available.
133
+ 4. **Match task to worker** — Prefer an idle member; role is a preference, not a limit (any member can code, write and research). Don't overload a busy worker when an idle one is available.
130
134
  5. **Delegate immediately** — Don't analyze endlessly. Once you have a clear task and an available worker, delegate. Speed matters.
131
135
 
132
136
  #### Step 3: Task Delegation
133
- 1. Evaluate each worker's role and current workload
134
- 2. Use **delegate-task** to assign tasks to the best-matched workers
137
+ 1. Evaluate each worker's current workload (idle first), then role as a tie-breaker
138
+ 2. Use **delegate-task** to assign tasks to available workers (idle first; pass `--thread <key>` for an owner request)
135
139
  3. **Rule**: Never give the same worker more than 2 concurrent tasks (prevents PTY blocking)
136
140
  4. Include clear acceptance criteria in every delegation
137
141
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: Ask Owner
3
- description: Ask the owner ONE decision with 2–3 options, a default and a deadline. Your own Slack bot posts it as a card with buttons where your work is (the ticket's thread, or your team channel); the owner taps an answer and you get a [DECISION …] message. Never ask the owner open-ended questions in chat.
3
+ description: Ask the owner ONE decision with 2–5 options, a default and a deadline. Your own Slack bot posts it as a card with buttons where your work is (the ticket's thread, or your team channel); the owner taps an answer and you get a [DECISION …] message. Never ask the owner open-ended questions in chat.
4
4
  version: 1.0.0
5
5
  category: communication
6
6
  skillType: claude-skill
@@ -52,12 +52,19 @@ bash execute.sh --question "Send the partner email on Monday?" \
52
52
  | Flag | Rule |
53
53
  |---|---|
54
54
  | `--question` | One line (8–280 characters). Name the decision, not "thoughts?" |
55
- | `--option` | 2–3 times. `"Label"` or `"Label — detail"`. The label is the button (≤ 40 characters). |
55
+ | `--option` | 2–5 times, one per alternative. `"Label"` or `"Label — detail"`. The label is the button (short, ≤ 40 characters). The owner picks exactly one; the answer comes back as that label. |
56
56
  | `--default` | The option you will take if there is no answer by the deadline, or `wait` |
57
57
  | `--deadline` | Optional, ISO (`2026-10-02T12:00`). Default: tomorrow 12:00 |
58
58
  | `--ticket APP-12 --project P` | Optional. Ask about a ticket; the card goes in the ticket's thread, asked by its assignee |
59
59
  | `--sensitive email\|publish\|deploy\|spend` | Required for messages to outside people, public publishing, prod deploys, spending money. These are never auto-applied: the owner is re-asked once, then the question is parked |
60
60
 
61
+ **"A or B?" is ONE card with options.** When the answers exclude each other (change it now / try the
62
+ current version first), post a single ask with one `--option` per alternative. Never post two Yes/No
63
+ cards for the two halves: the owner can tap Yes on both and you are left guessing. A second Yes/No
64
+ ask on the same ticket within 60 seconds that reads as an either/or (starts with 还是 / 或者 / "or ",
65
+ or the first one ends in an either/or) is rejected with an error telling you to post one card with
66
+ options. Use plain Yes/No only for a single question with one possible action.
67
+
61
68
  What happens:
62
69
 
63
70
  - The card is posted by your own Slack bot:
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # ask-owner — one structured owner decision (question + 2–3 options + default
2
+ # ask-owner — one structured owner decision (question + 2–5 options + default
3
3
  # + deadline), posted as a Slack card by the asking agent's own bot.
4
4
  # See specs/2026-10-01-decision-cards.md.
5
5
  set -euo pipefail
@@ -134,7 +134,7 @@ without a WorkItem). Team leads also have the `assign-ticket` skill.
134
134
 
135
135
  A **stopped** member is available: assigning starts it for the ticket (the
136
136
  answer's `wake` says `started`, or which gate refused it — then it picks the
137
- ticket up on its next start). Delegate by role; keep only lead-level work.
137
+ ticket up on its next start). Role is a preference, not a limit: prefer an idle member; keep only lead-level work.
138
138
 
139
139
  ## Link work already in flight (orchestrator / team lead)
140
140
 
@@ -163,7 +163,7 @@ bash execute.sh ask-owner --project P --id APP-12 \
163
163
  bash execute.sh ask-owner --project P --id APP-12 --clear --note "resolved in the review call"
164
164
  ```
165
165
 
166
- ONE question with 2–3 options and a default (an option, or `wait`). The deadline defaults to
166
+ ONE question with 2–5 options and a default (an option, or `wait`). The deadline defaults to
167
167
  tomorrow at 12:00. The ticket's assignee, else the lead, posts the question as a card in the
168
168
  ticket's Slack thread, using its own bot. The ticket gets `needs-owner` until the question is
169
169
  answered.
@@ -62,7 +62,7 @@ Usage:
62
62
  flight (a live WorkItem) to this ticket
63
63
  bash execute.sh ask-owner --project P --id APP-12 --question "…" --option "A" --option "B — detail"
64
64
  --default "B"|wait [--deadline ISO] [--sensitive email|publish|deploy|spend]
65
- Ask the owner ONE question with 2–3 options. The
65
+ Ask the owner ONE question with 2–5 options. The
66
66
  ticket's assignee (else its lead) posts it as a card
67
67
  in the ticket's Slack thread; the answer comes back
68
68
  to that agent as a [DECISION …] message
@@ -105,22 +105,29 @@ bash execute.sh --app 28au74d9cj --private # pr
105
105
  holding anything private.
106
106
  - The output says `Requested: the owner approves it by opening the app.` It
107
107
  only **records a request**: the owner sees it as a banner in the app and
108
- approves or declines it there, and the card you get with it tells them so.
109
- To approve, the owner signs in again **with Google** in the app (a Crewly
110
- login or token is not enough). **No agent can make an app public** — there
111
- is no command for it, and Crewly Cloud only changes visibility from the
112
- owner's own, freshly re-authenticated session. Do not tell the owner it is
113
- public until they approved it.
108
+ approves or declines it in the Crewly Cloud portal (Apps → the app →
109
+ Sharing), which lists exactly which collections become readable/submittable.
110
+ **No agent can make an app public** — there is no command for it, and Crewly
111
+ Cloud only changes visibility from the owner's own portal sign-in. Do not
112
+ tell the owner it is public until they approved it.
113
+ - **Visitors.** On a public link `await crewly.me()` returns `{ role: 'visitor' }`.
114
+ Visitors can only read `--public-read` collections and add to
115
+ `--public-submit` ones; every other write is refused. Build a read-only view
116
+ for them. **`window.crewly` and `crewly.db` are frozen** — never assign to
117
+ them (`crewly.db.set = noop` throws "Attempted to assign to readonly
118
+ property" in Safari). Route writes through your own wrapper, or replace the
119
+ global: `window.crewly = Object.freeze({ ...crewly, db: Object.freeze({ ...crewly.db, set: noop }) })`.
120
+ Test the public link as a visitor (a private window) before telling the owner.
114
121
  - **Names.** A public app's name (and the owner's display name) may not contain
115
122
  `crewly`, `sign in`, `sign-in`, `login`, `log in`, `password`, `verify`,
116
123
  `account`, `security` or `support` (any case). If you plan to ask for public,
117
124
  pick a name without them; `--public` with such a name is refused at once
118
125
  (`reason: "validation"`, the message names the word) — rename with `--name`.
119
- - **Every new version needs re-approval.** Publishing a new version of a
120
- **public** app, or rolling one back, makes it **private again** with a
121
- pending re-approval request. The output then has `"publicPaused": true` and
122
- a message: tell the owner the app is private until they re-approve it in the
123
- app. Batch your changes; do not publish a public app for small fixes.
126
+ - **Public stays public.** Publishing a new version of a public app (or
127
+ rolling it back) keeps it public with the same exposure — links the owner
128
+ shared keep working. So test a new version before publishing it to a public
129
+ app. Widening what is public (more `--public-read` / `--public-submit`
130
+ collections) still needs a new request the owner approves.
124
131
  - `--private` is always allowed and takes effect at once. Use it if anything
125
132
  looks wrong (spam, abuse, a leak).
126
133
 
@@ -8,7 +8,7 @@
8
8
  # applies) and sends their contents. specs/2026-10-04-crewly-apps-p2.md
9
9
  #
10
10
  # Publishing or rolling back a PUBLIC app takes it private until the owner
11
- # re-approves it (Cloud); the output then has "publicPaused": true.
11
+ # stays public (since 2026-10-04 a new version no longer pauses it).
12
12
  #
13
13
  # P3 (specs/2026-10-04-crewly-apps-p3.md): the card carries a signed
14
14
  # one-tap link the backend mints and posts to the owner DM only — this
@@ -49,6 +49,46 @@ Assigns a task to a worker within the Team Leader's subordinate scope. Validates
49
49
  | `--from` | `fromSession` | No | Delegating TL's session name (for monitoring) |
50
50
  | `--request-id` / `-R` | `requestId` | No | Ticket this work is for: the id (or `TKT-123`) from the `[TICKET:TKT-123 <id>]` line of the message you are acting on. Omit it and the task is still linked when your current turn has exactly one ticket |
51
51
  | `--ticket` | `ticket` | No | Project ticket this work is for (e.g. `APP-12`). Without it a ticket is created for you when the target works on a project — see Project tickets |
52
+ | `--thread` | `thread` | No | Owner request from Slack: the key from the `[SLACK-THREAD:<key>]` of the owner's message. The member answers the owner in that thread itself — see Owner requests |
53
+ | `--no-member-fits` | `noMemberFits` | No | You keep the work: record what is missing (access, tool, permission, everyone busy). Needs `--task`; nothing is delegated — see No member fits |
54
+ | `--work-item` | `workItemId` | No | With `--no-member-fits`: the WorkItem you keep (gets a `[NO-MEMBER-FITS]` note) |
55
+
56
+ ## Who to delegate to
57
+
58
+ Role is a preference, not a limit: every member runs the same runtime and can
59
+ code, write and research. Any member can take any work that needs no special
60
+ account, tool or permission. Prefer an idle (or stopped) member over doing it
61
+ yourself. Do hands-on work yourself only for lead-level work (review,
62
+ decisions, owner communication, cross-team coordination), when every member
63
+ is busy, or when the work truly needs your own judgment.
64
+
65
+ ## Owner requests (`--thread`)
66
+
67
+ When the owner asked in Slack, pass the thread key from the owner message's
68
+ `[SLACK-THREAD:<key>]`:
69
+
70
+ ```bash
71
+ bash execute.sh --to sage-session --task "Goal: … Outcome: … Eval: …" --thread C0THINK:1790000000.000100
72
+ ```
73
+
74
+ The WorkItem carries the thread and the member is told to post its progress
75
+ and result there itself (`reply --work-item <id>`), under its own name. You do
76
+ not relay its answer; the room's reply gate never holds the member's post.
77
+ Without `--thread` the thread of your last owner message is used. (A thread
78
+ in your DM with the owner: the member's bot cannot post there, so it answers
79
+ in its own DM with the owner, opened with `Re: <task>`.)
80
+
81
+ ## No member fits (`--no-member-fits`)
82
+
83
+ Rare. When you keep work yourself, record why — what is missing, not a role
84
+ mismatch — so the owner sees missing roles in the daily report:
85
+
86
+ ```bash
87
+ bash execute.sh --no-member-fits "needs the owner's Stripe login" --task "Update billing settings" [--work-item <id>] [--ticket CE-7]
88
+ ```
89
+
90
+ Nothing is delivered. With `--work-item` the WorkItem gets a
91
+ `[NO-MEMBER-FITS] <reason>` note.
52
92
 
53
93
  ## Project tickets
54
94
 
@@ -18,6 +18,9 @@ TL_MEMBER_ID=""
18
18
  FROM_SESSION=""
19
19
  REQUEST_ID=""
20
20
  PROJECT_TICKET=""
21
+ OWNER_THREAD=""
22
+ NO_MEMBER_FITS=""
23
+ KEPT_WORK_ITEM=""
21
24
 
22
25
  # Detect legacy JSON argument
23
26
  if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
@@ -38,10 +41,15 @@ while [[ $# -gt 0 ]]; do
38
41
  --from) FROM_SESSION="$2"; shift 2 ;;
39
42
  --request-id|-R) REQUEST_ID="$2"; shift 2 ;;
40
43
  --ticket) PROJECT_TICKET="$2"; shift 2 ;;
44
+ --thread) OWNER_THREAD="$2"; shift 2 ;;
45
+ --no-member-fits) NO_MEMBER_FITS="$2"; shift 2 ;;
46
+ --work-item) KEPT_WORK_ITEM="$2"; shift 2 ;;
41
47
  --json|-j) INPUT_JSON="$2"; shift 2 ;;
42
48
  --help|-h)
43
49
  echo "Usage: execute.sh --to worker-session --task 'implement feature' --priority high --project /path [--team teamId] [--tl-member memberId] [--request-id <ticket id from the [TICKET:TKT-123 <id>] line>] [--ticket <project ticket id, e.g. APP-12>]"
44
50
  echo "Work for a teammate on a project always runs through a project ticket: --ticket uses that backlog/ready ticket, otherwise one is created for you."
51
+ echo "Owner request from Slack: add --thread <key> (from [SLACK-THREAD:<key>]) — the member answers the owner in that thread itself."
52
+ echo "Keeping work yourself because no member fits: execute.sh --no-member-fits '<what is missing: access / tool / permission / everyone busy>' --task '<the work>' [--work-item <id>] [--ticket <ID>]"
45
53
  exit 0
46
54
  ;;
47
55
  --) shift; break ;;
@@ -79,6 +87,28 @@ if [ -n "$INPUT_JSON" ]; then
79
87
  [ -z "$FROM_SESSION" ] && FROM_SESSION=$(printf '%s' "$INPUT" | jq -r '.fromSession // empty')
80
88
  [ -z "$REQUEST_ID" ] && REQUEST_ID=$(printf '%s' "$INPUT" | jq -r '.requestId // empty')
81
89
  [ -z "$PROJECT_TICKET" ] && PROJECT_TICKET=$(printf '%s' "$INPUT" | jq -r '.ticket // .projectTicketId // empty')
90
+ [ -z "$OWNER_THREAD" ] && OWNER_THREAD=$(printf '%s' "$INPUT" | jq -r '.thread // .ownerThread // empty')
91
+ [ -z "$NO_MEMBER_FITS" ] && NO_MEMBER_FITS=$(printf '%s' "$INPUT" | jq -r '.noMemberFits // empty')
92
+ [ -z "$KEPT_WORK_ITEM" ] && KEPT_WORK_ITEM=$(printf '%s' "$INPUT" | jq -r '.workItemId // empty')
93
+ fi
94
+
95
+ # "No member fits" (crewly#1083, specs/2026-10-04-tl-delegation.md §4): the
96
+ # lead keeps this work. Record why, so the owner sees missing roles; nothing
97
+ # is delivered to anyone.
98
+ if [ -n "$NO_MEMBER_FITS" ]; then
99
+ require_param "task (--task)" "$TASK"
100
+ KEEP_BODY=$(jq -n --arg reason "$NO_MEMBER_FITS" --arg work "$(echo "$TASK" | head -c 300)" --arg workItemId "$KEPT_WORK_ITEM" --arg ticket "$PROJECT_TICKET" \
101
+ '{reason: $reason, work: $work} + (if $workItemId != "" then {workItemId: $workItemId} else {} end) + (if $ticket != "" then {ticket: $ticket} else {} end)')
102
+ KEEP_RESULT=$(api_call POST "/teams/lead-self-work" "$KEEP_BODY" 2>/dev/null || echo '{"success":false}')
103
+ if [ "$(echo "$KEEP_RESULT" | jq -r '.success // false' 2>/dev/null)" != "true" ]; then
104
+ error_exit "Could not record the kept work: $(echo "$KEEP_RESULT" | jq -r '.error // "unknown error"' 2>/dev/null)"
105
+ fi
106
+ if [ -n "$KEPT_WORK_ITEM" ]; then
107
+ NOTE_BODY=$(jq -n --arg author "${CREWLY_SESSION_NAME:-${FROM_SESSION:-team-leader}}" --arg note "[NO-MEMBER-FITS] ${NO_MEMBER_FITS}" '{author: $author, note: $note}')
108
+ api_call POST "/task-pool/items/${KEPT_WORK_ITEM}/notes" "$NOTE_BODY" >/dev/null 2>&1 || true
109
+ fi
110
+ echo "$KEEP_RESULT" | jq -c '{success: true, recorded: .data, info: "Recorded: you keep this work because no member fits. Nothing was delegated."}'
111
+ exit 0
82
112
  fi
83
113
 
84
114
  require_param "to (--to)" "$TO"
@@ -218,7 +248,8 @@ POOL_BODY=$(jq -n \
218
248
  --arg requestId "${REQUEST_ID:-}" \
219
249
  --arg projectTicketId "${PROJECT_TICKET:-}" \
220
250
  --arg delegatedBy "${CREWLY_SESSION_NAME:-${FROM_SESSION:-}}" \
221
- '{type: $type, owner: $owner, target: $target, title: $title, description: $description, briefMarkdown: $briefMarkdown, metadata: ({priority: $priority, directDelivery: true} + (if $projectPath != "" then {projectPath: $projectPath} else {} end) + (if $delegatedBy != "" then {delegatedBy: $delegatedBy} else {} end))} + (if $requestId != "" then {requestId: $requestId} else {} end) + (if $projectTicketId != "" then {projectTicketId: $projectTicketId} else {} end)')
251
+ --arg ownerThread "${OWNER_THREAD:-}" \
252
+ '{type: $type, owner: $owner, target: $target, title: $title, description: $description, briefMarkdown: $briefMarkdown, metadata: ({priority: $priority, directDelivery: true} + (if $projectPath != "" then {projectPath: $projectPath} else {} end) + (if $delegatedBy != "" then {delegatedBy: $delegatedBy} else {} end) + (if $ownerThread != "" then {ownerThread: $ownerThread} else {} end))} + (if $requestId != "" then {requestId: $requestId} else {} end) + (if $projectTicketId != "" then {projectTicketId: $projectTicketId} else {} end)')
222
253
  # metadata.directDelivery: this script delivers the brief itself (below), so
223
254
  # the backend's workitem:queued push holds off and only fires if that
224
255
  # delivery never lands — the task reaches the worker once.
@@ -258,6 +289,13 @@ if [ -n "$TASK_ID" ]; then
258
289
  DELIVER_MESSAGE="WorkItem ${TASK_ID} — ${TASK_MESSAGE//<your WorkItem id>/$TASK_ID}"
259
290
  fi
260
291
 
292
+ # Owner request from a Slack thread (crewly#1083): the backend names the
293
+ # thread and tells the member to answer the owner there itself.
294
+ OWNER_THREAD_NOTE=$(echo "$POOL_RESULT" | jq -r '.data.ownerThread.note // empty' 2>/dev/null || true)
295
+ if [ -n "$OWNER_THREAD_NOTE" ]; then
296
+ DELIVER_MESSAGE="${DELIVER_MESSAGE}\n\n${OWNER_THREAD_NOTE}"
297
+ fi
298
+
261
299
  if [ "$POOL_OK" != "true" ]; then
262
300
  # Unchanged from before the reorder: warn and still attempt delivery.
263
301
  # Aborting here instead would be a behavioural change beyond this WI's
@@ -6050,7 +6050,9 @@ export declare const DECISION_CONSTANTS: {
6050
6050
  readonly QUESTION_MIN_CHARS: 8;
6051
6051
  /** Allowed option count */
6052
6052
  readonly MIN_OPTIONS: 2;
6053
- readonly MAX_OPTIONS: 3;
6053
+ readonly MAX_OPTIONS: 5;
6054
+ /** Two yes/no asks on one ticket this close together that read as either/or are rejected (ms) */
6055
+ readonly EITHER_OR_WINDOW_MS: number;
6054
6056
  /** Max characters of an option label (Slack button text max is 75) */
6055
6057
  readonly OPTION_LABEL_MAX_CHARS: 40;
6056
6058
  /** Max characters of an option's optional detail line */
@@ -6203,6 +6205,47 @@ export declare const WORK_ITEM_DESTINATION_CONSTANTS: {
6203
6205
  /** The one line every prompt carries about where to answer */
6204
6206
  readonly PROMPT_LINE: "Answer where you were asked; a new topic goes in a new thread.";
6205
6207
  };
6208
+ /**
6209
+ * Team leads delegate instead of doing the work (crewly#1083,
6210
+ * specs/2026-10-04-tl-delegation.md): owner-thread hand-over, the execution
6211
+ * nudge, lead share of team tokens, and "no member fits" records.
6212
+ */
6213
+ export declare const TL_DELEGATION_CONSTANTS: {
6214
+ /** WorkItem.metadata / add-body field: the owner's Slack thread key a delegation answers */
6215
+ readonly OWNER_THREAD_FIELD: "ownerThread";
6216
+ /** A done item still lets its agent post in the owner's thread this long (ms) */
6217
+ readonly DONE_THREAD_GRACE_MS: number;
6218
+ /** Claude Code tools that count as hands-on file edits */
6219
+ readonly EDIT_TOOLS: readonly string[];
6220
+ /** Edits in the window that trigger a nudge */
6221
+ readonly EDIT_THRESHOLD: 6;
6222
+ /** Sliding window for counting edits (ms) */
6223
+ readonly WINDOW_MS: number;
6224
+ /** At most one nudge per lead per this long (ms) */
6225
+ readonly NUDGE_COOLDOWN_MS: number;
6226
+ /** A delegation this soon after a nudge counts as following it (ms) */
6227
+ readonly FOLLOW_WINDOW_MS: number;
6228
+ /** Members named in a nudge */
6229
+ readonly NUDGE_MAX_MEMBERS: 3;
6230
+ /** Tag that opens the nudge text */
6231
+ readonly NUDGE_TAG: "[CREWLY-NUDGE]";
6232
+ /** Note tag on a WorkItem the lead kept because no member fits */
6233
+ readonly NO_MEMBER_FITS_TAG: "[NO-MEMBER-FITS]";
6234
+ /** A lead share above this is flagged */
6235
+ readonly FLAG_SHARE: 0.5;
6236
+ /** Below this many team tokens in the period a share is not flagged (too little to judge) */
6237
+ readonly MIN_TEAM_TOKENS: 1000000;
6238
+ /** "This week" = the last N days */
6239
+ readonly WEEK_DAYS: 7;
6240
+ /** State file in CREWLY_HOME (nudge counts, kept-work records) */
6241
+ readonly STATE_FILENAME: "tl-delegation.json";
6242
+ /** Kept-work records retained */
6243
+ readonly MAX_RECORDS: 500;
6244
+ /** Max characters of a recorded reason / work line */
6245
+ readonly MAX_TEXT_CHARS: 300;
6246
+ /** Accepted tool_name from the hook */
6247
+ readonly TOOL_NAME_PATTERN: RegExp;
6248
+ };
6206
6249
  /**
6207
6250
  * Harness-owned reply routing (specs/2026-10-02-harness-owned-routing.md).
6208
6251
  */
@@ -6239,7 +6282,8 @@ export declare const USAGE_BACKFILL_CONSTANTS: {
6239
6282
  /**
6240
6283
  * Daily token caps with a hard stop, team caps and temporary boosts
6241
6284
  * (specs/2026-10-02-spend-cap.md). The unit is TOKENS, not dollars (owner,
6242
- * 2026-10-02): total tokens = input (fresh + cached) + output — see
6285
+ * 2026-10-02): caps compare cost-weighted budget tokens (cache reads x0.1,
6286
+ * crewly#1090); the raw total is input (fresh + cached) + output — see
6243
6287
  * `eventTokens` in token-usage.service. Caps are OFF until the owner sets
6244
6288
  * one. All owner-facing text is English.
6245
6289
  */