jishushell 0.4.30 → 0.5.22
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/Dockerfile.hermes-slim +2 -5
- package/apps/anythingllm-container.yaml +287 -0
- package/apps/browserless-chromium-container.yaml +18 -6
- package/apps/filebrowser-container.yaml +164 -0
- package/apps/ollama-binary.yaml +44 -0
- package/apps/ollama-with-hollama-binary.yaml +45 -1
- package/apps/openclaw-binary.yaml +8 -0
- package/apps/openclaw-container.yaml +9 -1
- package/apps/openclaw-with-searxng-container.yaml +4 -0
- package/apps/searxng-container.yaml +5 -4
- package/apps/weknora-container.yaml +471 -0
- package/dist/cli/doctor.js +144 -16
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/panel.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +99 -1
- package/dist/config.js.map +1 -1
- package/dist/install.js +4 -4
- package/dist/install.js.map +1 -1
- package/dist/routes/auth.js +2 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +64 -11
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/external-mounts.d.ts +17 -0
- package/dist/routes/external-mounts.js +73 -0
- package/dist/routes/external-mounts.js.map +1 -0
- package/dist/routes/file-mounts.d.ts +13 -0
- package/dist/routes/file-mounts.js +90 -0
- package/dist/routes/file-mounts.js.map +1 -0
- package/dist/routes/files-organize.d.ts +28 -0
- package/dist/routes/files-organize.js +167 -0
- package/dist/routes/files-organize.js.map +1 -0
- package/dist/routes/files.d.ts +31 -0
- package/dist/routes/files.js +321 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/instances.js +87 -12
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/internal.d.ts +2 -0
- package/dist/routes/internal.js +59 -0
- package/dist/routes/internal.js.map +1 -0
- package/dist/routes/llm.js +29 -0
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/setup.js +9 -9
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +1 -1
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +17 -0
- package/dist/routes/webdav.js +114 -0
- package/dist/routes/webdav.js.map +1 -0
- package/dist/server.js +358 -6
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.d.ts +3 -0
- package/dist/services/agent-apps/catalog.js +40 -13
- package/dist/services/agent-apps/catalog.js.map +1 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
- package/dist/services/agent-apps/installers/shell-script.js +19 -2
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
- package/dist/services/agent-apps/types.d.ts +3 -0
- package/dist/services/app/app-compiler.d.ts +1 -1
- package/dist/services/app/app-compiler.js +5 -5
- package/dist/services/app/app-compiler.js.map +1 -1
- package/dist/services/app/app-manager.d.ts +9 -0
- package/dist/services/app/app-manager.js +248 -43
- package/dist/services/app/app-manager.js.map +1 -1
- package/dist/services/app/custom-manager.js.map +1 -1
- package/dist/services/app/hermes-agent-manager.js +1 -0
- package/dist/services/app/hermes-agent-manager.js.map +1 -1
- package/dist/services/app/ollama-manager.js +1 -1
- package/dist/services/app/ollama-manager.js.map +1 -1
- package/dist/services/app/openclaw-manager.js +37 -5
- package/dist/services/app/openclaw-manager.js.map +1 -1
- package/dist/services/app/platform-transform.d.ts +32 -0
- package/dist/services/app/platform-transform.js +65 -0
- package/dist/services/app/platform-transform.js.map +1 -0
- package/dist/services/app-passwords.d.ts +61 -0
- package/dist/services/app-passwords.js +173 -0
- package/dist/services/app-passwords.js.map +1 -0
- package/dist/services/backup-manager.d.ts +11 -0
- package/dist/services/backup-manager.js +220 -8
- package/dist/services/backup-manager.js.map +1 -1
- package/dist/services/capability-endpoint-validator.js +26 -7
- package/dist/services/capability-endpoint-validator.js.map +1 -1
- package/dist/services/connection-apply.d.ts +2 -0
- package/dist/services/connection-apply.js +55 -1
- package/dist/services/connection-apply.js.map +1 -1
- package/dist/services/connection-resolver.js +1 -1
- package/dist/services/connection-resolver.js.map +1 -1
- package/dist/services/connection-transactor.d.ts +2 -0
- package/dist/services/connection-transactor.js +12 -2
- package/dist/services/connection-transactor.js.map +1 -1
- package/dist/services/external-mounts.d.ts +40 -0
- package/dist/services/external-mounts.js +187 -0
- package/dist/services/external-mounts.js.map +1 -0
- package/dist/services/files-manager.d.ts +252 -0
- package/dist/services/files-manager.js +1075 -0
- package/dist/services/files-manager.js.map +1 -0
- package/dist/services/files-mounts.d.ts +42 -0
- package/dist/services/files-mounts.js +207 -0
- package/dist/services/files-mounts.js.map +1 -0
- package/dist/services/instance-manager.js +90 -32
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +28 -0
- package/dist/services/llm-proxy/index.js +76 -3
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.js +6 -2
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/llm-proxy/validate-key.d.ts +41 -0
- package/dist/services/llm-proxy/validate-key.js +672 -0
- package/dist/services/llm-proxy/validate-key.js.map +1 -0
- package/dist/services/macos-launchd.d.ts +89 -0
- package/dist/services/macos-launchd.js +273 -0
- package/dist/services/macos-launchd.js.map +1 -0
- package/dist/services/nomad-manager.d.ts +11 -0
- package/dist/services/nomad-manager.js +343 -98
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/organize/applier.d.ts +46 -0
- package/dist/services/organize/applier.js +218 -0
- package/dist/services/organize/applier.js.map +1 -0
- package/dist/services/organize/rules.d.ts +57 -0
- package/dist/services/organize/rules.js +286 -0
- package/dist/services/organize/rules.js.map +1 -0
- package/dist/services/organize/scanner.d.ts +50 -0
- package/dist/services/organize/scanner.js +366 -0
- package/dist/services/organize/scanner.js.map +1 -0
- package/dist/services/organize/store.d.ts +14 -0
- package/dist/services/organize/store.js +82 -0
- package/dist/services/organize/store.js.map +1 -0
- package/dist/services/panel-manager.js +40 -11
- package/dist/services/panel-manager.js.map +1 -1
- package/dist/services/process-manager.js +3 -2
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/custom.js +56 -0
- package/dist/services/runtime/adapters/custom.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.d.ts +4 -3
- package/dist/services/runtime/adapters/hermes.js +166 -64
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
- package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
- package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
- package/dist/services/runtime/adapters/openclaw.js +1459 -49
- package/dist/services/runtime/adapters/openclaw.js.map +1 -1
- package/dist/services/runtime/instance.d.ts +1 -1
- package/dist/services/runtime/instance.js +1 -1
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
- package/dist/services/runtime/types.d.ts +31 -0
- package/dist/services/setup-manager.js +190 -68
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.js.map +1 -1
- package/dist/services/update-manager.js +32 -14
- package/dist/services/update-manager.js.map +1 -1
- package/dist/services/webdav/server.d.ts +24 -0
- package/dist/services/webdav/server.js +420 -0
- package/dist/services/webdav/server.js.map +1 -0
- package/dist/services/webdav/xml-builder.d.ts +73 -0
- package/dist/services/webdav/xml-builder.js +156 -0
- package/dist/services/webdav/xml-builder.js.map +1 -0
- package/dist/services/workspace-builder.d.ts +29 -0
- package/dist/services/workspace-builder.js +188 -0
- package/dist/services/workspace-builder.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/utils/path-locks.d.ts +30 -0
- package/dist/utils/path-locks.js +63 -0
- package/dist/utils/path-locks.js.map +1 -0
- package/dist/utils/path-safety.d.ts +41 -0
- package/dist/utils/path-safety.js +119 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/safe-write.d.ts +24 -0
- package/dist/utils/safe-write.js +82 -0
- package/dist/utils/safe-write.js.map +1 -0
- package/install/jishu-install.sh +247 -35
- package/install/jishu-uninstall.sh +45 -5
- package/package.json +20 -2
- package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
- package/public/assets/Dashboard-AuJESBlJ.js +1 -0
- package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
- package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
- package/public/assets/InitPassword-ka4wNpM5.js +1 -0
- package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
- package/public/assets/Login-aPajuQzf.js +1 -0
- package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
- package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
- package/public/assets/Settings-BYQnbLYL.js +1 -0
- package/public/assets/Setup-D05lwDOV.js +1 -0
- package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
- package/public/assets/index-HSXCsceK.css +1 -0
- package/public/assets/index-bnBu0nlQ.js +19 -0
- package/public/assets/registry-C_qeFTkZ.js +2 -0
- package/public/assets/usePolling-Bn93fe7M.js +1 -0
- package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
- package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
- package/public/index.html +4 -4
- package/scripts/check-app-spec.mjs +18 -4
- package/scripts/check-colima-launchd.mjs +230 -0
- package/scripts/check-new-file-tests.mjs +230 -0
- package/scripts/check-quarantine-expiry.mjs +105 -0
- package/scripts/perf/README.md +49 -0
- package/scripts/perf/auth.js +99 -0
- package/scripts/perf/config.js +63 -0
- package/scripts/perf/instances.js +143 -0
- package/scripts/perf/proxy.js +96 -0
- package/scripts/smoke/files-w1.sh +142 -0
- package/scripts/smoke-backend.mjs +122 -0
- package/scripts/smoke-post-publish.mjs +346 -0
- package/public/assets/Dashboard-rkWp-CXd.js +0 -1
- package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
- package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
- package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
- package/public/assets/Login-BItXqYAJ.js +0 -1
- package/public/assets/NewInstance-BousE6kY.js +0 -1
- package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
- package/public/assets/Settings-Bttc6QmM.js +0 -1
- package/public/assets/Setup-Bsxx1zgj.js +0 -1
- package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
- package/public/assets/index-8xZy1z5k.css +0 -1
- package/public/assets/index-Dw3HhUYE.js +0 -19
- package/public/assets/input-paste-CrNVAyOy.js +0 -1
- package/public/assets/providers-DtNXh9JD.js +0 -1
- package/public/assets/registry-5s2UB6is.js +0 -2
- package/public/assets/usePolling-Do5Erqm_.js +0 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Performance Baseline Tests (k6)
|
|
2
|
+
|
|
3
|
+
Latency baseline scripts for core API endpoints. Run nightly in CI to detect
|
|
4
|
+
performance regressions.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) installed on the runner
|
|
9
|
+
- JishuShell server running (the CI job starts one automatically)
|
|
10
|
+
|
|
11
|
+
## Scripts
|
|
12
|
+
|
|
13
|
+
| Script | Endpoints covered |
|
|
14
|
+
|--------|-------------------|
|
|
15
|
+
| `auth.js` | `/api/auth/status`, `/api/auth/login`, `/api/auth/logout` |
|
|
16
|
+
| `proxy.js` | `/proxy/v1/models`, `/api/llm/providers` |
|
|
17
|
+
| `instances.js` | `/api/instances` CRUD lifecycle |
|
|
18
|
+
|
|
19
|
+
## Environment Variables
|
|
20
|
+
|
|
21
|
+
| Variable | Default | Description |
|
|
22
|
+
|----------|---------|-------------|
|
|
23
|
+
| `BASE_URL` | `http://127.0.0.1:8090` | Server base URL |
|
|
24
|
+
| `PASSWORD` | `perf-test-password-2026` | Admin password |
|
|
25
|
+
| `SMOKE` | `false` | Set to `true` for quick 10s validation |
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Smoke test (quick validation)
|
|
31
|
+
k6 run -e SMOKE=true scripts/perf/auth.js
|
|
32
|
+
|
|
33
|
+
# Full baseline run
|
|
34
|
+
k6 run scripts/perf/auth.js
|
|
35
|
+
|
|
36
|
+
# All scripts with custom base URL
|
|
37
|
+
for script in scripts/perf/auth.js scripts/perf/proxy.js scripts/perf/instances.js; do
|
|
38
|
+
k6 run -e BASE_URL=http://localhost:9090 "$script"
|
|
39
|
+
done
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Thresholds
|
|
43
|
+
|
|
44
|
+
Default thresholds (see `config.js`):
|
|
45
|
+
- **p(95) < 500ms** for general endpoints
|
|
46
|
+
- **p(99) < 1000ms** tail latency
|
|
47
|
+
- **Error rate < 1%**
|
|
48
|
+
|
|
49
|
+
Per-endpoint overrides are defined in each script's `options.thresholds`.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import { check, group, sleep } from "k6";
|
|
3
|
+
import {
|
|
4
|
+
BASE_URL,
|
|
5
|
+
PASSWORD,
|
|
6
|
+
DEFAULT_THRESHOLDS,
|
|
7
|
+
DEFAULT_STAGES,
|
|
8
|
+
SMOKE_STAGES,
|
|
9
|
+
authHeaders,
|
|
10
|
+
} from "./config.js";
|
|
11
|
+
|
|
12
|
+
const isSmoke = __ENV.SMOKE === "true";
|
|
13
|
+
|
|
14
|
+
export const options = {
|
|
15
|
+
stages: isSmoke ? SMOKE_STAGES : DEFAULT_STAGES,
|
|
16
|
+
thresholds: {
|
|
17
|
+
...DEFAULT_THRESHOLDS,
|
|
18
|
+
"http_req_duration{name:login}": ["p(95)<300"],
|
|
19
|
+
"http_req_duration{name:auth_status}": ["p(95)<100"],
|
|
20
|
+
},
|
|
21
|
+
tags: { suite: "auth" },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Setup: initialize password and obtain a baseline token.
|
|
26
|
+
* Runs once before VUs start.
|
|
27
|
+
*/
|
|
28
|
+
export function setup() {
|
|
29
|
+
// Check if already initialized
|
|
30
|
+
const statusRes = http.get(`${BASE_URL}/api/auth/status`);
|
|
31
|
+
const initialized = statusRes.json("initialized");
|
|
32
|
+
|
|
33
|
+
let token;
|
|
34
|
+
if (!initialized) {
|
|
35
|
+
const initRes = http.post(
|
|
36
|
+
`${BASE_URL}/api/auth/init`,
|
|
37
|
+
JSON.stringify({ password: PASSWORD }),
|
|
38
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
39
|
+
);
|
|
40
|
+
check(initRes, { "init 200": (r) => r.status === 200 });
|
|
41
|
+
token = initRes.json("token");
|
|
42
|
+
} else {
|
|
43
|
+
const loginRes = http.post(
|
|
44
|
+
`${BASE_URL}/api/auth/login`,
|
|
45
|
+
JSON.stringify({ password: PASSWORD }),
|
|
46
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
47
|
+
);
|
|
48
|
+
check(loginRes, { "login 200": (r) => r.status === 200 });
|
|
49
|
+
token = loginRes.json("token");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!token) {
|
|
53
|
+
throw new Error("Failed to obtain auth token during setup");
|
|
54
|
+
}
|
|
55
|
+
return { token };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default function (data) {
|
|
59
|
+
group("auth-status", () => {
|
|
60
|
+
const res = http.get(`${BASE_URL}/api/auth/status`, {
|
|
61
|
+
tags: { name: "auth_status" },
|
|
62
|
+
});
|
|
63
|
+
check(res, {
|
|
64
|
+
"status 200": (r) => r.status === 200,
|
|
65
|
+
"has initialized field": (r) => r.json("initialized") !== undefined,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
sleep(0.5);
|
|
70
|
+
|
|
71
|
+
group("login", () => {
|
|
72
|
+
const res = http.post(
|
|
73
|
+
`${BASE_URL}/api/auth/login`,
|
|
74
|
+
JSON.stringify({ password: PASSWORD }),
|
|
75
|
+
{
|
|
76
|
+
headers: { "Content-Type": "application/json" },
|
|
77
|
+
tags: { name: "login" },
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
check(res, {
|
|
81
|
+
"login 200": (r) => r.status === 200,
|
|
82
|
+
"returns token": (r) => r.json("token") !== undefined,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
sleep(0.5);
|
|
87
|
+
|
|
88
|
+
group("logout", () => {
|
|
89
|
+
const res = http.post(`${BASE_URL}/api/auth/logout`, null, {
|
|
90
|
+
headers: authHeaders(data.token),
|
|
91
|
+
tags: { name: "logout" },
|
|
92
|
+
});
|
|
93
|
+
check(res, {
|
|
94
|
+
"logout 200": (r) => r.status === 200,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
sleep(1);
|
|
99
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared configuration and helpers for k6 performance tests.
|
|
3
|
+
*
|
|
4
|
+
* Environment variables:
|
|
5
|
+
* BASE_URL — server base URL (default: http://127.0.0.1:8090)
|
|
6
|
+
* PASSWORD — admin password for auth (default: perf-test-password-2026)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const BASE_URL = __ENV.BASE_URL || "http://127.0.0.1:8090";
|
|
10
|
+
export const PASSWORD = __ENV.PASSWORD || "perf-test-password-2026";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Standard k6 thresholds for API endpoints.
|
|
14
|
+
* - p(95) < 500ms: acceptable for most CRUD ops
|
|
15
|
+
* - p(99) < 1000ms: tail latency budget
|
|
16
|
+
* - error rate < 1%
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_THRESHOLDS = {
|
|
19
|
+
http_req_duration: ["p(95)<500", "p(99)<1000"],
|
|
20
|
+
http_req_failed: ["rate<0.01"],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Standard load profile for baseline tests.
|
|
25
|
+
* Ramp up to 10 VUs over 30s, hold for 2m, ramp down.
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_STAGES = [
|
|
28
|
+
{ duration: "30s", target: 10 },
|
|
29
|
+
{ duration: "2m", target: 10 },
|
|
30
|
+
{ duration: "10s", target: 0 },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Smoke profile for quick validation (CI gate).
|
|
35
|
+
* 1 VU for 10s — just verify the script works.
|
|
36
|
+
*/
|
|
37
|
+
export const SMOKE_STAGES = [
|
|
38
|
+
{ duration: "10s", target: 1 },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build standard headers with auth cookie.
|
|
43
|
+
* @param {string} token - JWT token from login/init
|
|
44
|
+
* @returns {object} headers object for k6 http calls
|
|
45
|
+
*/
|
|
46
|
+
export function authHeaders(token) {
|
|
47
|
+
return {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
Cookie: `jishushell_session=${token}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build standard headers with Bearer token (for proxy routes).
|
|
55
|
+
* @param {string} token - JWT token
|
|
56
|
+
* @returns {object} headers object for k6 http calls
|
|
57
|
+
*/
|
|
58
|
+
export function bearerHeaders(token) {
|
|
59
|
+
return {
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
Authorization: `Bearer ${token}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import { check, group, sleep } from "k6";
|
|
3
|
+
import {
|
|
4
|
+
BASE_URL,
|
|
5
|
+
PASSWORD,
|
|
6
|
+
DEFAULT_THRESHOLDS,
|
|
7
|
+
DEFAULT_STAGES,
|
|
8
|
+
SMOKE_STAGES,
|
|
9
|
+
authHeaders,
|
|
10
|
+
} from "./config.js";
|
|
11
|
+
|
|
12
|
+
const isSmoke = __ENV.SMOKE === "true";
|
|
13
|
+
|
|
14
|
+
export const options = {
|
|
15
|
+
stages: isSmoke ? SMOKE_STAGES : DEFAULT_STAGES,
|
|
16
|
+
thresholds: {
|
|
17
|
+
...DEFAULT_THRESHOLDS,
|
|
18
|
+
"http_req_duration{name:list_instances}": ["p(95)<300"],
|
|
19
|
+
"http_req_duration{name:create_instance}": ["p(95)<1000"],
|
|
20
|
+
"http_req_duration{name:get_instance}": ["p(95)<300"],
|
|
21
|
+
"http_req_duration{name:delete_instance}": ["p(95)<1000"],
|
|
22
|
+
},
|
|
23
|
+
tags: { suite: "instances" },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Setup: obtain auth token.
|
|
28
|
+
*/
|
|
29
|
+
export function setup() {
|
|
30
|
+
const statusRes = http.get(`${BASE_URL}/api/auth/status`);
|
|
31
|
+
const initialized = statusRes.json("initialized");
|
|
32
|
+
|
|
33
|
+
let token;
|
|
34
|
+
if (!initialized) {
|
|
35
|
+
const initRes = http.post(
|
|
36
|
+
`${BASE_URL}/api/auth/init`,
|
|
37
|
+
JSON.stringify({ password: PASSWORD }),
|
|
38
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
39
|
+
);
|
|
40
|
+
token = initRes.json("token");
|
|
41
|
+
} else {
|
|
42
|
+
const loginRes = http.post(
|
|
43
|
+
`${BASE_URL}/api/auth/login`,
|
|
44
|
+
JSON.stringify({ password: PASSWORD }),
|
|
45
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
46
|
+
);
|
|
47
|
+
token = loginRes.json("token");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!token) {
|
|
51
|
+
throw new Error("Failed to obtain auth token during setup");
|
|
52
|
+
}
|
|
53
|
+
return { token };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function (data) {
|
|
57
|
+
const headers = authHeaders(data.token);
|
|
58
|
+
const instanceId = `perf-${__VU}-${__ITER}`;
|
|
59
|
+
|
|
60
|
+
group("list-instances", () => {
|
|
61
|
+
const res = http.get(`${BASE_URL}/api/instances`, {
|
|
62
|
+
headers,
|
|
63
|
+
tags: { name: "list_instances" },
|
|
64
|
+
});
|
|
65
|
+
check(res, {
|
|
66
|
+
"list 200": (r) => r.status === 200,
|
|
67
|
+
"returns array": (r) => {
|
|
68
|
+
try {
|
|
69
|
+
return Array.isArray(r.json());
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
sleep(0.3);
|
|
78
|
+
|
|
79
|
+
group("create-instance", () => {
|
|
80
|
+
const payload = JSON.stringify({
|
|
81
|
+
id: instanceId,
|
|
82
|
+
name: `Perf Test ${instanceId}`,
|
|
83
|
+
description: "Created by k6 performance test",
|
|
84
|
+
agentType: "openclaw",
|
|
85
|
+
});
|
|
86
|
+
const res = http.post(`${BASE_URL}/api/instances`, payload, {
|
|
87
|
+
headers,
|
|
88
|
+
tags: { name: "create_instance" },
|
|
89
|
+
});
|
|
90
|
+
check(res, {
|
|
91
|
+
"create 200/201": (r) => r.status === 200 || r.status === 201,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
sleep(0.3);
|
|
96
|
+
|
|
97
|
+
group("get-instance", () => {
|
|
98
|
+
const res = http.get(`${BASE_URL}/api/instances/${instanceId}`, {
|
|
99
|
+
headers,
|
|
100
|
+
tags: { name: "get_instance" },
|
|
101
|
+
});
|
|
102
|
+
check(res, {
|
|
103
|
+
"get 200": (r) => r.status === 200,
|
|
104
|
+
"has id": (r) => {
|
|
105
|
+
try {
|
|
106
|
+
return r.json("id") === instanceId;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
sleep(0.3);
|
|
115
|
+
|
|
116
|
+
group("update-instance", () => {
|
|
117
|
+
const payload = JSON.stringify({
|
|
118
|
+
name: `Updated ${instanceId}`,
|
|
119
|
+
description: "Updated by k6 performance test",
|
|
120
|
+
});
|
|
121
|
+
const res = http.put(`${BASE_URL}/api/instances/${instanceId}`, payload, {
|
|
122
|
+
headers,
|
|
123
|
+
tags: { name: "update_instance" },
|
|
124
|
+
});
|
|
125
|
+
check(res, {
|
|
126
|
+
"update 200": (r) => r.status === 200,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
sleep(0.3);
|
|
131
|
+
|
|
132
|
+
group("delete-instance", () => {
|
|
133
|
+
const res = http.del(`${BASE_URL}/api/instances/${instanceId}`, null, {
|
|
134
|
+
headers,
|
|
135
|
+
tags: { name: "delete_instance" },
|
|
136
|
+
});
|
|
137
|
+
check(res, {
|
|
138
|
+
"delete 200": (r) => r.status === 200,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
sleep(1);
|
|
143
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import { check, group, sleep } from "k6";
|
|
3
|
+
import {
|
|
4
|
+
BASE_URL,
|
|
5
|
+
PASSWORD,
|
|
6
|
+
DEFAULT_THRESHOLDS,
|
|
7
|
+
DEFAULT_STAGES,
|
|
8
|
+
SMOKE_STAGES,
|
|
9
|
+
bearerHeaders,
|
|
10
|
+
} from "./config.js";
|
|
11
|
+
|
|
12
|
+
const isSmoke = __ENV.SMOKE === "true";
|
|
13
|
+
|
|
14
|
+
export const options = {
|
|
15
|
+
stages: isSmoke ? SMOKE_STAGES : DEFAULT_STAGES,
|
|
16
|
+
thresholds: {
|
|
17
|
+
...DEFAULT_THRESHOLDS,
|
|
18
|
+
"http_req_duration{name:list_models}": ["p(95)<300"],
|
|
19
|
+
},
|
|
20
|
+
tags: { suite: "proxy" },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Setup: obtain auth token for proxy requests.
|
|
25
|
+
*/
|
|
26
|
+
export function setup() {
|
|
27
|
+
const statusRes = http.get(`${BASE_URL}/api/auth/status`);
|
|
28
|
+
const initialized = statusRes.json("initialized");
|
|
29
|
+
|
|
30
|
+
let token;
|
|
31
|
+
if (!initialized) {
|
|
32
|
+
const initRes = http.post(
|
|
33
|
+
`${BASE_URL}/api/auth/init`,
|
|
34
|
+
JSON.stringify({ password: PASSWORD }),
|
|
35
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
36
|
+
);
|
|
37
|
+
token = initRes.json("token");
|
|
38
|
+
} else {
|
|
39
|
+
const loginRes = http.post(
|
|
40
|
+
`${BASE_URL}/api/auth/login`,
|
|
41
|
+
JSON.stringify({ password: PASSWORD }),
|
|
42
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
43
|
+
);
|
|
44
|
+
token = loginRes.json("token");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!token) {
|
|
48
|
+
throw new Error("Failed to obtain auth token during setup");
|
|
49
|
+
}
|
|
50
|
+
return { token };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default function (data) {
|
|
54
|
+
const headers = bearerHeaders(data.token);
|
|
55
|
+
|
|
56
|
+
group("list-models", () => {
|
|
57
|
+
const res = http.get(`${BASE_URL}/proxy/v1/models`, {
|
|
58
|
+
headers,
|
|
59
|
+
tags: { name: "list_models" },
|
|
60
|
+
});
|
|
61
|
+
check(res, {
|
|
62
|
+
"models 200": (r) => r.status === 200,
|
|
63
|
+
"has data array": (r) => {
|
|
64
|
+
try {
|
|
65
|
+
return Array.isArray(r.json("data"));
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
sleep(0.5);
|
|
74
|
+
|
|
75
|
+
group("list-providers", () => {
|
|
76
|
+
const res = http.get(`${BASE_URL}/api/llm/providers`, {
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
Cookie: `jishushell_session=${data.token}`,
|
|
80
|
+
},
|
|
81
|
+
tags: { name: "list_providers" },
|
|
82
|
+
});
|
|
83
|
+
check(res, {
|
|
84
|
+
"providers 200": (r) => r.status === 200,
|
|
85
|
+
"returns array": (r) => {
|
|
86
|
+
try {
|
|
87
|
+
return Array.isArray(r.json());
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
sleep(1);
|
|
96
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# End-to-end smoke for Files M1 W1 (PR-4).
|
|
4
|
+
#
|
|
5
|
+
# Drives the eight /api/files endpoints against a running jishushell panel:
|
|
6
|
+
# 1. GET /api/files (list root)
|
|
7
|
+
# 2. PUT /api/files?path=... (upload, octet-stream)
|
|
8
|
+
# 3. GET /api/files?path=... (list subdirectory)
|
|
9
|
+
# 4. POST /api/files/mkdir (create directory)
|
|
10
|
+
# 5. POST /api/files/move (move/rename)
|
|
11
|
+
# 6. GET /api/files/raw?path=... (stream download, sha verified)
|
|
12
|
+
# 7. GET /api/files/preview?path=... (text preview, truncation)
|
|
13
|
+
# 8. GET /api/files/quota (quota info)
|
|
14
|
+
# 9. DELETE /api/files?path=... (soft-delete)
|
|
15
|
+
# 10. PUT with Content-Type: text/plain → expect 415
|
|
16
|
+
# 11. PUT with path traversal → expect 400
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# # Either provide a token directly:
|
|
20
|
+
# JISHUSHELL_URL=http://pi2:8090 JISHUSHELL_TOKEN=... ./scripts/smoke/files-w1.sh
|
|
21
|
+
# # Or let the script log in for you:
|
|
22
|
+
# JISHUSHELL_URL=http://pi2:8090 JISHUSHELL_PASSWORD=... ./scripts/smoke/files-w1.sh
|
|
23
|
+
#
|
|
24
|
+
# Per docs/feedback_validate_before_commit.md, this script MUST run on a real
|
|
25
|
+
# Pi2 box before the Files W1 work is considered complete.
|
|
26
|
+
|
|
27
|
+
set -eo pipefail
|
|
28
|
+
|
|
29
|
+
PANEL_URL="${JISHUSHELL_URL:-http://127.0.0.1:8090}"
|
|
30
|
+
AUTH_TOKEN="${JISHUSHELL_TOKEN:-}"
|
|
31
|
+
PANEL_PASSWORD="${JISHUSHELL_PASSWORD:-}"
|
|
32
|
+
PREFIX="${PREFIX:-smoke-w1}"
|
|
33
|
+
|
|
34
|
+
if [[ -z "$AUTH_TOKEN" && -z "$PANEL_PASSWORD" ]]; then
|
|
35
|
+
echo "ERROR: set JISHUSHELL_TOKEN or JISHUSHELL_PASSWORD env var" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [[ -z "$AUTH_TOKEN" ]]; then
|
|
40
|
+
echo "[auth] logging in as admin"
|
|
41
|
+
AUTH_TOKEN=$(curl -sS -X POST -H "Content-Type: application/json" \
|
|
42
|
+
-d "{\"password\":\"$PANEL_PASSWORD\"}" \
|
|
43
|
+
"$PANEL_URL/api/auth/login" | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
|
|
44
|
+
if [[ -z "$AUTH_TOKEN" ]]; then
|
|
45
|
+
echo "ERROR: login failed" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
echo " ok: got JWT (${#AUTH_TOKEN} chars)"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
H_AUTH=(-H "Authorization: Bearer $AUTH_TOKEN")
|
|
52
|
+
fail() { echo "FAIL: $*" >&2; exit 1; }
|
|
53
|
+
pass() { echo " ok: $*"; }
|
|
54
|
+
|
|
55
|
+
# Cleanup any leftovers from a previous run (best-effort)
|
|
56
|
+
curl -s -X DELETE "$PANEL_URL/api/files?path=$PREFIX/x.txt" "${H_AUTH[@]}" >/dev/null || true
|
|
57
|
+
curl -s -X DELETE "$PANEL_URL/api/files?path=$PREFIX/y.txt" "${H_AUTH[@]}" >/dev/null || true
|
|
58
|
+
curl -s -X DELETE "$PANEL_URL/api/files?path=$PREFIX/sub" "${H_AUTH[@]}" >/dev/null || true
|
|
59
|
+
curl -s -X DELETE "$PANEL_URL/api/files?path=$PREFIX" "${H_AUTH[@]}" >/dev/null || true
|
|
60
|
+
|
|
61
|
+
echo "[1/11] List root"
|
|
62
|
+
RESP=$(curl -sS "${H_AUTH[@]}" "$PANEL_URL/api/files")
|
|
63
|
+
echo "$RESP" | grep -q '"entries"' || fail "list root missing entries: $RESP"
|
|
64
|
+
pass "list root ok"
|
|
65
|
+
|
|
66
|
+
echo "[2/11] mkdir $PREFIX"
|
|
67
|
+
RESP=$(curl -sS -X POST "${H_AUTH[@]}" -H "Content-Type: application/json" \
|
|
68
|
+
-d "{\"path\":\"$PREFIX\"}" "$PANEL_URL/api/files/mkdir")
|
|
69
|
+
echo "$RESP" | grep -q "\"$PREFIX\"" || fail "mkdir failed: $RESP"
|
|
70
|
+
pass "mkdir $PREFIX"
|
|
71
|
+
|
|
72
|
+
echo "[3/11] PUT octet-stream upload"
|
|
73
|
+
echo -n "hello smoke" > /tmp/files-smoke-w1.bin
|
|
74
|
+
RESP=$(curl -sS -X PUT "${H_AUTH[@]}" \
|
|
75
|
+
-H "Content-Type: application/octet-stream" \
|
|
76
|
+
--data-binary @/tmp/files-smoke-w1.bin \
|
|
77
|
+
"$PANEL_URL/api/files?path=$PREFIX/x.txt")
|
|
78
|
+
echo "$RESP" | grep -q '"size":11' || fail "upload size wrong: $RESP"
|
|
79
|
+
ETAG=$(echo "$RESP" | sed -n 's/.*"etag":"\([^"]*\)".*/\1/p')
|
|
80
|
+
[[ -n "$ETAG" ]] || fail "upload returned no etag: $RESP"
|
|
81
|
+
pass "uploaded $PREFIX/x.txt (etag=$ETAG)"
|
|
82
|
+
|
|
83
|
+
echo "[4/11] PUT with wrong Content-Type expects 415"
|
|
84
|
+
CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT "${H_AUTH[@]}" \
|
|
85
|
+
-H "Content-Type: text/plain" --data "x" \
|
|
86
|
+
"$PANEL_URL/api/files?path=$PREFIX/y.txt")
|
|
87
|
+
[[ "$CODE" == "415" ]] || fail "expected 415, got $CODE"
|
|
88
|
+
pass "wrong content-type → 415"
|
|
89
|
+
|
|
90
|
+
echo "[5/11] PUT path traversal expects 400"
|
|
91
|
+
CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT "${H_AUTH[@]}" \
|
|
92
|
+
-H "Content-Type: application/octet-stream" --data "x" \
|
|
93
|
+
"$PANEL_URL/api/files?path=../../../etc/passwd")
|
|
94
|
+
[[ "$CODE" == "400" ]] || fail "expected 400, got $CODE"
|
|
95
|
+
pass "path traversal → 400"
|
|
96
|
+
|
|
97
|
+
echo "[6/11] List $PREFIX"
|
|
98
|
+
RESP=$(curl -sS "${H_AUTH[@]}" "$PANEL_URL/api/files?path=$PREFIX")
|
|
99
|
+
echo "$RESP" | grep -q '"x.txt"' || fail "missing x.txt in list: $RESP"
|
|
100
|
+
pass "subdirectory listing ok"
|
|
101
|
+
|
|
102
|
+
echo "[7/11] Move x.txt → sub/x.txt"
|
|
103
|
+
curl -sS -X POST "${H_AUTH[@]}" -H "Content-Type: application/json" \
|
|
104
|
+
-d "{\"path\":\"$PREFIX/sub\"}" "$PANEL_URL/api/files/mkdir" >/dev/null
|
|
105
|
+
RESP=$(curl -sS -X POST "${H_AUTH[@]}" -H "Content-Type: application/json" \
|
|
106
|
+
-d "{\"from\":\"$PREFIX/x.txt\",\"to\":\"$PREFIX/sub/x.txt\"}" \
|
|
107
|
+
"$PANEL_URL/api/files/move")
|
|
108
|
+
echo "$RESP" | grep -q '"to":' || fail "move response missing 'to': $RESP"
|
|
109
|
+
pass "move ok"
|
|
110
|
+
|
|
111
|
+
echo "[8/11] GET /raw streams content"
|
|
112
|
+
GOT=$(curl -sS "${H_AUTH[@]}" "$PANEL_URL/api/files/raw?path=$PREFIX/sub/x.txt")
|
|
113
|
+
[[ "$GOT" == "hello smoke" ]] || fail "raw content mismatch: '$GOT'"
|
|
114
|
+
pass "raw download verified"
|
|
115
|
+
|
|
116
|
+
echo "[9/11] GET /preview truncates"
|
|
117
|
+
RESP=$(curl -sS "${H_AUTH[@]}" "$PANEL_URL/api/files/preview?path=$PREFIX/sub/x.txt&max_kb=1")
|
|
118
|
+
echo "$RESP" | grep -q '"truncated":false' || fail "preview missing truncated:false: $RESP"
|
|
119
|
+
echo "$RESP" | grep -q '"content":"hello smoke"' || fail "preview content mismatch: $RESP"
|
|
120
|
+
pass "preview ok"
|
|
121
|
+
|
|
122
|
+
echo "[10/11] GET /quota"
|
|
123
|
+
RESP=$(curl -sS "${H_AUTH[@]}" "$PANEL_URL/api/files/quota")
|
|
124
|
+
echo "$RESP" | grep -q '"quota_mb"' || fail "quota response missing quota_mb: $RESP"
|
|
125
|
+
pass "quota ok"
|
|
126
|
+
|
|
127
|
+
echo "[11/11] DELETE soft-deletes to .trash/"
|
|
128
|
+
CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X DELETE "${H_AUTH[@]}" \
|
|
129
|
+
"$PANEL_URL/api/files?path=$PREFIX/sub/x.txt")
|
|
130
|
+
[[ "$CODE" == "204" ]] || fail "expected 204, got $CODE"
|
|
131
|
+
# Confirm by listing — file should be gone from sub/
|
|
132
|
+
RESP=$(curl -sS "${H_AUTH[@]}" "$PANEL_URL/api/files?path=$PREFIX/sub")
|
|
133
|
+
echo "$RESP" | grep -q '"x.txt"' && fail "x.txt still in sub/ after delete"
|
|
134
|
+
pass "soft-delete ok"
|
|
135
|
+
|
|
136
|
+
# Final cleanup
|
|
137
|
+
curl -s -X DELETE "$PANEL_URL/api/files?path=$PREFIX/sub" "${H_AUTH[@]}" >/dev/null || true
|
|
138
|
+
curl -s -X DELETE "$PANEL_URL/api/files?path=$PREFIX" "${H_AUTH[@]}" >/dev/null || true
|
|
139
|
+
rm -f /tmp/files-smoke-w1.bin
|
|
140
|
+
|
|
141
|
+
echo ""
|
|
142
|
+
echo "✓ All 11 smoke checks passed against $PANEL_URL"
|