crewly 1.20.296 → 1.20.298
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/roles/developer/prompt.md +1 -1
- package/config/skills/agent/core/app-data/SKILL.md +13 -1
- package/config/skills/agent/core/app-data/execute.sh +60 -6
- package/config/skills/agent/core/app-data/execute.test.sh +42 -0
- package/config/skills/agent/core/docs-comment/SKILL.md +49 -4
- package/config/skills/agent/core/docs-comment/execute.sh +38 -2
- package/config/skills/agent/core/docs-comment/execute.test.sh +28 -0
- package/dist/backend/backend/src/constants.d.ts +48 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +48 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +2 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.controller.js +2 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +1 -0
- package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.js +3 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +20 -0
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +52 -9
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +31 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.js +198 -5
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +4 -0
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js +9 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +11 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +4 -2
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake-message.d.ts +33 -0
- package/dist/backend/backend/src/services/apps/app-wake-message.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/app-wake-message.js +48 -0
- package/dist/backend/backend/src/services/apps/app-wake-message.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.d.ts +23 -0
- package/dist/backend/backend/src/services/apps/apps.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.js +57 -6
- package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/docs-comment-gate.d.ts +166 -0
- package/dist/backend/backend/src/services/google/docs-comment-gate.d.ts.map +1 -0
- package/dist/backend/backend/src/services/google/docs-comment-gate.js +176 -0
- package/dist/backend/backend/src/services/google/docs-comment-gate.js.map +1 -0
- package/dist/backend/backend/src/services/google/docs-comments.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/docs-comments.service.js +2 -2
- package/dist/backend/backend/src/services/google/docs-comments.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +15 -0
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.js +16 -2
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-reauth.wiring.js +1 -1
- package/dist/backend/backend/src/services/google/google-reauth.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +12 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +18 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/worktree/runaway-file-watcher.d.ts +131 -0
- package/dist/backend/backend/src/services/worktree/runaway-file-watcher.d.ts.map +1 -0
- package/dist/backend/backend/src/services/worktree/runaway-file-watcher.js +251 -0
- package/dist/backend/backend/src/services/worktree/runaway-file-watcher.js.map +1 -0
- package/dist/backend/backend/src/services/worktree/worktree-dirty.d.ts +61 -0
- package/dist/backend/backend/src/services/worktree/worktree-dirty.d.ts.map +1 -0
- package/dist/backend/backend/src/services/worktree/worktree-dirty.js +121 -0
- package/dist/backend/backend/src/services/worktree/worktree-dirty.js.map +1 -0
- package/dist/backend/backend/src/services/worktree/worktree-janitor.git.d.ts +2 -0
- package/dist/backend/backend/src/services/worktree/worktree-janitor.git.d.ts.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-janitor.git.js +6 -1
- package/dist/backend/backend/src/services/worktree/worktree-janitor.git.js.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts +79 -5
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js +198 -15
- package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/worktree/worktree-landed.d.ts +76 -0
- package/dist/backend/backend/src/services/worktree/worktree-landed.d.ts.map +1 -0
- package/dist/backend/backend/src/services/worktree/worktree-landed.js +132 -0
- package/dist/backend/backend/src/services/worktree/worktree-landed.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +48 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +48 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +4 -2
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/package.json +1 -1
|
@@ -63,7 +63,7 @@ git worktree add /tmp/crewly-worktrees/{{SESSION_NAME}}-<task-slug> -b <feat-bra
|
|
|
63
63
|
cd /tmp/crewly-worktrees/{{SESSION_NAME}}-<task-slug>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Why: Crewly is a multi-agent system, and the main repo's working tree may be in any state (rebase pending, conflict markers, another agent's WIP). A private worktree is your *own* clean copy off the latest origin/main; you can build, test, and commit there without colliding. When you're done, push your branch and open a PR — the worktree
|
|
66
|
+
Why: Crewly is a multi-agent system, and the main repo's working tree may be in any state (rebase pending, conflict markers, another agent's WIP). A private worktree is your *own* clean copy off the latest origin/main; you can build, test, and commit there without colliding. When you're done, push your branch and open a PR — once the change has landed on `origin/main` (any git host, squash merges included) the Crewly janitor removes the worktree for you. If you already know it is merged and nobody needs it, run `git worktree remove <path>` yourself and keep the branch.
|
|
67
67
|
|
|
68
68
|
This pattern *is* the dogfood — it's a KR4 template candidate, treat it as default for any code-touching task.
|
|
69
69
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: App Data
|
|
3
3
|
description: Read and write the data of a Crewly App you published (the same collections the app's page sees through crewly.db) — list, get, set, update, add, delete. Use it to fill an app with content, act on what the owner entered, or answer an [APP CHANGES] message.
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
category: productivity
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -44,6 +44,7 @@ bash execute.sh --app 28au74d9cj --set meta settings --data '{"title":"Weekly sh
|
|
|
44
44
|
bash execute.sh --app 28au74d9cj --update items 1kx2-ab --data '{"done":true}' --if-rev 3
|
|
45
45
|
bash execute.sh --app 28au74d9cj --add items --data '{"name":"eggs","done":false}'
|
|
46
46
|
bash execute.sh --app 28au74d9cj --delete items 1kx2-ab
|
|
47
|
+
bash execute.sh --app 28au74d9cj --get-field items 1kx2-ab --field /photo --out ./photo.png --decode # full value of a cut field
|
|
47
48
|
bash execute.sh --app 28au74d9cj --request-access --reason "write the morning briefing" # ask the owner, see "Collaborators"
|
|
48
49
|
bash execute.sh --app 28au74d9cj --collaborators # who the owner let in
|
|
49
50
|
bash execute.sh --app 28au74d9cj --owner # who the app's comments go to
|
|
@@ -200,3 +201,14 @@ write private information there.
|
|
|
200
201
|
|
|
201
202
|
Collection names: `[A-Za-z0-9_-]{1,64}`. Doc ids: `[A-Za-z0-9_.:-]{1,128}`.
|
|
202
203
|
Documents are JSON objects up to 256 KB.
|
|
204
|
+
|
|
205
|
+
## Long values (over 65,536 characters)
|
|
206
|
+
|
|
207
|
+
`--get` and `--list` cut any string over 65,536 characters (a base64 photo, a
|
|
208
|
+
long document) so it cannot flood your terminal. A cut is never silent: the
|
|
209
|
+
answer carries `truncated: [{"field":"/photo","chars":123456}]` (in a list,
|
|
210
|
+
each entry also has the doc `id`). Do not parse a flagged value as if it were
|
|
211
|
+
whole. Fetch it with `--get-field <collection> <docId> --field <pointer> --out
|
|
212
|
+
<file>`: the full value goes to a file inside your project (nothing is printed
|
|
213
|
+
but `{file, bytes}`). Add `--decode` for base64 / data-URL values to get the
|
|
214
|
+
real binary (the image itself).
|
|
@@ -15,6 +15,7 @@ print_usage() {
|
|
|
15
15
|
Usage:
|
|
16
16
|
bash execute.sh --app <appId> --list <collection> [--limit 100] [--after <docId>]
|
|
17
17
|
bash execute.sh --app <appId> --get <collection> <docId>
|
|
18
|
+
bash execute.sh --app <appId> --get-field <collection> <docId> --field </json/pointer> --out <file> [--decode]
|
|
18
19
|
bash execute.sh --app <appId> --set <collection> <docId> --data '{"done":true}'
|
|
19
20
|
bash execute.sh --app <appId> --update <collection> <docId> --data '{"done":true}' [--if-rev 4]
|
|
20
21
|
bash execute.sh --app <appId> --add <collection> --data '{"name":"milk"}'
|
|
@@ -32,6 +33,10 @@ Options:
|
|
|
32
33
|
--if-rev Only update when the doc is still at this rev (409 conflict otherwise)
|
|
33
34
|
--limit Page size for --list (1-500, default 100)
|
|
34
35
|
--after Continue a --list after this doc id (the previous page's "next")
|
|
36
|
+
--get-field Save the FULL value of one field to a file (reads cut strings over 65,536 characters and list them in "truncated"); prints only {file, bytes}
|
|
37
|
+
--field JSON pointer into the doc's data, as listed in "truncated" (e.g. /photo, /items/0/image)
|
|
38
|
+
--out File to write for --get-field: inside your project directory, not an existing symlink
|
|
39
|
+
--decode With --get-field: base64-decode the value (a data:...;base64, prefix is dropped) so a photo is written as the real image
|
|
35
40
|
--upload Put a file (an image for a post) into the app and print its url; works for the owner's agents and for collaborators. Regular, non-symlink file inside your project directory, up to 10 MB (the app's own limit may be smaller)
|
|
36
41
|
--name File name to show for --upload (default: the file's name)
|
|
37
42
|
--request-access Ask the owner to let your team (default) or only you work in an app another team published; a card goes to the owner, nothing is granted until they tap Allow
|
|
@@ -51,10 +56,20 @@ fail_from() {
|
|
|
51
56
|
exit 1
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
# Run one API call. stdout (the whole response body, however many lines it
|
|
60
|
+
# spans) is kept apart from stderr (the missing-session warning, or the JSON
|
|
61
|
+
# error object api_call prints on failure) so neither pollutes the other.
|
|
54
62
|
call() {
|
|
55
|
-
local out
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
local out err errfile rc=0
|
|
64
|
+
errfile=$(mktemp "${TMPDIR:-/tmp}/app-data-err.XXXXXX") || errfile=/dev/null
|
|
65
|
+
out=$(api_call "$@" 2>"$errfile") || rc=$?
|
|
66
|
+
err=$(cat "$errfile" 2>/dev/null || true)
|
|
67
|
+
[ "$errfile" = /dev/null ] || rm -f "$errfile"
|
|
68
|
+
if [ "$rc" -ne 0 ]; then
|
|
69
|
+
# The error object is the last non-empty stderr line (warnings come first).
|
|
70
|
+
fail_from "$(printf '%s\n' "$err" | awk 'NF{l=$0} END{print l}')"
|
|
71
|
+
fi
|
|
72
|
+
printf '%s\n' "$out"
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
uri() { jq -rn --arg v "$1" '$v|@uri'; }
|
|
@@ -107,13 +122,35 @@ content_type_for() {
|
|
|
107
122
|
esac
|
|
108
123
|
}
|
|
109
124
|
|
|
110
|
-
|
|
125
|
+
# --out for --get-field: a new regular file inside the project directory
|
|
126
|
+
# (CREWLY_PROJECT_PATH, else the working directory), never under Crewly's home,
|
|
127
|
+
# never through a symlink. Prints the absolute target path.
|
|
128
|
+
resolve_out_path() {
|
|
129
|
+
local given="$1" dir base project real_dir
|
|
130
|
+
dir="$(dirname "$given")"; base="$(basename "$given")"
|
|
131
|
+
[ -d "$dir" ] || error_exit "the directory of --out does not exist: $dir"
|
|
132
|
+
real_dir="$(cd "$dir" && pwd -P)"
|
|
133
|
+
project="$(cd "${CREWLY_PROJECT_PATH:-$PWD}" && pwd -P)" || error_exit "cannot resolve the project directory"
|
|
134
|
+
case "$real_dir/" in "$project"/*) ;; *) error_exit "--out must be inside your project directory ($project)" ;; esac
|
|
135
|
+
case "$real_dir/" in "${CREWLY_HOME:-$HOME/.crewly}"/*|"$HOME/.crewly"/*) error_exit "--out is inside Crewly's home directory" ;; esac
|
|
136
|
+
[ ! -L "$real_dir/$base" ] || error_exit "--out is a symbolic link; pass a real file path"
|
|
137
|
+
[ ! -d "$real_dir/$base" ] || error_exit "--out is a directory"
|
|
138
|
+
printf '%s/%s' "$real_dir" "$base"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
APP=""; OP=""; UPLOAD=""; NAME=""; COLL=""; DOC=""; DATA=""; IF_REV=""; LIMIT=""; AFTER=""; SCOPE=""; REASON=""; SET_OWNER=""; WHO=""; FIELD=""; OUT_FILE=""; DECODE=""
|
|
111
142
|
while [[ $# -gt 0 ]]; do
|
|
112
143
|
case "$1" in
|
|
113
144
|
--app) [ $# -ge 2 ] || error_exit "--app requires a value"; APP="$2"; shift 2 ;;
|
|
114
145
|
--list|--add)
|
|
115
146
|
[ $# -ge 2 ] || error_exit "$1 requires a collection"
|
|
116
147
|
OP="${1#--}"; COLL="$2"; shift 2 ;;
|
|
148
|
+
--get-field)
|
|
149
|
+
[ $# -ge 3 ] || error_exit "--get-field requires a collection and a doc id"
|
|
150
|
+
OP="get-field"; COLL="$2"; DOC="$3"; shift 3 ;;
|
|
151
|
+
--field) [ $# -ge 2 ] || error_exit "--field requires a JSON pointer"; FIELD="$2"; shift 2 ;;
|
|
152
|
+
--out) [ $# -ge 2 ] || error_exit "--out requires a file path"; OUT_FILE="$2"; shift 2 ;;
|
|
153
|
+
--decode) DECODE=1; shift ;;
|
|
117
154
|
--get|--set|--update|--delete)
|
|
118
155
|
[ $# -ge 3 ] || error_exit "$1 requires a collection and a doc id"
|
|
119
156
|
OP="${1#--}"; COLL="$2"; DOC="$3"; shift 3 ;;
|
|
@@ -191,11 +228,28 @@ case "$OP" in
|
|
|
191
228
|
[ -n "$LIMIT" ] && Q="limit=$(uri "$LIMIT")"
|
|
192
229
|
[ -n "$AFTER" ] && Q="${Q:+$Q&}after=$(uri "$AFTER")"
|
|
193
230
|
RESPONSE=$(call GET "${BASE}${Q:+?$Q}") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
194
|
-
printf '%s' "$RESPONSE" | jq -c '{success: true, docs: [.data.docs[]? | {id, data, rev, updatedAt, updatedBy: (.updatedBy.kind // null)} + (if .updatedBy.kind == "agent" then {by: .updatedBy.id} else {} end)], next: .data.next}'
|
|
231
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, docs: [.data.docs[]? | {id, data, rev, updatedAt, updatedBy: (.updatedBy.kind // null)} + (if .updatedBy.kind == "agent" then {by: .updatedBy.id} else {} end)], next: .data.next} + (if .data.truncated then {truncated: .data.truncated, truncatedHint: .data.truncatedHint} else {} end)'
|
|
232
|
+
;;
|
|
233
|
+
get-field)
|
|
234
|
+
[ -n "$FIELD" ] || error_exit "--field is required for --get-field (a JSON pointer such as /photo)"
|
|
235
|
+
[ -n "$OUT_FILE" ] || error_exit "--out is required for --get-field"
|
|
236
|
+
TARGET="$(resolve_out_path "$OUT_FILE")" || exit 1
|
|
237
|
+
RESPONSE=$(CREWLY_SKILL_FULL_OUTPUT=1 call GET "${BASE}/$(uri "$DOC")/field?path=$(uri "$FIELD")") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
238
|
+
printf '%s' "$RESPONSE" | jq -e '.data.type == "string"' >/dev/null 2>&1 || error_exit "that field is not a string; read it with --get"
|
|
239
|
+
TMP_OUT="$(mktemp "${TARGET}.XXXXXX")" || error_exit "cannot write next to $OUT_FILE"
|
|
240
|
+
printf '%s' "$RESPONSE" | jq -j '.data.value' > "$TMP_OUT"
|
|
241
|
+
if [ -n "$DECODE" ]; then
|
|
242
|
+
sed -E '1s#^data:[^,]*;base64,##' "$TMP_OUT" | tr -d '\n\r ' > "${TMP_OUT}.b64"
|
|
243
|
+
{ base64 -d < "${TMP_OUT}.b64" > "${TMP_OUT}.bin" 2>/dev/null || base64 -D < "${TMP_OUT}.b64" > "${TMP_OUT}.bin" 2>/dev/null; } \
|
|
244
|
+
|| { rm -f "$TMP_OUT" "${TMP_OUT}.b64" "${TMP_OUT}.bin"; error_exit "the value is not valid base64"; }
|
|
245
|
+
mv "${TMP_OUT}.bin" "$TMP_OUT"; rm -f "${TMP_OUT}.b64"
|
|
246
|
+
fi
|
|
247
|
+
mv "$TMP_OUT" "$TARGET"
|
|
248
|
+
jq -cn --arg f "$TARGET" --argjson b "$(wc -c < "$TARGET" | tr -d ' ')" --argjson c "$(printf '%s' "$RESPONSE" | jq '.data.chars')" '{success: true, file: $f, bytes: $b, chars: $c}'
|
|
195
249
|
;;
|
|
196
250
|
get)
|
|
197
251
|
RESPONSE=$(call GET "${BASE}/$(uri "$DOC")") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
198
|
-
printf '%s' "$RESPONSE" | jq -c '{success: true, id: .data.id, data: .data.data, rev: .data.rev, updatedAt: .data.updatedAt}'
|
|
252
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, id: .data.id, data: .data.data, rev: .data.rev, updatedAt: .data.updatedAt} + (if .data.truncated then {truncated: .data.truncated, truncatedHint: .data.truncatedHint} else {} end)'
|
|
199
253
|
;;
|
|
200
254
|
set)
|
|
201
255
|
need_data
|
|
@@ -24,6 +24,18 @@ class H(BaseHTTPRequestHandler):
|
|
|
24
24
|
def send(code, obj):
|
|
25
25
|
self.send_response(code); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
26
26
|
self.wfile.write(json.dumps(obj).encode())
|
|
27
|
+
if self.path.endswith('/pretty'):
|
|
28
|
+
self.send_response(200); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
29
|
+
return self.wfile.write(json.dumps({'success': True, 'data': {'id': 'pretty', 'data': {'text': 'line1\nline2'}, 'rev': 3, 'updatedAt': 't'}}, indent=2).encode())
|
|
30
|
+
if '/field' in self.path:
|
|
31
|
+
from urllib.parse import urlparse, parse_qs
|
|
32
|
+
q = parse_qs(urlparse(self.path).query).get('path', [''])[0]
|
|
33
|
+
if q == '/photo':
|
|
34
|
+
return send(200, {'success': True, 'data': {'field': q, 'type': 'string', 'chars': 38, 'value': 'data:image/png;base64,aGVsbG8gd29ybGQ='}})
|
|
35
|
+
if q == '/n':
|
|
36
|
+
return send(200, {'success': True, 'data': {'field': q, 'type': 'number', 'value': 1}})
|
|
37
|
+
if self.path.endswith('/trunc'):
|
|
38
|
+
return send(200, {'success': True, 'data': {'id': 'trunc', 'data': {'photo': 'xx'}, 'rev': 1, 'updatedAt': 't', 'truncated': [{'field': '/photo', 'chars': 70000}], 'truncatedHint': 'cut'}})
|
|
27
39
|
if self.path.endswith('/missing'):
|
|
28
40
|
return send(404, {'success': False, 'error': 'not_found', 'message': 'Document not found.', 'hint': 'No such app, document or version for this account.'})
|
|
29
41
|
if self.path.endswith('/collaborators') and method == 'GET':
|
|
@@ -89,6 +101,15 @@ check "list: path" "$(jq -r .path "$STUB_LOG")" "/api/apps/$A/data/items?limit=5
|
|
|
89
101
|
OUT=$(run --app $A --get items milk)
|
|
90
102
|
check "get" "$OUT" '{"success":true,"id":"milk","data":{"name":"milk","done":false},"rev":2,"updatedAt":"t"}'
|
|
91
103
|
|
|
104
|
+
# A multi-line (pretty-printed) response body must survive whole (#1122).
|
|
105
|
+
OUT=$(run --app $A --get items pretty)
|
|
106
|
+
check "get: multi-line body kept whole" "$OUT" '{"success":true,"id":"pretty","data":{"text":"line1\nline2"},"rev":3,"updatedAt":"t"}'
|
|
107
|
+
# The missing-session warning goes to stderr and must not break the JSON (#1122).
|
|
108
|
+
OUT=$(env "${ENVS[@]}" CREWLY_SESSION_NAME= bash "$EXEC" --app $A --get items pretty 2>/dev/null)
|
|
109
|
+
check "get: stderr warning does not pollute stdout" "$(printf '%s' "$OUT" | jq -r .id)" "pretty"
|
|
110
|
+
OUT=$(env "${ENVS[@]}" CREWLY_SESSION_NAME= bash "$EXEC" --app $A --get items missing 2>/dev/null; true)
|
|
111
|
+
check "error with warning: reason still parsed" "$(printf '%s' "$OUT" | jq -r .reason)" "not_found"
|
|
112
|
+
|
|
92
113
|
OUT=$(run --app $A --get items missing; true)
|
|
93
114
|
check "get missing → not_found" "$(printf '%s' "$OUT" | jq -c '{success, status, reason}')" '{"success":false,"status":404,"reason":"not_found"}'
|
|
94
115
|
|
|
@@ -195,5 +216,26 @@ check "upload: 2xx without a url fails, not success with nulls" "$(printf '%s' "
|
|
|
195
216
|
OUT=$(run_err --app $A --upload || true)
|
|
196
217
|
check "upload needs a path" "$(printf '%s' "$OUT" | grep -c 'requires a file path')" "1"
|
|
197
218
|
|
|
219
|
+
# --get / --list carry the truncation flag through (#1145).
|
|
220
|
+
OUT=$(run --app $A --get items trunc)
|
|
221
|
+
check "get: truncation flag passed through" "$(printf '%s' "$OUT" | jq -c '.truncated')" '[{"field":"/photo","chars":70000}]'
|
|
222
|
+
|
|
223
|
+
# --get-field --out writes the full value to a file and prints no content (#1145).
|
|
224
|
+
OUT=$(run --app $A --get-field items milk --field /photo --out "$WORK/proj/photo.txt")
|
|
225
|
+
check "get-field: path" "$(jq -r .path "$STUB_LOG")" "/api/apps/$A/data/items/milk/field?path=%2Fphoto"
|
|
226
|
+
check "get-field: file holds the raw value" "$(cat "$WORK/proj/photo.txt")" 'data:image/png;base64,aGVsbG8gd29ybGQ='
|
|
227
|
+
check "get-field: prints a receipt, not the value" "$(printf '%s' "$OUT" | jq -c '{success, bytes, chars}')" '{"success":true,"bytes":38,"chars":38}'
|
|
228
|
+
run --app $A --get-field items milk --field /photo --out "$WORK/proj/photo.bin" --decode >/dev/null
|
|
229
|
+
check "get-field --decode: data URL decoded to bytes" "$(cat "$WORK/proj/photo.bin")" 'hello world'
|
|
230
|
+
OUT=$(run_err --app $A --get-field items milk --field /photo --out "$WORK/outside-photo.txt" || true)
|
|
231
|
+
check "get-field: --out outside the project refused" "$(printf '%s' "$OUT" | grep -c 'inside your project')" "1"
|
|
232
|
+
ln -s "$WORK/outside.json" "$WORK/proj/out-link.txt"
|
|
233
|
+
OUT=$(run_err --app $A --get-field items milk --field /photo --out "$WORK/proj/out-link.txt" || true)
|
|
234
|
+
check "get-field: symlink --out refused" "$(printf '%s' "$OUT" | grep -c 'symbolic link')" "1"
|
|
235
|
+
OUT=$(run_err --app $A --get-field items milk --field /n --out "$WORK/proj/n.txt" || true)
|
|
236
|
+
check "get-field: non-string field refused" "$(printf '%s' "$OUT" | grep -c 'not a string')" "1"
|
|
237
|
+
OUT=$(run_err --app $A --get-field items milk --out "$WORK/proj/x.txt" || true)
|
|
238
|
+
check "get-field: --field required" "$(printf '%s' "$OUT" | grep -c -- '--field is required')" "1"
|
|
239
|
+
|
|
198
240
|
echo "app-data: $PASS passed, $FAIL failed"
|
|
199
241
|
[ "$FAIL" -eq 0 ]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: Docs Comment
|
|
3
|
-
description: Handle feedback left as comments in a Google Doc — list comments and replies
|
|
4
|
-
version: 1.
|
|
3
|
+
description: Handle feedback left as comments in a Google Doc — list comments and replies; reply, resolve and add are queued for the owner's OK and posted as a batch only after they reply 「发 C<n>」 (via the Google Workspace grant held by Crewly Cloud).
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
category: productivity
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -41,6 +41,9 @@ bash execute.sh reply --doc 1AbC… --comment AAAB… --text "Updated the intr
|
|
|
41
41
|
bash execute.sh resolve --doc 1AbC… --comment AAAB… --text "Done in v2."
|
|
42
42
|
bash execute.sh resolve --doc 1AbC… --comment AAAB…
|
|
43
43
|
bash execute.sh add --doc 1AbC… --text "Is this number from Q2?" --quote "grew 40%"
|
|
44
|
+
bash execute.sh pending # what is queued and waiting on the owner
|
|
45
|
+
bash execute.sh send --batch C3 # post a batch AFTER the owner said 「发 C3」
|
|
46
|
+
bash execute.sh discard --batch C3
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
`--doc` takes a document id or its docs.google.com URL. `--account` picks a
|
|
@@ -56,8 +59,45 @@ connected Google account (see below).
|
|
|
56
59
|
with a question if it is unclear. Do not resolve a comment you did not
|
|
57
60
|
address.
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
## Posting needs the owner's OK (one OK per batch)
|
|
63
|
+
|
|
64
|
+
Comments and replies on a Google Doc are visible to every collaborator under
|
|
65
|
+
the owner's name, so `reply`, `resolve` and `add` are outbound actions like
|
|
66
|
+
`gmail-send`: **they do not post.** Each call queues the comment and returns
|
|
67
|
+
a batch code:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{"success":true,"held":true,"posted":false,"batch":"C3","queued":38,"message":"Queued as C3 …"}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
All your queued comments share one batch, so work through the whole document
|
|
74
|
+
first, then ask the owner ONCE, in plain words, with what will go out:
|
|
75
|
+
|
|
76
|
+
> "I have 38 replies ready on the Q3 plan doc (mostly "done, see para 2",
|
|
77
|
+
> 4 questions). Reply 「发 C3」 to post them all."
|
|
78
|
+
|
|
79
|
+
The owner answers from their phone with one message, `发 C3` (or `send C3`).
|
|
80
|
+
Only that genuine owner message counts — not a suggestion, a pre-filled line,
|
|
81
|
+
or your own paraphrase. When the `[SLACK…]`/`[CHAT:…]` message arrives, run:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
bash execute.sh send --batch C3
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Rules:
|
|
88
|
+
|
|
89
|
+
- The OK must come AFTER your last queued comment. If you queue one more
|
|
90
|
+
after asking, the earlier OK no longer covers the batch: ask again.
|
|
91
|
+
- `send` posts in order and stops on the first failure; whatever is not
|
|
92
|
+
posted stays queued, so running `send` again never double-posts.
|
|
93
|
+
- A refusal (`needs_owner_confirmation`) means the owner has not said it
|
|
94
|
+
yet. Do not look for another way to post.
|
|
95
|
+
- If the owner wants changes, `discard --batch C3` and queue fresh ones.
|
|
96
|
+
- Queued comments live in this Crewly process; if it restarts before the OK,
|
|
97
|
+
queue them again.
|
|
98
|
+
- `list` is read-only and always open.
|
|
99
|
+
|
|
100
|
+
Tell the owner what you posted once `send` succeeds.
|
|
61
101
|
|
|
62
102
|
## Output
|
|
63
103
|
|
|
@@ -71,6 +111,11 @@ Resolved comments are left out unless you pass `--include-resolved`.
|
|
|
71
111
|
`truncated: true` means the document has more than 500 comments and only the
|
|
72
112
|
first 500 were read.
|
|
73
113
|
|
|
114
|
+
`reply` / `resolve` / `add` queue the comment (see above) until `send --batch`
|
|
115
|
+
succeeds. A successful `send` returns `{"success":true,"batch":"C3","posted":38,"remaining":0,"results":[…]}`;
|
|
116
|
+
on a mid-batch failure `success` is false with `error: "partial"` and the number still queued.
|
|
117
|
+
When the batch posts, each result has the shape below.
|
|
118
|
+
|
|
74
119
|
`reply` / `resolve`:
|
|
75
120
|
|
|
76
121
|
```json
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
# bash execute.sh reply --doc <id|url> --comment <id> --text "…"
|
|
14
14
|
# bash execute.sh resolve --doc <id|url> --comment <id> [--text "…"]
|
|
15
15
|
# bash execute.sh add --doc <id|url> --text "…" [--quote "exact text"]
|
|
16
|
+
# bash execute.sh pending (queued comments awaiting the owner)
|
|
17
|
+
# bash execute.sh send --batch C3 (post a batch the owner approved)
|
|
18
|
+
# bash execute.sh discard --batch C3
|
|
16
19
|
# bash execute.sh '{"command":"list","doc":"<id>"}'
|
|
17
20
|
# =============================================================================
|
|
18
21
|
set -euo pipefail
|
|
@@ -26,6 +29,9 @@ Usage:
|
|
|
26
29
|
bash execute.sh reply --doc <id|url> --comment <id> --text "…"
|
|
27
30
|
bash execute.sh resolve --doc <id|url> --comment <id> [--text "…"]
|
|
28
31
|
bash execute.sh add --doc <id|url> --text "…" [--quote "exact text in the doc"]
|
|
32
|
+
bash execute.sh pending
|
|
33
|
+
bash execute.sh send --batch C3
|
|
34
|
+
bash execute.sh discard --batch C3
|
|
29
35
|
bash execute.sh '{"command":"reply","doc":"<id>","comment":"<id>","text":"…"}'
|
|
30
36
|
|
|
31
37
|
Options:
|
|
@@ -35,6 +41,10 @@ Options:
|
|
|
35
41
|
--quote Exact document text the new comment is about (add only;
|
|
36
42
|
Google Docs may still show it as an unanchored comment)
|
|
37
43
|
--include-resolved list: include resolved comments too
|
|
44
|
+
--batch send / discard: the batch code a reply / resolve / add returned (e.g. C3)
|
|
45
|
+
|
|
46
|
+
reply, resolve and add do NOT post: they queue the comment for the owner and
|
|
47
|
+
return a batch code. After the owner answers 「发 <code>」, run send --batch.
|
|
38
48
|
--account Which connected Google account to act as (default: your primary)
|
|
39
49
|
--help | -h Show this help
|
|
40
50
|
EOF_USAGE
|
|
@@ -66,7 +76,7 @@ elif [[ $# -gt 0 && ${1:0:1} != '-' ]]; then
|
|
|
66
76
|
COMMAND="$1"
|
|
67
77
|
shift
|
|
68
78
|
fi
|
|
69
|
-
DOC=""; COMMENT=""; TEXT=""; TEXT_SET=0; QUOTE=""; INCLUDE_RESOLVED=0; ACCOUNT=""
|
|
79
|
+
DOC=""; COMMENT=""; TEXT=""; TEXT_SET=0; QUOTE=""; INCLUDE_RESOLVED=0; ACCOUNT=""; BATCH=""
|
|
70
80
|
while [[ $# -gt 0 ]]; do
|
|
71
81
|
case "$1" in
|
|
72
82
|
--doc|--id) [ $# -ge 2 ] || error_exit "$1 requires a value"; DOC="$2"; shift 2 ;;
|
|
@@ -75,6 +85,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
75
85
|
--quote) [ $# -ge 2 ] || error_exit "--quote requires a value"; QUOTE="$2"; shift 2 ;;
|
|
76
86
|
--include-resolved) INCLUDE_RESOLVED=1; shift ;;
|
|
77
87
|
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
88
|
+
--batch) [ $# -ge 2 ] || error_exit "--batch requires a value"; BATCH="$2"; shift 2 ;;
|
|
78
89
|
--help|-h) print_usage; exit 0 ;;
|
|
79
90
|
*) error_exit "Unknown option: $1" ;;
|
|
80
91
|
esac
|
|
@@ -88,13 +99,35 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
88
99
|
[ -z "$QUOTE" ] && QUOTE=$(printf '%s' "$INPUT" | jq -r '.quote // empty')
|
|
89
100
|
[ "$INCLUDE_RESOLVED" -eq 0 ] && [ "$(printf '%s' "$INPUT" | jq -r '.includeResolved // false')" = "true" ] && INCLUDE_RESOLVED=1
|
|
90
101
|
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
102
|
+
[ -z "$BATCH" ] && BATCH=$(printf '%s' "$INPUT" | jq -r '.batch // empty')
|
|
91
103
|
fi
|
|
92
104
|
# Route the call at one connected Google account; unset means the default.
|
|
93
105
|
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
94
106
|
# Accept a full docs.google.com URL too.
|
|
95
107
|
DOC=$(printf '%s' "$DOC" | sed -E 's#.*/document/d/([^/?]+).*#\1#')
|
|
96
108
|
|
|
97
|
-
[ -n "$COMMAND" ] || error_exit "a command is required: list, reply, resolve or
|
|
109
|
+
[ -n "$COMMAND" ] || error_exit "a command is required: list, reply, resolve, add, pending, send or discard (see --help)"
|
|
110
|
+
BATCHES="/google/doc-comment-batches"
|
|
111
|
+
# Comment writes are outbound: an agent's are queued, not posted (HTTP 202).
|
|
112
|
+
# Print the queue answer instead of a post result.
|
|
113
|
+
held_or() {
|
|
114
|
+
if printf '%s' "$1" | jq -e '.data.held == true' >/dev/null 2>&1; then
|
|
115
|
+
printf '%s' "$1" | jq -c '{success: true, held: true, posted: false, batch: .data.batch, queued: .data.queued, message: .data.message}'
|
|
116
|
+
return 0
|
|
117
|
+
fi
|
|
118
|
+
return 1
|
|
119
|
+
}
|
|
120
|
+
case "$COMMAND" in
|
|
121
|
+
pending)
|
|
122
|
+
RESPONSE=$(call GET "$BATCHES") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
123
|
+
printf '%s' "$RESPONSE" | jq -c '{success: (.success // false), batches: [.data.batches[] | {batch: .code, agent: .agentSession, queued: (.items | length), items: [.items[] | {action, docId, commentId, text, quote}]}]}'
|
|
124
|
+
exit 0 ;;
|
|
125
|
+
send|discard)
|
|
126
|
+
require_param "batch (--batch)" "$BATCH"
|
|
127
|
+
RESPONSE=$(call POST "${BATCHES}/$(uri "$BATCH")/${COMMAND}" '{}') || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
128
|
+
printf '%s' "$RESPONSE" | jq -c '{success: (.success // false)} + .data'
|
|
129
|
+
exit 0 ;;
|
|
130
|
+
esac
|
|
98
131
|
require_param "doc (--doc)" "$DOC"
|
|
99
132
|
BASE="/google/docs/$(uri "$DOC")/comments"
|
|
100
133
|
|
|
@@ -111,18 +144,21 @@ case "$COMMAND" in
|
|
|
111
144
|
require_param "text (--text)" "$TEXT"
|
|
112
145
|
BODY=$(jq -cn --arg text "$TEXT" '{text: $text}')
|
|
113
146
|
RESPONSE=$(call POST "${BASE}/$(uri "$COMMENT")/replies" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
147
|
+
held_or "$RESPONSE" && exit 0
|
|
114
148
|
printf '%s' "$RESPONSE" | jq -c '{success: (.success // false), action: "reply", docId: .data.docId, commentId: .data.commentId, replyId: .data.id, content: .data.content}'
|
|
115
149
|
;;
|
|
116
150
|
resolve)
|
|
117
151
|
require_param "comment (--comment)" "$COMMENT"
|
|
118
152
|
BODY=$(jq -cn --arg text "$TEXT" 'if $text != "" then {text: $text} else {} end')
|
|
119
153
|
RESPONSE=$(call POST "${BASE}/$(uri "$COMMENT")/resolve" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
154
|
+
held_or "$RESPONSE" && exit 0
|
|
120
155
|
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
156
|
;;
|
|
122
157
|
add)
|
|
123
158
|
require_param "text (--text)" "$TEXT"
|
|
124
159
|
BODY=$(jq -cn --arg text "$TEXT" --arg quote "$QUOTE" '{text: $text} + (if $quote != "" then {quote: $quote} else {} end)')
|
|
125
160
|
RESPONSE=$(call POST "${BASE}" "$BODY") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
161
|
+
held_or "$RESPONSE" && exit 0
|
|
126
162
|
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
163
|
;;
|
|
128
164
|
*)
|
|
@@ -30,6 +30,14 @@ class H(BaseHTTPRequestHandler):
|
|
|
30
30
|
return self._send(403, {'success': False, 'error': 'reauth_required',
|
|
31
31
|
'message': 'Replying to or adding comments on this document needs Google Drive edit access, which this Google account has not granted yet.',
|
|
32
32
|
'hint': 'Ask the owner to reconnect Google Drive: run the google-connect skill with --product drive.'})
|
|
33
|
+
if '/docs/held/' in self.path:
|
|
34
|
+
return self._send(202, {'success': True, 'data': {'held': True, 'batch': 'C3', 'queued': 2, 'message': 'Queued as C3'}})
|
|
35
|
+
if self.path.startswith('/api/google/doc-comment-batches'):
|
|
36
|
+
if method == 'GET':
|
|
37
|
+
return self._send(200, {'success': True, 'data': {'batches': [{'code': 'C3', 'agentSession': 'ella', 'items': [{'action': 'reply', 'docId': 'd1', 'commentId': 'c1', 'text': 'Fixed'}]}]}})
|
|
38
|
+
if '/C9/' in self.path:
|
|
39
|
+
return self._send(403, {'success': False, 'error': 'needs_owner_confirmation', 'message': 'The owner has not confirmed batch C9'})
|
|
40
|
+
return self._send(200, {'success': True, 'data': {'batch': 'C3', 'posted': 2, 'remaining': 0}})
|
|
33
41
|
if method == 'GET':
|
|
34
42
|
return self._send(200, {'success': True, 'data': {'docId': 'd1', 'comments': [COMMENT], 'truncated': False}})
|
|
35
43
|
if self.path.endswith('/replies'):
|
|
@@ -93,6 +101,26 @@ check "reauth: exit code" "$CODE" "1"
|
|
|
93
101
|
check "reauth: reason" "$(printf '%s' "$OUT" | jq -r '.reason')" "reauth_required"
|
|
94
102
|
check "reauth: hint names the one-tap path" "$(printf '%s' "$OUT" | jq -r '.hint' | grep -c 'google-connect skill with --product drive')" "1"
|
|
95
103
|
|
|
104
|
+
# a held comment is reported as queued, never as posted
|
|
105
|
+
for ACT in "reply --doc held --comment c1 --text x" "resolve --doc held --comment c1" "add --doc held --text x"; do
|
|
106
|
+
OUT=$(run $ACT)
|
|
107
|
+
check "held (${ACT%% *}): output" "$OUT" '{"success":true,"held":true,"posted":false,"batch":"C3","queued":2,"message":"Queued as C3"}'
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
# pending / send / discard
|
|
111
|
+
OUT=$(run pending)
|
|
112
|
+
check "pending: request" "$(req)" 'GET /api/google/doc-comment-batches null'
|
|
113
|
+
check "pending: output" "$OUT" '{"success":true,"batches":[{"batch":"C3","agent":"ella","queued":1,"items":[{"action":"reply","docId":"d1","commentId":"c1","text":"Fixed","quote":null}]}]}'
|
|
114
|
+
OUT=$(run send --batch C3)
|
|
115
|
+
check "send: request" "$(req)" 'POST /api/google/doc-comment-batches/C3/send {}'
|
|
116
|
+
check "send: output" "$OUT" '{"success":true,"batch":"C3","posted":2,"remaining":0}'
|
|
117
|
+
run discard --batch C3 >/dev/null
|
|
118
|
+
check "discard: request" "$(req)" 'POST /api/google/doc-comment-batches/C3/discard {}'
|
|
119
|
+
OUT=$(run send --batch C9); CODE=$?
|
|
120
|
+
check "send refused: exit code" "$CODE" "1"
|
|
121
|
+
check "send refused: reason" "$(printf '%s' "$OUT" | jq -r '.reason')" "needs_owner_confirmation"
|
|
122
|
+
check "send needs --batch" "$(run_err send | grep -c 'batch')" "1"
|
|
123
|
+
|
|
96
124
|
# argument validation (no request sent)
|
|
97
125
|
check "no command" "$(run_err --doc d1 | grep -c 'command is required')" "1"
|
|
98
126
|
check "unknown command" "$(run_err delete --doc d1 | grep -c 'Unknown command')" "1"
|
|
@@ -829,6 +829,13 @@ export declare const BROWSER_BRIDGE_CONSTANTS: {
|
|
|
829
829
|
readonly WS_PATH: "/ws/browser";
|
|
830
830
|
/** Default timeout for commands sent to Chrome Extension (ms) */
|
|
831
831
|
readonly COMMAND_TIMEOUT_MS: 30000;
|
|
832
|
+
/**
|
|
833
|
+
* Timeout for screenshot / full-page-screenshot (ms). Capturing a heavy
|
|
834
|
+
* page (Slack web, long feeds) and relaying a multi-MB base64 image takes
|
|
835
|
+
* well beyond the 30 s default; the shorter limit made them look like a
|
|
836
|
+
* missing browser (crewly#1080).
|
|
837
|
+
*/
|
|
838
|
+
readonly SCREENSHOT_TIMEOUT_MS: 90000;
|
|
832
839
|
/**
|
|
833
840
|
* Per-tab dispatch (1 agent : 1 tab) — see
|
|
834
841
|
* `.crewly/specs/crewly-in-chrome-per-tab-fix-2026-04-25.md`.
|
|
@@ -2821,6 +2828,26 @@ export declare const WORKTREE_JANITOR_CONSTANTS: {
|
|
|
2821
2828
|
readonly LOW_DISK_REPORT_ITEMS: 5;
|
|
2822
2829
|
/** Timeout of one `du -sk` size probe (ms). */
|
|
2823
2830
|
readonly DU_TIMEOUT_MS: 60000;
|
|
2831
|
+
/** Runaway-file watcher (crewly#1078): how often agent scratch dirs and worktrees are scanned (1 minute). */
|
|
2832
|
+
readonly RUNAWAY_CHECK_INTERVAL_MS: number;
|
|
2833
|
+
/** A file this big in an agent dir is reported to the owner (5 GB); if it is also still growing, its writer is stopped. */
|
|
2834
|
+
readonly RUNAWAY_FILE_BYTES: number;
|
|
2835
|
+
/** A file that grew by this much per minute between two scans is a runaway whatever its size (1 GB/min). */
|
|
2836
|
+
readonly RUNAWAY_GROWTH_BYTES_PER_MIN: number;
|
|
2837
|
+
/** Files smaller than this are never tracked between scans (cheap pre-filter, 256 MB). */
|
|
2838
|
+
readonly RUNAWAY_TRACK_MIN_BYTES: number;
|
|
2839
|
+
/** Bytes of a stopped runaway file kept (the head, so the agent can see what it was writing, 1 MB). */
|
|
2840
|
+
readonly RUNAWAY_KEEP_HEAD_BYTES: number;
|
|
2841
|
+
/** How deep under each watched dir files are looked for. */
|
|
2842
|
+
readonly RUNAWAY_SCAN_DEPTH: 8;
|
|
2843
|
+
/** Cap on directory entries visited per scan. */
|
|
2844
|
+
readonly RUNAWAY_SCAN_MAX_ENTRIES: 200000;
|
|
2845
|
+
/** Wait after SIGTERM before SIGKILL for the writer (ms). */
|
|
2846
|
+
readonly RUNAWAY_KILL_GRACE_MS: 3000;
|
|
2847
|
+
/** Timeout of the `lsof` that finds a file's writers (ms). */
|
|
2848
|
+
readonly RUNAWAY_LSOF_TIMEOUT_MS: 10000;
|
|
2849
|
+
/** One line added to every agent's start prompt. */
|
|
2850
|
+
readonly PROMPT_HINT: "Disk hygiene: never let a command or script write an unbounded file. When you redirect or concatenate output (`> file`, `>> file`, `cat a b > all.md`), cap it (`| head -c 50M`, `| head -n 100000`) and never build a file from its own output. A file in your scratch dir or worktree that grows past a few GB is stopped by Crewly and the owner is alerted.";
|
|
2824
2851
|
/** State file under CREWLY_HOME remembering when the owner was last told about low disk. */
|
|
2825
2852
|
readonly STATE_FILENAME: "disk-janitor-state.json";
|
|
2826
2853
|
/**
|
|
@@ -2844,6 +2871,27 @@ export declare const WORKTREE_JANITOR_CONSTANTS: {
|
|
|
2844
2871
|
* instead of 24 h). `worktree-agent-` is Claude Code's automatic subagent branch.
|
|
2845
2872
|
*/
|
|
2846
2873
|
readonly AGENT_BRANCH_PREFIXES: readonly string[];
|
|
2874
|
+
/**
|
|
2875
|
+
* Untracked paths under a directory with one of these names (or a `*.log`
|
|
2876
|
+
* file) are regenerable output and do not make a worktree "dirty". Nested
|
|
2877
|
+
* packages often do not ignore their own `node_modules` / `.next`, so
|
|
2878
|
+
* `git status` lists them as untracked.
|
|
2879
|
+
*/
|
|
2880
|
+
readonly REGENERABLE_DIRS: readonly string[];
|
|
2881
|
+
/** File suffixes treated as regenerable output (logs). */
|
|
2882
|
+
readonly REGENERABLE_FILE_SUFFIXES: readonly string[];
|
|
2883
|
+
/** Directories deleted from kept (dirty / not-merged) worktrees under low disk — reinstalled by `npm install` / rebuilt by `next build`. */
|
|
2884
|
+
readonly RECLAIM_DIRS: readonly string[];
|
|
2885
|
+
/** A kept worktree must be idle this long before its regenerable dirs are reclaimed under low disk (24 hours). */
|
|
2886
|
+
readonly RECLAIM_MIN_IDLE_MS: number;
|
|
2887
|
+
/** How deep under a worktree reclaimable dirs are searched for (root = 0; `web/node_modules` = 1). */
|
|
2888
|
+
readonly RECLAIM_SEARCH_DEPTH: 3;
|
|
2889
|
+
/** Work completion (task done / verified) schedules one tidy pass after this quiet period (2 minutes); bursts share it. */
|
|
2890
|
+
readonly COMPLETION_TIDY_DELAY_MS: number;
|
|
2891
|
+
/** Idle needed by a completion-triggered pass: the agent said it is finished, so far less than the periodic threshold (10 minutes). */
|
|
2892
|
+
readonly COMPLETION_MIN_IDLE_MS: number;
|
|
2893
|
+
/** Commits of the default branch searched for a squash-merge patch-equivalent of a branch. */
|
|
2894
|
+
readonly SQUASH_SEARCH_MAX_COMMITS: 300;
|
|
2847
2895
|
/** Branches that are never deleted, whatever the verdict. */
|
|
2848
2896
|
readonly PROTECTED_BRANCHES: readonly string[];
|
|
2849
2897
|
/** Timeout of one local git command (ms). */
|