crewly 1.20.204 → 1.20.206

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 (218) hide show
  1. package/config/skills/_common/lib.sh +4 -3
  2. package/config/skills/agent/core/docs-comment/SKILL.md +121 -0
  3. package/config/skills/agent/core/docs-comment/execute.sh +131 -0
  4. package/config/skills/agent/core/docs-comment/execute.test.sh +105 -0
  5. package/config/skills/agent/core/project-tickets/SKILL.md +13 -1
  6. package/config/skills/agent/core/project-tickets/execute.sh +20 -6
  7. package/config/skills/agent/core/project-tickets/execute.test.sh +8 -2
  8. package/dist/backend/backend/src/constants.d.ts +76 -0
  9. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  10. package/dist/backend/backend/src/constants.js +82 -0
  11. package/dist/backend/backend/src/constants.js.map +1 -1
  12. package/dist/backend/backend/src/controllers/google/google.controller.d.ts +30 -0
  13. package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
  14. package/dist/backend/backend/src/controllers/google/google.controller.js +75 -0
  15. package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
  16. package/dist/backend/backend/src/controllers/google/google.routes.d.ts +4 -0
  17. package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
  18. package/dist/backend/backend/src/controllers/google/google.routes.js +9 -1
  19. package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
  20. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
  21. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  22. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +9 -1
  23. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  24. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +1 -1
  25. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +1 -1
  26. package/dist/backend/backend/src/controllers/settings/role.controller.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/settings/role.controller.js +21 -7
  28. package/dist/backend/backend/src/controllers/settings/role.controller.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/settings/settings.controller.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/settings/settings.controller.js +26 -20
  31. package/dist/backend/backend/src/controllers/settings/settings.controller.js.map +1 -1
  32. package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts +13 -0
  33. package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts.map +1 -1
  34. package/dist/backend/backend/src/controllers/skill/skill.controller.js +21 -0
  35. package/dist/backend/backend/src/controllers/skill/skill.controller.js.map +1 -1
  36. package/dist/backend/backend/src/controllers/system/system-control.controller.d.ts +11 -1
  37. package/dist/backend/backend/src/controllers/system/system-control.controller.d.ts.map +1 -1
  38. package/dist/backend/backend/src/controllers/system/system-control.controller.js +36 -2
  39. package/dist/backend/backend/src/controllers/system/system-control.controller.js.map +1 -1
  40. package/dist/backend/backend/src/index.d.ts +9 -0
  41. package/dist/backend/backend/src/index.d.ts.map +1 -1
  42. package/dist/backend/backend/src/index.js +28 -0
  43. package/dist/backend/backend/src/index.js.map +1 -1
  44. package/dist/backend/backend/src/middleware/agent-origin.middleware.d.ts +3 -2
  45. package/dist/backend/backend/src/middleware/agent-origin.middleware.d.ts.map +1 -1
  46. package/dist/backend/backend/src/middleware/agent-origin.middleware.js +5 -3
  47. package/dist/backend/backend/src/middleware/agent-origin.middleware.js.map +1 -1
  48. package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts +36 -0
  49. package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts.map +1 -1
  50. package/dist/backend/backend/src/middleware/caller-identity.middleware.js +97 -1
  51. package/dist/backend/backend/src/middleware/caller-identity.middleware.js.map +1 -1
  52. package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts +4 -0
  53. package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts.map +1 -1
  54. package/dist/backend/backend/src/routes/modules/terminal.routes.js +19 -5
  55. package/dist/backend/backend/src/routes/modules/terminal.routes.js.map +1 -1
  56. package/dist/backend/backend/src/scripts/input-guard-classify.d.ts +44 -0
  57. package/dist/backend/backend/src/scripts/input-guard-classify.d.ts.map +1 -0
  58. package/dist/backend/backend/src/scripts/input-guard-classify.js +116 -0
  59. package/dist/backend/backend/src/scripts/input-guard-classify.js.map +1 -0
  60. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +8 -0
  61. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +10 -0
  63. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  64. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +8 -0
  65. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +21 -0
  67. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
  68. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
  69. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +13 -27
  70. package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
  71. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  73. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  74. package/dist/backend/backend/src/services/google/docs-comments.service.d.ts +171 -0
  75. package/dist/backend/backend/src/services/google/docs-comments.service.d.ts.map +1 -0
  76. package/dist/backend/backend/src/services/google/docs-comments.service.js +218 -0
  77. package/dist/backend/backend/src/services/google/docs-comments.service.js.map +1 -0
  78. package/dist/backend/backend/src/services/google/google-api.client.d.ts +8 -0
  79. package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
  81. package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +10 -0
  82. package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/google/google-workspace-token.service.js +10 -0
  84. package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
  85. package/dist/backend/backend/src/services/messaging/forced-delivery.d.ts +75 -0
  86. package/dist/backend/backend/src/services/messaging/forced-delivery.d.ts.map +1 -0
  87. package/dist/backend/backend/src/services/messaging/forced-delivery.js +87 -0
  88. package/dist/backend/backend/src/services/messaging/forced-delivery.js.map +1 -0
  89. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +130 -4
  90. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +115 -3
  92. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
  93. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.d.ts +122 -0
  94. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.d.ts.map +1 -0
  95. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.js +274 -0
  96. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.js.map +1 -0
  97. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +34 -2
  98. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +59 -2
  100. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
  101. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-retro.js +1 -1
  102. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-retro.js.map +1 -1
  103. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts +6 -1
  104. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts.map +1 -1
  105. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js +9 -1
  106. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js.map +1 -1
  107. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts +13 -3
  108. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts.map +1 -1
  109. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js +28 -2
  110. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js.map +1 -1
  111. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +92 -7
  112. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
  113. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +316 -41
  114. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
  115. package/dist/backend/backend/src/services/settings/api-key-access.service.d.ts +126 -0
  116. package/dist/backend/backend/src/services/settings/api-key-access.service.d.ts.map +1 -0
  117. package/dist/backend/backend/src/services/settings/api-key-access.service.js +223 -0
  118. package/dist/backend/backend/src/services/settings/api-key-access.service.js.map +1 -0
  119. package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  120. package/dist/backend/backend/src/services/skill/skill-executor.service.js +17 -0
  121. package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -1
  122. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  123. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +29 -8
  124. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  125. package/dist/backend/backend/src/services/system/auto-update.service.d.ts +24 -0
  126. package/dist/backend/backend/src/services/system/auto-update.service.d.ts.map +1 -1
  127. package/dist/backend/backend/src/services/system/auto-update.service.js +78 -8
  128. package/dist/backend/backend/src/services/system/auto-update.service.js.map +1 -1
  129. package/dist/backend/backend/src/services/system/auto-update.utils.d.ts +1 -1
  130. package/dist/backend/backend/src/services/system/auto-update.utils.d.ts.map +1 -1
  131. package/dist/backend/backend/src/services/system/auto-update.utils.js.map +1 -1
  132. package/dist/backend/backend/src/services/system/input-guard-release-check.d.ts +162 -0
  133. package/dist/backend/backend/src/services/system/input-guard-release-check.d.ts.map +1 -0
  134. package/dist/backend/backend/src/services/system/input-guard-release-check.js +264 -0
  135. package/dist/backend/backend/src/services/system/input-guard-release-check.js.map +1 -0
  136. package/dist/backend/backend/src/services/system/system-control.service.d.ts +36 -0
  137. package/dist/backend/backend/src/services/system/system-control.service.d.ts.map +1 -1
  138. package/dist/backend/backend/src/services/system/system-control.service.js +109 -10
  139. package/dist/backend/backend/src/services/system/system-control.service.js.map +1 -1
  140. package/dist/backend/backend/src/types/skill.types.d.ts +16 -0
  141. package/dist/backend/backend/src/types/skill.types.d.ts.map +1 -1
  142. package/dist/backend/backend/src/types/skill.types.js.map +1 -1
  143. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +21 -0
  144. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
  145. package/dist/backend/backend/src/types/ticket-autopilot.types.js +48 -0
  146. package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -1
  147. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +1 -1
  148. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  149. package/dist/backend/backend/src/types/v2/work-item.types.js +1 -0
  150. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  151. package/dist/backend/build-info.json +2 -2
  152. package/dist/cli/backend/src/constants.d.ts +76 -0
  153. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  154. package/dist/cli/backend/src/constants.js +82 -0
  155. package/dist/cli/backend/src/constants.js.map +1 -1
  156. package/dist/cli/backend/src/scripts/input-guard-classify.d.ts +44 -0
  157. package/dist/cli/backend/src/scripts/input-guard-classify.d.ts.map +1 -0
  158. package/dist/cli/backend/src/scripts/input-guard-classify.js +116 -0
  159. package/dist/cli/backend/src/scripts/input-guard-classify.js.map +1 -0
  160. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +8 -0
  161. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  162. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +10 -0
  163. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  164. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +8 -0
  165. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
  166. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +21 -0
  167. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
  168. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
  169. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +13 -27
  170. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
  171. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  172. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  173. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  174. package/dist/cli/backend/src/services/messaging/drain-queued-delivery.d.ts +59 -0
  175. package/dist/cli/backend/src/services/messaging/drain-queued-delivery.d.ts.map +1 -0
  176. package/dist/cli/backend/src/services/messaging/drain-queued-delivery.js +70 -0
  177. package/dist/cli/backend/src/services/messaging/drain-queued-delivery.js.map +1 -0
  178. package/dist/cli/backend/src/services/messaging/forced-delivery.d.ts +75 -0
  179. package/dist/cli/backend/src/services/messaging/forced-delivery.d.ts.map +1 -0
  180. package/dist/cli/backend/src/services/messaging/forced-delivery.js +87 -0
  181. package/dist/cli/backend/src/services/messaging/forced-delivery.js.map +1 -0
  182. package/dist/cli/backend/src/services/messaging/spend-capped-delivery.d.ts +54 -0
  183. package/dist/cli/backend/src/services/messaging/spend-capped-delivery.d.ts.map +1 -0
  184. package/dist/cli/backend/src/services/messaging/spend-capped-delivery.js +65 -0
  185. package/dist/cli/backend/src/services/messaging/spend-capped-delivery.js.map +1 -0
  186. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  187. package/dist/cli/backend/src/services/skill/skill-executor.service.js +17 -0
  188. package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -1
  189. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  190. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +29 -8
  191. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  192. package/dist/cli/backend/src/services/system/auto-update.utils.d.ts +1 -1
  193. package/dist/cli/backend/src/services/system/auto-update.utils.d.ts.map +1 -1
  194. package/dist/cli/backend/src/services/system/auto-update.utils.js.map +1 -1
  195. package/dist/cli/backend/src/services/system/input-guard-release-check.d.ts +162 -0
  196. package/dist/cli/backend/src/services/system/input-guard-release-check.d.ts.map +1 -0
  197. package/dist/cli/backend/src/services/system/input-guard-release-check.js +264 -0
  198. package/dist/cli/backend/src/services/system/input-guard-release-check.js.map +1 -0
  199. package/dist/cli/backend/src/types/skill.types.d.ts +16 -0
  200. package/dist/cli/backend/src/types/skill.types.d.ts.map +1 -1
  201. package/dist/cli/backend/src/types/skill.types.js.map +1 -1
  202. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +21 -0
  203. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
  204. package/dist/cli/backend/src/types/ticket-autopilot.types.js +48 -0
  205. package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -1
  206. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +1 -1
  207. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  208. package/dist/cli/backend/src/types/v2/work-item.types.js +1 -0
  209. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  210. package/dist/cli/cli/src/commands/doctor.d.ts +20 -0
  211. package/dist/cli/cli/src/commands/doctor.d.ts.map +1 -1
  212. package/dist/cli/cli/src/commands/doctor.js +49 -0
  213. package/dist/cli/cli/src/commands/doctor.js.map +1 -1
  214. package/dist/cli/cli/src/index.js +9 -2
  215. package/dist/cli/cli/src/index.js.map +1 -1
  216. package/frontend/dist/assets/{index-63a33dbf.js → index-f64b333a.js} +258 -258
  217. package/frontend/dist/index.html +1 -1
  218. package/package.json +2 -1
@@ -225,9 +225,10 @@ api_call() {
225
225
  # Include agent session identity header for heartbeat tracking
226
226
  # Use ${VAR:-} pattern to avoid 'unbound variable' error under set -u (nounset)
227
227
  # The agent badge (#999): the credential that makes the backend treat this
228
- # call as this agent. Injected by the harness at launch; without it the
229
- # session header alone is accepted for one release only. Never the owner
230
- # either way.
228
+ # call as this agent. Injected by the harness at launch. Without a valid
229
+ # one, the session header counts only when the backend finds this shell's
230
+ # process under that agent's PTY; terminal writes and API-key reads refuse
231
+ # it otherwise (#1024). Never the owner either way.
231
232
  if [ -n "${CREWLY_AGENT_BADGE:-}" ]; then
232
233
  args+=(-H "X-Agent-Badge: $CREWLY_AGENT_BADGE")
233
234
  fi
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: Docs Comment
3
+ description: Handle feedback left as comments in a Google Doc — list comments and replies, reply, resolve, or add a comment (via the Google Workspace grant held by Crewly Cloud). Writes as the owner's Google account — say what you posted.
4
+ version: 1.0.0
5
+ category: productivity
6
+ skillType: claude-skill
7
+ assignableRoles:
8
+ - orchestrator
9
+ - team-leader
10
+ - developer
11
+ - operations
12
+ - ops
13
+ - sales
14
+ - support
15
+ - generalist
16
+ triggers:
17
+ - comments in the doc
18
+ - google doc comments
19
+ - reply to the comment
20
+ - resolve the comment
21
+ - feedback in the doc
22
+ - add a comment to the doc
23
+ tags:
24
+ - google
25
+ - docs
26
+ - comments
27
+ execution:
28
+ type: script
29
+ script:
30
+ file: execute.sh
31
+ interpreter: bash
32
+ timeoutMs: 60000
33
+ ---
34
+
35
+ # Docs Comment
36
+
37
+ ```bash
38
+ bash execute.sh list --doc "https://docs.google.com/document/d/1AbC…/edit"
39
+ bash execute.sh list --doc 1AbC… --include-resolved
40
+ bash execute.sh reply --doc 1AbC… --comment AAAB… --text "Updated the intro, see paragraph 2."
41
+ bash execute.sh resolve --doc 1AbC… --comment AAAB… --text "Done in v2."
42
+ bash execute.sh resolve --doc 1AbC… --comment AAAB…
43
+ bash execute.sh add --doc 1AbC… --text "Is this number from Q2?" --quote "grew 40%"
44
+ ```
45
+
46
+ `--doc` takes a document id or its docs.google.com URL. `--account` picks a
47
+ connected Google account (see below).
48
+
49
+ ## Working through feedback
50
+
51
+ 1. `list` the open comments. Each one has the text it is about (`quote`),
52
+ who wrote it and when, and any replies.
53
+ 2. Make the change the comment asks for (for example with `docs-write`), or
54
+ decide it needs the owner.
55
+ 3. `resolve` the comment with a one-line note of what changed, or `reply`
56
+ with a question if it is unclear. Do not resolve a comment you did not
57
+ address.
58
+
59
+ Everything you post appears under the owner's Google name. Tell the owner
60
+ what you replied to or resolved.
61
+
62
+ ## Output
63
+
64
+ `list`:
65
+
66
+ ```json
67
+ {"success":true,"docId":"1AbC…","count":1,"truncated":false,"comments":[{"id":"AAAB…","author":"Steve","createdTime":"2026-10-03T09:12:00.000Z","content":"Too long","quote":"In this document we…","anchor":"kix.x1","resolved":false,"replies":[{"id":"AAAC…","author":"Ella","createdTime":"…","content":"Trimmed."}]}]}
68
+ ```
69
+
70
+ Resolved comments are left out unless you pass `--include-resolved`.
71
+ `truncated: true` means the document has more than 500 comments and only the
72
+ first 500 were read.
73
+
74
+ `reply` / `resolve`:
75
+
76
+ ```json
77
+ {"success":true,"action":"reply","docId":"1AbC…","commentId":"AAAB…","replyId":"AAAD…","content":"Updated the intro."}
78
+ ```
79
+
80
+ `add`:
81
+
82
+ ```json
83
+ {"success":true,"action":"add","docId":"1AbC…","commentId":"AAAE…","content":"Is this number from Q2?","quote":"grew 40%"}
84
+ ```
85
+
86
+ ## Comments you add are not anchored
87
+
88
+ Google Docs does not show a comment created through the API as attached to
89
+ text. `--quote` is sent to Google as the quoted text, but in the Docs UI the
90
+ comment **may appear as a general (unanchored) comment** on the whole
91
+ document. When the location matters, quote the passage in the comment text
92
+ too, for example `--text "On 'grew 40%': is this Q2?"`.
93
+
94
+ ## Failures
95
+
96
+ `{"success":false,"reason":"reauth_required",…}` (exit 1): the Google grant
97
+ can read comments but not write them on this document. Reply, resolve and add
98
+ on a document Crewly did not create need Google Drive edit access, which
99
+ older connections do not include. Run `google-connect --product drive --channel <chat-channel-id>` to
100
+ post a one-tap card for the owner, and tell them in one line what you will do
101
+ once it is granted. Do not paste a link yourself.
102
+
103
+ `{"success":false,"reason":"not_connected","hint":"…"}` (exit 1): Google
104
+ Drive is not connected. Use `google-connect --product drive` the same way.
105
+
106
+ `{"success":false,"reason":"not_permitted",…}` (exit 1): this Google
107
+ connection belongs to someone else and is not shared with the person you are
108
+ working for. Say so; do not try another account.
109
+
110
+ `reason: "google_error"` with a 404 means the document id or comment id is
111
+ wrong, or the account cannot see the document.
112
+
113
+ ## Choosing a Google account
114
+
115
+ Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
116
+
117
+ ```bash
118
+ bash execute.sh list --doc 1AbC… --account work@company.com
119
+ ```
120
+
121
+ The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
@@ -0,0 +1,131 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # docs-comment — List, reply to, resolve or add comments in a Google Doc
4
+ #
5
+ # Backed by:
6
+ # GET /api/google/docs/:id/comments[?includeResolved=1]
7
+ # POST /api/google/docs/:id/comments { text, quote? }
8
+ # POST /api/google/docs/:id/comments/:commentId/replies { text }
9
+ # POST /api/google/docs/:id/comments/:commentId/resolve { text? }
10
+ #
11
+ # Usage:
12
+ # bash execute.sh list --doc <id|url> [--include-resolved]
13
+ # bash execute.sh reply --doc <id|url> --comment <id> --text "…"
14
+ # bash execute.sh resolve --doc <id|url> --comment <id> [--text "…"]
15
+ # bash execute.sh add --doc <id|url> --text "…" [--quote "exact text"]
16
+ # bash execute.sh '{"command":"list","doc":"<id>"}'
17
+ # =============================================================================
18
+ set -euo pipefail
19
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ source "${SCRIPT_DIR}/../../_common/lib.sh"
21
+
22
+ print_usage() {
23
+ cat <<'EOF_USAGE'
24
+ Usage:
25
+ bash execute.sh list --doc <id|url> [--include-resolved]
26
+ bash execute.sh reply --doc <id|url> --comment <id> --text "…"
27
+ bash execute.sh resolve --doc <id|url> --comment <id> [--text "…"]
28
+ bash execute.sh add --doc <id|url> --text "…" [--quote "exact text in the doc"]
29
+ bash execute.sh '{"command":"reply","doc":"<id>","comment":"<id>","text":"…"}'
30
+
31
+ Options:
32
+ --doc Document id or docs.google.com URL
33
+ --comment Comment id (from list)
34
+ --text Reply / comment text (optional for resolve)
35
+ --quote Exact document text the new comment is about (add only;
36
+ Google Docs may still show it as an unanchored comment)
37
+ --include-resolved list: include resolved comments too
38
+ --account Which connected Google account to act as (default: your primary)
39
+ --help | -h Show this help
40
+ EOF_USAGE
41
+ }
42
+
43
+ fail_from() {
44
+ printf '%s' "$1" | jq -c '{success: false, reason: (.details.error // .details // .error // "unknown"), hint: (.details.hint // ""), message: (.details.message // "")}' 2>/dev/null \
45
+ || jq -n --arg r "$1" '{success: false, reason: $r}'
46
+ exit 1
47
+ }
48
+
49
+ uri() { jq -rn --arg v "$1" '$v|@uri'; }
50
+
51
+ # api_call may print a one-line warning to stderr (no CREWLY_SESSION_NAME);
52
+ # the backend answer is always the last line. On failure print the mapped
53
+ # failure JSON (fail_from) and return 1.
54
+ call() {
55
+ local out
56
+ out=$(api_call "$@" 2>&1) || { fail_from "$(printf '%s\n' "$out" | tail -n 1)"; }
57
+ printf '%s\n' "$out" | tail -n 1
58
+ }
59
+
60
+ INPUT_JSON=""
61
+ COMMAND=""
62
+ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
63
+ INPUT_JSON="$1"
64
+ shift || true
65
+ elif [[ $# -gt 0 && ${1:0:1} != '-' ]]; then
66
+ COMMAND="$1"
67
+ shift
68
+ fi
69
+ DOC=""; COMMENT=""; TEXT=""; TEXT_SET=0; QUOTE=""; INCLUDE_RESOLVED=0; ACCOUNT=""
70
+ while [[ $# -gt 0 ]]; do
71
+ case "$1" in
72
+ --doc|--id) [ $# -ge 2 ] || error_exit "$1 requires a value"; DOC="$2"; shift 2 ;;
73
+ --comment) [ $# -ge 2 ] || error_exit "--comment requires a value"; COMMENT="$2"; shift 2 ;;
74
+ --text) [ $# -ge 2 ] || error_exit "--text requires a value"; TEXT="$2"; TEXT_SET=1; shift 2 ;;
75
+ --quote) [ $# -ge 2 ] || error_exit "--quote requires a value"; QUOTE="$2"; shift 2 ;;
76
+ --include-resolved) INCLUDE_RESOLVED=1; shift ;;
77
+ --account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
78
+ --help|-h) print_usage; exit 0 ;;
79
+ *) error_exit "Unknown option: $1" ;;
80
+ esac
81
+ done
82
+ if [ -n "$INPUT_JSON" ]; then
83
+ INPUT=$(read_json_input "$INPUT_JSON")
84
+ [ -z "$COMMAND" ] && COMMAND=$(printf '%s' "$INPUT" | jq -r '.command // .action // empty')
85
+ [ -z "$DOC" ] && DOC=$(printf '%s' "$INPUT" | jq -r '.doc // .id // .documentId // empty')
86
+ [ -z "$COMMENT" ] && COMMENT=$(printf '%s' "$INPUT" | jq -r '.comment // .commentId // empty')
87
+ [ "$TEXT_SET" -eq 0 ] && TEXT=$(printf '%s' "$INPUT" | jq -r '.text // empty')
88
+ [ -z "$QUOTE" ] && QUOTE=$(printf '%s' "$INPUT" | jq -r '.quote // empty')
89
+ [ "$INCLUDE_RESOLVED" -eq 0 ] && [ "$(printf '%s' "$INPUT" | jq -r '.includeResolved // false')" = "true" ] && INCLUDE_RESOLVED=1
90
+ [ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
91
+ fi
92
+ # Route the call at one connected Google account; unset means the default.
93
+ [ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
94
+ # Accept a full docs.google.com URL too.
95
+ DOC=$(printf '%s' "$DOC" | sed -E 's#.*/document/d/([^/?]+).*#\1#')
96
+
97
+ [ -n "$COMMAND" ] || error_exit "a command is required: list, reply, resolve or add (see --help)"
98
+ require_param "doc (--doc)" "$DOC"
99
+ BASE="/google/docs/$(uri "$DOC")/comments"
100
+
101
+ case "$COMMAND" in
102
+ list)
103
+ QS=""; [ "$INCLUDE_RESOLVED" -eq 1 ] && QS="?includeResolved=1"
104
+ RESPONSE=$(call GET "${BASE}${QS}") || { printf '%s\n' "$RESPONSE"; exit 1; }
105
+ # Oversized answers come back as the output-cap envelope; pass it through.
106
+ if printf '%s' "$RESPONSE" | jq -e '.truncated == true and has("file")' >/dev/null 2>&1; then printf '%s\n' "$RESPONSE"; exit 0; fi
107
+ printf '%s' "$RESPONSE" | jq -c '{success: (.success // false), docId: .data.docId, count: (.data.comments | length), truncated: (.data.truncated // false), comments: .data.comments}'
108
+ ;;
109
+ reply)
110
+ require_param "comment (--comment)" "$COMMENT"
111
+ require_param "text (--text)" "$TEXT"
112
+ BODY=$(jq -cn --arg text "$TEXT" '{text: $text}')
113
+ RESPONSE=$(call POST "${BASE}/$(uri "$COMMENT")/replies" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
114
+ printf '%s' "$RESPONSE" | jq -c '{success: (.success // false), action: "reply", docId: .data.docId, commentId: .data.commentId, replyId: .data.id, content: .data.content}'
115
+ ;;
116
+ resolve)
117
+ require_param "comment (--comment)" "$COMMENT"
118
+ BODY=$(jq -cn --arg text "$TEXT" 'if $text != "" then {text: $text} else {} end')
119
+ RESPONSE=$(call POST "${BASE}/$(uri "$COMMENT")/resolve" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
120
+ printf '%s' "$RESPONSE" | jq -c '{success: (.success // false), action: "resolve", docId: .data.docId, commentId: .data.commentId, replyId: .data.id, content: (.data.content // ""), resolved: true}'
121
+ ;;
122
+ add)
123
+ require_param "text (--text)" "$TEXT"
124
+ BODY=$(jq -cn --arg text "$TEXT" --arg quote "$QUOTE" '{text: $text} + (if $quote != "" then {quote: $quote} else {} end)')
125
+ RESPONSE=$(call POST "${BASE}" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
126
+ printf '%s' "$RESPONSE" | jq -c '{success: (.success // false), action: "add", docId: .data.docId, commentId: .data.id, content: .data.content} + (if .data.quote then {quote: .data.quote} else {} end)'
127
+ ;;
128
+ *)
129
+ error_exit "Unknown command: $COMMAND (expected list, reply, resolve or add)"
130
+ ;;
131
+ esac
@@ -0,0 +1,105 @@
1
+ #!/bin/bash
2
+ # Tests for docs-comment — run with: bash execute.test.sh
3
+ # Python HTTP stub as the backend; asserts the request the skill sends and
4
+ # the JSON it prints. No Google call is made. Exit 0 on pass.
5
+ set -u
6
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ EXEC="$HERE/execute.sh"
8
+ PASS=0; FAIL=0
9
+ check() {
10
+ local name="$1"; local got="$2"; local want="$3"
11
+ if [ "$got" = "$want" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $name"; echo " want: $want"; echo " got: $got"; fi
12
+ }
13
+
14
+ PORT=18831
15
+ STUB_LOG="$(mktemp)"; export STUB_LOG; export STUB_PORT="$PORT"
16
+ python3 -u <<'PY' >/dev/null 2>&1 &
17
+ from http.server import BaseHTTPRequestHandler, HTTPServer
18
+ import json, os
19
+ LOG = os.environ['STUB_LOG']; PORT = int(os.environ['STUB_PORT'])
20
+ COMMENT = {'id': 'c1', 'author': 'Steve', 'createdTime': 't0', 'content': 'Too long', 'quote': 'intro',
21
+ 'resolved': False, 'replies': [{'id': 'r0', 'author': 'Ella', 'createdTime': 't1', 'content': 'Trimmed'}]}
22
+ class H(BaseHTTPRequestHandler):
23
+ def _send(self, status, payload):
24
+ self.send_response(status); self.send_header('Content-Type', 'application/json'); self.end_headers()
25
+ self.wfile.write(json.dumps(payload).encode())
26
+ def _reply(self, method, data=None):
27
+ open(LOG, 'w').write(json.dumps({'method': method, 'path': self.path, 'body': data,
28
+ 'account': self.headers.get('X-Google-Account')}))
29
+ if '/docs/narrow/' in self.path:
30
+ return self._send(403, {'success': False, 'error': 'reauth_required',
31
+ 'message': 'Replying to or adding comments on this document needs Google Drive edit access, which this Google account has not granted yet.',
32
+ 'hint': 'Ask the owner to reconnect Google Drive: run the google-connect skill with --product drive.'})
33
+ if method == 'GET':
34
+ return self._send(200, {'success': True, 'data': {'docId': 'd1', 'comments': [COMMENT], 'truncated': False}})
35
+ if self.path.endswith('/replies'):
36
+ return self._send(200, {'success': True, 'data': {'docId': 'd1', 'commentId': 'c1', 'id': 'r9', 'content': data['text']}})
37
+ if self.path.endswith('/resolve'):
38
+ return self._send(200, {'success': True, 'data': {'docId': 'd1', 'commentId': 'c1', 'id': 'r10', 'action': 'resolve', 'content': data.get('text', ''), 'resolved': True}})
39
+ out = {'docId': 'd1', 'id': 'c7', 'content': data['text'], 'resolved': False, 'replies': []}
40
+ if 'quote' in data: out['quote'] = data['quote']
41
+ return self._send(200, {'success': True, 'data': out})
42
+ def do_GET(self): self._reply('GET')
43
+ def do_POST(self):
44
+ n = int(self.headers.get('content-length', '0')); body = self.rfile.read(n).decode() if n else ''
45
+ self._reply('POST', json.loads(body or '{}'))
46
+ def log_message(self, *a, **k): pass
47
+ HTTPServer(('127.0.0.1', PORT), H).serve_forever()
48
+ PY
49
+ STUB_PID=$!
50
+ disown "$STUB_PID" 2>/dev/null || true
51
+ trap 'kill "$STUB_PID" >/dev/null 2>&1; rm -f "$STUB_LOG"' EXIT
52
+ for i in $(seq 1 30); do curl -s -o /dev/null -m 0.2 "http://127.0.0.1:${PORT}/" 2>/dev/null && break; sleep 0.1; done
53
+
54
+ run() { CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>/dev/null; }
55
+ run_err() { CREWLY_API_URL="http://127.0.0.1:${PORT}" bash "$EXEC" "$@" 2>&1 >/dev/null; }
56
+ req() { jq -r '.method + " " + .path + " " + (.body|tojson)' "$STUB_LOG"; }
57
+
58
+ # list
59
+ OUT=$(run list --doc "https://docs.google.com/document/d/d1/edit?tab=t.0")
60
+ check "list: request" "$(req)" 'GET /api/google/docs/d1/comments null'
61
+ check "list: output" "$(printf '%s' "$OUT" | jq -c '{success, docId, count, truncated, first: .comments[0].id, quote: .comments[0].quote, replies: (.comments[0].replies|length)}')" \
62
+ '{"success":true,"docId":"d1","count":1,"truncated":false,"first":"c1","quote":"intro","replies":1}'
63
+ run list --doc d1 --include-resolved >/dev/null
64
+ check "list: --include-resolved" "$(req)" 'GET /api/google/docs/d1/comments?includeResolved=1 null'
65
+
66
+ # reply
67
+ OUT=$(run reply --doc d1 --comment c1 --text "Fixed, see para 2")
68
+ check "reply: request" "$(req)" 'POST /api/google/docs/d1/comments/c1/replies {"text":"Fixed, see para 2"}'
69
+ check "reply: output" "$OUT" '{"success":true,"action":"reply","docId":"d1","commentId":"c1","replyId":"r9","content":"Fixed, see para 2"}'
70
+
71
+ # resolve, with and without a message
72
+ OUT=$(run resolve --doc d1 --comment c1 --text "Done")
73
+ check "resolve: request" "$(req)" 'POST /api/google/docs/d1/comments/c1/resolve {"text":"Done"}'
74
+ check "resolve: output" "$OUT" '{"success":true,"action":"resolve","docId":"d1","commentId":"c1","replyId":"r10","content":"Done","resolved":true}'
75
+ run resolve --doc d1 --comment c1 >/dev/null
76
+ check "resolve: no text" "$(req)" 'POST /api/google/docs/d1/comments/c1/resolve {}'
77
+
78
+ # add, with and without a quote
79
+ OUT=$(run add --doc d1 --text "Source?" --quote "grew 40%")
80
+ check "add: request" "$(req)" 'POST /api/google/docs/d1/comments {"text":"Source?","quote":"grew 40%"}'
81
+ check "add: output" "$OUT" '{"success":true,"action":"add","docId":"d1","commentId":"c7","content":"Source?","quote":"grew 40%"}'
82
+ run add --doc d1 --text "General" >/dev/null
83
+ check "add: no quote" "$(req)" 'POST /api/google/docs/d1/comments {"text":"General"}'
84
+
85
+ # JSON input + --account
86
+ run '{"command":"reply","doc":"d1","comment":"c1","text":"via json","account":"work@x.com"}' >/dev/null
87
+ check "json input: request" "$(req)" 'POST /api/google/docs/d1/comments/c1/replies {"text":"via json"}'
88
+ check "json input: account header" "$(jq -r '.account' "$STUB_LOG")" 'work@x.com'
89
+
90
+ # reauth_required is passed through with its English hint, exit 1
91
+ OUT=$(run reply --doc narrow --comment c1 --text "x"); CODE=$?
92
+ check "reauth: exit code" "$CODE" "1"
93
+ check "reauth: reason" "$(printf '%s' "$OUT" | jq -r '.reason')" "reauth_required"
94
+ check "reauth: hint names the one-tap path" "$(printf '%s' "$OUT" | jq -r '.hint' | grep -c 'google-connect skill with --product drive')" "1"
95
+
96
+ # argument validation (no request sent)
97
+ check "no command" "$(run_err --doc d1 | grep -c 'command is required')" "1"
98
+ check "unknown command" "$(run_err delete --doc d1 | grep -c 'Unknown command')" "1"
99
+ check "reply needs --comment" "$(run_err reply --doc d1 --text x | grep -c 'comment')" "1"
100
+ check "reply needs --text" "$(run_err reply --doc d1 --comment c1 | grep -c 'text')" "1"
101
+ check "add needs --text" "$(run_err add --doc d1 | grep -c 'text')" "1"
102
+ check "needs --doc" "$(run_err list | grep -c 'doc')" "1"
103
+
104
+ echo "docs-comment: $PASS passed, $FAIL failed"
105
+ [ "$FAIL" -eq 0 ]
@@ -193,6 +193,18 @@ orchestrator.
193
193
  `--retro on|off|default` switches the team lead's daily retro (default: on
194
194
  while an autopilot experiment on the project is running).
195
195
 
196
+ When the project has a goal (its goals log or an active project OKR) but
197
+ nothing is left to triage and someone on the team is idle, the team lead gets
198
+ ONE `goal_replan` WorkItem: the goal, the tickets closed in the last 7 days,
199
+ any open experiment card, and the ask "open the next tickets toward this goal,
200
+ or say why there are none". The lead opens the tickets; the autopilot never
201
+ makes them ready or starts work. At most `--replans-per-day <0-5>` a day
202
+ (default 1; 0 = off), within the same daily budget and in-progress cap. A
203
+ replan still open after `--replan-ttl-hours` (default 4) is expired. After a
204
+ replan that opened no tickets the autopilot backs off (skips 2 days, then 4,
205
+ then 7) until a new goal / OKR is set or a ticket is created. Only goals-log
206
+ entries from the last 30 days count as an active goal.
207
+
196
208
  ## Autopilot numbers and retro (owner / orchestrator / team lead)
197
209
 
198
210
  ```bash
@@ -201,7 +213,7 @@ bash execute.sh runs --project P [--days 7] [--label feed] # run trace + tic
201
213
  ```
202
214
 
203
215
  `stats` counts tickets triaged / started / done (worker finished) / verified
204
- (ticket done) / sent back / stalled, cycle times, owner touches by kind,
216
+ (ticket done) / sent back / stalled, goal replans, cycle times, owner touches by kind,
205
217
  stalls by cause, harness interventions, traced tokens and $ against the daily
206
218
  budget, and time paused on the budget. Read a day's run with `trace-read
207
219
  --trace <runTraceId>`.
@@ -23,7 +23,8 @@
23
23
  # bash execute.sh ask-owner --project P --id APP-12 --clear [--note "answer"]
24
24
  # bash execute.sh autopilot --project P [--on|--off] [--driver <session>|--driver default]
25
25
  # [--daily-budget <tokens, e.g. 20M>] [--max-in-flight <n>] (owner / orchestrator)
26
- # [--retro on|off|default]
26
+ # [--retro on|off|default] [--replans-per-day <0-5>|default]
27
+ # [--replan-ttl-hours <1-48>|default]
27
28
  # bash execute.sh stats --project P [--days 14] [--label feed] (owner / orc / lead)
28
29
  # bash execute.sh runs --project P [--days 7] [--label feed] (owner / orc / lead)
29
30
  # bash execute.sh retro --project P --day YYYY-MM-DD --summary "…" [--problem "class|title|detail|evidence" …]
@@ -63,10 +64,15 @@ Usage:
63
64
  The owner answered: remove the needs-owner mark
64
65
  bash execute.sh autopilot --project P [--on|--off] [--driver <session>|default]
65
66
  [--daily-budget <tokens, e.g. 20M>] [--max-in-flight <n>] [--retro on|off|default]
67
+ [--replans-per-day <0-5>|default] [--replan-ttl-hours <1-48>|default]
66
68
  Owner / orchestrator: show or change the ticket
67
69
  autopilot (no flags = show). --retro: the lead's
68
70
  daily retro (default: on while an autopilot
69
- experiment runs)
71
+ experiment runs). --replans-per-day: how often a
72
+ day the lead is woken to plan toward the goal when
73
+ nothing is left to triage (default 1, 0 = off);
74
+ --replan-ttl-hours: a replan still open after this
75
+ long is expired (default 4)
70
76
  bash execute.sh stats --project P [--days 14] [--label feed]
71
77
  Autopilot numbers per day: tickets triaged /
72
78
  started / done / verified / sent back / stalled,
@@ -90,7 +96,7 @@ STATUS=""; SOURCE=""; REQUEST_ID=""; NOTE=""; OWNER_REVIEW=""; ASSIGNEE=""; STAR
90
96
  ACCEPTANCE_JSON="null"
91
97
  HAS_DESCRIPTION=0
92
98
  QUESTION=""; CLEAR=""; OPTIONS_JSON="[]"; DEFAULT_OPT=""; DEADLINE=""; SENSITIVE=""; AP_ENABLED=""; AP_DRIVER=""; AP_BUDGET=""; AP_MAX=""
93
- AP_RETRO=""; DAYS=""; LABEL=""; DAY=""; SUMMARY=""; PROBLEMS_JSON="[]"
99
+ AP_RETRO=""; AP_REPLANS=""; AP_REPLAN_TTL=""; DAYS=""; LABEL=""; DAY=""; SUMMARY=""; PROBLEMS_JSON="[]"
94
100
 
95
101
  if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
96
102
  J="$1"; shift
@@ -124,6 +130,8 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
124
130
  AP_BUDGET=$(printf '%s' "$J" | jq -r '.dailyBudgetTokens // empty')
125
131
  AP_MAX=$(printf '%s' "$J" | jq -r '.maxInFlightPerMember // empty')
126
132
  AP_RETRO=$(printf '%s' "$J" | jq -r 'if (.retro|type) == "boolean" then (if .retro then "on" else "off" end) else (.retro // empty) end')
133
+ AP_REPLANS=$(printf '%s' "$J" | jq -r '.replansPerDay // empty')
134
+ AP_REPLAN_TTL=$(printf '%s' "$J" | jq -r '.replanTtlHours // empty')
127
135
  DAYS=$(printf '%s' "$J" | jq -r '.days // empty')
128
136
  LABEL=$(printf '%s' "$J" | jq -r '.label // empty')
129
137
  DAY=$(printf '%s' "$J" | jq -r '.day // empty')
@@ -166,6 +174,10 @@ while [[ $# -gt 0 ]]; do
166
174
  [ $# -ge 2 ] || error_exit "--daily-budget requires a value"; AP_BUDGET="$2"; shift 2 ;;
167
175
  --max-in-flight) [ $# -ge 2 ] || error_exit "--max-in-flight requires a value"; AP_MAX="$2"; shift 2 ;;
168
176
  --retro) [ $# -ge 2 ] || error_exit "--retro requires on, off or default"; AP_RETRO="$2"; shift 2 ;;
177
+ --replans-per-day)
178
+ [ $# -ge 2 ] || error_exit "--replans-per-day requires 0-5 or default"; AP_REPLANS="$2"; shift 2 ;;
179
+ --replan-ttl-hours)
180
+ [ $# -ge 2 ] || error_exit "--replan-ttl-hours requires 1-48 or default"; AP_REPLAN_TTL="$2"; shift 2 ;;
169
181
  --days) [ $# -ge 2 ] || error_exit "--days requires a value"; DAYS="$2"; shift 2 ;;
170
182
  --label) [ $# -ge 2 ] || error_exit "--label requires a value"; LABEL="$2"; shift 2 ;;
171
183
  --day) [ $# -ge 2 ] || error_exit "--day requires a value"; DAY="$2"; shift 2 ;;
@@ -282,13 +294,15 @@ case "$ACTION" in
282
294
  ;;
283
295
  autopilot)
284
296
  require_param "project" "$PROJECT"
285
- if [ -z "$AP_ENABLED$AP_DRIVER$AP_BUDGET$AP_MAX$AP_RETRO" ]; then
297
+ if [ -z "$AP_ENABLED$AP_DRIVER$AP_BUDGET$AP_MAX$AP_RETRO$AP_REPLANS$AP_REPLAN_TTL" ]; then
286
298
  api_call GET "/project-ticket-autopilot/$(enc "$PROJECT")" | jq '{success, autopilot: .data}'
287
299
  else
288
- BODY=$(jq -n --arg enabled "$AP_ENABLED" --arg driver "$AP_DRIVER" --arg budget "$AP_BUDGET" --arg max "$AP_MAX" --arg retro "$AP_RETRO" \
300
+ BODY=$(jq -n --arg enabled "$AP_ENABLED" --arg driver "$AP_DRIVER" --arg budget "$AP_BUDGET" --arg max "$AP_MAX" --arg retro "$AP_RETRO" --arg replans "$AP_REPLANS" --arg ttl "$AP_REPLAN_TTL" \
289
301
  '{}
290
302
  + (if $enabled != "" then {enabled: ($enabled == "true")} else {} end)
291
303
  + (if $retro == "default" then {retro: null} elif $retro != "" then {retro: $retro} else {} end)
304
+ + (if $replans == "default" then {replansPerDay: null} elif $replans != "" then {replansPerDay: ($replans | tonumber? // $replans)} else {} end)
305
+ + (if $ttl == "default" then {replanTtlHours: null} elif $ttl != "" then {replanTtlHours: ($ttl | tonumber? // $ttl)} else {} end)
292
306
  + (if $driver == "default" then {driver: null} elif $driver != "" then {driver: $driver} else {} end)
293
307
  + (if $budget == "default" then {dailyBudgetTokens: null} elif $budget != "" then {dailyBudgetTokens: ($budget | tonumber? // $budget)} else {} end)
294
308
  + (if $max == "default" then {maxInFlightPerMember: null} elif $max != "" then {maxInFlightPerMember: ($max | tonumber? // $max)} else {} end)')
@@ -303,7 +317,7 @@ case "$ACTION" in
303
317
  if [ "$ACTION" = "stats" ]; then
304
318
  api_call GET "/project-ticket-autopilot/$(enc "$PROJECT")/stats${QS:+?$QS}" \
305
319
  | jq '{success, stats: (.data | if . == null then null else {project, label, range, pausedForToday, total, labels,
306
- days: [.days[] | {day, triaged, started, done, verified, sentBack, stalled, ownerTouches: .ownerTouches.total, stallMs: .stalls.totalMs, costUsd, pausedMs, runTraceId}]} end)}'
320
+ days: [.days[] | {day, triaged, replans, started, done, verified, sentBack, stalled, ownerTouches: .ownerTouches.total, stallMs: .stalls.totalMs, costUsd, pausedMs, runTraceId}]} end)}'
307
321
  else
308
322
  api_call GET "/project-ticket-autopilot/$(enc "$PROJECT")/runs${QS:+?$QS}" | jq '{success, runs: .data}'
309
323
  fi
@@ -32,7 +32,7 @@ class H(BaseHTTPRequestHandler):
32
32
  return self.reply(200, {'success': True, 'data': T})
33
33
  return self.reply(200, {'success': True, 'data': {'project': {'id': 'p1'}, 'tickets': [T], 'invalid': []}})
34
34
  if self.path.startswith('/api/project-ticket-autopilot/') and '/stats' in self.path:
35
- day = {'day': '2026-10-02', 'triaged': 3, 'started': 2, 'done': 2, 'verified': 1, 'sentBack': 0, 'stalled': 1,
35
+ day = {'day': '2026-10-02', 'triaged': 3, 'replans': 1, 'started': 2, 'done': 2, 'verified': 1, 'sentBack': 0, 'stalled': 1,
36
36
  'ownerTouches': {'total': 2}, 'stalls': {'totalMs': 60000}, 'costUsd': 1.5, 'pausedMs': 0, 'runTraceId': 'tr-x', 'extra': 1}
37
37
  return self.reply(200, {'success': True, 'data': {'project': {'id': 'p1'}, 'label': 'feed', 'range': {}, 'pausedForToday': False,
38
38
  'total': {'verified': 1}, 'labels': ['feed'], 'days': [day]}})
@@ -158,11 +158,17 @@ run autopilot --project p1 --retro on >/dev/null
158
158
  check "autopilot retro on: body" "$(last '.body | tostring')" '{"retro":"on"}'
159
159
  run autopilot --project p1 --retro default >/dev/null
160
160
  check "autopilot retro default: body" "$(last '.body | tostring')" '{"retro":null}'
161
+ run autopilot --project p1 --replans-per-day 2 >/dev/null
162
+ check "autopilot replans: body" "$(last '.body | tostring')" '{"replansPerDay":2}'
163
+ run autopilot --project p1 --replans-per-day default >/dev/null
164
+ check "autopilot replans default: body" "$(last '.body | tostring')" '{"replansPerDay":null}'
165
+ run autopilot --project p1 --replan-ttl-hours 6 >/dev/null
166
+ check "autopilot replan ttl: body" "$(last '.body | tostring')" '{"replanTtlHours":6}'
161
167
 
162
168
  # --- stats / runs / retro (specs/2026-10-03-autopilot-experiments.md) ---
163
169
  OUT=$(run stats --project p1 --days 7 --label feed)
164
170
  check "stats: path" "$(last .path)" "/api/project-ticket-autopilot/p1/stats?days=7&label=feed"
165
- check "stats: day row" "$(printf '%s' "$OUT" | jq -c '.stats.days[0] | [.day, .verified, .ownerTouches, .stallMs, .runTraceId, has("extra")]')" '["2026-10-02",1,2,60000,"tr-x",false]'
171
+ check "stats: day row" "$(printf '%s' "$OUT" | jq -c '.stats.days[0] | [.day, .verified, .replans, .ownerTouches, .stallMs, .runTraceId, has("extra")]')" '["2026-10-02",1,1,2,60000,"tr-x",false]'
166
172
  OUT=$(run runs --project p1)
167
173
  check "runs: path" "$(last .path)" "/api/project-ticket-autopilot/p1/runs"
168
174
  check "runs: output" "$(printf '%s' "$OUT" | jq -c '.runs.days[0].runTraceId')" '"tr-x"'
@@ -145,6 +145,39 @@ export declare const TICKET_AUTOPILOT_CONSTANTS: {
145
145
  /** Option labels of that card */
146
146
  readonly RETRO_APPROVE_LABEL: "Approve";
147
147
  readonly RETRO_SKIP_LABEL: "Skip";
148
+ /** WorkItem type / `metadata.kind` of the goal replan the driver receives when nothing is left to triage */
149
+ readonly REPLAN_WORK_ITEM_TYPE: "goal_replan";
150
+ readonly REPLAN_METADATA_KIND: "goal_replan";
151
+ /** Default and largest number of goal replans per project per local day (0 = off) */
152
+ readonly DEFAULT_REPLANS_PER_DAY: 1;
153
+ readonly REPLANS_PER_DAY_LIMIT: 5;
154
+ /** Tickets closed (done / cancelled) this many days back are listed in the replan brief */
155
+ readonly REPLAN_CLOSED_LOOKBACK_DAYS: 7;
156
+ /** Most closed tickets listed in the replan brief */
157
+ readonly REPLAN_MAX_CLOSED_TICKETS: 20;
158
+ /** Most characters of goal text quoted in the replan brief */
159
+ readonly REPLAN_GOAL_MAX_CHARS: 2000;
160
+ /** Most goals.md entries quoted (newest first) */
161
+ readonly REPLAN_GOAL_MAX_ENTRIES: 3;
162
+ /** Most open experiment cards listed in the replan brief */
163
+ readonly REPLAN_MAX_EXPERIMENTS: 5;
164
+ /**
165
+ * A replan still live (any live status) after this many hours is expired:
166
+ * cancelled where the pool allows it, and never counted as live again, so
167
+ * it cannot hold triage forever. Default and bounds of `replanTtlHours`.
168
+ */
169
+ readonly DEFAULT_REPLAN_TTL_HOURS: 4;
170
+ readonly REPLAN_TTL_HOURS_LIMIT: 48;
171
+ /** A live replan older than this stops holding triage once there are tickets to triage (ms) */
172
+ readonly REPLAN_YIELD_AFTER_MS: number;
173
+ /**
174
+ * After a replan that opened no tickets: skip the next 2 days, then 4,
175
+ * then 7 (doubling, capped). Reset by a new goal / OKR or a new ticket.
176
+ */
177
+ readonly REPLAN_BACKOFF_FIRST_DAYS: 2;
178
+ readonly REPLAN_BACKOFF_MAX_DAYS: 7;
179
+ /** Goals-log entries older than this are not an active goal (days); OKRs keep their own status */
180
+ readonly REPLAN_GOAL_ACTIVE_DAYS: 30;
148
181
  };
149
182
  /**
150
183
  * Safe restart: drain in-flight agent turns before a shutdown kills the PTYs,
@@ -4148,6 +4181,16 @@ export declare const GOOGLE_WORKSPACE_CONSTANTS: {
4148
4181
  readonly DRIVE_MAX_CONTENT_BYTES: number;
4149
4182
  /** Google-native MIME types and what they export to as text */
4150
4183
  readonly DRIVE_EXPORT_MIME: Record<string, string>;
4184
+ /**
4185
+ * Full Drive scope. Needed to reply to, resolve or add comments on a
4186
+ * document Crewly did not create (`comments.create` / `replies.create`
4187
+ * accept only `drive` or `drive.file`). Optional on the Cloud grant.
4188
+ */
4189
+ readonly DRIVE_FULL_SCOPE: "https://www.googleapis.com/auth/drive";
4190
+ /** Comments fetched per Drive `comments.list` page (Google's max is 100) */
4191
+ readonly COMMENTS_PAGE_SIZE: 100;
4192
+ /** Pages read per `docs-comment list` before stopping (caps a huge thread) */
4193
+ readonly COMMENTS_MAX_PAGES: 5;
4151
4194
  /** Sheets range used when the caller names none */
4152
4195
  readonly SHEETS_DEFAULT_RANGE: "A1:Z1000";
4153
4196
  /** Cap on rows accepted per Sheets write */
@@ -4174,6 +4217,8 @@ export declare const GOOGLE_WORKSPACE_CONSTANTS: {
4174
4217
  readonly NETWORK: "network";
4175
4218
  /** Caller sent an invalid request */
4176
4219
  readonly VALIDATION: "validation";
4220
+ /** The grant lacks a scope this call needs; the owner must reconnect the product */
4221
+ readonly REAUTH_REQUIRED: "reauth_required";
4177
4222
  };
4178
4223
  };
4179
4224
  /**
@@ -6325,4 +6370,35 @@ export declare const EXPERIMENT_CONSTANTS: {
6325
6370
  readonly CHECK_IN_RETRY_MIN_MS: number;
6326
6371
  readonly CHECK_IN_RETRY_MAX_MS: number;
6327
6372
  };
6373
+ /**
6374
+ * Release input-guard check (specs/2026-10-04-release-input-guard-check.md):
6375
+ * classify every live agent's input box with the NEW build before restarting.
6376
+ */
6377
+ export declare const INPUT_GUARD_CHECK_CONSTANTS: {
6378
+ /** Script of the new build that classifies views, relative to a build's `dist/` */
6379
+ readonly SCRIPT_RELATIVE: "backend/backend/src/scripts/input-guard-classify.js";
6380
+ /** Longest the child process may run (ms) */
6381
+ readonly CHILD_TIMEOUT_MS: number;
6382
+ /** Largest child output accepted (bytes) */
6383
+ readonly CHILD_MAX_OUTPUT_BYTES: number;
6384
+ /** Env switch: skip the gate in the auto-update path */
6385
+ readonly SKIP_ENV: "CREWLY_SKIP_INPUT_GUARD_CHECK";
6386
+ /** Once-per-version "blocked" ledger under CREWLY_HOME */
6387
+ readonly LEDGER_FILE: "input-guard-blocked.json";
6388
+ /** Route (under /api) */
6389
+ readonly ROUTE: "/system/input-guard-check";
6390
+ /** Marker of a build installed on disk but not restarted onto, under CREWLY_HOME */
6391
+ readonly BLOCKED_BUILD_FILE: "blocked-build.json";
6392
+ /** Only the bottom N non-empty screen rows are searched for markers (a transcript can quote anything) */
6393
+ readonly TAIL_LINES: 12;
6394
+ /**
6395
+ * Lower-case footer / composer hints that mean "this runtime is at its chat
6396
+ * prompt": Claude Code's status footer (`⏵⏵ … (shift+tab to cycle)`), the
6397
+ * `? for shortcuts` hint (Codex, Antigravity, Gemini) and Gemini's composer
6398
+ * placeholder. Only with one of these on screen is a missing box a failure.
6399
+ */
6400
+ readonly READY_MARKERS: readonly string[];
6401
+ /** Lower-case text of dialogs, login and picker screens (no input box expected) */
6402
+ readonly DIALOG_MARKERS: readonly string[];
6403
+ };
6328
6404
  //# sourceMappingURL=constants.d.ts.map