crewly 1.20.265 → 1.20.267
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/skills/_common/lib.sh +15 -2
- package/config/skills/agent/core/app-data/SKILL.md +13 -1
- package/config/skills/agent/core/app-data/execute.sh +41 -5
- package/config/skills/agent/core/app-data/execute.test.sh +47 -1
- package/config/skills/agent/core/publish-app/SKILL.md +1 -1
- package/config/skills/agent/core/slack-file/SKILL.md +53 -0
- package/config/skills/agent/core/slack-file/execute.sh +72 -0
- package/config/skills/agent/core/slack-file/execute.test.sh +34 -0
- package/dist/backend/backend/src/constants.d.ts +59 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +59 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +7 -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 +23 -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 +9 -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 +19 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.js +6 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.d.ts +69 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.js +203 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp-proxy.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts +16 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js +126 -5
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +37 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +5 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +2 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-cloud.client.d.ts +2 -0
- package/dist/backend/backend/src/services/apps/apps-cloud.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-cloud.client.js +1 -0
- package/dist/backend/backend/src/services/apps/apps-cloud.client.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.d.ts +17 -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 +24 -0
- package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +4 -3
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.d.ts +43 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.js +98 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth-card.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.d.ts +277 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.js +719 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-auth.service.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts +29 -5
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js +40 -12
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.d.ts +196 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.js +351 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-oauth.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts +3 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js +8 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/credential-files.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/credential-files.js +2 -0
- package/dist/backend/backend/src/services/core/credential-files.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.d.ts +119 -0
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.js +415 -0
- package/dist/backend/backend/src/services/slack/slack-file-fetch.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.d.ts +69 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.js +177 -0
- package/dist/backend/backend/src/services/slack/slack-file-ref.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +16 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +15 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +84 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +11 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +59 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +59 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +2 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts +29 -5
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js +40 -12
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/credential-files.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/credential-files.js +2 -0
- package/dist/cli/backend/src/services/core/credential-files.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.d.ts +119 -0
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.js +415 -0
- package/dist/cli/backend/src/services/slack/slack-file-fetch.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.d.ts +69 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.js +177 -0
- package/dist/cli/backend/src/services/slack/slack-file-ref.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +15 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +84 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +11 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/frontend/dist/assets/{index-83188f0b.js → index-7e5ebf78.js} +111 -111
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -221,7 +221,16 @@ api_call() {
|
|
|
221
221
|
local method="$1" endpoint="$2" body="${3:-}"
|
|
222
222
|
local url="${CREWLY_API_URL}/api${endpoint}"
|
|
223
223
|
_prune_skill_output
|
|
224
|
-
local args=(-s -w '\n%{http_code}' -X "$method"
|
|
224
|
+
local args=(-s -w '\n%{http_code}' -X "$method")
|
|
225
|
+
# A raw file upload (app-data --upload): CREWLY_API_UPLOAD_FILE is sent as the
|
|
226
|
+
# body, byte for byte, with CREWLY_API_UPLOAD_TYPE as its Content-Type and the
|
|
227
|
+
# URI-encoded CREWLY_API_UPLOAD_NAME as X-File-Name. Everything else is JSON.
|
|
228
|
+
if [ -n "${CREWLY_API_UPLOAD_FILE:-}" ]; then
|
|
229
|
+
args+=(-H "Content-Type: ${CREWLY_API_UPLOAD_TYPE:-application/octet-stream}")
|
|
230
|
+
[ -n "${CREWLY_API_UPLOAD_NAME:-}" ] && args+=(-H "X-File-Name: ${CREWLY_API_UPLOAD_NAME}")
|
|
231
|
+
else
|
|
232
|
+
args+=(-H "Content-Type: application/json")
|
|
233
|
+
fi
|
|
225
234
|
# Include agent session identity header for heartbeat tracking
|
|
226
235
|
# Use ${VAR:-} pattern to avoid 'unbound variable' error under set -u (nounset)
|
|
227
236
|
# The agent badge (#999): the credential that makes the backend treat this
|
|
@@ -280,7 +289,11 @@ api_call() {
|
|
|
280
289
|
if [ -n "${CREWLY_AGENT_GOAL:-}" ]; then
|
|
281
290
|
args+=(-H "X-Agent-Goal: b64:$(printf '%s' "$CREWLY_AGENT_GOAL" | base64 | tr -d '\n')")
|
|
282
291
|
fi
|
|
283
|
-
[ -n "$
|
|
292
|
+
if [ -n "${CREWLY_API_UPLOAD_FILE:-}" ]; then
|
|
293
|
+
args+=(--data-binary "@${CREWLY_API_UPLOAD_FILE}")
|
|
294
|
+
elif [ -n "$body" ]; then
|
|
295
|
+
args+=(-d "$body")
|
|
296
|
+
fi
|
|
284
297
|
|
|
285
298
|
local response
|
|
286
299
|
response=$(curl "${args[@]}" "$url")
|
|
@@ -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.5.0
|
|
5
5
|
category: productivity
|
|
6
6
|
skillType: claude-skill
|
|
7
7
|
assignableRoles:
|
|
@@ -64,6 +64,18 @@ Output:
|
|
|
64
64
|
otherwise).
|
|
65
65
|
- `--data-file <path>` reads the JSON from a regular file inside your project
|
|
66
66
|
directory (not a symlink, not under `~/.crewly`, at most 1 MB).
|
|
67
|
+
- `--upload <path> [--name <file name>]` puts a file (an image for a post) into
|
|
68
|
+
the app and prints `{ fileId, name, size, contentType, url }`. Same rules as
|
|
69
|
+
`--data-file` (regular file inside your project directory, not a symlink, not
|
|
70
|
+
under `~/.crewly`), up to 10 MB; the app's own limit may be smaller and its
|
|
71
|
+
refusal comes back as-is (`too_large`, quota). It works for the app's owner
|
|
72
|
+
agents and for collaborators; anyone else gets `not_your_app`. Put the `url`
|
|
73
|
+
into a document the page renders (for example `images[].src`) with
|
|
74
|
+
`--update`; PNG, JPEG, GIF and WebP show inline, other types download.
|
|
75
|
+
```bash
|
|
76
|
+
bash execute.sh --app <appId> --upload ./cover.png
|
|
77
|
+
bash execute.sh --app <appId> --update posts <docId> --data '{"images":[{"src":"<url from the upload>"}]}'
|
|
78
|
+
```
|
|
67
79
|
- `--list` pages with `--after <next>` until `next` is null.
|
|
68
80
|
- `--update` is a shallow merge; `--set` replaces the whole document.
|
|
69
81
|
- `--if-rev` makes an update conditional: if the owner changed the doc since
|
|
@@ -19,6 +19,7 @@ Usage:
|
|
|
19
19
|
bash execute.sh --app <appId> --update <collection> <docId> --data '{"done":true}' [--if-rev 4]
|
|
20
20
|
bash execute.sh --app <appId> --add <collection> --data '{"name":"milk"}'
|
|
21
21
|
bash execute.sh --app <appId> --delete <collection> <docId>
|
|
22
|
+
bash execute.sh --app <appId> --upload <path-to-image> [--name <file name>]
|
|
22
23
|
bash execute.sh --app <appId> --request-access [--scope team|agent] [--reason "why"]
|
|
23
24
|
bash execute.sh --app <appId> --collaborators
|
|
24
25
|
bash execute.sh --app <appId> --owner
|
|
@@ -31,6 +32,8 @@ Options:
|
|
|
31
32
|
--if-rev Only update when the doc is still at this rev (409 conflict otherwise)
|
|
32
33
|
--limit Page size for --list (1-500, default 100)
|
|
33
34
|
--after Continue a --list after this doc id (the previous page's "next")
|
|
35
|
+
--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
|
+
--name File name to show for --upload (default: the file's name)
|
|
34
37
|
--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
|
|
35
38
|
--collaborators Who the owner let work in the app
|
|
36
39
|
--scope team (your team, default) or agent (only you), with --request-access
|
|
@@ -60,7 +63,7 @@ uri() { jq -rn --arg v "$1" '$v|@uri'; }
|
|
|
60
63
|
# (CREWLY_PROJECT_PATH, else the working directory), never under Crewly's home.
|
|
61
64
|
read_data_file() {
|
|
62
65
|
command -v node >/dev/null 2>&1 || error_exit "node is required for --data-file"
|
|
63
|
-
DF_PATH="$1" DF_PROJECT="${CREWLY_PROJECT_PATH:-$PWD}" node <<'NODE'
|
|
66
|
+
DF_MODE="${2:-read}" DF_PATH="$1" DF_PROJECT="${CREWLY_PROJECT_PATH:-$PWD}" node <<'NODE'
|
|
64
67
|
const fs = require('fs'); const path = require('path'); const os = require('os');
|
|
65
68
|
const fail = (m) => { process.stderr.write(JSON.stringify({ error: m }) + '\n'); process.exit(1); };
|
|
66
69
|
const real = (p) => { try { return fs.realpathSync(p); } catch { return null; } };
|
|
@@ -76,12 +79,35 @@ if (!project) fail('cannot resolve the project directory');
|
|
|
76
79
|
const homes = [path.join(os.homedir(), '.crewly'), process.env.CREWLY_HOME].filter(Boolean).map((p) => real(p) || path.resolve(p));
|
|
77
80
|
if (homes.some((h) => within(r, h))) fail(`${given} is inside Crewly's home directory`);
|
|
78
81
|
if (!within(r, project)) fail(`${given} is outside your project directory (${project})`);
|
|
79
|
-
if (
|
|
80
|
-
|
|
82
|
+
if (process.env.DF_MODE === 'path') {
|
|
83
|
+
if (st.size === 0) fail(`${given} is empty`);
|
|
84
|
+
if (st.size > 10 * 1024 * 1024) fail(`${given} is larger than 10 MB`);
|
|
85
|
+
process.stdout.write(r);
|
|
86
|
+
} else {
|
|
87
|
+
if (st.size > 1024 * 1024) fail(`${given} is larger than 1 MB`);
|
|
88
|
+
process.stdout.write(fs.readFileSync(r, 'utf8'));
|
|
89
|
+
}
|
|
81
90
|
NODE
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
|
|
93
|
+
# Content type from the extension. JSON-like types are sent as octet-stream so
|
|
94
|
+
# the backend receives raw bytes; Cloud decides what a browser may show inline.
|
|
95
|
+
content_type_for() {
|
|
96
|
+
case "$(printf '%s' "${1##*.}" | tr '[:upper:]' '[:lower:]')" in
|
|
97
|
+
png) echo image/png ;;
|
|
98
|
+
jpg|jpeg) echo image/jpeg ;;
|
|
99
|
+
gif) echo image/gif ;;
|
|
100
|
+
webp) echo image/webp ;;
|
|
101
|
+
svg) echo image/svg+xml ;;
|
|
102
|
+
pdf) echo application/pdf ;;
|
|
103
|
+
mp4) echo video/mp4 ;;
|
|
104
|
+
webm) echo video/webm ;;
|
|
105
|
+
txt) echo text/plain ;;
|
|
106
|
+
*) echo application/octet-stream ;;
|
|
107
|
+
esac
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
APP=""; OP=""; UPLOAD=""; NAME=""; COLL=""; DOC=""; DATA=""; IF_REV=""; LIMIT=""; AFTER=""; SCOPE=""; REASON=""; SET_OWNER=""; WHO=""
|
|
85
111
|
while [[ $# -gt 0 ]]; do
|
|
86
112
|
case "$1" in
|
|
87
113
|
--app) [ $# -ge 2 ] || error_exit "--app requires a value"; APP="$2"; shift 2 ;;
|
|
@@ -96,6 +122,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
96
122
|
--if-rev) [ $# -ge 2 ] || error_exit "--if-rev requires a value"; IF_REV="$2"; shift 2 ;;
|
|
97
123
|
--limit) [ $# -ge 2 ] || error_exit "--limit requires a value"; LIMIT="$2"; shift 2 ;;
|
|
98
124
|
--after) [ $# -ge 2 ] || error_exit "--after requires a value"; AFTER="$2"; shift 2 ;;
|
|
125
|
+
--upload) [ $# -ge 2 ] || error_exit "--upload requires a file path"; OP="upload"; UPLOAD="$2"; shift 2 ;;
|
|
126
|
+
--name) [ $# -ge 2 ] || error_exit "--name requires a value"; NAME="$2"; shift 2 ;;
|
|
99
127
|
--request-access) OP="request-access"; shift ;;
|
|
100
128
|
--collaborators) OP="collaborators"; shift ;;
|
|
101
129
|
--owner) OP="owner"; shift ;;
|
|
@@ -109,13 +137,21 @@ while [[ $# -gt 0 ]]; do
|
|
|
109
137
|
done
|
|
110
138
|
|
|
111
139
|
[ -n "$APP" ] || error_exit "--app is required"
|
|
112
|
-
[ -n "$OP" ] || error_exit "one of --list / --get / --set / --update / --add / --delete / --request-access / --collaborators / --owner / --add-collaborator is required"
|
|
140
|
+
[ -n "$OP" ] || error_exit "one of --list / --get / --set / --update / --add / --delete / --upload / --request-access / --collaborators / --owner / --add-collaborator is required"
|
|
113
141
|
[[ "$APP" =~ ^[a-z0-9]{10}$ ]] || error_exit "--app must be a 10-character app id"
|
|
114
142
|
case "$COLL" in .|..) error_exit "collection cannot be . or .." ;; esac
|
|
115
143
|
case "$DOC" in .|..) error_exit "doc id cannot be . or .." ;; esac
|
|
116
144
|
BASE="/apps/${APP}/data/$(uri "$COLL")"
|
|
117
145
|
|
|
118
146
|
case "$OP" in
|
|
147
|
+
upload)
|
|
148
|
+
FILE_PATH="$(read_data_file "$UPLOAD" path)" || exit 1
|
|
149
|
+
[ -n "$NAME" ] || NAME="$(basename "$FILE_PATH")"
|
|
150
|
+
RESPONSE=$(CREWLY_API_UPLOAD_FILE="$FILE_PATH" CREWLY_API_UPLOAD_TYPE="$(content_type_for "$FILE_PATH")" CREWLY_API_UPLOAD_NAME="$(uri "$NAME")" call POST "/apps/${APP}/files") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
151
|
+
printf '%s' "$RESPONSE" | jq -e '.data.url | type == "string" and length > 0' >/dev/null 2>&1 || error_exit "the upload answered without a url; the file may not be stored"
|
|
152
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, fileId: .data.fileId, name: .data.name, size: .data.size, contentType: .data.contentType, url: .data.url}'
|
|
153
|
+
exit 0
|
|
154
|
+
;;
|
|
119
155
|
request-access)
|
|
120
156
|
case "$SCOPE" in ""|team|agent) ;; *) error_exit "--scope is team or agent" ;; esac
|
|
121
157
|
BODY=$(jq -cn --arg s "$SCOPE" --arg r "$REASON" '{} + (if $s != "" then {scope: $s} else {} end) + (if $r != "" then {reason: $r} else {} end)')
|
|
@@ -51,7 +51,20 @@ class H(BaseHTTPRequestHandler):
|
|
|
51
51
|
def _body(self, m):
|
|
52
52
|
n = int(self.headers.get('content-length', '0')); body = self.rfile.read(n).decode() if n else ''
|
|
53
53
|
self._reply(m, json.loads(body or '{}'))
|
|
54
|
-
def do_POST(self):
|
|
54
|
+
def do_POST(self):
|
|
55
|
+
if self.path.endswith('/files'):
|
|
56
|
+
n = int(self.headers.get('content-length', '0')); raw = self.rfile.read(n)
|
|
57
|
+
name = self.headers.get('x-file-name', '')
|
|
58
|
+
open(LOG, 'w').write(json.dumps({'method': 'POST', 'path': self.path, 'ct': self.headers.get('content-type'), 'name': name, 'len': n, 'hex': raw.hex(), 'session': self.headers.get('x-agent-session')}))
|
|
59
|
+
def send(code, obj):
|
|
60
|
+
self.send_response(code); self.send_header('Content-Type', 'application/json'); self.end_headers()
|
|
61
|
+
self.wfile.write(json.dumps(obj).encode())
|
|
62
|
+
if name == 'nourl.png':
|
|
63
|
+
return send(201, {'success': True, 'data': {'fileId': 'f1'}})
|
|
64
|
+
if name == 'refuse.png':
|
|
65
|
+
return send(403, {'success': False, 'error': 'not_your_app', 'message': 'This app is not yours.'})
|
|
66
|
+
return send(201, {'success': True, 'data': {'fileId': 'f1', 'name': name, 'size': n, 'contentType': self.headers.get('content-type'), 'url': 'https://apps.crewlyai.com/_f/tok/f1'}})
|
|
67
|
+
self._body('POST')
|
|
55
68
|
def do_PUT(self): self._body('PUT')
|
|
56
69
|
def do_PATCH(self): self._body('PATCH')
|
|
57
70
|
def log_message(self, *a, **k): pass
|
|
@@ -149,5 +162,38 @@ check "set-owner needs a value" "$(printf '%s' "$OUT" | grep -c 'requires an own
|
|
|
149
162
|
run --app $A --set items milk --data '{"done":true}' >/dev/null
|
|
150
163
|
check "--set is still a data write" "$(jq -r '.method + " " + .path' "$STUB_LOG")" "PUT /api/apps/$A/data/items/milk"
|
|
151
164
|
|
|
165
|
+
# --upload: raw bytes, content type from the extension, name URI-encoded; same project-dir guard as --data-file.
|
|
166
|
+
printf '\x89PNG\r\n\x1a\n\x00\x01' > "$WORK/proj/pic one.png"
|
|
167
|
+
OUT=$(run --app $A --upload "$WORK/proj/pic one.png")
|
|
168
|
+
check "upload: output has the url" "$(printf '%s' "$OUT" | jq -c '{success, fileId, name, size, contentType, url}')" '{"success":true,"fileId":"f1","name":"pic%20one.png","size":10,"contentType":"image/png","url":"https://apps.crewlyai.com/_f/tok/f1"}'
|
|
169
|
+
check "upload: request" "$(jq -c '{method, path, ct, name, len, hex, session}' "$STUB_LOG")" "{\"method\":\"POST\",\"path\":\"/api/apps/$A/files\",\"ct\":\"image/png\",\"name\":\"pic%20one.png\",\"len\":10,\"hex\":\"89504e470d0a1a0a0001\",\"session\":\"dev-ella\"}"
|
|
170
|
+
OUT=$(run --app $A --upload "$WORK/proj/pic one.png" --name "shot 1.png")
|
|
171
|
+
check "upload: --name overrides" "$(jq -r .name "$STUB_LOG")" "shot%201.png"
|
|
172
|
+
printf '{"a":1}' > "$WORK/proj/data.json"
|
|
173
|
+
run --app $A --upload "$WORK/proj/data.json" >/dev/null
|
|
174
|
+
check "upload: json is sent as octet-stream" "$(jq -r .ct "$STUB_LOG")" "application/octet-stream"
|
|
175
|
+
# A normal call after an upload is JSON again (the upload env does not leak).
|
|
176
|
+
run --app $A --add items --data '{"n":1}' >/dev/null
|
|
177
|
+
check "upload does not leak into the next call" "$(jq -c .body "$STUB_LOG")" '{"data":{"n":1}}'
|
|
178
|
+
printf 'x' > "$WORK/outside.png"
|
|
179
|
+
OUT=$(run_err --app $A --upload "$WORK/outside.png" || true)
|
|
180
|
+
check "upload: outside the project refused" "$(printf '%s' "$OUT" | grep -c 'outside your project directory')" "1"
|
|
181
|
+
ln -s "$WORK/proj/pic one.png" "$WORK/proj/link.png"
|
|
182
|
+
OUT=$(run_err --app $A --upload "$WORK/proj/link.png" || true)
|
|
183
|
+
check "upload: symlink refused" "$(printf '%s' "$OUT" | grep -c 'symbolic link')" "1"
|
|
184
|
+
OUT=$(run_err --app $A --upload "$WORK/proj/nope.png" || true)
|
|
185
|
+
check "upload: missing file" "$(printf '%s' "$OUT" | grep -c 'file not found')" "1"
|
|
186
|
+
: > "$WORK/proj/empty.png"
|
|
187
|
+
OUT=$(run_err --app $A --upload "$WORK/proj/empty.png" || true)
|
|
188
|
+
check "upload: empty file refused" "$(printf '%s' "$OUT" | grep -c 'is empty')" "1"
|
|
189
|
+
printf '\x89PNG' > "$WORK/proj/refuse.png"
|
|
190
|
+
OUT=$(run --app $A --upload "$WORK/proj/refuse.png"; true)
|
|
191
|
+
check "upload: Cloud refusal passed through, not a fake success" "$(printf '%s' "$OUT" | jq -c '{success, status, reason}')" '{"success":false,"status":403,"reason":"not_your_app"}'
|
|
192
|
+
printf '\x89PNG' > "$WORK/proj/nourl.png"
|
|
193
|
+
OUT=$(run_err --app $A --upload "$WORK/proj/nourl.png" || true)
|
|
194
|
+
check "upload: 2xx without a url fails, not success with nulls" "$(printf '%s' "$OUT" | grep -c 'without a url')" "1"
|
|
195
|
+
OUT=$(run_err --app $A --upload || true)
|
|
196
|
+
check "upload needs a path" "$(printf '%s' "$OUT" | grep -c 'requires a file path')" "1"
|
|
197
|
+
|
|
152
198
|
echo "app-data: $PASS passed, $FAIL failed"
|
|
153
199
|
[ "$FAIL" -eq 0 ]
|
|
@@ -88,7 +88,7 @@ Output:
|
|
|
88
88
|
`--list`) — never publish a second app with the same name; the owner then
|
|
89
89
|
sees two. Limits:
|
|
90
90
|
300 files, 5 MB per file, 25 MB per version. The last 10 versions are kept.
|
|
91
|
-
- Read and write the app's data from your side with `app-data`.
|
|
91
|
+
- Read and write the app's data from your side with `app-data`. To put an image into the app, `app-data --app <id> --upload <file>` returns a url you can store in a document.
|
|
92
92
|
|
|
93
93
|
## Public apps (only the owner can make one)
|
|
94
94
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Slack File
|
|
3
|
+
description: Get a Slack file by link or id and save it locally — also a file posted in a channel your own bot is not in, or by an agent on another machine. Prints the saved path and, for text files, the first lines.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: communication
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
triggers:
|
|
8
|
+
- can't open the slack file
|
|
9
|
+
- read this slack attachment
|
|
10
|
+
- download the file from slack
|
|
11
|
+
- 打不开 slack 文件
|
|
12
|
+
- 读一下这个附件
|
|
13
|
+
tags:
|
|
14
|
+
- slack
|
|
15
|
+
- file
|
|
16
|
+
- attachment
|
|
17
|
+
execution:
|
|
18
|
+
type: script
|
|
19
|
+
script:
|
|
20
|
+
file: execute.sh
|
|
21
|
+
interpreter: bash
|
|
22
|
+
timeoutMs: 120000
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Slack File
|
|
26
|
+
|
|
27
|
+
When you see a Slack file you cannot open — a link a colleague pasted, a
|
|
28
|
+
file posted in another channel, `[Slack: ... could not be read]` — fetch it
|
|
29
|
+
yourself. **Never ask a colleague to paste the text.**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bash execute.sh get https://acme.slack.com/files/U0ELLA1234/F0ABC12345/longform-en.md
|
|
33
|
+
bash execute.sh get F0ABC12345
|
|
34
|
+
bash execute.sh get https://acme.slack.com/archives/C0MKTG1234/p1696771234567890 # a message with a file
|
|
35
|
+
bash execute.sh get F0ABC12345 --out /path/to/project/longform-en.md
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
It tries this machine's Slack bots first (yours, the uploader's, the
|
|
39
|
+
channel's agents, the workspace bot), then Crewly Cloud, which holds every
|
|
40
|
+
bot of your account on every machine.
|
|
41
|
+
|
|
42
|
+
Output: `{ "path", "name", "mimetype", "size", "via", "preview"? }`.
|
|
43
|
+
`path` is where the file is saved (`~/.crewly/tmp/slack-files/` unless
|
|
44
|
+
`--out` is given); `preview` holds the first lines of a text file. Read
|
|
45
|
+
the full file at `path`.
|
|
46
|
+
|
|
47
|
+
| Error `code` | Meaning / what to do |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `not_visible` | No bot of your account is in the file's channel. Ask the owner to invite you (or the Crewly app) there. |
|
|
50
|
+
| `foreign_workspace` | The link is from a Slack workspace your account has not connected. |
|
|
51
|
+
| `file_deleted` | The file was deleted. |
|
|
52
|
+
| `too_large` | Over 25 MB. |
|
|
53
|
+
| `no_file_in_message` | The message link has no file attached. |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# slack-file — get a Slack file by link or id (also one your own bot cannot see).
|
|
3
|
+
#
|
|
4
|
+
# Backed by POST /api/slack/files/fetch: this machine's bot tokens first, then
|
|
5
|
+
# Crewly Cloud (every bot of the account).
|
|
6
|
+
#
|
|
7
|
+
# Usage: execute.sh get <link|file id> [--out <path>]
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
# shellcheck source=/dev/null
|
|
11
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
12
|
+
|
|
13
|
+
usage() {
|
|
14
|
+
cat <<'USAGE'
|
|
15
|
+
slack-file — get a Slack file by link or id
|
|
16
|
+
|
|
17
|
+
get <link|id> File link (https://<ws>.slack.com/files/...), file id (F...),
|
|
18
|
+
download URL, or a message link carrying a file (required)
|
|
19
|
+
--out <path> Also copy the file to this path
|
|
20
|
+
USAGE
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
CMD=""; REF=""; OUT=""
|
|
24
|
+
while [ $# -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
get) CMD="get"; REF="${2:-}"; shift; [ $# -gt 0 ] && shift ;;
|
|
27
|
+
--out|-o) OUT="${2:-}"; shift 2 ;;
|
|
28
|
+
-h|--help) usage; exit 0 ;;
|
|
29
|
+
*) if [ -z "$REF" ] && [ "$CMD" = "get" ]; then REF="$1"; shift; else echo "{\"error\":\"Unknown argument: $1\"}" >&2; exit 1; fi ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
if [ "$CMD" != "get" ] || [ -z "$REF" ]; then
|
|
34
|
+
echo '{"error":"Usage: execute.sh get <link|file id> [--out <path>]"}' >&2
|
|
35
|
+
usage >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Slack mrkdwn links arrive as <url|label>; the backend unwraps them too.
|
|
40
|
+
BODY=$(jq -cn --arg fileRef "$REF" '{fileRef: $fileRef}')
|
|
41
|
+
|
|
42
|
+
# Test hook: print the request instead of calling the backend.
|
|
43
|
+
if [ "${CREWLY_SKILL_DRY_RUN:-}" = "1" ]; then
|
|
44
|
+
echo "POST /slack/files/fetch $BODY"
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if ! RESPONSE=$(api_call POST "/slack/files/fetch" "$BODY" 2>&1); then
|
|
49
|
+
echo "$RESPONSE" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
FILE_PATH=$(printf '%s' "$RESPONSE" | jq -r '.data.path // empty')
|
|
54
|
+
if [ -z "$FILE_PATH" ]; then
|
|
55
|
+
echo "$RESPONSE" >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ -n "$OUT" ]; then
|
|
60
|
+
mkdir -p "$(dirname "$OUT")"
|
|
61
|
+
cp "$FILE_PATH" "$OUT"
|
|
62
|
+
FILE_PATH="$OUT"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
printf '%s' "$RESPONSE" | jq --arg path "$FILE_PATH" '{
|
|
66
|
+
path: $path,
|
|
67
|
+
name: .data.file.name,
|
|
68
|
+
mimetype: .data.file.mimetype,
|
|
69
|
+
size: .data.file.size,
|
|
70
|
+
via: .data.file.via
|
|
71
|
+
} + (if .data.preview then {preview: .data.preview} else {} end)
|
|
72
|
+
+ (if (.data.file.otherFileIds // []) | length > 0 then {otherFileIds: .data.file.otherFileIds} else {} end)'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for slack-file: argument parsing, request body, --out copy.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PASS=0; FAIL=0
|
|
6
|
+
assert_eq() { if [ "$2" = "$3" ]; then echo "PASS: $1"; PASS=$((PASS+1)); else echo "FAIL: $1 — expected '$2', got '$3'"; FAIL=$((FAIL+1)); fi; }
|
|
7
|
+
assert_contains() { if [[ "$3" == *"$2"* ]]; then echo "PASS: $1"; PASS=$((PASS+1)); else echo "FAIL: $1 — expected '$2' in: $3"; FAIL=$((FAIL+1)); fi; }
|
|
8
|
+
|
|
9
|
+
LINK='https://acme.slack.com/files/U0ELLA1234/F0ABC12345/longform-en.md'
|
|
10
|
+
assert_eq "get <link> → POST with fileRef" "POST /slack/files/fetch {\"fileRef\":\"$LINK\"}" "$(CREWLY_SKILL_DRY_RUN=1 bash "$SCRIPT_DIR/execute.sh" get "$LINK")"
|
|
11
|
+
assert_eq "get <id>" 'POST /slack/files/fetch {"fileRef":"F0ABC12345"}' "$(CREWLY_SKILL_DRY_RUN=1 bash "$SCRIPT_DIR/execute.sh" get F0ABC12345)"
|
|
12
|
+
assert_eq "quotes in the ref stay JSON-safe" 'POST /slack/files/fetch {"fileRef":"F0\"x"}' "$(CREWLY_SKILL_DRY_RUN=1 bash "$SCRIPT_DIR/execute.sh" get 'F0"x')"
|
|
13
|
+
assert_contains "missing ref → usage error" "Usage" "$(bash "$SCRIPT_DIR/execute.sh" get 2>&1 || true)"
|
|
14
|
+
assert_contains "--help prints usage" "slack-file" "$(bash "$SCRIPT_DIR/execute.sh" --help)"
|
|
15
|
+
|
|
16
|
+
# --out copies the saved file and reports the new path (api_call stubbed).
|
|
17
|
+
TMP="$(mktemp -d)"
|
|
18
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
19
|
+
printf 'hello\n' > "$TMP/saved.md"
|
|
20
|
+
STUB="$TMP/stub-lib.sh"
|
|
21
|
+
cat > "$STUB" <<STUB_EOF
|
|
22
|
+
api_call() { printf '%s' '{"success":true,"data":{"path":"$TMP/saved.md","file":{"name":"longform-en.md","mimetype":"text/markdown","size":6,"via":"cloud:agent:team-ella"},"preview":"hello"}}'; }
|
|
23
|
+
STUB_EOF
|
|
24
|
+
mkdir -p "$TMP/skills/agent/core/slack-file" "$TMP/skills/agent/_common"
|
|
25
|
+
cp "$SCRIPT_DIR/execute.sh" "$TMP/skills/agent/core/slack-file/execute.sh"
|
|
26
|
+
cp "$STUB" "$TMP/skills/agent/_common/lib.sh"
|
|
27
|
+
OUT_JSON="$(bash "$TMP/skills/agent/core/slack-file/execute.sh" get "$LINK" --out "$TMP/out/longform-en.md")"
|
|
28
|
+
assert_eq "--out path reported" "$TMP/out/longform-en.md" "$(printf '%s' "$OUT_JSON" | jq -r .path)"
|
|
29
|
+
assert_eq "--out file copied" "hello" "$(cat "$TMP/out/longform-en.md")"
|
|
30
|
+
assert_eq "preview passed through" "hello" "$(printf '%s' "$OUT_JSON" | jq -r .preview)"
|
|
31
|
+
assert_eq "via passed through" "cloud:agent:team-ella" "$(printf '%s' "$OUT_JSON" | jq -r .via)"
|
|
32
|
+
|
|
33
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
34
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -4114,6 +4114,35 @@ export declare const SLACK_RECONNECT_CONSTANTS: {
|
|
|
4114
4114
|
/** Number of consecutive ping failures before forcing a reconnect */
|
|
4115
4115
|
readonly PING_FAILURES_BEFORE_RECONNECT: 2;
|
|
4116
4116
|
};
|
|
4117
|
+
/**
|
|
4118
|
+
* Fetching a Slack file by link or id (`slack-file get`, and files linked
|
|
4119
|
+
* in an inbound message). Local bot tokens first, then Crewly Cloud, which
|
|
4120
|
+
* holds every bot of the account (`POST /api/cloud/slack/files/fetch`).
|
|
4121
|
+
*/
|
|
4122
|
+
export declare const SLACK_FILE_FETCH_CONSTANTS: {
|
|
4123
|
+
/** Largest file fetched (bytes) — same cap as Cloud */
|
|
4124
|
+
readonly MAX_BYTES: number;
|
|
4125
|
+
/** Cloud endpoint, appended to the cloud URL */
|
|
4126
|
+
readonly CLOUD_FETCH_PATH: "/api/cloud/slack/files/fetch";
|
|
4127
|
+
/** Header carrying the file metadata on Cloud's raw answer (base64url JSON) */
|
|
4128
|
+
readonly CLOUD_META_HEADER: "x-crewly-slack-file";
|
|
4129
|
+
/** Local bot tokens tried at most */
|
|
4130
|
+
readonly MAX_LOCAL_CANDIDATES: 12;
|
|
4131
|
+
/** Slack API / Cloud call timeout (ms) */
|
|
4132
|
+
readonly API_TIMEOUT_MS: 15000;
|
|
4133
|
+
/** Download timeout (ms) */
|
|
4134
|
+
readonly DOWNLOAD_TIMEOUT_MS: 60000;
|
|
4135
|
+
/** Redirect hops followed on a download */
|
|
4136
|
+
readonly MAX_REDIRECTS: 5;
|
|
4137
|
+
/** Linked files auto-fetched per inbound message */
|
|
4138
|
+
readonly MAX_LINKED_PER_MESSAGE: 3;
|
|
4139
|
+
/** Linked files larger than this are not auto-fetched (the agent can still run `slack-file get`) */
|
|
4140
|
+
readonly MAX_LINKED_BYTES: number;
|
|
4141
|
+
/** Lines of a text file shown inline by the skill */
|
|
4142
|
+
readonly PREVIEW_LINES: 20;
|
|
4143
|
+
/** Characters of a text file shown inline by the skill */
|
|
4144
|
+
readonly PREVIEW_CHARS: 2000;
|
|
4145
|
+
};
|
|
4117
4146
|
export declare const SLACK_FILE_DOWNLOAD_CONSTANTS: {
|
|
4118
4147
|
/** Temp directory for downloaded files (relative to ~/.crewly/) */
|
|
4119
4148
|
readonly TEMP_DIR: "tmp/slack-files";
|
|
@@ -4656,6 +4685,9 @@ export declare const CREWLY_APPS_CONSTANTS: {
|
|
|
4656
4685
|
readonly PUBLISH_CLOSE_ABOVE_BYTES: number;
|
|
4657
4686
|
/** The publish route, for the dedicated body parser registered before the global one */
|
|
4658
4687
|
readonly PUBLISH_ROUTE: "/api/apps/publish";
|
|
4688
|
+
/** A file upload to an app (POST /api/apps/:appId/files): body cap here; Cloud applies its own, smaller one and its answer is passed through */
|
|
4689
|
+
readonly UPLOAD_BODY_LIMIT: "10mb";
|
|
4690
|
+
readonly UPLOAD_BODY_MAX_BYTES: number;
|
|
4659
4691
|
/** Cloud request timeout (publish uploads can be large) */
|
|
4660
4692
|
readonly REQUEST_TIMEOUT_MS: 120000;
|
|
4661
4693
|
/** Default entry file of a bundle */
|
|
@@ -7225,6 +7257,33 @@ export declare const REMOTE_MCP_CONSTANTS: {
|
|
|
7225
7257
|
readonly MAX_TOOL_NAMES: 50;
|
|
7226
7258
|
/** Catalog presets the add form offers. */
|
|
7227
7259
|
readonly PROVIDERS: readonly string[];
|
|
7260
|
+
/**
|
|
7261
|
+
* OAuth state (clients, tokens, pending sign-ins) under CREWLY_HOME:
|
|
7262
|
+
* AES-256-GCM with the install's master key, mode 0600.
|
|
7263
|
+
*/
|
|
7264
|
+
readonly OAUTH_STORE_FILE: "remote-mcp-oauth.enc";
|
|
7265
|
+
/** Master key the OAuth store is encrypted with (under CREWLY_HOME). */
|
|
7266
|
+
readonly OAUTH_MASTER_KEY_FILE: "credentials/master.key";
|
|
7267
|
+
/** Cloud broker that hosts the public redirect URI (crewly-services auth). */
|
|
7268
|
+
readonly OAUTH_CLOUD_PATH: "/api/cloud/mcp-oauth";
|
|
7269
|
+
/** Client name sent in dynamic client registration. */
|
|
7270
|
+
readonly OAUTH_CLIENT_NAME: "Crewly";
|
|
7271
|
+
/** Refresh an access token this long before it expires (Zoho's last ~1 h). */
|
|
7272
|
+
readonly OAUTH_REFRESH_MARGIN_MS: number;
|
|
7273
|
+
/** Assumed lifetime when the token endpoint omits expires_in. */
|
|
7274
|
+
readonly OAUTH_DEFAULT_TOKEN_TTL_MS: number;
|
|
7275
|
+
/** Discovery / registration / token request timeout. */
|
|
7276
|
+
readonly OAUTH_REQUEST_TIMEOUT_MS: 15000;
|
|
7277
|
+
/** Poll the Cloud broker this often while the link is untapped. */
|
|
7278
|
+
readonly OAUTH_POLL_SLOW_MS: 20000;
|
|
7279
|
+
/** ...and this often once the owner has opened it. */
|
|
7280
|
+
readonly OAUTH_POLL_FAST_MS: 2000;
|
|
7281
|
+
/** Post the "sign in" card for a server at most this often while its link is unused. */
|
|
7282
|
+
readonly OAUTH_CARD_THROTTLE_MS: number;
|
|
7283
|
+
/** Agent → backend proxy path suffix: `/api/connectors/remote-mcp/<id>/mcp`. */
|
|
7284
|
+
readonly PROXY_SUFFIX: "/mcp";
|
|
7285
|
+
/** JSON-RPC error code the proxy answers with when the owner must sign in. */
|
|
7286
|
+
readonly PROXY_NEEDS_AUTH_RPC_CODE: -32001;
|
|
7228
7287
|
};
|
|
7229
7288
|
/**
|
|
7230
7289
|
* Reconciler wake pacing (CREW-304). On 2026-10-06 the reconciler traced
|